Skip to main content

HyperHQ Plugin API Reference

Complete reference for developers building HyperHQ plugins.

Required Plugin Methods

Every plugin must implement these methods:

initialize(data)

Purpose: Receive settings and prepare plugin for use Called: When plugin starts or settings change Parameters: { settings: {}, pluginData: {} } Must Return: "initialized" on success, {error: "message"} on failure

// Example
function initialize(data) {
this.settings = data.settings;
this.apiKey = this.settings.apiKey;

if (!this.apiKey) {
return { error: "API Key is required" };
}

return "initialized";
}

execute(data)

Purpose: Perform main plugin functionality Called: When user activates plugin Parameters: { action: "action_name", ...otherData } Must Return: Any data or {error: "message"} on failure

// Example
function execute(data) {
const action = data.action || "default";

switch (action) {
case "scan_files":
return this.scanFiles(data.path);
case "get_status":
return { status: "running", files: 1234 };
default:
return { error: `Unknown action: ${action}` };
}
}

test(data)

Purpose: Health check for plugin Called: When HyperHQ needs to verify plugin works Parameters: {} Must Return: true if healthy, false if not

// Example
function test(data) {
try {
// Test if we can perform basic operations
const testResult = this.performQuickTest();
return testResult.success;
} catch (error) {
return false;
}
}

shutdown(data) (Optional)

Purpose: Clean up before plugin exits Called: When HyperHQ is closing Parameters: {} Must Return: "ok"

Communication Protocol

All HyperHQ plugins communicate via Socket.IO (WebSocket-based real-time bidirectional communication).

Connection Details:

  • Server URL: http://localhost:${HYPERHQ_SOCKET_PORT}
  • Namespace: default Socket.IO namespace (/)
  • Transport: WebSocket, with polling fallback for standard Socket.IO clients
  • Authentication: Challenge-response model
  • Environment Variables:
    • HYPERHQ_PLUGIN_ID - Your plugin's unique identifier
    • HYPERHQ_AUTH_CHALLENGE - Authentication challenge token
    • HYPERHQ_SOCKET_PORT - Socket server port. HyperHQ tries 52789 first, then 52790-52818, then an OS-assigned port.
    • HYPERHQ_PLUGIN_NAME - Your plugin display name
    • HYPERHQ_PLUGIN_VERSION - Your plugin version

Benefits:

  • Real-time bidirectional communication
  • Event-based architecture
  • Progress updates and logging
  • Request/response pattern
  • Connection state management

Implemented Socket.IO event names:

DirectionEventPurpose
Plugin -> HyperHQauthenticateSend {pluginId, challenge} or reconnect with {pluginId, sessionToken}
HyperHQ -> PluginauthenticatedReturns {success, pluginId, sessionToken, serverPort}
HyperHQ -> PluginrequestCalls plugin methods such as initialize, execute, test, and shutdown
Plugin -> HyperHQresponseResponds to a request message with {id, type: "response", data}
Plugin -> HyperHQrequestData or request_dataRequests HyperHQ data/actions
HyperHQ -> PlugindataResponse and data_responseResponse to requestData/request_data
Plugin -> HyperHQsubscribeEventsSubscribes to broadcast events
HyperHQ -> PlugineventsSubscribedConfirms event subscriptions
HyperHQ -> PluginhyperHqEventBroadcasts subscribed events
Plugin -> HyperHQstatusUpdateSends progress/status text and keeps active requests alive
Plugin -> HyperHQrequestFileRequests a file from the plugin's own install directory
HyperHQ -> PluginfileDataReturns base64 file data or an error

See Socket.IO Connection Guide for complete implementation details.

HyperHQ Plugin API (JavaScript Plugins)

JavaScript plugins have access to the global hyperai object:

Data Access Methods

hyperai.data = {
// Get all ROMs, optionally filtered by system
getRoms: async (systemId?: string) => Promise<Rom[]>,

// Get all systems
getSystems: async () => Promise<System[]>,

// Get all games for a specific system
getGames: async (systemId: string) => Promise<Game[]>,

// Get media file for a game
getMedia: async (gameId: string, mediaType: string) => Promise<MediaFile>
}

Settings Management

hyperai.settings = {
// Get a single setting value
get: async (key: string) => Promise<any>,

// Set a single setting value
set: async (key: string, value: any) => Promise<void>,

// Get all plugin settings
getAll: async () => Promise<Record<string, any>>,

// Update multiple settings at once
setAll: async (settings: Record<string, any>) => Promise<void>
}

UI Interactions

