Расширения
Структура пакета, поля spiritExtension, API активации и список в официальном реестре.
Расширение — это папка с package.json, содержащая spiritExtension. Spirit устанавливает его для каждого пользователя и хоста, а не в каталог .spirit/ рабочей области.
| Хост | Путь |
|---|---|
| Desktop | {spiritDataDir}/extensions/desktop/ |
| CLI | {spiritDataDir}/extensions/cli/ |
Это отличается от Skills (папки SKILL.md), MCP (внешние серверы) и Hooks (скрипты hooks.json). contributes.cli.hooks оформляет слоты CLI TUI. Это не hooks.json.
Нет отдельного переключателя включения для каждого расширения. После установки расширение вносит вклад. Переключатель на панели CLI — это заглушка.
Структура пакета
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
}
]
}
}name в package.json — это идентификатор расширения. Он должен соответствовать ^(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$.
Поля package.json
| Поле | Описание |
|---|---|
name | Обязательное. Имя npm-пакета; используется как идентификатор расширения |
version | Обязательное. Строка версии |
spiritExtension | Обязательное. Объект. См. ниже |
description | Необязательное. Строка |
author | Необязательное. Строка или { name } |
homepage | Необязательное. Строка |
main | Необязательное. Относительный путь к точке входа activate. Требуется, если расширение использует activationEvents, выполнение инструментов или системный промпт |
main, spiritExtension.icon, пути CSS и пути хуков CLI должны быть относительными и оставаться внутри пакета.
spiritExtension
| Поле | Описание |
|---|---|
schemaVersion | Необязательное. По умолчанию 1. Поддерживается только 1 |
displayName | Обязательное. Имя, видимое пользователю |
icon | Необязательное. Путь относительно корня пакета |
supportedHosts | Обязательное. Непустой массив из cli и/или desktop |
activationEvents | Необязательное. См. События активации |
requestedCapabilities | Необязательное. См. Возможности |
contributes | Необязательное. tools, desktop и/или cli |
settingsSchema | Необязательное. Определения настроек |
secretSlots | Необязательное. Определения слотов секретов |
Возможности
| Значение | Среда выполнения |
|---|---|
tool-definitions | Требуется с tool-execution для предоставления contributes.tools модели |
tool-execution | Требуется с tool-definitions для запуска этих инструментов |
system-prompt | Требуется с main для добавления фрагмента системного промпта |
desktop-ui | Должен использоваться вместе с contributes.desktop |
cli-ui | Должен использоваться вместе с contributes.cli |
approval-flow | Только объявлено. Одобрение определяется параметром approvalMode каждого инструмента |
questions-flow | Только объявлено. Вопросы определяются параметром approvalMode: need-questions |
settings | Только объявлено. Настройки берутся из settingsSchema |
secret-storage | Используется с secretSlots |
structured-results | Только объявлено |
desktop-ui / cli-ui и соответствующий блок contributes должны либо присутствовать оба, либо отсутствовать оба.
События активации
| Событие | Когда срабатывает |
|---|---|
onStartup | Прогрев хоста |
onExtensionInstalled | После установки из ZIP или маркетплейса |
onSessionOpened | Сеанс становится активным |
onSessionReset | Сброс сеанса |
onUserMessage | Пользователь отправляет сообщение |
onToolCall | Инструмент собирается запуститься |
onToolResult | Результат инструмента доступен |
onApprovalResolved | Решение об одобрении принято |
Хост вызывает activate, когда расширение перечисляет событие и имеет читаемый main.
contributes.tools
| Поле | Описание |
|---|---|
name | Обязательно. [a-z0-9]+(?:[._-][a-z0-9]+)* |
description | Обязательно. Показывается модели |
inputSchema | Обязательно. Объект JSON Schema |
outputSchema | Необязательно. Объект JSON Schema |
approvalMode | Необязательно. allowed, need-approval или need-questions |
executionMode | Необязательно. foreground или background |
Модель не видит name как есть. Хост создаёт имя вызова, например, extension__{id}__{tool}__{hash}.
contributes.desktop
Требует desktop-ui.
| Поле | Описание |
|---|---|
css[].path | Обязательно. CSS-файл относительно корня пакета |
css[].media | Необязательно. CSS media query |
settingsPage | Необязательно. true, {} или { title } — добавляет запись в настройки Desktop |
contributes.cli
Требует cli-ui. hooks — это объект с path, а не встроенный массив:
{
"hooks": { "path": "cli-hooks.json" }
}Файл должен быть { "hooks": [ ... ] }.
| Поле | Описание |
|---|---|
slot | Обязательно. Один из слотов ниже |
variant | Необязательно. default, accented, muted, warning, success, danger |
tokens | Необязательно. { foreground?, border?, accent? } |
prefix | Необязательно. Строка |
suffix | Необязательно. Строка |
Слоты: message.user, message.assistant, message.tool, assistant.thinking, input.frame, bottom_form, bottom_form.section, slash_suggestions, approval.panel, questions.panel.
Роли токенов: default, primary, secondary, muted, accent, success, warning, danger.
{
"hooks": [
{
"slot": "input.frame",
"variant": "accented",
"tokens": { "border": "accent" },
"prefix": "[",
"suffix": "]"
}
]
}settingsSchema
| Поле | Описание |
|---|---|
key | Обязательно. Тот же шаблон, что и для имён инструментов |
type | Обязательно. string, boolean, number или select |
title | Обязательно. Ярлык интерфейса |
description | Необязательно |
placeholder | Необязательно |
required | Необязательно. Логическое значение |
defaultValue | Необязательно. Должно соответствовать type |
options | Обязательно для select. Массив { value, label, description? } |
Значения — string, number, boolean или null. null очищает необязательную настройку.
secretSlots
| Поле | Описание |
|---|---|
key | Обязательно |
title | Обязательно |
description | Необязательно |
required | Необязательно. Логическое значение |
Desktop хранит секреты в системной связке ключей. В режиме демона CLI secrets.set / secrets.delete могут быть недоступны.
activate
main загружается с помощью динамического import. Экспортируйте одно из:
export function activate(ctx) { ... }export default function activate(ctx) { ... }export default { activate }
ctx
| Поле | Описание |
|---|---|
extension | { id, name, version, directoryPath, manifestPath, main } |
host | API хоста. Desktop реализует showMessageBox. CLI — {} |
log | (message: string) => void |
settings | get(key), getAll(), set(key, value), setAll(values) |
secrets | get(key), has(key), set(key, value), delete(key) — ключи должны быть объявлены в secretSlots |
activationEvent | Необязательно. { type, detail? } |
Возвращаемое значение
Верните объект или экспортируйте те же поля из модуля.
| Поле | Описание |
|---|---|
tools | Record<string, (ctx) => unknown>. Ключи — имена инструментов манифеста |
invokeTool | (ctx) => unknown. Используется вместо tools[name], если присутствует |
systemPrompt | Статический фрагмент системного промпта |
getSystemPrompt | () => string | Promise<string>. Используется вместо systemPrompt, если присутствует |
onEvent | (event) => void |
dispose | () => void. Вызывается при удалении или перезагрузке |
Контекст обработчика инструмента ctx
| Поле | Описание |
|---|---|
extension | Та же информация о времени выполнения, что и в activate |
host | Тот же хост API |
toolName | Имя инструмента из манифеста |
arguments | Объект из модели |
log | Тот же логгер |
settings | Тот же доступ к настройкам |
secrets | Тот же доступ к секретам |
toolCallId | Необязательно |
questionsResult | Необязательно. Устанавливается, когда approvalMode имеет значение need-questions |
Результат string передается как есть. Другие значения — JSON.stringify(value, null, 2). undefined превращается в "".
Desktop host.showMessageBox
| Поле | Описание |
|---|---|
title | Обязательно. Строка |
message | Обязательно. Строка |
detail | Необязательно. Строка |
buttons | Необязательно. Массив строк |
cancelId | Необязательно. Число |
defaultId | Необязательно. Число |
noLink | Необязательно. Логическое значение |
type | Необязательно. 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");
},
};
}Установка
- ZIP — архив должен содержать ровно один
package.json(он может находиться в подкаталоге). Импортируйте с помощьюspirit extension import ./extension.zipили Settings → Extensions. - Tarball из маркетплейса — в корне архива должна находиться директория
package/.
supportedHosts должен включать текущий хост. Повторная установка того же id по умолчанию не заменяет существующую копию. См. Marketplace для каналов и интерфейса установки.
Публикация в официальный реестр
Репозиторий SpiritAgents/registry является индексом маркетплейса. Он не содержит исходный код расширений, dist, ZIP-файлы или tarball.
Выполните следующие шаги по порядку:
- Опубликуйте публичный npm-пакет.
- Откройте pull request, в котором указан этот пакет.
1. Опубликуйте npm-пакет
Опубликованный package.json является источником истины, особенно spiritExtension. Сборщик реестра требует:
spiritExtension.schemaVersionspiritExtension.displayNamespiritExtension.supportedHostsspiritExtension.requestedCapabilities
Он также читает name, version, description, author, repository, homepage, keywords и опциональный spiritExtension.icon. Если вы установили icon, включите этот файл в опубликованный пакет. Отсутствие объекта spiritExtension приводит к сбою сборки реестра для этой версии.
Официальный пример: @spiritagent/extension-system-message-demo (spiritagent.system-message-demo).
2. Укажите пакет
Создайте registry/extensions/<extension-id>/ со следующими файлами:
| Файл | Описание |
|---|---|
entry.json | Управление маркетплейсом |
README.md | Текст описания для маркетплейса |
Не редактируйте эти файлы вручную (перегенерируйте их):
registry/catalog.jsonregistry/extensions/<extension-id>/detail.json
extensionId должен содержать как минимум два сегмента, разделенных точкой, состоящих из строчных букв и цифр, с точками или дефисами внутри сегмента. Примеры: spiritagent.system-message-demo, yourteam.some-extension. packageName и extensionId должны быть уникальными в репозитории.
entry.json
| Поле | Описание |
|---|---|
schemaVersion | Обязательное. 1 |
extensionId | Обязательное. См. правила выше |
packageName | Обязательное. Имя npm-пакета |
status | Обязательное. listed, hidden, deprecated или blocked |
featured | Обязательное. Логическое значение |
defaultVersion | Обязательное. Строка версии по умолчанию |
defaultReviewStatus | Обязательное. unverified, verified или revoked |
versions | Обязательное. Как минимум один элемент |
Каждый элемент versions[]:
| Поле | Описание |
|---|---|
version | Обязательное |
channel | Обязательное. stable, preview или experimental |
reviewStatus | Обязательное. unverified, verified или revoked |
changelog | Необязательное. { summary, body } — оба обязательны, если присутствуют |
{
"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."
}
}
]
}В README маркетплейса следует описать, что делает расширение, имя пакета, версию по умолчанию, совместимость с хостами и возможностями, а также примечания для читателей маркетплейса.
Перегенерируйте производные файлы локально:
./scripts/build-registry.ps1Скрипт получает метаданные npm для указанных версий, пересобирает catalog.json и каждый detail.json, а затем проверяет согласованность.
Запрос на включение
Включите entry.json, README.md из маркетплейса и перегенерированные catalog.json / detail.json. Укажите только те версии, которые уже опубликованы в npm. Установите reviewStatus и channel точно. Новые версии обычно начинаются как unverified.
Не коммитьте исходные деревья расширений, dist, ZIP-файлы, тарболы и другие бинарные артефакты.
Открытие запроса на включение не гарантирует публикацию. Проверка может потребовать изменений до того, как версия будет одобрена или проверена.