Spirit Agent
Download

Extensions

Package layout, spiritExtension fields, activate API, and listing on the official registry.

An extension is a folder with a package.json that contains spiritExtension. Spirit installs it per user and per host, not under the workspace .spirit/ directory.

HostPath
Desktop{spiritDataDir}/extensions/desktop/
CLI{spiritDataDir}/extensions/cli/

That is different from Skills (SKILL.md folders), MCP (external servers), and Hooks (hooks.json scripts). contributes.cli.hooks styles CLI TUI slots. It is not hooks.json.

There is no per-extension enable switch. After install, the extension contributes. The CLI panel toggle is a placeholder.

Package layout

example-extension/
  package.json
  dist/index.js
  assets/icon.svg
  styles/desktop.css
  cli-hooks.json
{
  "name": "@example/spirit-extension",
  "version": "0.1.0",
  "description": "Example Spirit Agent extension.",
  "author": { "name": "example" },
  "homepage": "https://example.com/spirit-extension",
  "main": "dist/index.js",
  "spiritExtension": {
    "schemaVersion": 1,
    "displayName": "Example extension",
    "icon": "assets/icon.svg",
    "supportedHosts": ["cli", "desktop"],
    "activationEvents": ["onStartup", "onUserMessage"],
    "requestedCapabilities": [
      "tool-definitions",
      "tool-execution",
      "system-prompt",
      "settings",
      "secret-storage",
      "desktop-ui",
      "cli-ui"
    ],
    "contributes": {
      "tools": [
        {
          "name": "lookup_item",
          "description": "Look up an item by id.",
          "inputSchema": {
            "type": "object",
            "properties": {
              "id": { "type": "string" }
            },
            "required": ["id"]
          },
          "approvalMode": "allowed",
          "executionMode": "foreground"
        }
      ],
      "desktop": {
        "css": [{ "path": "styles/desktop.css" }],
        "settingsPage": { "title": "Example extension" }
      },
      "cli": {
        "hooks": { "path": "cli-hooks.json" }
      }
    },
    "settingsSchema": [
      {
        "key": "region",
        "type": "select",
        "title": "Region",
        "required": true,
        "defaultValue": "us",
        "options": [
          { "value": "us", "label": "US" },
          { "value": "eu", "label": "EU" }
        ]
      }
    ],
    "secretSlots": [
      {
        "key": "api_token",
        "title": "API token",
        "required": true
      }
    ]
  }
}