hyperai.ui = {
// Show a notification to the user
showNotification: (message: string, type?: 'info' | 'success' | 'warning' | 'error') => void,

// Show a dialog and wait for user response
showDialog: async (options: DialogOptions) => Promise<any>,

// Update plugin status display
updateStatus: (status: string) => void
}

File System (Sandboxed)

hyperai.fs = {
// Read file contents as string
readFile: async (path: string) => Promise<string>,

// Write string content to file
writeFile: async (path: string, content: string) => Promise<void>,

// Check if file/directory exists
exists: async (path: string) => Promise<boolean>,

// List files in directory
list: async (path: string) => Promise<string[]>
}

Logging

hyperai.log = {
debug: (message: string, data?: any) => void,
info: (message: string, data?: any) => void,
warn: (message: string, data?: any) => void,
error: (message: string, data?: any) => void
}

Socket.IO Data Request Methods

Executable plugins using Socket.IO can request data from HyperHQ.

Quick Reference

Blocking Methods (await response):

Fire-and-Forget Methods (immediate acknowledgment):

How Data Requests Work

Request Format

socket.emit('requestData', {
method: 'methodName',
params: { /* parameters */ },
requestId: 'unique-request-id',
sessionToken: sessionToken // From authentication
});

request_data is also accepted for compatibility. HyperHQ emits both dataResponse and data_response for each data request response; listen to one of them and de-duplicate by requestId if your client subscribes to both.

Available Data Methods

getSystems (Blocking - Awaits Response)

Retrieves all game systems.

Flow:

Code:

// Request
socket.emit('requestData', {
method: 'getSystems',
params: {},
requestId: 'get-systems-001',
sessionToken: sessionToken
});

// Response arrives via 'dataResponse' event
socket.on('dataResponse', (response) => {
if (response.requestId === 'get-systems-001') {
const systems = response.data.systems;
}
});

Returns: Array of system objects with properties:

  • id: string - Unique system identifier
  • name: string - Display name
  • displayName: string - Display name from HyperHQ
  • platform: string - Platform name
  • referenceId: string - External reference ID
  • gamesCount: number - Number of ROMs/games

getMediaFolders (Blocking - Awaits Response)

Retrieves media folder paths for a system.

Flow:

Code:

// Request
socket.emit('requestData', {
method: 'getMediaFolders',
params: {
systemReferenceId: 'steam',
mediaTypes: ['boxart', 'background', 'screenshot', 'logo']
},
requestId: 'get-folders-001',
sessionToken: sessionToken
});

// Response
{
requestId: 'get-folders-001',
success: true,
data: {
folders: {
boxart: 'C:\\HyperSpin\\Media\\Steam\\Images\\Artwork1',
background: 'C:\\HyperSpin\\Media\\Steam\\Images\\Background',
screenshot: 'C:\\HyperSpin\\Media\\Steam\\Images\\Screenshot',
logo: 'C:\\HyperSpin\\Media\\Steam\\Images\\Logo'
}
}
}

createSystem (Fire-and-Forget)

Creates a new game system.

Flow:

Code:

socket.emit('requestData', {
method: 'createSystem',
params: {
name: 'Steam',
description: 'Steam games library',
platform: 'PC',
referenceId: 'steam', // Plugin-provided identifier
allowedExtensions: '.exe',
enabled: true
},
requestId: 'create-system-001',
sessionToken: sessionToken
});

createEmulator (Fire-and-Forget)

Creates a new emulator configuration.

Flow:

Code:

socket.emit('requestData', {
method: 'createEmulator',
params: {
name: 'RetroArch',
type: 'RetroArch',
executable: 'C:\\RetroArch\\retroarch.exe',
commandLine: '-L cores/[core] [romPath]',
extensions: '.nes,.snes,.gb',
enabled: true
},
requestId: 'create-emu-001',
sessionToken: sessionToken
});

addGames (Fire-and-Forget)

Adds games/ROMs to a system.

Flow:

Code:

socket.emit('requestData', {
method: 'addGames',
params: {
systemName: 'Steam',
games: [
{
name: 'Half-Life 2',
fileName: 'hl2.exe',
romPath: 'C:\\Steam\\HL2',
gameReferenceId: 'steam_220',
developer: 'Valve',
publisher: 'Valve',
releaseYear: '2004',
genres: 'FPS'
}
]
},
requestId: 'add-games-001',
sessionToken: sessionToken
});

getGamesForSystem (Blocking - Awaits Response)

Retrieves all games for a specific system.

Flow:

Code:

