Skip to main content

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)

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:

LanguageTemplate LinkBeginner Friendly
PythonDownload⭐⭐⭐⭐⭐
JavaScript (Node.js)Download⭐⭐⭐⭐⭐
C#Download⭐⭐⭐⭐
GoDownload⭐⭐⭐
RustDownload⭐⭐

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)

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:

MethodPurposeRequired
initializeSetup plugin with settings✅ Yes
executePerform main plugin action✅ Yes
testVerify plugin works correctly✅ Yes
shutdownClean up before exit⚠️ Optional

Step 3: Implement the Plugin (Python Example)

plugin.py
#!/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

Terminal
# 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:

types.ts
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

TypeWhen to UseExample
requestHyperHQ calling your pluginMethod execution
responseYour plugin respondingReturn data/results
errorSomething went wrongException occurred
eventNotify about status changesProgress 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:

plugin.py
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

ComponentPurposeProps
text-inputText entryplaceholder, maxLength, validation
number-inputNumeric entrymin, max, step
select-dropdownChoose from optionsoptions, multiple
file-pickerSelect filesextensions, directory
progress-barShow progressvalue, max, animated
buttonUser actionslabel, style, disabled
toggle-switchBoolean optionslabel, defaultValue
info-displayShow datatitle, value, format

UI Definition Example

plugin.json
{
"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:

plugin.py
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

TypeUse ForValidation Options
textStrings, pathspattern, minLength, maxLength
numberIntegers, floatsmin, max, step
booleanTrue/false flagsnone
selectMultiple choiceoptions array
fileFile pathsextensions filter
directoryFolder pathsnone

Settings Definition

plugin.json
{
"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

plugin.py
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

Pros: Easy Socket.IO integration, great libraries, simple deployment Cons: Larger executable size

plugin.py
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

plugin.js
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

Program.cs
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

main.go
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:

  1. Install the plugin in HyperHQ's plugin directory
  2. Launch HyperHQ - it will start the Socket.IO server
  3. Enable your plugin in HyperHQ settings
  4. Monitor the console for connection and authentication messages
  5. Test plugin functionality through the HyperHQ interface

Automated Testing

test_plugin.py
# 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:

plugin.py
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:

  1. Build your plugin following the build instructions for your language

  2. Create a plugin folder in the HyperHQ plugins directory:

    HyperHQ/plugins/your-plugin-name/
  3. Copy required files to your plugin folder:

    • Your compiled executable (e.g., plugin.exe)
    • The plugin.json manifest file
    • Optional: icon.png (128x128 PNG)
    • Optional: Any additional data files
  4. 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 executable field 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

plugin.json
{
"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

plugin.py
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

Terminal
# 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!