Plugin SDK
A plugin is described by one manifest.json. The contract that validates the
manifest during development is the same one the platform enforces at install
time, so a manifest that passes validate is a manifest the marketplace will
accept. The aihummer plugin CLI covers the whole lifecycle: from scaffold to
signing and publishing.
One manifest, one contract
A plugin has exactly one source of truth — its manifest.json. It declares the
plugin kind (kind), how it is configured (config[]), its capabilities, and —
for host-native services — the install[] steps and start command that the
SystemdDeployer runs. Because development
and installation use the same validation contract, “valid manifest” and
“installable plugin” mean the same thing.
[!NOTE] The manifest describes a plugin’s contract, not its store-page name. The machine
slugcomes from the directory/bundle name (private side-load) or from a submission field (public) — see Publishing a plugin.
CLI
aihummer plugin bundles the development, packaging and publishing commands:
# Scaffold a manifest (kind: connector | service | openapi | mcp)
aihummer plugin init <kind> [dir]
# Validate a manifest against the install contract
aihummer plugin validate <manifest.json>
# Generate an ed25519 author key (writes <prefix>.key and <prefix>.pub)
aihummer plugin keygen [--out <prefix>]
# Build and package the plugin into a release tarball + .sha256
aihummer plugin package <dir> [--out <file>] [--slug <slug>] [--build "<cmd>"]
# Sign the release identity (slug\0version\0source_ref); with --manifest the
# signature is embedded into the manifest.signature field
aihummer plugin sign --key <priv> [--manifest <m.json>] <bundle|dir>
# Upload a private plugin into your own instance (side-load)
aihummer plugin publish --private --instance <url> --token <admin> <bundle.tar.gz>
# Open a PR to the public AiHummer/marketplace-catalog
aihummer plugin publish --public --dir <dir> --key <priv> \
--artifact-url <url> --publisher <name> \
--description <text> --icon <url> [--screenshot <url> ...] \
[--channel stable|beta] [--register-key <pub>]
| Command | What it does |
|---|---|
init <kind> [dir] | Writes a starter manifest.json for the chosen kind. |
validate <m.json> | Validates the manifest with the same contract as install. |
keygen | Generates the author key pair: .key (private, keep secret) and .pub, prints the key id. |
package <dir> | Builds (opt. --build) and packs into <slug>-<version>.tar.gz with a --strip-components=1 layout, writes .sha256. Never packs .env, *.key, node_modules, .git. |
sign --key <priv> | Signs the release identity; prints the signature and key id; with --manifest embeds the signature into the manifest. |
publish --private | Uploads a bundle to your instance’s POST /v1/admin/modules/upload. |
publish --public | Opens a PR to AiHummer/marketplace-catalog. |
Both publishing modes are detailed on Publishing a plugin.
Manifest fields
Whether a field is required depends on the kind and on whether the plugin is public. Base and identity fields:
| Field | Type | Required | Purpose |
|---|---|---|---|
kind | string | always | Kind: connector | service | openapi | mcp. |
version | string | yes | Plugin version (semver), e.g. 1.0.0. |
contract | string | for channels | Contract ID, e.g. aihummer.channel.v1. |
scope | string | no | Access model: shared (default) or personal. |
capabilities | string[] | no | Declared capabilities. |
config | object[] | no | Config form fields; each needs key, plus label, secret, required. |
oauth | object | no | OAuth2 (authorize_url, token_url, scopes[]) to connect a user’s account. |
signature | string | when signed | base64 ed25519 signature over the release identity (embedded by sign). |
Kind-specific fields — exactly one block is filled depending on kind:
| Field | For kind | Required | Purpose |
|---|---|---|---|
host_native.exec_start | connector, service | yes | Command that runs the long-lived service. |
host_native.runtime | connector, service, mcp | no | node | python | binary. |
host_native.install | connector, service, mcp | no | Install steps (array of shell commands), run on the host after extraction. |
host_native.port | connector, service | no | Preferred TCP port (the deployer may reassign via $PORT). |
host_native.health_path | connector, service | no | Health-check path (default /healthz). |
openapi.spec_url | openapi | yes | URL of the OpenAPI 3.x spec. |
openapi.base_url | openapi | no | Override servers[0].url. |
openapi.allowed_hosts | openapi | no | Egress allowlist for the synthesized tools. |
openapi.auth | openapi | no | Map securityScheme → secret name. |
openapi.tool_prefix | openapi | no | Tool-name prefix. |
mcp.transport | mcp | yes | stdio or http. |
mcp.command / mcp.args | mcp (stdio) | yes for stdio | Server executable and arguments. |
mcp.url | mcp (http) | yes for http | MCP endpoint URL. |
mcp.auth_header / mcp.secret_token_key | mcp (http) | no | Header and secret key for the bearer token. |
Store-page and identity fields (for public plugins)
To enter the public catalog, a manifest must carry the publisher identity and store-page fields. A private side-load needs none of these — such a plugin is trusted at the instance level.
| Field | Type | Required | Purpose |
|---|---|---|---|
visibility | string | no | public | private | unlisted. Empty = legacy/first-party (no identity requirement). |
publisher | string | for public | Publisher namespace, ^[a-z0-9][a-z0-9-]{1,38}$. Public slugs are named @publisher/slug. |
publisher_key_id | string | for public | key id of the key the artifact is signed with. |
description | string | for public | Store-page blurb in the catalog. |
icon | string | for public | Plugin icon: an https:// URL or a data: URI. |
screenshots | string[] | no | Store-page screenshots (array of https:// URLs; each non-empty). |
[!TIP] Run
aihummer plugin validatebefore every publish. The install and validation contract are identical, so a manifest that passes locally will be accepted both by the marketplace deployer and by the public catalog validator.
Minimal manifests
A service scaffold (what aihummer plugin init service writes):
{
"version": "1.0.0",
"kind": "service",
"scope": "shared",
"contract": "aihummer.channel.v1",
"host_native": {
"runtime": "node",
"install": ["npm ci --omit=dev"],
"exec_start": "node dist/main.js",
"port": 8800,
"health_path": "/healthz"
},
"config": [
{ "key": "api_token", "label": "API token", "secret": true, "required": true }
]
}
A zero-code openapi manifest is even shorter — it just points at the spec:
{
"version": "1.0.0",
"kind": "openapi",
"scope": "shared",
"openapi": {
"spec_url": "https://api.example.com/openapi.json",
"tool_prefix": "example_",
"allowed_hosts": ["api.example.com"],
"auth": { "bearerAuth": "api_token" }
},
"config": [
{ "key": "api_token", "label": "API token", "secret": true, "required": true }
]
}
An mcp manifest (stdio transport):
{
"version": "1.0.0",
"kind": "mcp",
"scope": "shared",
"host_native": { "runtime": "node", "install": ["npm ci --omit=dev"] },
"mcp": { "transport": "stdio", "command": "node", "args": ["server.js"] }
}
From manifest to marketplace
After validation, a plugin is packaged (package), signed (sign) and published
in one of two modes:
- Private (for yourself) — side-load into your instance via the Admin UI or
publish --private. The artifact never leaves the instance. - Public (for everyone) —
publish --publicopens a PR to the public catalog; after review AiHummer counter-signs the release and publishes it to the community catalog.
See Publishing a plugin for the full walkthrough.
Where next
- Publishing a plugin — private side-load and the public registry, the submission contract, moderation.
- Zero-code integrations — the
openapiandmcpkinds in detail. - Install & updates — what drives
install[], the health gate, trust and signed updates. - Marketplace: overview & tiers — where each kind lives and how the official catalog differs from community.