// Request
socket.emit('requestData', {
method: 'getGamesForSystem',
params: {
systemId: 'steam'
},
requestId: 'get-games-001',
sessionToken: sessionToken
});

// Response arrives via 'dataResponse' event
socket.on('dataResponse', (response) => {
if (response.requestId === 'get-games-001') {
const games = response.data.games;
const systemId = response.data.systemId;
const systemName = response.data.systemName;
}
});

Returns: Array of game objects with properties:

  • id: string - Unique game identifier
  • name: string - Game name
  • displayName: string - Display name
  • fileName: string - ROM filename
  • romPath: string - Full path to ROM file
  • systemId: string - Parent system ID
  • referenceId: string - External reference ID (optional)
  • developer: string - Game developer
  • publisher: string - Game publisher
  • releaseYear: string - Release year
  • plays: number - Play count
  • hidden: boolean - Whether game is hidden
  • favourite: boolean - Whether game is marked as favorite
  • steamAppId: string - Steam App ID when available
  • isSteam: boolean - Whether game is a Steam game

launchGame (Fire-and-Forget)

Launches a game by its ID.

Flow:

Code:

socket.emit('requestData', {
method: 'launchGame',
params: {
gameId: '12345-abcde-67890'
},
requestId: 'launch-game-001',
sessionToken: sessionToken
});

Parameters:

  • gameId: string - The unique ID of the game to launch

Notes:

  • This is an asynchronous operation that returns immediately
  • Game launch happens in the background
  • Listen for gameLaunched events to know when the game actually starts
  • Errors during launch will be emitted as separate events

Response Format

All Socket.IO data responses follow this structure:

{
requestId: string; // Matches your request
success: boolean; // true if successful
data?: any; // Response data (if success)
error?: string; // Error message (if failed)
}

Event Subscription System

Plugins can subscribe to HyperHQ events to receive real-time notifications about game launches, system changes, and other activities.

How Event Subscription Works

Subscribing to Events

After authentication, subscribe to events you want to receive:

// Subscribe to multiple events
socket.emit('subscribeEvents', [
'gameLaunched',
'gameClosed',
'systemChanged',
'romSelected'
]);

// Confirmation
socket.on('eventsSubscribed', (data) => {
console.log('Subscribed to events:', data.events);
});

Listening for Events

Once subscribed, listen for the hyperHqEvent event:

socket.on('hyperHqEvent', (event) => {
console.log('Event type:', event.type);
console.log('Event data:', event.data);
console.log('Timestamp:', event.timestamp);

// Handle specific events
switch (event.type) {
case 'gameLaunched':
handleGameLaunched(event.data);
break;
case 'gameClosed':
handleGameClosed(event.data);
break;
case 'systemChanged':
handleSystemChanged(event.data);
break;
case 'romSelected':
handleRomSelected(event.data);
break;
}
});

Available Events

EventWhen TriggeredData Format
gameLaunchedWhen a game starts{gameId: string, gameName: string, systemId: string, timestamp: number}
gameClosedWhen a game exits{gameId: string, gameName: string, systemId: string, exitCode: number, timestamp: number}
systemChangedWhen active system changes{systemId: string, systemName: string, timestamp: number}
romSelectedWhen user selects a ROM{gameId: string, gameName: string, systemId: string, timestamp: number}
mediaProgressMedia download progress{gameId: string, mediaType: string, progress: number, timestamp: number}
mediaDownloadStartMedia download starts{gameId: string, mediaType: string, timestamp: number}
mediaDownloadFinishMedia download completes{gameId: string, mediaType: string, success: boolean, timestamp: number}
dbConnectedDatabase connection established{timestamp: number}

Complete Example

// After authentication
socket.on('authenticated', (response) => {
if (response.success) {
console.log('Authenticated. Session token:', response.sessionToken);

// Subscribe to game lifecycle events
socket.emit('subscribeEvents', [
'gameLaunched',
'gameClosed'
]);
}
});

// Handle subscription confirmation
socket.on('eventsSubscribed', (data) => {
console.log('Subscribed to:', data.events);
});

// Listen for events
socket.on('hyperHqEvent', (event) => {
if (event.type === 'gameLaunched') {
console.log(`Game launched: ${event.data.gameName}`);
console.log(` Game ID: ${event.data.gameId}`);
console.log(` System: ${event.data.systemId}`);

// Example: Track play time
startPlayTimeTracking(event.data.gameId);
}

if (event.type === 'gameClosed') {
console.log(`Game closed: ${event.data.gameName}`);
console.log(` Exit code: ${event.data.exitCode}`);
console.log(` Duration: ${event.data.playTime}ms`);

// Example: Save play session
savePlaySession(event.data);
}
});

