# Расширения
URL: /ru/docs/develop/extensions

Структура пакета, поля spiritExtension, API активации и список в официальном реестре.



Расширение — это папка с `package.json`, содержащая `spiritExtension`. Spirit устанавливает его для каждого пользователя и хоста, а не в каталог `.spirit/` рабочей области.

| Хост    | Путь                                  |
| ------- | ------------------------------------- |
| Desktop | `{spiritDataDir}/extensions/desktop/` |
| CLI     | `{spiritDataDir}/extensions/cli/`     |

Это отличается от [Skills](../customize/skills.mdx) (папки `SKILL.md`), [MCP](../customize/mcp.mdx) (внешние серверы) и [Hooks](../customize/hooks.mdx) (скрипты `hooks.json`). `contributes.cli.hooks` оформляет слоты CLI TUI. Это не `hooks.json`.

Нет отдельного переключателя включения для каждого расширения. После установки расширение вносит вклад. Переключатель на панели CLI — это заглушка.

## Структура пакета [#структура-пакета]

```text
example-extension/
  package.json
  dist/index.js
  assets/icon.svg
  styles/desktop.css
  cli-hooks.json
```

```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` [#поля-packagejson]

| Поле              | Описание                                                                                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`            | Обязательное. Имя npm-пакета; используется как идентификатор расширения                                                                                             |
| `version`         | Обязательное. Строка версии                                                                                                                                         |
| `spiritExtension` | Обязательное. Объект. См. ниже                                                                                                                                      |
| `description`     | Необязательное. Строка                                                                                                                                              |
| `author`          | Необязательное. Строка или `{ name }`                                                                                                                               |
| `homepage`        | Необязательное. Строка                                                                                                                                              |
| `main`            | Необязательное. Относительный путь к точке входа `activate`. Требуется, если расширение использует `activationEvents`, выполнение инструментов или системный промпт |

`main`, `spiritExtension.icon`, пути CSS и пути хуков CLI должны быть относительными и оставаться внутри пакета.

## `spiritExtension` [#spiritextension]

| Поле                    | Описание                                                    |
| ----------------------- | ----------------------------------------------------------- |
| `schemaVersion`         | Необязательное. По умолчанию `1`. Поддерживается только `1` |
| `displayName`           | Обязательное. Имя, видимое пользователю                     |
| `icon`                  | Необязательное. Путь относительно корня пакета              |
| `supportedHosts`        | Обязательное. Непустой массив из `cli` и/или `desktop`      |
| `activationEvents`      | Необязательное. См. [События активации](#activation-events) |
| `requestedCapabilities` | Необязательное. См. [Возможности](#capabilities)            |
| `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` [#contributestools]

| Поле            | Описание                                                       |
| --------------- | -------------------------------------------------------------- |
| `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` [#contributesdesktop]

Требует `desktop-ui`.

| Поле           | Описание                                                                           |
| -------------- | ---------------------------------------------------------------------------------- |
| `css[].path`   | Обязательно. CSS-файл относительно корня пакета                                    |
| `css[].media`  | Необязательно. CSS media query                                                     |
| `settingsPage` | Необязательно. `true`, `{}` или `{ title }` — добавляет запись в настройки Desktop |

## `contributes.cli` [#contributescli]

Требует `cli-ui`. `hooks` — это объект с `path`, а не встроенный массив:

```json
{
  "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`.

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

## `settingsSchema` [#settingsschema]

| Поле           | Описание                                                          |
| -------------- | ----------------------------------------------------------------- |
| `key`          | Обязательно. Тот же шаблон, что и для имён инструментов           |
| `type`         | Обязательно. `string`, `boolean`, `number` или `select`           |
| `title`        | Обязательно. Ярлык интерфейса                                     |
| `description`  | Необязательно                                                     |
| `placeholder`  | Необязательно                                                     |
| `required`     | Необязательно. Логическое значение                                |
| `defaultValue` | Необязательно. Должно соответствовать `type`                      |
| `options`      | Обязательно для `select`. Массив `{ value, label, description? }` |

Значения — `string`, `number`, `boolean` или `null`. `null` очищает необязательную настройку.

## `secretSlots` [#secretslots]

| Поле          | Описание                           |
| ------------- | ---------------------------------- |
| `key`         | Обязательно                        |
| `title`       | Обязательно                        |
| `description` | Необязательно                      |
| `required`    | Необязательно. Логическое значение |

Desktop хранит секреты в системной связке ключей. В режиме демона CLI `secrets.set` / `secrets.delete` могут быть недоступны.

## `activate` [#activate]

`main` загружается с помощью динамического `import`. Экспортируйте одно из:

* `export function activate(ctx) { ... }`
* `export default function activate(ctx) { ... }`
* `export default { activate }`

### `ctx` [#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` [#контекст-обработчика-инструмента-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` [#desktop-hostshowmessagebox]

| Поле        | Описание                                                      |
| ----------- | ------------------------------------------------------------- |
| `title`     | Обязательно. Строка                                           |
| `message`   | Обязательно. Строка                                           |
| `detail`    | Необязательно. Строка                                         |
| `buttons`   | Необязательно. Массив строк                                   |
| `cancelId`  | Необязательно. Число                                          |
| `defaultId` | Необязательно. Число                                          |
| `noLink`    | Необязательно. Логическое значение                            |
| `type`      | Необязательно. `none`, `info`, `error`, `question`, `warning` |

```js
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](../customize/marketplace.mdx) для каналов и интерфейса установки.

## Публикация в официальный реестр [#публикация-в-официальный-реестр]

Репозиторий [SpiritAgents/registry](https://github.com/SpiritAgents/registry) является индексом маркетплейса. Он не содержит исходный код расширений, `dist`, ZIP-файлы или tarball.

Выполните следующие шаги по порядку:

1. Опубликуйте публичный npm-пакет.
2. Откройте pull request, в котором указан этот пакет.

### 1. Опубликуйте npm-пакет [#1-опубликуйте-npm-пакет]

Опубликованный `package.json` является источником истины, особенно `spiritExtension`. Сборщик реестра требует:

* `spiritExtension.schemaVersion`
* `spiritExtension.displayName`
* `spiritExtension.supportedHosts`
* `spiritExtension.requestedCapabilities`

Он также читает `name`, `version`, `description`, `author`, `repository`, `homepage`, `keywords` и опциональный `spiritExtension.icon`. Если вы установили `icon`, включите этот файл в опубликованный пакет. Отсутствие объекта `spiritExtension` приводит к сбою сборки реестра для этой версии.

Официальный пример: [`@spiritagent/extension-system-message-demo`](https://www.npmjs.com/package/@spiritagent/extension-system-message-demo) (`spiritagent.system-message-demo`).

### 2. Укажите пакет [#2-укажите-пакет]

Создайте `registry/extensions/<extension-id>/` со следующими файлами:

| Файл         | Описание                        |
| ------------ | ------------------------------- |
| `entry.json` | Управление маркетплейсом        |
| `README.md`  | Текст описания для маркетплейса |

Не редактируйте эти файлы вручную (перегенерируйте их):

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

`extensionId` должен содержать как минимум два сегмента, разделенных точкой, состоящих из строчных букв и цифр, с точками или дефисами внутри сегмента. Примеры: `spiritagent.system-message-demo`, `yourteam.some-extension`. `packageName` и `extensionId` должны быть уникальными в репозитории.

### `entry.json` [#entryjson]

| Поле                  | Описание                                                     |
| --------------------- | ------------------------------------------------------------ |
| `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 }` — оба обязательны, если присутствуют |

```json
{
  "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 маркетплейса следует описать, что делает расширение, имя пакета, версию по умолчанию, совместимость с хостами и возможностями, а также примечания для читателей маркетплейса.

Перегенерируйте производные файлы локально:

```powershell
./scripts/build-registry.ps1
```

Скрипт получает метаданные npm для указанных версий, пересобирает `catalog.json` и каждый `detail.json`, а затем проверяет согласованность.

### Запрос на включение [#запрос-на-включение]

Включите `entry.json`, `README.md` из маркетплейса и перегенерированные `catalog.json` / `detail.json`. Укажите только те версии, которые уже опубликованы в npm. Установите `reviewStatus` и `channel` точно. Новые версии обычно начинаются как `unverified`.

Не коммитьте исходные деревья расширений, `dist`, ZIP-файлы, тарболы и другие бинарные артефакты.

Открытие запроса на включение не гарантирует публикацию. Проверка может потребовать изменений до того, как версия будет одобрена или проверена.