package.json name is the extension id. It must match ^(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$.

package.json fields

FieldDescription
nameRequired. npm package name; used as the extension id
versionRequired. Version string
spiritExtensionRequired. Object. See below
descriptionOptional. String
authorOptional. String or { name }
homepageOptional. String
mainOptional. Relative path to the activate entry. Required when the extension uses activationEvents, tool execution, or a system prompt

main, spiritExtension.icon, CSS paths, and CLI hook paths must be relative and must stay inside the package.

spiritExtension

FieldDescription
schemaVersionOptional. Defaults to 1. Only 1 is supported
displayNameRequired. User-visible name
iconOptional. Path relative to the package root
supportedHostsRequired. Non-empty array of cli and/or desktop
activationEventsOptional. See Activation events
requestedCapabilitiesOptional. See Capabilities
contributesOptional. tools, desktop, and/or cli
settingsSchemaOptional. Setting definitions
secretSlotsOptional. Secret slot definitions

Capabilities

ValueRuntime
tool-definitionsRequired with tool-execution to expose contributes.tools to the model
tool-executionRequired with tool-definitions to run those tools
system-promptRequired with main to contribute a system prompt fragment
desktop-uiMust be paired with contributes.desktop
cli-uiMust be paired with contributes.cli
approval-flowDeclared only. Approval is driven by each tool's approvalMode
questions-flowDeclared only. Questions are driven by approvalMode: need-questions
settingsDeclared only. Settings come from settingsSchema
secret-storageUsed with secretSlots
structured-resultsDeclared only

desktop-ui / cli-ui and the matching contributes block must both be present or both be absent.

Activation events

EventWhen it fires
onStartupHost warmup
onExtensionInstalledAfter ZIP or marketplace install
onSessionOpenedSession becomes active
onSessionResetSession reset
onUserMessageUser submits a message
onToolCallA tool is about to run
onToolResultA tool result is available
onApprovalResolvedAn approval decision is resolved

The host calls activate when the extension lists the event and has a readable main.

contributes.tools

FieldDescription
nameRequired. [a-z0-9]+(?:[._-][a-z0-9]+)*
descriptionRequired. Shown to the model
inputSchemaRequired. JSON Schema object
outputSchemaOptional. JSON Schema object
approvalModeOptional. allowed, need-approval, or need-questions
executionModeOptional. foreground or background

The model does not see name as-is. The host builds an invocation name such as extension__{id}__{tool}__{hash}.

contributes.desktop

Requires desktop-ui.

FieldDescription
css[].pathRequired. CSS file relative to the package root
css[].mediaOptional. CSS media query
settingsPageOptional. true, {}, or { title } — adds a Desktop settings entry

contributes.cli

Requires cli-ui. hooks is an object with path, not an inline array:

{
  "hooks": { "path": "cli-hooks.json" }
}

The file must be { "hooks": [ ... ] }.

FieldDescription
slotRequired. One of the slots below
variantOptional. default, accented, muted, warning, success, danger
tokensOptional. { foreground?, border?, accent? }
prefixOptional. String
suffixOptional. String

Slots: message.user, message.assistant, message.tool, assistant.thinking, input.frame, bottom_form, bottom_form.section, slash_suggestions, approval.panel, questions.panel.

Token roles: default, primary, secondary, muted, accent, success, warning, danger.

{
  "hooks": [
    {
      "slot": "input.frame",
      "variant": "accented",
      "tokens": { "border": "accent" },
      "prefix": "[",
      "suffix": "]"
    }
  ]
}

settingsSchema

FieldDescription
keyRequired. Same pattern as tool names
typeRequired. string, boolean, number, or select
titleRequired. UI label
descriptionOptional
placeholderOptional
requiredOptional. Boolean
defaultValueOptional. Must match type
optionsRequired for select. Array of { value, label, description? }

Values are string, number, boolean, or null. null clears a non-required setting.

secretSlots

FieldDescription
keyRequired
titleRequired
descriptionOptional
requiredOptional. Boolean

Desktop stores secrets in the OS keyring. On the CLI daemon path, secrets.set / secrets.delete may be unavailable.

activate

main is loaded with dynamic import. Export one of:

  • export function activate(ctx) { ... }
  • export default function activate(ctx) { ... }
  • export default { activate }

ctx

FieldDescription
extension{ id, name, version, directoryPath, manifestPath, main }
hostHost API. Desktop implements showMessageBox. CLI is {}
log(message: string) => void
settingsget(key), getAll(), set(key, value), setAll(values)
secretsget(key), has(key), set(key, value), delete(key) — keys must be declared in secretSlots
activationEventOptional. { type, detail? }

Return value

Return an object, or export the same fields from the module.

FieldDescription
toolsRecord<string, (ctx) => unknown>. Keys are manifest tool names
invokeTool(ctx) => unknown. Used instead of tools[name] when present
systemPromptStatic system fragment
getSystemPrompt() => string | Promise<string>. Used instead of systemPrompt when present
onEvent(event) => void
dispose() => void. Called on remove or reload

Tool handler ctx

FieldDescription
extensionSame runtime info as activate
hostSame host API
toolNameManifest tool name
argumentsObject from the model
logSame logger
settingsSame settings accessor
secretsSame secrets accessor
toolCallIdOptional
questionsResultOptional. Set when approvalMode is need-questions

A string result is passed through. Other values are JSON.stringify(value, null, 2). undefined becomes "".

Desktop host.showMessageBox

FieldDescription
titleRequired. String
messageRequired. String
detailOptional. String
buttonsOptional. String array
cancelIdOptional. Number
defaultIdOptional. Number
noLinkOptional. Boolean
typeOptional. none, info, error, question, warning
export function activate(ctx) {
  return {
    systemPrompt: "Use lookup_item when the user asks for an item by id.",
    async invokeTool({ toolName, arguments: args, settings, secrets }) {
      if (toolName !== "lookup_item") {
        throw new Error(`Unknown tool: ${toolName}`);
      }
      const region = await settings.get("region");
      const token = await secrets.get("api_token");
      return { id: args.id, region, hasToken: Boolean(token) };
    },
    dispose() {
      ctx.log("example-extension disposed");
    },
  };
}

Install

  • ZIP — the archive must contain exactly one package.json (it may sit in a subdirectory). Import with spirit extension import ./extension.zip or Settings → Extensions.
  • Marketplace tarball — the archive root must contain a package/ directory.

supportedHosts must include the current host. A second install of the same id does not replace the existing copy by default. See Marketplace for channels and the install UI.

Publish to the official registry

The SpiritAgents/registry repository is a marketplace index. It does not host extension source, dist, ZIP files, or tarballs.

Do these steps in order:

  1. Publish a public npm package.
  2. Open a pull request that lists that package.

1. Publish the npm package

The published package.json is the source of truth, especially spiritExtension. The registry builder requires:

  • spiritExtension.schemaVersion
  • spiritExtension.displayName
  • spiritExtension.supportedHosts
  • spiritExtension.requestedCapabilities

It also reads name, version, description, author, repository, homepage, keywords, and optional spiritExtension.icon. If you set icon, include that file in the published package. A missing spiritExtension object fails the registry build for that version.

Official example: @spiritagent/extension-system-message-demo (spiritagent.system-message-demo).

2. List the package

Create registry/extensions/<extension-id>/ with:

FileDescription
entry.jsonMarketplace governance
README.mdMarketplace detail copy

Do not edit these by hand (regenerate them):

  • registry/catalog.json
  • registry/extensions/<extension-id>/detail.json

extensionId needs at least two dot-separated segments, lowercase letters and digits, with dots or hyphens inside a segment. Examples: spiritagent.system-message-demo, yourteam.some-extension. packageName and extensionId must be unique in the repository.

entry.json

FieldDescription
schemaVersionRequired. 1
extensionIdRequired. See the rules above
packageNameRequired. npm package name
statusRequired. listed, hidden, deprecated, or blocked
featuredRequired. Boolean
defaultVersionRequired. Default version string
defaultReviewStatusRequired. unverified, verified, or revoked
versionsRequired. At least one item

Each versions[] item:

FieldDescription
versionRequired
channelRequired. stable, preview, or experimental
reviewStatusRequired. unverified, verified, or revoked
changelogOptional. { summary, body } — both required when present
{
  "schemaVersion": 1,
  "extensionId": "example.spirit-extension",
  "packageName": "@example/spirit-extension",
  "status": "listed",
  "featured": false,
  "defaultVersion": "0.1.0",
  "defaultReviewStatus": "unverified",
  "versions": [
    {
      "version": "0.1.0",
      "channel": "stable",
      "reviewStatus": "unverified",
      "changelog": {
        "summary": "Initial public release.",
        "body": "- Initial public release."
      }
    }
  ]
}

The marketplace README should cover what the extension does, the package name, the default approved version, host and capability compatibility, and notes for marketplace readers.

Regenerate derived files locally:

./scripts/build-registry.ps1

The script fetches npm metadata for the listed versions, rebuilds catalog.json and each detail.json, then validates consistency.

Pull request

Include entry.json, the marketplace README.md, and the regenerated catalog.json / detail.json. List only versions that are already public on npm. Set reviewStatus and channel accurately. New versions typically start as unverified.

Do not commit extension source trees, dist, ZIP files, tarballs, or other binary artifacts.

Opening a pull request does not guarantee listing. Review may request changes before a version is approved or verified.