// Example: Launch a game and wait for confirmation
socket.emit('requestData', {
method: 'launchGame',
params: { gameId: 'game-123' },
requestId: 'launch-001',
sessionToken: sessionToken
});

// The launchGame method returns immediately, but you can listen for the actual launch
socket.on('hyperHqEvent', (event) => {
if (event.type === 'gameLaunched' && event.data.gameId === 'game-123') {
console.log('Game actually started');
}
});

Event Flow for Game Launch

Best Practices

Do:

  • Subscribe only to events you actually need.
  • Handle events asynchronously to avoid blocking.
  • Unsubscribe from events when no longer needed, if your client supports it.
  • Check event.type before processing event.data.
  • Validate event data structure before using it.

Avoid:

  • Subscribing to all events "just in case".
  • Performing heavy operations directly in event handlers.
  • Assuming every event has the exact same shape.
  • Blocking the event handler with synchronous work.

Plugin Logging and Status

HyperHQ currently captures plugin process output through the launcher and accepts live status updates over Socket.IO. The Socket.IO server does not currently implement plugin_log, pluginLog, plugin:progress, or plugin:notify as documented events.

Use local logging for detailed diagnostics, and use statusUpdate for short user-visible progress or heartbeat messages while a HyperHQ-initiated request is running.

Diagnostics Flow

Status Update Event

statusUpdate accepts this payload:

{
status: string;
message?: string;
}

Example:

socket.emit('statusUpdate', {
status: 'syncing',
message: 'Imported 120 of 450 games'
});

When a plugin is handling a request from HyperHQ, sending statusUpdate also resets the active request timeout. This is the supported way to keep long operations such as scans, imports, downloads, and setup wizards alive.

Long-Running Request Flow

Error Handling Flow

For persistent diagnostic logs, write to stdout/stderr and/or a local plugin log file. Do not send secrets such as auth headers, cookies, API keys, OAuth tokens, or session tokens.

Plugin Settings System

Setting Types

TypeUse ForValidation
textStringspattern, minLength, maxLength
passwordSensitive stringsminLength, maxLength
numberNumbersmin, max, step
booleanTrue/falseNone
selectMultiple choiceoptions array
fileFile pathsextensions filter
directoryFolder pathsNone

Settings Definition

{
"settings": [
{
"key": "apiKey",
"type": "password",
"label": "API Key",
"defaultValue": "",
"description": "Your API key for external service",
"required": true,
"validation": {
"minLength": 16,
"maxLength": 64
}
},
{
"key": "timeout",
"type": "number",
"label": "Request Timeout",
"defaultValue": 30,
"description": "Timeout in seconds",
"validation": {
"min": 5,
"max": 300,
"step": 5
}
},
{
"key": "autoConnect",
"type": "boolean",
"label": "Auto Connect",
"defaultValue": true,
"description": "Automatically connect on startup"
},
{
"key": "logLevel",
"type": "select",
"label": "Log Level",
"defaultValue": "info",
"options": [
{"label": "Debug", "value": "debug"},
{"label": "Info", "value": "info"},
{"label": "Warning", "value": "warning"},
{"label": "Error", "value": "error"}
]
}
]
}

Plugin Manifest Schema

Complete Manifest Example

{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "Short description of what your plugin does",
"author": "Your Name <[email protected]>",
"homepage": "https://github.com/user/my-plugin",
"repository": "https://github.com/user/my-plugin",
"license": "MIT",
"keywords": ["utility", "automation", "sync"],

"type": "executable",
"executable": "plugin.exe",
"executableProviders": {
"windows": "plugin.exe",
"linux": "plugin",
"macos": "plugin"
},
"main": "index.js",

"communication": {
"preferred": "socketio",
"fallback": "stdio",
"socketio": {
"enabled": true,
"autoReconnect": true,
"heartbeat": 30000,
"events": ["gameLaunched", "gameClosed", "mediaProgress"],
"fileStreaming": true,
"dataRequests": true
}
},

"capabilities": [
{
"name": "game-import",
"description": "Import games into HyperHQ",
"required": true
},
{
"name": "media-download",
"description": "Download media assets",
"required": false
}
],

"permissions": [
{
"type": "file",
"scope": "plugin-directory",
"description": "Read files bundled with this plugin"
},
{
"type": "network",
"scope": "external-apis",
"description": "Call external service APIs"
}
],

"ui": {
"hasSettingsPanel": true,
"hasMainPanel": false,
"hasStatusIndicator": true
},

"settings": [...],

"actions": [
{
"id": "scan_library",
"label": "Scan Game Library",
"description": "Scan for new games",
"icon": "scan",
"type": "primary"
}
],

"hyperaiVersion": "1.2.0",
"platforms": ["windows", "macos", "linux"]
}

