Структура пакета
Плагин — это обычный npm-пакет. Здесь описаны его части: манифест, правило зависимостей, шаблон плагина, артефакты сборки и их метаданные, формирование id плагина и состояние на диске. Манифест плагина — это файл package.json. Его верхний уровень парсится как .passthrough(), а блок bb и вложенный bb.branding — как .strict(), поэтому любой неизвестный ключ внутри bb ломает манифест.
Манифест: поля bb.* и engines
Заголовок раздела «Манифест: поля bb.* и engines»| Поле | Обяз. | Что делает | Особенность |
|---|---|---|---|
name | да | npm-идентификатор и единственный источник id плагина | по соглашению принято bb-plugin-<x>; пакеты со скоупом @acme/bb-plugin-<x> тоже работают; id всех плагинов из поставки bb зарезервированы |
version | да | записывается в артефакты | управляемая установка отклоняет dist/*.meta.json, если его pluginVersion не совпадает с манифестом |
engines.bb | нет | semver-диапазон для версии приложения; проверяется в рантайме и при выборе обновления | dev-сборки (0.0.0) не проверяются, но сообщают об этом в результате |
engines.bbPluginSdk | нет | диапазон SDK, читается как нижняя, а не верхняя граница | отсутствие означает устаревший манифест. Установка из git: или npm: отклоняет плагин, если его SDK новее хоста или имеет другую мажорную версию; установка по локальному пути сообщает о статусе incompatible при загрузке |
bb.name | да | человекочитаемое имя в Settings, магазине и боковой панели | это не name и не id плагина |
bb.description | да | одно предложение: карточка в Browse и первый абзац страницы | ограничьтесь ~140 символами и синхронизируйте с PLUGIN_OVERVIEW.md |
bb.branding | да | айдентика плагина; строгий объект | требуется хотя бы icon или logo.light |
bb.branding.icon | одно из | имя иконки bb ("Zap") или путь к SVG в плагине ("./assets/icon.svg") | здесь запрещён глиф с неймспейсом "<pluginId>/<name>"; путь в плагине должен заканчиваться на .svg |
bb.branding.logo | нет | { light, dark? }: более крупная айдентика для просторных мест | .svg/.png/.webp; dark без light вызывает ошибку; bb plugin build отклоняет SVG-логотипы со скриптами в векторах; автоопределения логотипа в корне нет |
bb.branding.experimental_icons | нет | Record<name, "./x.svg">: собственный словарь иконок плагина для строк таймлайна и значков провайдера | имена соответствуют [a-z0-9-], начинаются с буквы, максимум 48 символов, максимум 64 записи; файлы до 32 КиБ; reject-only валидатор SVG (без script/style/iframe/foreignObject/image/a, on*, ссылок кроме #, xml:base); нарушение отменяет загрузку плагина и называет иконку |
bb.server | да | серверная точка входа | обязательна даже для плагинов только с UI: plugins/scheduled-send/server.ts — это намеренная заглушка. Установка по локальному пути загружает TypeScript напрямую, без сборки |
bb.app | нет | клиентская точка входа → dist/app.js + app.css + app.meta.json | установки по локальному пути и из git собирают её при установке; npm-пакет должен поставлять готовый dist/ |
bb.host | нет, одна | ESM-точка входа Node 22 с полным доверием → dist/host.js | вызывается из собственной серверной точки входа через типизированный host RPC; демон скачивает её лениво и проверяет хеш-сумму |
bb.skills | нет | перемещает корни автоматически импортируемых скиллов | по умолчанию skills/; [] отключает импорт. В репозитории значение всегда буквально ["skills"] (каталог, а не имена); 11 плагинов поставляют skills/ вообще без этого поля |
bb.themes | нет | массив { id, name, description?, css, codeTheme? } → Settings → Appearance и bb theme list | id для выбора — plugin:<plugin-id>:<id>; вклад вносят только загруженные плагины. Ни один плагин из поставки bb не использует это поле |
dependencies против devDependencies: асимметричное правило
Заголовок раздела «dependencies против devDependencies: асимметричное правило»dependencies. Сюда идёт всё, что плагин импортирует в рантайме и что bb не заменяет шимами. Команда bb plugin build инлайнит эти зависимости, а установка из git скачивает только их. Если пакет нужен для сборки, но лежит в devDependencies, плагин не установится из git. Это касается и npm-запуска самой упакованной CLI: она работает с NODE_ENV=production.
Пакет zod намеренно оставили без шима. Слот для его неймспейса добавил бы 193 КБ (33 КБ в brotli) к загрузочному пейлоаду хоста. Поэтому zod бандлится в каждый плагин и живёт в dependencies.
Есть одно исключение. Плагин-провайдер держит @get-bb/plugin-sdk в dependencies, потому что хостовая сборка бандлит подпуть /provider-bridge из собственной установки плагина.
devDependencies. Сюда идут типы, инструменты сборки и любые пакеты, для которых есть шим. Единственный источник истины — RUNTIME_SLOT_BY_SPECIFIER. Если пакет с шимом окажется в dependencies, плагин притащит вторую копию синглтона вдобавок к хостовой.
Команда bb plugin types привязывает SDK и type-only зависимости в devDependencies к версиям запущенного bb. Флаг bb plugin types --check предназначен для CI: команда ничего не пишет на диск и завершается с ошибкой, если версии не совпадают.
// packages/plugin-build/src/runtime-shims.mjs — the full shim listreact · react-dom · react-dom/client · react/jsx-runtime · react/jsx-dev-runtime@get-bb/plugin-sdk/app (+ legacy @bb/plugin-sdk/app)@pierre/diffs · @pierre/diffs/react@radix-ui/react-{alert-dialog, context-menu, dialog, dropdown-menu, hover-card, menubar, navigation-menu, popover, select, tooltip}sonner · vaul · clsx · tailwind-merge · class-variance-authority@bb/shared-ui/icon · @bb/shared-ui/question-form-hostВ этом же файле закреплены два критерия для шимов. Во-первых, семантика синглтонов: в приложении должен быть один React, одна вселенная порталов radix, вызов toast() должен попадать в тостер хоста, vaul мутирует document.body, а @pierre/diffs требует единого контекста. Во-вторых, общие библиотеки, которые иначе бандлились бы в каждый плагин: tailwind-merge ^3, clsx ^2, cva ^0.7 и компонент Icon из shared-ui (около 110 КБ на каждую копию карты hugeicons).
Шаблон плагина bb plugin new <name>
Заголовок раздела «Шаблон плагина bb plugin new <name>»У этой команды нет флагов: ни --app, ни --headless, ни --yes. Шаблон плагина всегда включает фронтенд. Как сказано в README, для headless-плагина удалите bb.app, app.tsx, components/, hooks/ и lib/. Каталог всегда называется bb-plugin-<derived-id>, независимо от скоупа.
bb-plugin-<id>/├── package.json # manifest: version 0.1.0, type module, engines {bb, bbPluginSdk}├── server.ts # server entry: a todo store on bb.storage.kv → RPC + CLI + skill├── app.tsx # app entry: definePluginApp + one navPanel├── tsconfig.json # strict, ES2022, moduleResolution bundler, paths {"@/*": ["./*"]}├── components.json # shadcn config; the @bb registry is pinned to the release tag of the current bb├── .gitignore # exactly "dist/\nnode_modules/"├── README.md # a long author's README├── PLUGIN_OVERVIEW.md # store copy: ≤4000 chars (target 700–1800), ## sections, no HTML or images├── skills/│ └── example-todos/SKILL.md # agent instructions for the bb <id> command├── components/ui/ # pre-vendored button, card, checkbox, dialog, input│ ├── icon.tsx · icon-extended.tsx · icon-registry.ts│ ├── motion.ts · overlay-trigger.ts · responsive-overlay.tsx · coarse-pointer-sizing.ts│ └── hooks/ use-compact-viewport.tsx · use-media-query.ts · use-pointer-coarse.ts├── hooks/useBrowserDimmingModal.ts└── lib/ portal-scope.ts · utils.tsdist/ и meta.json
Заголовок раздела «dist/ и meta.json»| Артефакт | Файлы |
|---|---|
| сервер (всегда) | dist/server.js, server.js.map, server.meta.json |
клиентская часть (если указан bb.app) | dist/app.js, app.css, app.meta.json |
хост (если указан bb.host) | dist/host.js, host.js.map, host.meta.json |
esbuild собирает все три артефакта во временную директорию внутри dist/ (через mkdtemp), а затем переименовывает их на месте.
{ "sdkMajor": <PLUGIN_SDK_MAJOR>, "sdkVersion": "<PLUGIN_SDK_VERSION>", "artifactFormatVersion": 1, "pluginId": "<derivePluginId(name)>", "pluginVersion": "<package.json version>", "builtWith": { "bbVersion": "…", "pluginSdkVersion": "…" }}// host.meta.json adds a seventh field:// "artifactDigest": "<sha256 hex of host.js>"На стороне потребителя validatePluginArtifactMeta проверяет метаданные: sdkMajor должен равняться текущему PLUGIN_SDK_MAJOR, а pluginId и pluginVersion — совпадать с манифестом. Поле builtWith.pluginSdkVersion должно быть валидным semver и совпадать с sdkVersion.
Формирование id плагина
Заголовок раздела «Формирование id плагина»// packages/domain/src/plugin-id.ts — derivePluginId1. if the package name is scoped, take the segment after "/"2. strip the leading "bb-plugin-"3. lowercase; any character outside [a-z0-9-] → "-"; trim "-" at the edges4. an empty result throws
pluginIdSchema = /^[a-z0-9][a-z0-9-]*$/u
@acme/bb-plugin-Notes → notesbb-plugin-simple-notes → simple-notes // directory plugins/docs, the only case of id ≠ directoryЭтот id служит неймспейсом для маршрутов, хранилища, настроек и команд CLI. Кроме того, команда bb plugin new не даёт использовать зарезервированные имена системных команд.
Состояние на диске
Заголовок раздела «Состояние на диске»<dataDir>/plugins/<id>/data.db its own SQLite (WAL, busy_timeout 5000)<dataDir>/plugins/<id>/secrets/ secret settings, one file per key, + .http-token<dataDir>/plugins/<id>/logs/plugin.log JSONL, rotated into plugin.log.1 at 5 MiB<dataDir>/plugins/git/ · npm/ managed installs<dataDir>/plugins/cache/git|npm/… materialized checkouts of the sources<dataDir>/plugins/toolchain-* the downloaded esbuild/tailwind toolchain (cache it in CI)<dataDir>/skills-generated/ the server-generated plugin-commands skill
<daemonDataDir>/plugins/<encoded id>/host-data the host side<daemonDataDir>/plugin-host-artifacts/<encoded id>/<sha256>/host.mjs the artifact cacheПо умолчанию dataDir указывает на ~/.bb/ для упакованного приложения. Запуск через pnpm dev перемещает его в ~/.bb-dev/<checkout-instance>/. Именно поэтому существует bb.server.experimental_dataDir — чтобы не приходилось угадывать путь к ~/.bb.