HyperHQ Plugin Developer Guide
This guide walks through the shape of a HyperHQ plugin, starting with a small working example and then moving into the pieces you will use in real plugins.
Important: For real-time communication and Socket.IO connectivity, see the Socket.IO Connection Guide
Table of Contents
- Quick Start (5 minutes)
- Understanding HyperHQ Plugins
- Your First Plugin
- Communication with HyperHQ
- Creating UI Components
- Working with Settings
- Language-Specific Examples
- Testing Your Plugin
- Publishing Guidelines
- Troubleshooting
Quick Start (5 minutes)
What You'll Build
A simple "Hello World" plugin that displays a message and has one configurable setting.
Prerequisites
- Any programming language that can:
- Connect to Socket.IO servers
- Parse JSON
- Be compiled to an executable
Step 1: Choose Your Starter Template
Pick the template closest to the language you already like using:
| Language | Template Link | Good Fit |
|---|---|---|
| Python | Download | First plugin, quick scripts |
| JavaScript (Node.js) | Download | Web developers, async workflows |
| C# | Download | Windows tooling, .NET projects |
| Go | Download | Small executables, system utilities |
Step 2: Download and Test
# Download your chosen template
# Extract to a new folder
# Follow the README.md in the template
Step 3: Build and Test
# Each template includes build instructions
# Example for Python:
pip install pyinstaller
pyinstaller --onefile plugin.py
At this point you have the basic plugin loop working: build, launch, authenticate, respond.
Understanding HyperHQ Plugins
What is a HyperHQ Plugin?
A HyperHQ plugin is:
- A standalone executable that runs independently
- Communicates via Socket.IO for real-time communication
- Has a manifest file (
plugin.json) describing its capabilities - Packaged as a ZIP file for easy distribution
Why This Approach?
- Portable: Users do not need to install your language runtime separately.
- Crash-safe: Your plugin runs outside the main HyperHQ process.
- Real-time: Socket.IO gives you request/response calls and event subscriptions.
- Permissioned: The manifest tells users what the plugin needs to access.
Basic Plugin Structure
my-plugin/
├── plugin.json # Plugin manifest (required)
├── plugin.exe # Your executable (required)
├── icon.png # Plugin icon (optional)
├── README.md # Documentation (optional)
└── data/ # Any additional files (optional)
Your First Plugin
Let's create a "System Info" plugin that displays basic system information.
Step 1: Create the Manifest (plugin.json)
{
"id": "system-info",
"name": "System Info",
"version": "1.0.0",
"description": "Display system information and disk usage",
"author": "Your Name",
"type": "executable",
"executable": "plugin.exe",
"communication": {
"preferred": "socketio",
"fallback": "stdio",
"socketio": {
"enabled": true,
"autoReconnect": true,
"heartbeat": 30000,
"events": ["gameLaunched", "gameClosed"],
"dataRequests": true
}
},
"capabilities": [
{
"name": "system-info",
"description": "Show basic system information",
"required": true
}
],
"ui": {
"hasSettingsPanel": true,
"hasMainPanel": false,
"hasStatusIndicator": true
},
"settings": [
{
"key": "refreshInterval",
"label": "Refresh Interval (seconds)",
"type": "number",
"defaultValue": 5,
"description": "How often to refresh system info",
"validation": {
"min": 1,
"max": 60
}
},
{
"key": "showDiskUsage",
"label": "Show Disk Usage",
"type": "boolean",
"defaultValue": true,
"description": "Display disk usage information"
}
],
"permissions": [
{
"type": "system",
"scope": "system-info",
"description": "Read basic system information"
}
]
}
Step 2: Understand the Required Methods
Every plugin must implement these methods:
| Method | Purpose | Required |
|---|---|---|
initialize | Setup plugin with settings | Yes |
execute | Perform main plugin action | Yes |
test | Verify plugin works correctly | Yes |
shutdown | Clean up before exit | ⚠️ Optional |
Step 3: Implement the Plugin (Python Example)
#!/usr/bin/env python3
import socketio
import asyncio
import os
import psutil
import platform
class SystemInfoPlugin:
def __init__(self):
self.sio = socketio.AsyncClient()
self.settings = {}
self.session_token = None
# Read environment variables
self.plugin_id = os.environ.get('HYPERHQ_PLUGIN_ID')
self.auth_challenge = os.environ.get('HYPERHQ_AUTH_CHALLENGE')
self.socket_port = os.environ.get('HYPERHQ_SOCKET_PORT', '52789')
self.setup_handlers()
def setup_handlers(self):
@self.sio.on('connect')
async def on_connect():
print('Connected to HyperHQ')
await self.sio.emit('authenticate', {
'pluginId': self.plugin_id,
'challenge': self.auth_challenge
})
@self.sio.on('authenticated')
async def on_authenticated(response):
if response.get('success'):
self.session_token = response.get('sessionToken')
print('Authentication successful')
# Ready to receive `request` events and call `requestData`.
@self.sio.on('request')
async def on_request(data):
response = await self.handle_request(data)
await self.sio.emit('response', {
'id': data['id'],
'type': 'response',
'data': response,
'sessionToken': self.session_token
})
async def initialize(self, data):
"""Called when plugin starts. Receive settings here."""
self.settings = data.get('settings', {})
self.refresh_interval = self.settings.get('refreshInterval', 5)
self.show_disk_usage = self.settings.get('showDiskUsage', True)
return "initialized"
async def execute(self, data):
"""Main plugin logic. Called when user activates plugin."""
action = data.get('action', 'get_info')
if action == 'get_info':
return self.get_system_info()
else:
return {"error": f"Unknown action: {action}"}
def test(self):
"""Test if plugin is working. Should return True if healthy."""
try:
# Simple test - can we get system info?
info = self.get_system_info()
return len(info.get('processor', '')) > 0
except:
return False
def get_system_info(self):
"""Gather system information."""
info = {
"system": platform.system(),
"release": platform.release(),
"machine": platform.machine(),
"processor": platform.processor(),
"cpu_percent": psutil.cpu_percent(interval=1),
"memory_percent": psutil.virtual_memory().percent,
}
if self.show_disk_usage:
disk = psutil.disk_usage('/')
info["disk_usage"] = {
"total_gb": round(disk.total / (1024**3), 1),
"used_gb": round(disk.used / (1024**3), 1),
"free_gb": round(disk.free / (1024**3), 1),
"percent": round((disk.used / disk.total) * 100, 1)
}
return info
async def connect(self):
server_url = f"http://localhost:{self.socket_port}"
await self.sio.connect(server_url)
# Wait for authentication
timeout = 10
start_time = asyncio.get_event_loop().time()
while not self.session_token:
if asyncio.get_event_loop().time() - start_time > timeout:
raise TimeoutError('Authentication timeout')
await asyncio.sleep(0.1)
async def handle_request(self, message):
method = message.get('method')
data = message.get('data', {})
if method == 'initialize':
return await self.initialize(data)
elif method == 'execute':
return await self.execute(data)
elif method == 'test':
return self.test()
elif method == 'shutdown':
return 'ok'
else:
return {'error': f'Unknown method: {method}'}
async def main():
plugin = SystemInfoPlugin()
await plugin.connect()
# Keep the plugin running
await plugin.sio.wait()
if __name__ == '__main__':
asyncio.run(main())
Step 4: Build the Executable
# Install dependencies
pip install psutil python-socketio pyinstaller
# Build single executable
pyinstaller --onefile plugin.py
# Copy executable to plugin folder
cp dist/plugin.exe my-plugin/
Step 5: Test Your Plugin
Launch your plugin through HyperHQ to test the Socket.IO connection and functionality. The plugin will automatically connect to HyperHQ's Socket.IO server and authenticate using the provided environment variables.
Communication with HyperHQ
HyperHQ plugins use Socket.IO for real-time bidirectional communication. For complete details, see the Socket.IO Connection Guide.
Message Format
All communication uses JSON messages with this structure:
interface PluginMessage {
id: string; // Unique request ID
type: 'request' | 'response' | 'error' | 'event';
method?: string; // For requests: method to call
data?: any; // Message payload
error?: string; // Error message (if type is 'error')
}
Request Types
| Type | When to Use | Example |
|---|---|---|
request | HyperHQ calling your plugin | Method execution |
response | Your plugin responding | Return data/results |
error | Something went wrong | Exception occurred |
event | Notify about status changes | Progress updates |
Example Communication Flow
// HyperHQ sends initialize request via Socket.IO
socket.emit('request', {
"id":"init-1",
"method":"initialize",
"data":{"settings":{"theme":"dark"}}
});
// Your plugin responds
socket.emit('response', {
"id":"init-1",
"type":"response",
"data":"initialized"
});
// HyperHQ sends execute request
socket.emit('request', {
"id":"exec-1",
"method":"execute",
"data":{"action":"scan_files"}
});
// Your plugin can send progress events
socket.emit('statusUpdate', {
"status":"scanning",
"message":"Scanning folder 1 of 4"
});
socket.emit('statusUpdate', {
"status":"scanning",
"message":"Scanning folder 2 of 4"
});
// Your plugin sends final response
socket.emit('response', {
"id":"exec-1",
"type":"response",
"data":{"files_found":1234}
});
Data Requests to HyperHQ
Your plugin can request data from HyperHQ via Socket.IO:
async def request_rom_list(self):
"""Ask HyperHQ for list of ROMs"""
await self.sio.emit('requestData', {
"method": "getRomList",
"params": {"system": "arcade"},
"requestId": "rom-request-1",
"sessionToken": self.session_token
})
# HyperHQ will respond with ROM data via 'dataResponse' event