Manifest Field Reference

FieldTypeRequiredDescription
idstringRequiredUnique plugin identifier using lowercase letters, numbers, hyphens, or underscores
namestringRequiredDisplay name
versionstringRequiredSemantic version, such as 1.0.0
descriptionstringRequiredBrief description
authorstringRequiredAuthor name/email
typestringRequiredjavascript or executable
executablestringFor executableDefault executable filename
executableProvidersobjectOptionalPlatform-specific executable names for windows, linux, and macos
mainstringFor JavaScriptEntry point file
communicationobjectOptionalPreferred/fallback communication and Socket.IO flags
capabilitiesobject[]RequiredCapabilities shaped as {name, description, required}
permissionsobject[]OptionalPermissions shaped as {type, scope, description}
settingsobject[]OptionalPlugin settings schema
actionsobject[]OptionalUI actions shaped as {id, label, description, icon, type}
onboardingobjectOptionalSetup wizards and wizard steps
hyperaiVersionstringOptionalCompatible HyperAI/HyperHQ host version
platformsstring[]OptionalSupported platforms: windows, macos, linux

Permission Types

PermissionPurposeExample
fileFile access{ "type": "file", "scope": "plugin-directory", "description": "Read bundled assets" }
networkNetwork access{ "type": "network", "scope": "external-apis", "description": "Call provider APIs" }
systemSystem operations{ "type": "system", "scope": "emulator-executable", "description": "Launch emulator tools" }
databaseHyperHQ data changes{ "type": "database", "scope": "roms", "description": "Add games to HyperHQ" }

Error Handling

Common Error Patterns

// Validation errors
if (!requiredParam) {
return { error: "Missing required parameter: requiredParam" };
}

// Operation errors
try {
const result = await riskyOperation();
return result;
} catch (error) {
return { error: `Operation failed: ${error.message}` };
}

// Timeout errors
const timeout = setTimeout(() => {
return { error: "Operation timed out" };
}, 30000);

Error Response Format

{
id: "request-id",
type: "error",
error: "Human readable error message",
details: { // Optional additional info
code: "ERROR_CODE",
stack: "...",
context: {...}
}
}

Best Practices

Performance

  • Use Socket.IO events for all communication
  • Send progress events for long operations (>5 seconds)
  • Cache expensive computations
  • Use streaming for large data sets where applicable
  • Batch data requests when possible

Security

  • Validate all input parameters
  • Don't trust external data
  • Use minimal required permissions
  • Sanitize file paths
  • Never log sensitive data (API keys, passwords)

Reliability

  • Handle all exceptions gracefully
  • Test with invalid/missing data
  • Implement proper timeouts (30s max)
  • Provide meaningful error messages
  • Implement graceful shutdown

User Experience

  • Use clear, helpful setting descriptions
  • Provide good default values
  • Send progress updates for slow operations
  • Include usage examples in README
  • Test on clean systems

Environment Variables

HyperHQ provides these environment variables to plugins:

VariableDescriptionExample
HYPERHQ_PLUGIN_IDYour plugin's ID"my-plugin"
HYPERHQ_AUTH_CHALLENGEAuthentication challenge"abc123..."
HYPERHQ_SOCKET_PORTSocket.IO server port"52789"
PLUGIN_DATA_DIRYour plugin's data folder"C:\ProgramData\..."
PLUGIN_DEBUGDebug mode flag"true" / "false"

Quick Test Commands

# Test initialize
echo '{"id":"1","type":"request","method":"initialize","data":{"settings":{}}}' | ./plugin.exe

# Test execute
echo '{"id":"2","type":"request","method":"execute","data":{"action":"test"}}' | ./plugin.exe

# Test health
echo '{"id":"3","type":"request","method":"test","data":{}}' | ./plugin.exe

# Test shutdown
echo '{"id":"4","type":"request","method":"shutdown","data":{}}' | ./plugin.exe

Language Templates

Download ready-to-use starter templates:

Each template includes:

  • Complete working plugin example with Socket.IO support
  • Authentication implementation
  • Build scripts for your platform
  • Testing utilities
  • Comprehensive documentation

For complete tutorials and examples, see: