name: toby-plugin
description: >-
Create a new Toby installable integration plugin or migrate a built-in
integration from packages/core to toby-plugin-. Use when the user asks
to build a plugin, convert an integration to a plugin, implement plugin
protocol v1, port azuread/gmail/slack-style modules externally, or wire
release/install for toby-plugin binaries.
Toby integration plugin
Goal
Ship an integration as toby-plugin-<name> under ~/.toby/plugins/, adapted
into IntegrationModule by @toby/core. Two paths:
| Path | When | Reference |
|---|---|---|
| New plugin | Greenfield integration, no built-in module | apps/plugin-sample/ |
| Migration | Replace built-in in BUILTIN_MODULES with plugin |
apps/plugin-azuread/, apps/plugin-gmail/, apps/plugin-jira/ (Swift), apps/plugin-websearch/ (Swift; global tool bridge in core), apps/plugin-applecalendar/ (Swift/EventKit) |
Read first: docs/plugin-protocol.md,
docs/create-integration.md (migration section).
Hard rules
- Binary name:
toby-plugin-<name>where<name>matches/^[a-z0-9_-]+$/. - Never read or write
~/.toby/from the plugin — config arrives on stdin; Toby merges optionalconfigwriteback from responses. - stdout = one JSON object only; stderr for human diagnostics.
- Exit codes:
0success,1business failure,2contract error. - Plugins cannot collide with names still in
BUILTIN_MODULES— remove built-in before installing a plugin with the same name. - Chat-capable plugins must return
status.chatModelPrepwhencapabilitiesincludes"chat".
Decide complexity
Minimal (API key, static config): sample plugin pattern only — status,
connect, disconnect, config shape|get|set, tools list|execute.
Complex (OAuth, auth-method UI, deep health, rich chat): also use v1
extensions documented under Complex integrations in
docs/plugin-protocol.md:
configwriteback onconnect,disconnect,tools executeauthMethodsonstatus;showForAuthMethodsonconfig shapefieldsvalidateToolson status stdin →tools[]health rowschatModelPreponstatus(adapter adds persona + global tools section)chatReadinessonstatuswhen envelope presentconfig getnormalization (e.g. inferredauthMethod)
Extend protocol in packages/core/src/integrations/plugins/ only when no
existing extension fits. Prefer additive v1 changes.
Workflow A — New plugin
Task progress:
- [ ] 1. Scaffold apps/plugin-<name>/
- [ ] 2. Implement protocol subcommands
- [ ] 3. Wire build + optional release bundle
- [ ] 4. Tests + doctor
1. Scaffold
Mirror apps/plugin-sample/package.json:
apps/plugin-<name>/
package.json # build → ../../dist/toby-plugin-<name>
README.md
src/
cli.ts # argv router
protocol.ts # stdin JSON, emitJson, parseEnvelope
... # client, tools, auth, prompts as needed
Root package.json: add build:plugin:<name> and
include in build:plugins if shipping in release.
2. Subcommands (all required unless noted)
| Subcommand | stdin | Notes |
|---|---|---|
status |
optional envelope | Identity, protocolVersion: "1", capabilities |
connect |
envelope | Validate; return config patch if tokens change |
disconnect |
optional envelope | Clear sensitive fields via config patch |
config shape |
— | Field defs; Toby namespaces as <name>.<key> |
config get |
envelope | Normalized config |
config set |
envelope | Optional sync hook |
tools list |
— | JSON Schema per tool |
tools execute |
tool request JSON | Honor dryRun; appliedActions for mutations |
setup |
optional envelope | Optional one-time setup; set status.setupAvailable |
3. Build & install
bun run build:plugin:<name>
toby plugins install ./dist/toby-plugin-<name> # or --link --force for dev
toby plugins doctor
toby plugins setup <name> # when setupAvailable
toby plugins inspect <name>
4. Tests
Add contract tests under apps/cli/tests/ (see
plugins.test.ts,
plugins-azuread.test.ts,
plugins-gmail.test.ts):
status, connect failure/success, tools list/execute, config shape, registry
discovery.
Run: bun run typecheck, bun run test.
Workflow B — Migrate built-in → plugin
Use parity-checklist.md for full replacement. Summary:
Task progress:
- [ ] 1. Audit built-in IntegrationModule
- [ ] 2. Protocol gaps → extend adapter if needed
- [ ] 3. Port code to apps/plugin-<name>/
- [ ] 4. Remove built-in + config helpers + legacy migration
- [ ] 5. Release wiring (if first-party ship)
- [ ] 6. Parity verification
1. Audit
Read packages/core/src/integrations/<name>/ and list every
IntegrationModule surface:
- Lifecycle:
connect,disconnect,isConnected,testConnection - Credentials:
getCredentialDescriptors,seedCredentialValues,mergeCredentialsPatch,authMethods - Chat:
createChatTools,chat,chatModelPrep,chatReadiness - Optional:
summarize,organize,registerCommands,chatInbound
Map each to a protocol subcommand/response field.
2. Port
- Move
client.ts,auth.ts,tools.ts, prompts into pluginsrc/. - Replace
readCredentials/writeCredentialswith envelopeconfig+configwriteback in responses. - OAuth browser flows run inside plugin
connect; tokens return inconfig. - Token refresh during tools →
tools executeconfigpatch.
3. Remove built-in
- Delete
packages/core/src/integrations/<name>/ - Remove from
BUILTIN_MODULESinpackages/core/src/integrations/index.ts - Remove integration-specific helpers from
packages/core/src/config/index.ts - Add legacy credential migration in
packages/core/src/integrations/plugins/migrate.tsif top-levelcredentials.<name>existed
4. Release wiring (first-party plugins)
Update in lockstep:
scripts/build-release-artifacts.shscripts/verify-release-artifacts.mjsinstall-toby.sh→~/.toby/plugins/.github/workflows/release.ymlcodesignapps/cli/src/upgrade/index.tsstaged install
5. Verify
| Command | Expect |
|---|---|
toby configure |
Auth selector, conditional fields, round-trip |
toby connect <name> |
Each auth path; token writeback |
toby status integration -i <name> |
Health details |
toby status integration -i <name> --validate-tools |
Per-tool rows |
toby chat <name> "..." |
Tools + prompts |
toby disconnect <name> |
Tokens cleared, state removed |
toby plugins doctor |
Pass |
Update docs/integrations.md, help-site page,
and docs/create-integration.md if the
migration template changes.
Core touchpoints (migrations / protocol work)
| Area | Path |
|---|---|
| Protocol types | packages/core/src/integrations/plugins/protocol.ts |
| Adapter | packages/core/src/integrations/plugins/adapter.ts |
| Subprocess client | packages/core/src/integrations/plugins/client.ts |
| Discovery / registry | packages/core/src/integrations/plugins/registry.ts |
| Install / doctor | packages/core/src/integrations/plugins/install.ts |
| CLI commands | apps/cli/src/commands/plugins.ts |
Common mistakes
- Forgetting
chatModelPrepon chat plugins →loadPluginMetadatafails. - Reading
~/.toby/credentials.jsoninside plugin → breaks protocol contract. - Leaving built-in in
BUILTIN_MODULES→builtin_collisionon install. - OAuth without
configwriteback → tokens lost after connect/refresh. capabilities: []vs omitted: empty array disables chat; omitted defaults to["chat"]and requireschatModelPrep.
Additional resources
- Full parity table: parity-checklist.md
- Minimal example:
apps/plugin-sample/ - Full migration examples:
apps/plugin-azuread/,apps/plugin-gmail/,apps/plugin-jira/(Swift/macOS),apps/plugin-websearch/(Swift;bravesearch→websearch)