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
C# (.NET)
Pros: Excellent performance, small executables with AOT Cons: Steeper learning curve
using SocketIOClient;
using System;
var socket = new SocketIOClient.SocketIO("http://localhost:52789/plugin");
socket.On("connect", response => {
socket.EmitAsync("authenticate", new {
pluginId = Environment.GetEnvironmentVariable("HYPERHQ_PLUGIN_ID"),
challenge = Environment.GetEnvironmentVariable("HYPERHQ_AUTH_CHALLENGE")
});
});
socket.On("request", response => {
// Handle message...
socket.EmitAsync("plugin:response", result);
});
await socket.ConnectAsync();
await Task.Delay(-1);
// Build with:
// dotnet add package SocketIOClient
// dotnet publish -c Release --self-contained -r win-x64
Go
Pros: Fast, small executables, simple deployment Cons: More verbose Socket.IO handling
package main
import (
"os"
socketio_client "github.com/zhouhui8915/go-socket.io-client"
)
func main() {
opts := &socketio_client.Options{
Transport: "websocket",
Query: make(map[string]string),
}
client, _ := socketio_client.NewClient("http://localhost:52789/plugin", opts)
client.On("connect", func() {
client.Emit("authenticate", map[string]string{
"pluginId": os.Getenv("HYPERHQ_PLUGIN_ID"),
"challenge": os.Getenv("HYPERHQ_AUTH_CHALLENGE"),
})
})
client.On("request", func(data map[string]interface{}) {
// Handle message...
client.Emit("plugin:response", response)
})
}
// Build with:
// go get github.com/zhouhui8915/go-socket.io-client
// go build -o plugin.exe
Testing Your Plugin
Manual Testing
Test your plugin by launching it through HyperHQ:
- Install the plugin in HyperHQ's plugin directory
- Launch HyperHQ - it will start the Socket.IO server
- Enable your plugin in HyperHQ settings
- Monitor the console for connection and authentication messages
- Test plugin functionality through the HyperHQ interface
Automated Testing
# test_plugin.py
import socketio
import asyncio
import os
async def test_plugin():
"""Test plugin Socket.IO connection and methods"""
sio = socketio.AsyncClient()
# Set environment variables for testing
os.environ['HYPERHQ_PLUGIN_ID'] = 'test-plugin'
os.environ['HYPERHQ_AUTH_CHALLENGE'] = 'test-challenge-123'
os.environ['HYPERHQ_SOCKET_PORT'] = '52789'
connected = False
authenticated = False
responses = {}
@sio.on('connect', namespace='/plugin')
async def on_connect():
nonlocal connected
connected = True
print("✓ Connected to Socket.IO server")
@sio.on('authenticated', namespace='/plugin')
async def on_authenticated(response):
nonlocal authenticated
if response.get('success'):
authenticated = True
print("✓ Authentication successful")
@sio.on('plugin:response', namespace='/plugin')
async def on_response(data):
responses[data['id']] = data
print(f"✓ Received response: {data['id']}")
# Connect to server
await sio.connect('http://localhost:52789', namespaces=['/plugin'])
await asyncio.sleep(1)
assert connected, "Failed to connect"
assert authenticated, "Failed to authenticate"
print("All tests passed!")
if __name__ == '__main__':
asyncio.run(test_plugin())
Debug Mode
Add debug logging to your plugin:
import logging
import os
# Enable debug logging if environment variable set
if os.getenv('PLUGIN_DEBUG'):
logging.basicConfig(
filename='plugin-debug.log',
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def log_debug(message):
if os.getenv('PLUGIN_DEBUG'):
logging.debug(message)
# Usage:
log_debug(f"Received message: {message}")
log_debug(f"Sending response: {response}")
Plugin Installation
Manual Installation Process
Since the HyperHQ marketplace is not yet available, plugins must be installed manually:
-
Build your plugin following the build instructions for your language
-
Create a plugin folder in the HyperHQ plugins directory:
HyperHQ/plugins/your-plugin-name/ -
Copy required files to your plugin folder:
- Your compiled executable (e.g.,
plugin.exe) - The
plugin.jsonmanifest file - Optional:
icon.png(128x128 PNG) - Optional: Any additional data files
- Your compiled executable (e.g.,
-
Restart HyperHQ - your plugin will be automatically loaded
Plugin Folder Structure
HyperHQ/
└── plugins/
└── your-plugin-name/
├── plugin.json # Required manifest
├── plugin.exe # Required executable
├── icon.png # Optional 128x128 PNG icon
├── README.md # Optional documentation
└── data/ # Optional plugin data folder
Important Notes
- Plugin folder name should match the plugin ID in
plugin.json - Executable name must match the
executablefield in manifest - All file paths are relative to the plugin folder
- HyperHQ will create a
%PLUGIN_DATA%folder for your plugin's data
Publishing Guidelines
Package Structure (for future distribution)
my-plugin-v1.0.0.zip
├── plugin.json # Required manifest
├── plugin.exe # Required executable
├── icon.png # Recommended 128x128 PNG
├── README.md # Installation/usage docs
├── LICENSE.txt # Your chosen license
└── examples/ # Optional usage examples
├── test-data.json
└── sample-config.json
Quality Checklist
Before publishing, ensure your plugin:
- Works on clean Windows system (test on VM)
- Handles all required methods (initialize, execute, test)
- Validates user settings (check required fields)
- Has proper error handling (doesn't crash on bad input)
- Includes clear documentation (README.md)
- Follows naming conventions (kebab-case plugin ID)
- Has reasonable resource usage (memory, CPU)
- Responds within 30 seconds (for long operations, send progress)
Manifest Best Practices
{
"id": "kebab-case-plugin-name", // Required: lowercase, hyphens only
"name": "Human Readable Name", // Required: title case
"version": "1.0.0", // Required: semantic versioning
"description": "Brief description (under 100 chars)", // Required
"author": "Your Name <[email protected]>", // Required
"homepage": "https://github.com/user/plugin", // Recommended
"license": "MIT", // Recommended
"keywords": ["emulation", "arcade", "utility"], // Recommended
"minHyperHQVersion": "2.0.0" // Optional: compatibility
}
Troubleshooting
Common Issues
Plugin doesn't respond
- Check executable has correct permissions
- Verify JSON parsing isn't failing
- Add debug logging to see what's happening
- Test manually with echo commands
Settings not working
- Verify setting keys match manifest exactly
- Check default values are appropriate types
- Validate required settings in initialize method
- Log received settings for debugging
UI not displaying
- Check manifest UI syntax is valid JSON
- Verify component types are supported
- Ensure component IDs are unique
- Test with minimal UI first
Performance issues
- Profile your plugin's memory usage
- Use streaming for large data processing
- Send progress updates for long operations
- Consider caching expensive computations
Debug Tools
Message Inspector: Log all Socket.IO messages
import logging
# Setup logging
logging.basicConfig(
filename='plugin-messages.log',
level=logging.DEBUG,
format='%(asctime)s - %(message)s'
)
# Log incoming messages
@sio.on('request', namespace='/plugin')
async def on_request(data):
logging.debug(f"IN: {data}")
response = await handle_request(data)
logging.debug(f"OUT: {response}")
await sio.emit('plugin:response', response, namespace='/plugin')
Plugin Validator: Test your plugin package
# Download validator tool
curl -o plugin-validator.exe https://hyperai.dev/tools/plugin-validator.exe
# Validate your plugin
./plugin-validator.exe my-plugin.zip
Ready to build something amazing? Start with one of our starter templates and join the HyperHQ plugin community!