HyperHQ Plugin Developer Guide
Welcome to HyperHQ Plugin Development!
This guide will walk you through creating your first plugin, regardless of your programming background. We'll start simple and build up to more complex examples.
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
We provide ready-to-use templates for popular languages:
| Language | Template Link | Beginner Friendly |
|---|---|---|
| Python | Download | ⭐⭐⭐⭐⭐ |
| JavaScript (Node.js) | Download | ⭐⭐⭐⭐⭐ |
| C# | Download | ⭐⭐⭐⭐ |
| Go | Download | ⭐⭐⭐ |
| Rust | Download | ⭐⭐ |
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
Congratulations! You've just created your first HyperHQ plugin.
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?
✅ Works Everywhere: No need to install Python, .NET, Node.js, etc. ✅ Crash-Safe: Your plugin can't crash HyperHQ ✅ Real-time Communication: Socket.IO enables bidirectional, event-driven communication ✅ Secure: Runs in its own process with controlled permissions
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",
"executable": "plugin.exe",
"ui": {
"displayName": "System Information",
"icon": "icon.png",
"category": "utility",
"settings": [
{
"key": "refreshInterval",
"name": "Refresh Interval (seconds)",
"type": "number",
"defaultValue": 5,
"description": "How often to refresh system info",
"validation": {
"min": 1,
"max": 60
}
},
{
"key": "showDiskUsage",
"name": "Show Disk Usage",
"type": "boolean",
"defaultValue": true,
"description": "Display disk usage information"
}
]
},
"permissions": {
"filesystem": ["%PLUGIN_DATA%"],
"processLaunching": false,
"networkAccess": false
}
}
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', namespace='/plugin')
async def on_connect():
print('Connected to HyperHQ')
await self.sio.emit('authenticate', {
'pluginId': self.plugin_id,
'challenge': self.auth_challenge
}, namespace='/plugin')
@self.sio.on('authenticated', namespace='/plugin')
async def on_authenticated(response):
if response.get('success'):
self.session_token = response.get('sessionToken')
print('Authentication successful')
await self.register()
@self.sio.on('request', namespace='/plugin')
async def on_request(data):
response = await self.handle_request(data)
await self.sio.emit('plugin:response', {
'id': data['id'],
'type': 'response',
'data': response,
'sessionToken': self.session_token
}, namespace='/plugin')
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, namespaces=['/plugin'])
# 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 register(self):
await self.sio.emit('plugin:register', {
'id': self.plugin_id,
'version': '1.0.0',
'capabilities': ['socketio', 'systemInfo']
}, namespace='/plugin')
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('plugin: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('plugin:progress', {
"id":"exec-1",
"progress":25,
"message":"Scanning folder 1 of 4"
});
socket.emit('plugin:progress', {
"id":"exec-1",
"progress":50,
"message":"Scanning folder 2 of 4"
});
// Your plugin sends final response
socket.emit('plugin: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
}, namespace='/plugin')
# HyperHQ will respond with ROM data via 'dataResponse' event
Creating UI Components
HyperHQ provides a library of pre-built UI components for consistent appearance.
Available Components
| Component | Purpose | Props |
|---|---|---|
text-input | Text entry | placeholder, maxLength, validation |
number-input | Numeric entry | min, max, step |
select-dropdown | Choose from options | options, multiple |
file-picker | Select files | extensions, directory |
progress-bar | Show progress | value, max, animated |
button | User actions | label, style, disabled |
toggle-switch | Boolean options | label, defaultValue |
info-display | Show data | title, value, format |
UI Definition Example
{
"ui": {
"type": "simple",
"theme": "default",
"layout": {
"components": [
{
"id": "rom-path",
"type": "file-picker",
"label": "ROM Directory",
"props": {
"directory": true,
"placeholder": "Select ROM folder..."
},
"validation": "required"
},
{
"id": "scan-progress",
"type": "progress-bar",
"label": "Scanning Progress",
"props": {
"animated": true,
"showPercentage": true
},
"dataSource": {
"type": "plugin-event",
"eventType": "progress"
}
},
{
"id": "results-display",
"type": "info-display",
"label": "Scan Results",
"props": {
"format": "key-value-list"
},
"dataSource": {
"type": "plugin-response",
"method": "execute"
}
}
]
}
}
}
Dynamic UI Updates
Your plugin can update UI components in real-time via Socket.IO:
async def update_progress(self, current, total, message):
"""Update progress bar component"""
await self.sio.emit('plugin:progress', {
"componentId": "scan-progress",
"progress": current,
"total": total,
"message": message
}, namespace='/plugin')
Working with Settings
Setting Types
| Type | Use For | Validation Options |
|---|---|---|
text | Strings, paths | pattern, minLength, maxLength |
number | Integers, floats | min, max, step |
boolean | True/false flags | none |
select | Multiple choice | options array |
file | File paths | extensions filter |
directory | Folder paths | none |
Settings Definition
{
"settings": [
{
"key": "apiKey",
"name": "API Key",
"type": "text",
"defaultValue": "",
"description": "Your API key for external service",
"required": true,
"validation": {
"pattern": "^[A-Za-z0-9]{32}$",
"minLength": 32,
"maxLength": 32
}
},
{
"key": "timeout",
"name": "Request Timeout",
"type": "number",
"defaultValue": 30,
"description": "Timeout in seconds",
"validation": {
"min": 5,
"max": 300
}
},
{
"key": "logLevel",
"name": "Log Level",
"type": "select",
"defaultValue": "info",
"options": ["debug", "info", "warning", "error"],
"description": "Logging verbosity"
}
]
}
Accessing Settings in Your Plugin
def initialize(self, data):
"""Receive settings when plugin starts"""
settings = data.get('settings', {})
self.api_key = settings.get('apiKey', '')
self.timeout = settings.get('timeout', 30)
self.log_level = settings.get('logLevel', 'info')
# Validate required settings
if not self.api_key:
return {"error": "API Key is required"}
return "initialized"
Language-Specific Examples
Python (Recommended for Beginners)
Pros: Easy Socket.IO integration, great libraries, simple deployment Cons: Larger executable size
import socketio
import asyncio
import os
sio = socketio.AsyncClient()
@sio.on('connect', namespace='/plugin')
async def on_connect():
await sio.emit('authenticate', {
'pluginId': os.environ.get('HYPERHQ_PLUGIN_ID'),
'challenge': os.environ.get('HYPERHQ_AUTH_CHALLENGE')
}, namespace='/plugin')
@sio.on('request', namespace='/plugin')
async def on_request(data):
# Handle message...
await sio.emit('plugin:response', response, namespace='/plugin')
async def main():
await sio.connect('http://localhost:52789', namespaces=['/plugin'])
await sio.wait()
# Build with:
# pip install python-socketio pyinstaller
# pyinstaller --onefile plugin.py
JavaScript/Node.js
Pros: Familiar for web developers, native Socket.IO support Cons: Large Node.js runtime overhead
const io = require('socket.io-client');
const socket = io('http://localhost:52789/plugin');
socket.on('connect', () => {
socket.emit('authenticate', {
pluginId: process.env.HYPERHQ_PLUGIN_ID,
challenge: process.env.HYPERHQ_AUTH_CHALLENGE
});
});
socket.on('request', (data) => {
// Handle message...
socket.emit('plugin:response', response);
});
// Build with pkg:
// npm install socket.io-client
// npm install -g pkg
// pkg plugin.js --target node18-win-x64