Skip to main content

Skills

Skills are plugin packages that extend Open Genie with new actions. They're loaded dynamically at boot from the data/skills/ directory.

Structure

Each skill is a directory containing a manifest and action definitions:

data/skills/
├── weather/
│ ├── manifest.json # Skill metadata and configuration
│ └── actions.ts # Action handler implementations
└── media-grab/
├── manifest.json
└── actions.ts

Manifest Format

{
"name": "weather",
"version": "1.0.0",
"description": "Fetch weather forecasts and current conditions",
"actions": ["get_weather", "get_forecast"],
"cron": [
{
"schedule": "0 6 * * *",
"action": "get_forecast"
}
],
"config": {
"api_key": {
"type": "string",
"description": "Weather API key",
"required": true
},
"units": {
"type": "string",
"description": "Temperature units",
"default": "metric"
}
}
}

Manifest Fields

FieldTypeRequiredDescription
namestringyesUnique skill identifier
versionstringyesSemver version
descriptionstringyesHuman-readable description
actionsstring[]yesList of action names this skill provides
cronarraynoAuto-scheduled jobs to create
configobjectnoConfiguration schema for the skill

Action File

The actions.ts (or actions.js) file must export action definitions that match the action registry format:

// data/skills/weather/actions.ts

export const get_weather = {
name: "get_weather",
description: "Get current weather for a location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City name or coordinates",
},
},
required: ["location"],
},
handler: async (params: { location: string }) => {
const response = await fetch(
`https://api.weather.example/current?q=${params.location}`
);
const data = await response.json();
return { output: JSON.stringify(data) };
},
};

export const get_forecast = {
name: "get_forecast",
description: "Get 5-day weather forecast",
parameters: {
type: "object",
properties: {
location: { type: "string", description: "City name" },
},
required: ["location"],
},
handler: async (params: { location: string }) => {
// ... fetch forecast
return { output: JSON.stringify(forecastData) };
},
};

Loading Process

At boot, loadSkills() (lib/skills/loader.ts):

  1. Scans each subdirectory in data/skills/
  2. Reads and validates manifest.json
  3. Dynamically imports actions.ts or actions.js
  4. Registers each exported action in the global actionRegistry
  5. If the manifest defines cron entries, schedules them
data/skills/weather/
→ Read manifest.json
→ Import actions.ts
→ Register get_weather, get_forecast
→ Schedule daily forecast job

Creating a Skill

  1. Create a directory under data/skills/:

    mkdir data/skills/my-skill
  2. Write a manifest.json:

    {
    "name": "my-skill",
    "version": "1.0.0",
    "description": "What my skill does",
    "actions": ["my_action"]
    }
  3. Write actions.ts:

    export const my_action = {
    name: "my_action",
    description: "What this action does",
    parameters: {
    type: "object",
    properties: {
    input: { type: "string", description: "Input value" },
    },
    required: ["input"],
    },
    handler: async (params: { input: string }) => {
    return { output: `Result: ${params.input}` };
    },
    };
  4. Restart the server — the skill will be loaded automatically.

REST API

GET /api/skills

Returns all loaded skills with their manifests.

Key Files

FilePurpose
lib/skills/loader.tsSkill discovery, loading, and registration
data/skills/*/manifest.jsonSkill metadata
data/skills/*/actions.tsAction implementations