--- url: /guide/introduction.md description: >- Learn how AgentPlugins unifies AI agent plugins across Claude Code, Codex, OpenCode, and more with a single manifest file. --- # Introduction AgentPlugins is a toolchain for AI agent plugins. Write a plugin once, ship it to every supported agent harness from a single manifest. ## The problem Every AI agent framework ships its own plugin system with its own manifest format, hook lifecycle, and handler conventions: | Framework | Manifest | Handler types | | ------------------ | ---------------------------- | --------------------- | | Claude Code | `.claude-plugin/plugin.json` | command, http, prompt | | Codex CLI | `.codex-plugin/plugin.json` | command only | | GitHub Copilot CLI | `plugin.json` | command, http, prompt | | Gemini CLI | `gemini-extension.json` | command only | | Kimi | `kimi.plugin.json` | command only | | OpenCode | TypeScript plugins | inline only | | Pi Mono | TypeScript extensions | inline only | **Seven frameworks, seven different APIs.** A plugin author who wants to reach across the ecosystem maintains seven forks of the same logic. Users who switch harnesses lose every plugin they configured. ## The solution AgentPlugins introduces a **universal manifest** ([`agentplugins.config.ts`](/guide/manifest)) and a **universal store** (`~/.agents/plugins/`). You declare hooks, skills, tools, MCP servers, and commands once. The CLI compiles that manifest down to each platform's native format and symlinks the result into every detected agent. ```mermaid flowchart LR plugin["Your Plugin"] --> core["AgentPlugins"] core --> a1["Claude Code"] core --> a2["Codex CLI"] core --> a3["GitHub Copilot"] core --> a4["Gemini CLI"] core --> a5["+ more"] ``` ## Supported platforms AgentPlugins targets four harnesses as primary compile targets with universal codegen — the same plugin behaviour across all four: | Agent | Binary | Skill path | | ----------- | ---------- | --------------------------- | | Claude Code | `claude` | `~/.claude/skills` | | Codex CLI | `codex` | `~/.codex/skills` | | OpenCode | `opencode` | `~/.config/opencode/skills` | | Pi Mono | `pi` | `~/.pi/extensions` | Three additional harnesses are tracked with decreasing capability coverage: | Agent | Binary | Skill path | | ------------------ | --------- | ------------------- | | GitHub Copilot CLI | `copilot` | `~/.copilot/skills` | | Gemini CLI | `gemini` | `~/.gemini/skills` | | Kimi | `kimi` | `~/.kimi/skills` | See the [agent paths reference](/reference/agent-paths) for the full registry and the [adapters reference](/reference/adapters) for what each platform emits. ## Custom harnesses If you maintain an internal harness, you can add it as a custom compile target. Register a private adapter via the `plugins` field in `defineConfig` — see [Extending the Build Pipeline](/guide/extending) for the full guide. ## Where to go next * [Install](/guide/installation) the CLI. * Walk through the [quick start](/guide/quick-start). * Learn the [manifest format](/guide/manifest). --- --- url: /guide/installation.md description: 'Install the AgentPlugins CLI via npm, Homebrew, curl, or mise' --- # Installation Pick whichever install method fits your environment. ## npm / bun The CLI is published to npm as `@agentplugins/cli`. Install globally: ::: code-group ```bash [npm] npm install -g @agentplugins/cli ``` ```bash [bun] bun add -g @agentplugins/cli ``` ::: Or run it ad-hoc with `npx`: ```bash npx @agentplugins/cli@latest --version ``` ## Homebrew Install from the [sigilco/homebrew-tap-agentplugins](https://github.com/sigilco/homebrew-tap-agentplugins) tap: ```bash brew install sigilco/tap-agentplugins/agentplugins ``` Upgrade with `brew upgrade agentplugins`. ## curl The install script downloads the correct prebuilt binary for your platform and drops it into `/usr/local/bin`: ```bash curl -fsSL __DOCS_SITE__/install.sh | bash ``` ::: tip Inspect the script before running it: `curl -fsSL __DOCS_SITE__/install.sh | less`. ::: ## mise Manage AgentPlugins as a version-pinned tool with [mise](https://mise.jdx.dev/) (uses [ubi](https://github.com/houseabsolute/ubi) under the hood to pull GitHub releases): ```bash mise use -g ubi:sigilco/agentplugins ``` This pins the latest release globally. Use `mise use ubi:sigilco/agentplugins@1.2.0` to pin a specific version per project. ## Verify the install Whichever method you chose, confirm the binary is on your `PATH`: ```bash agentplugins --version # agentplugins/x.y.z ``` Then run `doctor` to verify AgentPlugins can detect every installed agent harness on your machine: ```bash agentplugins doctor ``` ```text AgentPlugins doctor ──────────────────────────────────────── CLI version x.y.z Store path ~/.agents/plugins ✓ Skills path ~/.agents/skills ✓ Detected agents claude ~/.claude/skills ✓ codex ~/.codex/skills ✓ opencode ~/.config/opencode ✓ gemini ~/.gemini/skills ✗ (not installed) copilot ~/.copilot/skills ✓ kimi ~/.kimi/skills ✗ (not installed) pimono ~/.pi/extensions ✗ (not installed) 4 agents detected. Plugins will fan out to those harnesses. ``` ::: warning `doctor` only reports detection. Plugins still install to the universal store (`~/.agents/plugins/`) regardless of how many agents are found. Symlinks are created only for detected agents. ::: ## Next steps * [Quick start](/guide/quick-start) — install your first plugin. * [Creating plugins](/guide/creating-plugins) — scaffold a new plugin from a template. --- --- url: /guide/quick-start.md description: >- Install AgentPlugins and create your first universal AI agent plugin in under 2 minutes. --- # Quick Start This walkthrough takes you from zero to a working plugin in five commands. Make sure you've [installed](/guide/installation) the CLI first. ## 1. Install the CLI Verify `agentplugins` is on your `PATH`: ```bash agentplugins --version # agentplugins 0.6.0 ``` ## 2. Add a plugin from GitHub Install any plugin hosted on GitHub with `agentplugins add`. The argument is `owner/repo`: ```bash agentplugins add user/my-plugin ``` ```text ✓ Cloned user/my-plugin → ~/.agents/plugins/my-plugin ✓ Detected manifest: agentplugins.config.ts ✓ Symlinked to: ~/.claude/skills/my-plugin ~/.codex/skills/my-plugin ~/.config/opencode/skills/my-plugin ~/.copilot/skills/my-plugin Installed my-plugin@1.2.0 to 4 agent(s). ``` ::: tip You can also pass a full URL (`https://github.com/user/my-plugin`), a local path, or a `gist:` reference. ::: ## 3. List installed plugins See everything in the universal store and which agents each one is linked into: ```bash agentplugins list ``` ```text Plugins in ~/.agents/plugins my-plugin 1.2.0 claude, codex, opencode, copilot security-guard 0.4.1 claude, codex, opencode, gemini, copilot format-on-save 2.0.0 claude, opencode 3 plugins installed. ``` ## 4. Scaffold a new plugin Bootstrap a plugin from a template with `agentplugins init`: ```bash agentplugins init ``` ```text ? Plugin name (kebab-case) › my-awesome-plugin ? Description › Does awesome things across every agent ? Template › - Use arrow-keys. Return to submit. > minimal Bare manifest logger Logs every hook event security-guard preToolUse block-list formatter postToolUse auto-format ? Targets › claude, codex, copilot, gemini, kimi, opencode, pimono ✓ Created my-awesome-plugin/ my-awesome-plugin/agentplugins.config.ts my-awesome-plugin/package.json my-awesome-plugin/tsconfig.json my-awesome-plugin/.gitignore my-awesome-plugin/README.md ``` ## 5. Build for every target Compile the manifest into each platform's native format: ```bash cd my-awesome-plugin agentplugins build ``` ```text Building my-awesome-plugin@1.0.0 claude → dist/claude/.claude-plugin/plugin.json ✓ codex → dist/codex/.codex-plugin/plugin.json ✓ copilot → dist/copilot/plugin.json ✓ gemini → dist/gemini/gemini-extension.json ✓ kimi → dist/kimi/kimi.plugin.json ✓ opencode → dist/opencode/plugin.ts + opencode.json ✓ pimono → dist/pimono/index.ts + package.json ✓ Built 7 targets in 142ms. ``` ## Next steps * Read the [manifest reference](/guide/manifest) for every field. * Walk through [creating plugins](/guide/creating-plugins) end-to-end. * Lint your plugin before publishing with [`agentplugins lint`](/guide/linting). --- --- url: /guide/capability-matrix.md description: >- What each supported harness supports — universal codegen, guided per-harness, or unsupported. Includes the native escape hatch for every gap so you can ship on the native path today. --- # Capability Matrix **Supported harnesses:** Claude Code · Codex · OpenCode · Pi Mono **Additional (tracked, not blocking):** Copilot · Gemini · Kimi Legend: * ✅ **Universal codegen** — adapter emits native output automatically from the manifest. * ⚠️ **Guided per-harness** — no native primitive of its own; a documented escape-hatch pattern covers the gap. Emits a WARN (not error) on portable manifests. * ❌ **Unsupported** — no viable path on this harness; recorded here, not blocking. * n/a — not applicable (mechanism differs but functionality is covered). > **Reading the "Escape hatch" column:** for every ⚠️ cell there's a deliberate reason we don't emit a universal primitive for that capability on that harness yet — the work to express it universally is significant, and a native path already does the job. If you're shipping today, use the escape hatch. If you want a universal primitive, see the **Decision tree for authors** at the bottom. ## Capability table | Capability | Claude Code | Codex | OpenCode | Pi Mono | Escape hatch | Notes | | ------------------------ | :----------: | :----------: | :------: | :-----: | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `skills` | ✅ | ✅ | ✅ | ✅ | — | Universal codegen | | `hooks` (lifecycle) | ✅ | ✅ | ✅ | ✅ | — | Universal codegen | | `commands` | ✅ | ✅ | ✅ | ✅ | — | Universal codegen | | `mcpServers` | ✅ | ✅ | ✅ | ⚠️ | Pi has no built-in MCP. Ship tools via native `tools[]` (emitted natively), or bridge an MCP server through a Pi extension via `nativeEntry.pimono`. | Universal on Claude · Codex · OpenCode. Pi Mono has no MCP; emits WARN when `mcpServers` is set. See [MCP on Pi](/guide/porting#mcp-on-pi). | | `agents[]` | ✅ | ✅ | ✅ | ⚠️ | Pi has no named-agent declaration primitive. Use `nativeEntry.pimono` to spawn custom agents via a Pi extension. | Pi adapter emits nothing for `agents[]`; use `nativeEntry.pimono` for custom agent wiring on Pi. | | `agents[].model` | ✅ | ⚠️ | ✅ | ⚠️ | Codex/Pi: use the harness's own per-agent model config (or simply omit and accept the harness default). | Claude + OpenCode emit `model:` frontmatter when set. Codex/Pi have no per-agent file concept; model unset → harness default. | | `subagentStart` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: intercept `subagent` tool calls with `preToolUse` matcher (subagents launch via the `subagent` tool). | Emits WARN on OpenCode. Pi maps to `agent.AgentStart` lifecycle event. | | `subagentStop` | ✅ | ✅ | ⚠️ | ✅ | OpenCode: detect via `postToolUse` / `postToolUseFailure` on the `subagent` tool. | Emits WARN on OpenCode. Pi maps to `agent.AgentStop` lifecycle event. Pi `stop`↔`subagentStop` collision fixed in v0.3.0. | | `tools[]` (first-class) | ⚠️ | ⚠️ | ✅ | ✅ | Claude/Codex: ship tools via `mcpServers` — works on all four harnesses (universal). | WARN emitted; OpenCode/Pi emit first-class `tools[]` natively. | | `stop` / `continueWith` | ⚠️ | ⚠️ | ⚠️ | ⚠️ | Each harness already has a `stop`-class lifecycle hook natively — emit nothing in portable manifests until v0.5.0. | New universal primitive — v0.5.0; all-harness design. | | Native-entry passthrough | n/a (JSON) | n/a (JSON) | ⚠️ | ⚠️ | OpenCode: drop a `.ts` file directly into `~/.config/opencode/plugins//` — Bun runs it as ESM, no codegen needed. | `nativeEntry` escape hatch — ships in v0.5.0; OpenCode native modules must be `.ts` (file-drop path). | | Inline hook handlers | ✅ auto-wrap | ✅ auto-wrap | ✅ | ✅ | — | Codex/Kimi: auto-wrapped as Node.js command scripts (v0.5.0). | ## Additional harnesses | Capability | Copilot | Gemini | Kimi | Escape hatch | Notes | | -------------------------------- | :-----: | :----: | :--: | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | `skills` | ✅ | ✅ | ✅ | — | Universal. | | `hooks` (lifecycle) | ⚠️ | ⚠️ | ❌ | Copilot/Gemini: use HTTP or `command` handlers (see [Hooks](/guide/hooks)). | Kimi supports a subset (see below). | | `subagentStart` / `subagentStop` | ❌ | ❌ | ❌ | TUI fidelity only — implement per-harness via native plugin config if you really need it. | No universal primitive planned. | | `tools[]` | ⚠️ | ⚠️ | ❌ | Use `mcpServers` instead (not emitted on these Tier-2 harnesses). | No first-class tool emission; emit WARN. | | `mcpServers` | ❌ | ❌ | ❌ | Wire the MCP server directly into the harness's native config — no agentplugins path needed. | Not on the manifest path; harness-level wiring only. | Kimi supported hooks: `preToolUse`, `userPromptSubmit`, `sessionStart`, `notification`, `permissionRequest`. Inline handlers auto-wrapped as Node.js command scripts (v0.5.0). ## Decision tree for authors ``` Does universal codegen cover this capability across all four core harnesses? YES → use it; adapter handles the rest NO → does the native escape hatch (Escape hatch column above) cover it today? YES → ship the escape hatch now; document the limitation in the manifest NO → can you express it per-harness via nativeEntry + a hand-written module? YES → use nativeEntry; emit WARN (not error) NO → is the gap TUI-grade fidelity only? YES → acceptable degradation; note in this matrix NO → open a primitive proposal (v0.5.0+ scope) ``` *** *This matrix is the living contract for the project. Update it as capabilities land or gaps are discovered.* --- --- url: /guide/manifest.md description: Define your plugin with the AgentPlugins universal manifest format --- # Manifest Every plugin is described by a single manifest file: `agentplugins.config.ts` at the plugin root. The manifest is the universal contract — adapters compile it into each platform's native format at build time. ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ // ...fields }) ``` Use `definePlugin` for editor autocomplete and compile-time validation. You can also author the manifest as static JSON (`agentplugins.json`) — both forms are supported. ::: tip Add `"$schema": "https://raw.githubusercontent.com/sigilco/agentplugins/main/spec/v1/manifest.schema.json"` to JSON manifests for editor autocomplete in VS Code, JetBrains, and any JSON-Schema-aware editor. See the [JSON Schema reference](/reference/schema). ::: ## Required fields | Field | Type | Rule | |---|---|---| | `name` | `string` | Kebab-case (`^[a-z][a-z0-9-]*$`), max 64 chars. MUST NOT be prefixed with `agentplugin`. | | `version` | `string` | Semantic version ([semver](https://semver.org/)). | | `description` | `string` | Short human-readable description, minimum 10 characters. | ## Metadata fields | Field | Type | Notes | |---|---|---| | `displayName` | `string` | Human-readable name shown in UIs. | | `author` | `string \| { name, email?, url? }` | Author or organization. | | `homepage` | `string` (URL) | Landing page. | | `repository` | `string` (URL) | Source repository. | | `license` | `string` | SPDX identifier (e.g. `MIT`, `Apache-2.0`). | | `keywords` | `string[]` | Discovery tags. | ## Targets `targets` restricts which platforms the plugin compiles for. Omit it to target every supported platform. ```typescript targets: ['claude', 'codex', 'copilot', 'gemini', 'kimi', 'opencode', 'pimono'] ``` See the [adapters reference](/reference/adapters) for what each target emits. ## Hooks The `hooks` object maps universal lifecycle event names to handlers. There are **19 universal hook names** covering the entire agent lifecycle: | Category | Hooks | |---|---| | Session | `sessionStart`, `sessionEnd` | | Setup | `setup` | | Prompt | `userPromptSubmit`, `userPromptExpansion` | | Tool | `preToolUse`, `postToolUse`, `postToolUseFailure` | | Permission | `permissionRequest`, `permissionDenied` | | Subagent | `subagentStart`, `subagentStop` | | Context | `preCompact`, `postCompact` | | Lifecycle | `stop`, `stopFailure`, `notification` | | File | `fileChanged`, `cwdChanged` | ```typescript hooks: { preToolUse: { matcher: 'bash', handler: { /* ... */ }, }, sessionStart: { handler: { /* ... */ }, }, } ``` See the [Hooks guide](/guide/hooks) for handler types, matchers, and worked examples. ## Skills An array of [SKILL.md](/guide/skills)-compatible skill definitions. Each skill is namespaced as `{plugin}:{skill}` when installed. ```typescript skills: [ { name: 'security-guard', description: 'Security policy enforcement', path: './skills/security-guard/SKILL.md', tags: ['security', 'safety'], }, ], ``` | Field | Type | Notes | |---|---|---| | `name` | `string` | Skill identifier. | | `description` | `string` | Short description shown to the agent. | | `path` | `string` | Relative path to the `SKILL.md` body (or use `content` inline). | | `tags` | `string[]` | Optional discovery tags. | ## MCP servers The `mcpServers` object declares [Model Context Protocol](https://modelcontextprotocol.io/) servers to start. Keys are server names. ```typescript mcpServers: { filesystem: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '${HOME}/projects'], env: { NODE_ENV: 'production', }, }, }, ``` | Field | Type | Notes | |---|---|---| | `command` | `string` | Executable to run. Required. | | `args` | `string[]` | Arguments passed to the command. | | `env` | `Record` | Environment variables. | See [MCP Servers](/guide/mcp-servers) for transport options and placeholders. ## Tools The `tools` array declares tools the agent can call. Parameters follow JSON Schema. ```typescript tools: [ { name: 'lookup-user', description: 'Look up a user by ID', parameters: { type: 'object', properties: { id: { type: 'string', description: 'User identifier' }, }, required: ['id'], }, }, ], ``` | Field | Type | Notes | |---|---|---| | `name` | `string` | Tool identifier. | | `description` | `string` | What the tool does. The agent reads this. | | `parameters` | JSON Schema | JSON Schema describing the tool input. | See [Tools](/guide/tools) for the full parameter schema. ## Commands The `commands` array declares slash commands the agent can invoke. Each command maps to a handler. ```typescript commands: [ { name: 'format', description: 'Format the current file', handler: { type: 'command', command: '${PLUGIN_ROOT}/scripts/format.sh', }, }, ], ``` | Field | Type | Notes | |---|---|---| | `name` | `string` | Command name (without leading `/`). | | `description` | `string` | Short help text. | | `handler` | `HookHandler` | Same handler types as hooks. | ## Agents The `agents` array declares subagents the plugin provides. Each subagent has its own prompt and tool allow-list. ```typescript agents: [ { name: 'reviewer', description: 'Reviews code changes', tools: ['read', 'diff'], }, ], ``` ## Sidecar ::: warning EXPERIMENTAL `sidecar` is experimental. It is accepted by the schema and validated, but **no adapter currently starts or stops sidecar processes**. Do not rely on it for production plugins. **For a long-running background process today:** ship a stdio MCP server (consumed natively by Claude Code, Codex, and OpenCode via `mcpServers`) or declare a `setup` command that starts it. On Pi Mono (which has no MCP), ship a Pi extension via `nativeEntry.pimono` that starts the process using Pi's extension API. ::: ```typescript sidecar: { command: 'node server.js', args: ['--port', '3000'], restart: 'on-failure', health: 'http://localhost:3000/health', }, ``` | Field | Type | Notes | |---|---|---| | `command` | `string` | Executable to run. Required. | | `args` | `string[]` | Arguments passed to the command. | | `env` | `Record` | Environment variables. | | `port` | `number` | Expected port for health checks. | | `health` | `string` | Health check URL. | | `restart` | `'always' \| 'on-failure' \| 'no'` | Restart policy. | ## Install setup The optional top-level `setup` field declares a one-shot install command. After `agentplugins add`, the CLI prompts for trust and runs it once. It is distinct from the `hooks.setup` lifecycle hook. ```typescript setup: { command: './scripts/install.sh', } ``` | Field | Type | Required | Notes | |---|---|---|---| | `command` | `string` | yes | Shell command to run after install. | | `args` | `string[]` | no | Arguments passed to the command. | If `setup` is omitted, the CLI auto-detects `install.sh` → `setup.sh` → `postinstall.mjs` → `postinstall.js` (first hit). See [Linting](/guide/linting) and [Porting](/guide/porting#security--setup-on-install) for details. ## Rules The `rules` array declares behavioral rules — allow/deny/warn patterns applied to tool calls. ```typescript rules: [ { name: 'no-root-rm', description: 'Block recursive root deletion', pattern: 'rm\\s+-rf\\s+/', action: 'deny', }, ], ``` ## LSP servers The `lspServers` array declares Language Server Protocol servers to attach. ```typescript lspServers: [ { name: 'eslint', command: 'vscode-eslint-language-server', args: ['--stdio'], languages: ['javascript', 'typescript'], }, ], ``` ## Complete example ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ name: 'my-security-guard', version: '1.0.0', description: 'Blocks dangerous commands across all agents', displayName: 'Security Guard', author: { name: 'Jane Doe', url: 'https://janedoe.dev' }, homepage: 'https://github.com/user/my-security-guard', repository: 'https://github.com/user/my-security-guard', license: 'Apache-2.0', keywords: ['security', 'safety', 'guard'], targets: ['claude', 'codex', 'copilot', 'gemini', 'kimi', 'opencode', 'pimono'], hooks: { preToolUse: { matcher: 'bash', handler: { type: 'command', command: '${PLUGIN_ROOT}/hooks/pre-tool-use.sh', }, }, sessionStart: { handler: { type: 'command', command: '${PLUGIN_ROOT}/hooks/session-start.sh', }, }, userPromptSubmit: { handler: { type: 'reference', reference: 'my-security-guard:prompt-guard', }, }, }, skills: [ { name: 'security-guard', description: 'Security policy enforcement', path: './skills/security-guard/SKILL.md', tags: ['security', 'safety'], }, ], mcpServers: { vault: { command: '${PLUGIN_ROOT}/bin/vault-mcp', args: ['--stdio'], env: { VAULT_ADDR: 'https://vault.example.com' }, }, }, tools: [ { name: 'scan-secret', description: 'Scan a file for committed secrets', parameters: { type: 'object', properties: { path: { type: 'string', description: 'File to scan' }, }, required: ['path'], }, }, ], commands: [ { name: 'audit', description: 'Run a full security audit on the workspace', handler: { type: 'command', command: '${PLUGIN_ROOT}/scripts/audit.sh', }, }, ], agents: [ { name: 'auditor', description: 'Dedicated security auditor subagent', tools: ['read', 'scan-secret'], }, ], rules: [ { name: 'no-root-rm', pattern: 'rm\\s+-rf\\s+/', action: 'deny', }, ], }) ``` ## Trust boundary `adapterOverrides` and handler `source` paths are resolved relative to the plugin root and sanitized to prevent path traversal, but they still execute arbitrary code — only install plugins you trust. ## Next steps * [Hooks](/guide/hooks) — the 19 lifecycle events in depth. * [Creating plugins](/guide/creating-plugins) — scaffold and build a real plugin. * [JSON Schema](/reference/schema) — validate manifests programmatically. --- --- url: /guide/hooks.md description: Intercept agent lifecycle events with universal hooks --- # Hooks Hooks are the core extensibility primitive. A hook is a named lifecycle event that fires at a specific point in the agent's turn, paired with a handler that runs when the event fires. AgentPlugins defines **19 universal hooks** that compile down to each platform's native equivalent. ## The 19 universal hooks | Category | Hook | Fires when | |---|---|---| | Session | `sessionStart` | A new agent session begins. | | Session | `sessionEnd` | A session ends. | | Prompt | `userPromptSubmit` | The user submits a prompt. | | Prompt | `userPromptExpansion` | A prompt is expanded before sending. | | Tool | `preToolUse` | Before any tool call (filtered by `matcher`). | | Tool | `postToolUse` | After a tool call returns successfully. | | Tool | `postToolUseFailure` | After a tool call throws or exits non-zero. | | Permission | `permissionRequest` | The agent requests permission for an action. | | Permission | `permissionDenied` | A permission request is denied. | | Subagent | `subagentStart` | A subagent is spawned. | | Subagent | `subagentStop` | A subagent finishes. | | Context | `preCompact` | Before context compaction runs. | | Context | `postCompact` | After context compaction completes. | | Lifecycle | `stop` | The agent stops generating. | | Lifecycle | `stopFailure` | The agent's stop handler errors. | | Lifecycle | `notification` | The agent emits a notification. | | File | `fileChanged` | A file on disk changes. | | File | `cwdChanged` | The working directory changes. | | Lifecycle | `setup` | Plugin setup/installation hook. | ::: warning Not every platform implements every hook. At build time the adapter reports which hooks are unsupported for each target — those hooks are silently ignored on that platform. See the [adapters reference](/reference/adapters) for the coverage matrix. ::: ## Hook shape Each hook is an object with an optional `matcher` and a required `handler`: ```typescript { matcher?: string handler: HookHandler } ``` ## Handler types There are three handler types. Every platform supports at least one; the build step auto-wraps where needed. ### 1. `command` — shell command Runs a shell command. Supported by every platform. Paths must be `./`-prefixed or use placeholders. ```typescript hooks: { preToolUse: { matcher: 'bash', handler: { type: 'command', command: '${PLUGIN_ROOT}/hooks/pre-tool-use.sh', statusMessage: 'Scanning command...', }, }, } ``` | Field | Type | Notes | |---|---|---| | `type` | `'command'` | Always the literal `command`. | | `command` | `string` | Shell command to run. Supports `${PLUGIN_ROOT}`, `${PLUGIN_DATA}`, `${HOME}` placeholders. | | `statusMessage` | `string` | Optional message shown to the user while the hook runs. | | `shell` | `'bash' \| 'powershell' \| 'cmd'` | Override the default shell. | The hook receives context on stdin as JSON. Exit code `0` allows the action, exit code `2` blocks it. ### 2. `http` — POST endpoint POSTs the hook context to a URL. Supported by Claude and Copilot. ```typescript hooks: { preToolUse: { matcher: 'bash', handler: { type: 'http', url: 'https://hooks.example.com/pre-tool-use', headers: { Authorization: 'Bearer ${PLUGIN_DATA}/token', }, }, }, } ``` | Field | Type | Notes | |---|---|---| | `type` | `'http'` | Always the literal `http`. | | `url` | `string` (URL) | Endpoint to POST to. | | `headers` | `Record` | Optional HTTP headers. | The response body determines allow/block semantics, mirroring the command handler. ### 3. `reference` / `inline` — TypeScript function References a named handler in the plugin's handler module, or inlines a TypeScript function directly. Natively supported by OpenCode and Pi Mono; auto-wrapped as a shell command for other platforms. ```typescript hooks: { preToolUse: { matcher: 'bash', handler: { type: 'inline', handler: async (ctx) => { const input = JSON.stringify(ctx.toolInput) if (input.includes('rm -rf /')) { return { block: true, reason: 'Root deletion blocked' } } }, }, }, } ``` Or by namespaced reference: ```typescript hooks: { preToolUse: { matcher: 'bash', handler: { type: 'reference', reference: 'my-plugin:guard', }, }, } ``` | Field | Type | Notes | |---|---|---| | `type` | `'reference' \| 'inline'` | Handler kind. | | `reference` | `string` | Namespaced as `{plugin}:{component}`. Required for `reference`. | | `handler` | `(ctx) => Promise` | Inline function. Required for `inline`. | ## Matchers The `matcher` field narrows when a hook fires. Without a matcher, the hook fires for every occurrence of the event. ```typescript hooks: { preToolUse: { matcher: 'bash', // fires only for the bash tool handler: { /* ... */ }, }, postToolUse: { matcher: 'edit|write', // regex-style alternation handler: { /* ... */ }, }, } ``` Matchers are matched against the tool name for tool-related hooks and against the command string for command hooks. ## Worked example: security guard A plugin that blocks `rm -rf /` regardless of which agent runs it: ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ name: 'security-guard', version: '1.0.0', description: 'Blocks dangerous shell commands across all agents', targets: ['claude', 'codex', 'copilot', 'gemini', 'kimi', 'opencode', 'pimono'], hooks: { preToolUse: { matcher: 'bash', handler: { type: 'inline', handler: async (ctx) => { const cmd = JSON.stringify(ctx.toolInput) const dangerous = [/rm\s+-rf\s+\//, /:\(\)\s*\{\s*:\|/, /dd\s+if=\/dev\/zero/] for (const pattern of dangerous) { if (pattern.test(cmd)) { return { block: true, reason: `Blocked: command matched ${pattern}`, } } } }, }, }, permissionRequest: { handler: { type: 'command', command: '${PLUGIN_ROOT}/hooks/check-permission.sh', }, }, sessionStart: { handler: { type: 'inline', handler: async () => ({ additionalContext: 'Security guard plugin active. Dangerous commands will be blocked.', }), }, }, }, }) ``` ## Hook context Every handler receives a context object. The exact shape varies slightly per event, but the common fields are: ```typescript interface HookContext { sessionId: string cwd: string toolName?: string // for tool hooks toolInput?: unknown // for tool hooks prompt?: string // for prompt hooks agentName?: string // for subagent hooks error?: string // for postToolUseFailure, stopFailure } ``` ## Hook result Returning an object lets you influence the agent's behavior: ```typescript interface HookResult { block?: boolean // stop the action reason?: string // explanation (shown to the agent) additionalContext?: string // inject context into the turn modifiedInput?: unknown // rewrite the tool input } ``` ## Next steps * Read the [manifest reference](/guide/manifest) for the full hook schema. * Learn [creating plugins](/guide/creating-plugins) end-to-end. * See the [adapters reference](/reference/adapters) for per-platform hook coverage. --- --- url: /guide/skills.md description: Reusable agent capabilities via SKILL.md --- # Skills A **skill** is a markdown document with YAML frontmatter that teaches an agent a reusable capability. Existing `SKILL.md` files work without modification. ## SKILL.md format A skill is a markdown file named `SKILL.md` with a YAML frontmatter block: ```markdown --- name: security-guard description: Enforces security policies when executing shell commands. tags: - security - safety --- # Security Guard When executing shell commands, validate against the following policies: 1. Never run `rm -rf /` or any recursive deletion of system paths. 2. Never pipe untrusted input into `eval` or `sh -c`. 3. Prompt for confirmation before any network egress to an unknown host. ``` ### Frontmatter fields | Field | Type | Required | Notes | |---|---|---|---| | `name` | `string` | yes | Skill identifier. Kebab-case. | | `description` | `string` | yes | One-line description shown to the agent. | | `tags` | `string[]` | no | Discovery tags. | The body of the file is free-form markdown. Agents read it as instructions when the skill is active. ## How `agentplugins add` reads skills When you run `agentplugins add user/my-plugin`, the CLI looks for skills in this order: 1. A declared manifest (`agentplugins.config.ts` or `agentplugins.json`) with a `skills` array. 2. A `SKILL.md` file at the plugin root. 3. A `skills/` directory containing nested `SKILL.md` files. If only a `SKILL.md` is present, AgentPlugins **synthesizes a manifest** from the frontmatter: * `name` ← `SKILL.md` frontmatter `name` (or the directory name). * `version` ← `0.1.0` if no `package.json` is present. * `description` ← `SKILL.md` frontmatter `description`. * `skills[0]` ← the `SKILL.md` itself. This means a bare `SKILL.md` repo is a valid AgentPlugins plugin — no manifest authoring required. ```bash agentplugins add user/cool-skill ``` ```text ✓ No manifest found — synthesizing from SKILL.md ✓ cool-skill@0.1.0 (description: "Cool skill does cool things") ✓ Symlinked to 4 detected agents. ``` ## Symlink behavior Installed skills live in the universal store and are symlinked into each agent's skill path: ``` ~/.agents/plugins// └── skills/ └── / └── SKILL.md ~/.claude/skills/ → symlink ~/.codex/skills/ → symlink ~/.config/opencode/skills/ → symlink ... ``` ::: tip AgentPlugins also picks up skills placed in `~/.agents/skills/`, so existing setups require no migration. ::: ## Declaring skills in a manifest For plugins with multiple skills or non-default metadata, declare them explicitly in `agentplugins.config.ts`: ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ name: 'my-bundle', version: '1.0.0', description: 'A bundle of related skills', skills: [ { name: 'security-guard', description: 'Security policy enforcement', path: './skills/security-guard/SKILL.md', tags: ['security', 'safety'], }, { name: 'format-on-save', description: 'Auto-format files after edit', path: './skills/format-on-save/SKILL.md', tags: ['formatting'], }, ], }) ``` | Field | Type | Notes | |---|---|---| | `name` | `string` | Skill identifier. | | `description` | `string` | One-line description shown to the agent. | | `path` | `string` | Relative path to the `SKILL.md` body. | | `tags` | `string[]` | Optional discovery tags. | | `content` | `string` | Inline markdown body (alternative to `path`). | You can use `path` (preferred for multi-file plugins) or `content` (preferred for single-file plugins). ## Namespacing All skills are namespaced as `{plugin}:{skill}` when installed to avoid collisions across plugins. A skill named `guard` in plugin `my-bundle` is exposed as `my-bundle:guard`. ## Next steps * [Creating plugins](/guide/creating-plugins) — scaffold a plugin with skills. * [Manifest reference](/guide/manifest) — every manifest field. * [Agent paths](/reference/agent-paths) — where skills are symlinked per platform. --- --- url: /guide/mcp-servers.md description: 'Configure MCP (Model Context Protocol) servers once, deploy to all harnesses' --- # MCP Servers [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) servers extend an agent with external tools and data sources. Declare them once in the manifest and AgentPlugins wires them into every supported harness. ## Declaration MCP servers are keyed by name under `mcpServers`: ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ name: 'my-plugin', version: '1.0.0', description: 'Wires MCP servers into every agent', mcpServers: { filesystem: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '${HOME}/projects'], env: { NODE_ENV: 'production', }, }, github: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'], env: { GITHUB_TOKEN: '${PLUGIN_DATA}/github-token', }, }, }, }) ``` ## Fields | Field | Type | Required | Notes | |---|---|---|---| | `command` | `string` | yes | Executable to run. | | `args` | `string[]` | no | Arguments passed to the command. | | `env` | `Record` | no | Environment variables. | | `transport` | `'stdio' \| 'http'` | no | Transport mode. Defaults to `stdio`. | ## Placeholders Commands, arguments, and environment values support placeholder expansion: | Placeholder | Resolves to | |---|---| | `${PLUGIN_ROOT}` | The plugin's directory in the universal store. | | `${PLUGIN_DATA}` | Per-plugin data directory (`~/.agents/plugins//data`). | | `${HOME}` | User home directory. | Use `${PLUGIN_DATA}` for secrets and per-install state — that directory is never overwritten on plugin update. ::: warning Never hard-code secrets into the manifest. Commit a reference to `${PLUGIN_DATA}` and have a `setup` hook write the actual value on first run. The [linting](/guide/linting) rule `secrets` catches common leak patterns. ::: ## Transport modes ### `stdio` (default) The agent spawns the MCP server as a subprocess and communicates over stdin/stdout. This is the universal default — every supported agent implements it. ```typescript mcpServers: { myServer: { command: '${PLUGIN_ROOT}/bin/my-server', args: ['--stdio'], transport: 'stdio', }, } ``` ### `http` The agent connects to a long-running MCP server over HTTP. Supported by Claude and Copilot; other platforms fall back to `stdio`. ```typescript mcpServers: { remote: { command: '${PLUGIN_ROOT}/bin/my-server', args: ['--port', '3000'], transport: 'http', }, } ``` ## Per-platform behavior | Platform | stdio | http | Notes | |---|---|---|---| | Claude | ✓ | ✓ | Native MCP support. | | Codex | ✓ | — | stdio only. | | Copilot | ✓ | ✓ | Native MCP support. | | Gemini | ✓ | — | stdio only. | | Kimi | ✓ | — | stdio only. | | OpenCode | ✓ | ✓ | Native MCP support. | | Pi Mono | ✓ | — | stdio only. | Unsupported transports are dropped at build time with a warning. ## Next steps * [Manifest reference](/guide/manifest) for the full `mcpServers` schema. * [Linting](/guide/linting) to catch leaked secrets before publish. * [Adapters reference](/reference/adapters) for per-platform MCP support. --- --- url: /guide/tools.md description: Define callable tools that agents can invoke --- # Tools Tools are callable functions the agent can invoke during a turn. Declare them once in the manifest; AgentPlugins compiles them into each platform's native tool format. ## Declaration The `tools` array lists tool definitions. Each tool has a name, a description the agent reads, and a parameters schema. ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ name: 'my-tools', version: '1.0.0', description: 'A bundle of agent-callable tools', tools: [ { name: 'lookup-user', description: 'Look up a user by ID. Returns name and email.', parameters: { type: 'object', properties: { id: { type: 'string', description: 'The user identifier', }, verbose: { type: 'boolean', description: 'Include extended profile fields', }, }, required: ['id'], }, }, { name: 'create-issue', description: 'Create a GitHub issue in the current repo', parameters: { type: 'object', properties: { title: { type: 'string', description: 'Issue title' }, body: { type: 'string', description: 'Issue body (markdown)' }, labels: { type: 'array', items: { type: 'string' }, description: 'Labels to apply', }, }, required: ['title'], }, }, ], }) ``` ## Fields | Field | Type | Required | Notes | |---|---|---|---| | `name` | `string` | yes | Tool identifier. Namespaced as `{plugin}:{tool}` when installed. | | `description` | `string` | yes | What the tool does. The agent reads this to decide when to call the tool. | | `parameters` | JSON Schema | yes | JSON Schema describing the tool input. | ## Parameters schema `parameters` is a standard [JSON Schema](https://json-schema.org/) object describing the tool's input. The required fields are `type` and `properties`: ```typescript parameters: { type: 'object', properties: { // ...property definitions }, required: ['fieldName'], } ``` ### Property types | Type | Example | |---|---| | `string` | `{ type: 'string', description: 'A name' }` | | `number` | `{ type: 'number', description: 'An age' }` | | `boolean` | `{ type: 'boolean', description: 'Is active' }` | | `array` | `{ type: 'array', items: { type: 'string' } }` | | `object` | `{ type: 'object', properties: { /* nested */ } }` | ### Enums Constrain a property to a fixed set of values: ```typescript role: { type: 'string', enum: ['admin', 'member', 'guest'], description: 'User role', } ``` ### Nested objects ```typescript parameters: { type: 'object', properties: { filter: { type: 'object', properties: { field: { type: 'string' }, op: { type: 'string', enum: ['eq', 'ne', 'gt', 'lt'] }, value: { type: 'string' }, }, required: ['field', 'op'], }, }, required: ['filter'], } ``` ## Writing descriptions The agent uses `description` fields to decide when and how to call a tool. Treat them as documentation for a smart but literal reader: * Be specific about what the tool does: `"Look up a user by ID"` beats `"User lookup"`. * Document side effects: `"Deletes the file permanently"` is better than `"Removes a file"`. * Mention units and formats: `"ISO 8601 timestamp"`, `"size in bytes"`. ## Tool handlers The manifest declares the **shape** of a tool. The implementation — what runs when the agent invokes the tool — is provided by an MCP server or an inline handler referenced from the plugin module. See [MCP servers](/guide/mcp-servers) and [Hooks](/guide/hooks) for handler details. ## Per-platform behavior `tools[]` is natively emitted by **OpenCode** and **Pi Mono** only. For all other platforms — including Claude Code and Codex — **`mcpServers` is the recommended universal tool delivery mechanism**. | Platform | `tools[]` | `mcpServers` | Notes | |---|:---:|:---:|---| | Claude Code | ⚠️ | ✅ | `tools[]` not emitted; use `mcpServers` | | Codex | ⚠️ | ✅ | Same as Claude Code | | OpenCode | ✅ | ✅ | First-class `tools[]` + `mcpServers` both supported | | Pi Mono | ✅ | ⚠️ | Pi has no built-in MCP. Use first-class `tools[]` (emitted natively) or bridge via `nativeEntry.pimono`. See [MCP on Pi](/guide/porting#mcp-on-pi). | | Copilot | ⚠️ | ❌ | Neither emitted; Tier-2 only | | Gemini | ⚠️ | ❌ | Neither emitted; Tier-2 only | | Kimi | ⚠️ | ❌ | Neither emitted; Tier-2 only | > ⚠️ When `tools[]` is declared and the target platform does not natively emit it, `agentplugins validate` emits a **WARNING** (not an error) with a pointer to `mcpServers`. The build still succeeds. > > ⚠️ Pi Mono has no built-in MCP support. On Pi, the native `tools[]` emission is the recommended path. `agentplugins validate` emits a WARNING when `mcpServers` is set with `pimono` as a target. ### Recommended cross-harness pattern For plugins targeting Claude, Codex, and OpenCode, back tools with an MCP server. For Pi Mono, declare `tools[]` directly (emitted natively) or use `nativeEntry.pimono` to bridge an MCP server through a Pi extension. ```typescript export default definePlugin({ name: 'my-tools', version: '1.0.0', // MCP path: consumed by Claude Code, Codex, OpenCode (not Pi Mono) mcpServers: { 'my-tools-server': { command: 'npx', args: ['my-tools-mcp-server'], }, }, // Optional: declare tool shapes for OpenCode/Pi Mono native emission tools: [ { name: 'lookup-user', description: 'Look up a user by ID. Returns name and email.', parameters: { type: 'object', properties: { id: { type: 'string', description: 'The user identifier' }, }, required: ['id'], }, }, ], }) ``` See the [Capability Matrix](/guide/capability-matrix) for full cross-harness tool support details. ## Next steps * [MCP Servers](/guide/mcp-servers) — recommended universal tool mechanism for all supported harnesses. * [Manifest reference](/guide/manifest) for the full `tools` schema. * [Capability Matrix](/guide/capability-matrix) for cross-harness support details. --- --- url: /guide/creating-plugins.md description: >- Step-by-step guide to authoring an AgentPlugins manifest and shipping to seven AI agent harnesses. --- # Creating Plugins This guide walks through creating a plugin from scratch: scaffolding, writing hooks, adding skills and MCP servers, building, testing, and publishing. ## 1. Scaffold Run `agentplugins init` to bootstrap a plugin from a template: ```bash agentplugins init ``` You'll be prompted for: * **Plugin name** — kebab-case identifier. * **Description** — minimum 10 characters. * **Template** — one of the starter templates. * **Targets** — which platforms to compile for (default: all). ### Templates | Template | What you get | |---|---| | `minimal` | Bare manifest. Good starting point. | | `logger` | A plugin that logs every hook event to `${PLUGIN_DATA}/log.jsonl`. | | `security-guard` | A `preToolUse` block-list for dangerous commands. | | `formatter` | A `postToolUse` hook that runs your formatter of choice. | Pick `minimal` if you're not sure — you can add hooks later. ```text ? Plugin name (kebab-case) › my-plugin ? Description › Does awesome things across every agent ? Template › minimal ? Targets › claude, codex, copilot, gemini, kimi, opencode, pimono ✓ Created my-plugin/ my-plugin/agentplugins.config.ts my-plugin/package.json my-plugin/tsconfig.json my-plugin/.gitignore my-plugin/README.md ``` Hooks, skills, and commands are declared inline in `agentplugins.config.ts` — there is no separate `SKILL.md` or `hooks/` directory in the scaffold. Open `agentplugins.config.ts` to start editing. ## 2. Write hooks Open `agentplugins.config.ts` and add hooks to the `hooks` object. See the [Hooks guide](/guide/hooks) for the 19 universal hooks and the three handler types. ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ name: 'my-plugin', version: '1.0.0', description: 'Does awesome things across every agent', hooks: { preToolUse: { matcher: 'bash', handler: { type: 'command', command: '${PLUGIN_ROOT}/hooks/pre-tool-use.sh', }, }, sessionStart: { handler: { type: 'inline', handler: async () => ({ additionalContext: 'my-plugin is active.', }), }, }, }, }) ``` ### `defineConfig` — extended config format Use `defineConfig` instead of `definePlugin` when you need to: * Target a **subset of platforms** without editing the manifest * Wire in a **private adapter** for an internal harness * Add **build pipeline plugins** (custom lint rules, IR transforms, post-emit hooks) ```typescript import { defineConfig } from '@agentplugins/core' export default defineConfig({ manifest: { name: 'my-plugin', version: '1.0.0', description: 'Does awesome things across every agent', hooks: { /* ... */ }, }, // Override which targets are built — does not affect the manifest targets: ['claude', 'codex'], }) ``` `definePlugin` and `defineConfig` produce the same dist output for the same manifest. Pick `definePlugin` for simple cross-platform plugins; reach for `defineConfig` when you need the extras above. See [Extending the Build Pipeline](/guide/extending) for `plugins: [...]` and custom adapters. ::: tip Place hook scripts under `hooks/` and reference them with `${PLUGIN_ROOT}/hooks/...`. The placeholder resolves to the plugin's directory in the universal store at runtime. ::: ## 3. Add skills Drop a `SKILL.md` file into `skills//`: ```bash mkdir -p skills/my-skill $EDITOR skills/my-skill/SKILL.md ``` ```markdown --- name: my-skill description: Teaches the agent how to do something specific. tags: - productivity --- # My Skill When the user asks to do X, follow these steps: 1. ... 2. ... ``` Then declare it in the manifest: ```typescript skills: [ { name: 'my-skill', description: 'Teaches the agent how to do something specific.', path: './skills/my-skill/SKILL.md', tags: ['productivity'], }, ], ``` See the [Skills guide](/guide/skills) for the full `SKILL.md` spec. ## 4. Add MCP servers Wire external tools and data sources into the agent with [MCP servers](/guide/mcp-servers): ```typescript mcpServers: { filesystem: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '${HOME}/projects'], }, }, ``` ## 5. Build Compile the manifest into each target platform's native format: ```bash agentplugins build ``` ```text Building my-plugin@1.0.0 claude → dist/claude/.claude-plugin/plugin.json ✓ codex → dist/codex/.codex-plugin/plugin.json ✓ copilot → dist/copilot/plugin.json ✓ gemini → dist/gemini/gemini-extension.json ✓ kimi → dist/kimi/kimi.plugin.json ✓ opencode → dist/opencode/plugin.ts + opencode.json ✓ pimono → dist/pimono/index.ts + package.json ✓ Built 7 targets in 142ms. ``` Inspect `dist/` to see exactly what each adapter emitted. ## 6. Test locally Install the plugin from your local working copy to verify the symlinks land in the right place: ```bash agentplugins add ./my-plugin ``` ```text ✓ Installed my-plugin@1.0.0 from local path ✓ Symlinked to 4 detected agents. ``` Then run `list` to confirm: ```bash agentplugins list ``` Validate the manifest and surface cross-platform issues: ```bash agentplugins validate ``` And run the linter to catch common mistakes: ```bash agentplugins lint ``` See [Linting](/guide/linting) for the full rule set. ## 7. Publish Push the plugin to a public GitHub repository. Once it's on GitHub, anyone can install it: ```bash agentplugins add your-username/my-plugin ``` ### Recommended repository layout ```text my-plugin/ ├── agentplugins.config.ts # manifest (or agentplugins.json) ├── SKILL.md # optional root skill ├── skills/ │ └── my-skill/ │ └── SKILL.md ├── hooks/ │ ├── pre-tool-use.sh │ └── session-start.sh ├── bin/ │ └── my-server # MCP server binary (if any) ├── README.md └── LICENSE ``` ::: tip Tag releases with semver (`v1.0.0`, `v1.1.0`, ...). `agentplugins update` resolves to the latest tag, and users can pin a specific version with `agentplugins add user/repo@1.0.0`. ::: ## Next steps * [Manifest reference](/guide/manifest) — every field. * [Hooks](/guide/hooks) — the 19 lifecycle events. * [Extending the Build Pipeline](/guide/extending) — custom adapters, lint rules, and pipeline plugins. * [CLI reference](/reference/commands) — every command and flag. --- --- url: /guide/porting.md description: >- Port an existing per-harness plugin to the AgentPlugins universal manifest with functional parity across all supported harnesses. --- # Porting an Existing Plugin This guide walks through porting an existing per-harness plugin to AgentPlugins so it delivers the **same functionality** everywhere — universal codegen first, per-harness escape hatch only when a capability has no universal primitive. ::: info Parity is the bar The four supported harnesses are **Claude Code, Codex, OpenCode, and Pi Mono**. A capability must work across all four at the *functionality* level — not necessarily with identical TUI chrome, but the same underlying behaviour. See the [Capability Matrix](/guide/capability-matrix) for the per-capability verdict. ::: *** ## The decision tree For each capability in your existing plugin, work through this tree: ``` 1. Does universal codegen cover it across all four core harnesses? YES → declare it in the manifest; the adapter handles the rest NO → continue ↓ 2. Can all four harnesses support it via a per-harness escape hatch? YES → implement guided per-harness (see below); emit WARN on build NO → continue ↓ 3. Is the gap TUI-grade fidelity only (overlays, widgets)? YES → acceptable degradation; note in compat matrix; ship it NO → open a primitive proposal (don't block the port) ``` *** ## Step 1 — Audit your existing plugin List every capability your plugin provides: | Capability | What it does | Hook / mechanism | |---|---|---| | Session banner | Prints a welcome message | `sessionStart` | | Tool guard | Blocks dangerous commands | `preToolUse` | | Custom command | `/reset` clears state | `commands[]` | | Overlay UI | Side panel in Pi TUI | Pi-specific | For each row, check the [Capability Matrix](/guide/capability-matrix) to see whether it's universal-codegen, guided-per-harness, or unsupported. *** ## Step 2 — Universal codegen (most capabilities) Capabilities expressed through hooks, skills, commands, and mcpServers map directly to the manifest. This covers the majority of real-world plugins: ```typescript import { definePlugin } from '@agentplugins/core' export default definePlugin({ name: 'my-plugin', version: '1.0.0', description: 'My cross-harness plugin', hooks: { sessionStart: { handler: { type: 'command', command: './hooks/session-start.sh' }, }, preToolUse: { handler: { type: 'command', command: './hooks/tool-guard.sh' }, }, }, commands: [ { name: 'reset', description: 'Reset plugin state', command: './scripts/reset.sh' }, ], // Tools via MCP — works on all four mcpServers: { 'my-tools': { command: 'npx', args: ['my-tools-mcp-server'] }, }, }) ``` Build and verify on each harness: ```bash agentplugins build agentplugins validate ``` ### Authoring primitives available in the manifest These ship as of v0.4.0+ — reach for them before falling back to per-harness code: * **`continueWith`** — chain a follow-up prompt into the session via the `stop` hook (per-session cap of 20; lint-guarded). * **`nativeEntry` / `nativeCopies`** — pass non-JS artifacts (binaries, hand-written TS) through to a specific harness. * **`adapterOverrides`** — override a single adapter's output per-harness (`manifest.adapterOverrides.opencode` / `.pimono`); paths are sanitized against the plugin root. * **`capabilities: ['subprocess']`** — declares a capability so the build-time lint rules don't flag subprocess patterns in your command handlers. *** ## Step 3 — Guided per-harness (escape hatch) Some capabilities have no universal primitive but can be implemented per-harness. The build succeeds with a WARN; you implement the harness-specific path yourself. **Example: subagentStart/Stop on OpenCode** OpenCode has no native subagent lifecycle event. Instead, intercept the subagent tool call via `preToolUse`/`postToolUse` and filter on tool name: ```typescript hooks: { preToolUse: { handler: { type: 'command', command: './hooks/tool-intercept.sh', // tool-intercept.sh checks $TOOL_NAME == "subagent" and acts accordingly }, }, }, ``` The WARN emitted on `agentplugins validate` for OpenCode points here. Record the gap in the [Capability Matrix](/guide/capability-matrix). **Per-harness fallback via `nativeEntry`:** Provide a hand-written TS file for code-emitting adapters: ```typescript // agentplugins.config.ts nativeEntry: { pimono: './src/pimono-native.ts', opencode: './src/opencode-native.ts', } ``` This file is copied verbatim into the dist and has full access to the harness's own SDK. **OpenCode native module rule:** OpenCode auto-discovers only `.ts` files dropped in `~/.config/opencode/plugins/`. Ship all native OpenCode modules as `.ts` (ESM syntax is valid TypeScript; Bun runs it natively without type-checking). Sibling files (hooks, helpers) resolve correctly because `agentplugins install` symlinks the module back into the plugin store — `import.meta.url` resolves to the store path, so relative `require`/`import` paths work without copies. You do not need to edit `~/.config/opencode/config.json`. The `.ts` file-drop is sufficient. If you ship a `.mjs` source, `agentplugins` automatically links it under a `.ts` name (safety net) but emits a WARN. Rename the source to `.ts` to silence it. ### MCP on Pi {#mcp-on-pi} Pi Mono has no built-in MCP support ("No MCP" per the Pi README). When your manifest sets `mcpServers` and `pimono` is a target, `agentplugins validate` emits a WARNING pointing here. Two paths: **Option A — native `tools[]` (recommended for Pi):** Declare your tool shapes in `tools[]`. The Pi adapter emits `pi.registerTool()` calls natively. No MCP server needed on Pi. **Option B — MCP bridge via `nativeEntry.pimono`:** Write a Pi extension that spawns your MCP server as a child process and bridges it through Pi's `pi.tool()` API. Wire it with: ```typescript nativeEntry: { pimono: './src/pi-mcp-bridge.ts', } ``` The file is copied verbatim to `index.ts` in the Pi dist; codegen is skipped. The bridge has full access to Pi's extension API (`ExtensionAPI`) and can call your MCP server over stdio. Use Option A when your tools are already declared in `tools[]`. Use Option B when you have a shared MCP server that must run on all four harnesses and you want a single implementation. *** ## Step 4 — TUI-only features (acceptable degradation) Overlays, side panels, and interactive widgets that use Pi's TUI system (`@earendil-works/pi-tui`) are Pi-only by nature. This is the **one allowed degradation** category: * Implement the TUI feature on Pi via `nativeEntry`. * On other harnesses: omit the widget; the underlying functionality (data, hooks, state) must still work. * Record in compat matrix as "TUI fidelity — Pi only". *** ## Step 5 — Security & setup on install When you (or your users) run `agentplugins add `, the install flow does a few things automatically that affect how you should ship your port. You don't need to do anything for most of these — just be aware. ::: tip Setup scripts (optional) If your plugin needs a one-shot install step (generate a config, fetch a model, seed data), declare a top-level **`setup`** command in the manifest: ```json { "name": "my-plugin", "version": "1.0.0", "setup": "./scripts/install.sh" } ``` After `agentplugins add`, the CLI prompts for trust and runs it. `agentplugins setup my-plugin` re-runs it later. If you don't declare `setup`, the CLI auto-detects `install.sh` → `setup.sh` → `postinstall.mjs` → `postinstall.js` (first hit). This is **distinct from the `hooks.setup` lifecycle hook**, which fires on session setup. ::: Trust is on-first-use: the command plus the contents of any referenced script are hashed (sha256) and recorded in `.agentplugins-meta.json`. A matching hash re-runs silently next time; a changed hash re-prompts. **What's always enforced on install** (no opt-out — these protect the user): * **Hard denylist** blocks pipe-to-shell and destructive commands (`curl|sh`, `wget|sh`, `npx --yes`, `rm -rf /`, `chmod 777`, `eval`, `base64 -d|sh`) even if the user passes `--yes` or has trusted the plugin before. * **Integrity check** — if you declare `manifest.integrity`, the cloned source is verified against it before linking. * **Clone URL is validated** to `https://github.com/...` (GitHub-only) before fetch; redirects are re-checked against private-IP / allow-lists (SSRF guard). * **Symlink-safe** — install only `unlinkSync`s existing entries; it never `rmSync`s user files. Flags the user controls: `--yes` (skip the prompt, still denylist-gated), `--no-setup` (skip setup on `add`), and `AGENTPLUGINS_SETUP_SCRIPTS=0` (hard kill-switch). *** ## Step 6 — Verify parity For each supported harness: ```bash # Build agentplugins build --target claude agentplugins build --target codex agentplugins build --target opencode agentplugins build --target pimono # Validate agentplugins validate # Install and smoke-test locally agentplugins add ./ # installs from local path ``` Confirm the **same observable behaviour** on all four: same hooks fire, same commands work, same tools are reachable. *** ## What stays out * **Universal orchestration runtime** — don't build one. Subagent spawning = per-harness primitives + userland provider protocol. * **Mechanical ports** — don't translate existing config files 1:1. Rewrite on the manifest; let adapters generate the platform-native output. * **Plugin composition / inheritance** — native composition (extending one plugin from another) is not a Tier-1 primitive on any supported harness. If you need to combine behavior from two plugins, express it per-harness via `nativeEntry` where the harness supports extension chaining, or simply declare the same hooks in both plugins and let the harness merge them. There is no universal `extends` or `compose` field in the manifest, and none is planned for v0.4.0. *** ## See also * [Ecosystem](/guide/ecosystem) — plugins already ported for Tier-1 parity * [Capability Matrix](/guide/capability-matrix) — full capability table * [Creating Plugins](/guide/creating-plugins) — authoring guide from scratch * [JSON Schema](/reference/schema) — manifest schema for editor autocomplete --- --- url: /guide/extending.md description: >- Add custom adapters, lint rules, IR transforms, and post-emit hooks to the AgentPlugins build pipeline without forking the tool. --- # Extending the Build Pipeline AgentPlugins ships seven built-in adapters. If you maintain an internal harness, want to add custom lint rules, or need to transform the manifest IR before code generation, the `plugins` field in `defineConfig` gives you full access to the build pipeline without forking anything. ## When to use `plugins` | Need | Mechanism | |---|---| | Compile to a private/internal harness | `plugin.adapter` | | Add project-specific lint checks | `plugin.lintRules` | | Add a new emit language | `plugin.emitters` | | Validate or reject the manifest before build | `plugin.preValidate` middleware | | Mutate the manifest IR (e.g. inject metadata) | `plugin.transformIR` middleware | | Inspect or rewrite emitted files per-target | `plugin.postEmit` middleware | | Gate or audit install steps | `plugin.onInstall` / `plugin.onAudit` middleware | ## Basic setup ```typescript // agentplugins.config.ts import { defineConfig } from '@agentplugins/core' export default defineConfig({ manifest: { name: 'my-plugin', version: '1.0.0', description: 'My cross-platform plugin', hooks: { /* ... */ }, }, plugins: [ { name: 'my-extension', // fields below — mix and match }, ], }) ``` All `plugins` entries are composed on top of the built-in adapter set. Built-in adapters (claude, codex, …) are always registered first; your plugins run after and may override them by registering the same target name. *** ## Custom adapter A `PlatformAdapter` tells the build system how to validate and compile the manifest into your harness's native format. ```typescript // src/my-harness-adapter.ts import type { PlatformAdapter } from '@agentplugins/core' export const myHarnessAdapter: PlatformAdapter = { name: 'my-harness', // target id — must match the targets[] list displayName: 'My Harness', supportedHooks: ['sessionStart', 'preToolUse', 'postToolUse'], supportedHandlers: ['command', 'inline'], manifestPath: 'my-harness.json', manifestFormat: 'json', validate(plugin) { // Return ValidationIssue[] — errors abort build, warnings are printed return [] }, compile(plugin) { return { files: [ { path: 'my-harness.json', content: JSON.stringify({ name: plugin.name, version: plugin.version }, null, 2), }, ], manifest: {}, warnings: [], issues: [], } }, } ``` Wire it in via `defineConfig`: ```typescript import { defineConfig } from '@agentplugins/core' import { myHarnessAdapter } from './src/my-harness-adapter.js' export default defineConfig({ manifest: { name: 'my-plugin', version: '1.0.0', description: '…' }, plugins: [ { name: 'my-harness-adapter', adapter: myHarnessAdapter }, ], targets: ['claude', 'my-harness'], }) ``` `my-harness` is now a valid target id. Unknown target ids that have no registered adapter are skipped at build time with a warning. ::: tip Full working example `plugins/example-custom-adapter/` in the repository shows this pattern end-to-end, producing `dist/claude/` and `dist/my-harness/` from one `agentplugins build` run. ::: *** ## Custom lint rules Add build-time checks that run alongside the built-in lint rules: ```typescript import type { LintRule } from '@agentplugins/pipeline' const requireLicenseRule: LintRule = { id: 'require-license', description: 'All plugins in this org must declare a license', run(ctx) { if (!ctx.manifest.license) { return [{ severity: 'error', field: 'license', message: 'license is required for org plugins', suggestion: 'Add license: "MIT" to your manifest', }] } return [] }, } export default defineConfig({ manifest: { /* … */ }, plugins: [ { name: 'org-rules', lintRules: [requireLicenseRule] }, ], }) ``` Custom rules run in strict mode by default — errors abort the build, warnings are printed. *** ## Pipeline middleware Middleware functions follow the standard `(ctx, next) => Promise` onion pattern. Call `await next()` to proceed, or `ctx.abort(reason)` to stop the pipeline. ### `preValidate` — reject before validation Runs before `validateUniversal()`. Use it to enforce org-wide manifest constraints: ```typescript { name: 'org-guard', preValidate: async (ctx, next) => { if (!ctx.manifest.name.startsWith('acme-')) { ctx.abort('All ACME plugins must be named acme-*') } await next() }, } ``` ### `transformIR` — mutate the manifest IR Runs after validation, before code generation. Use it to inject metadata or normalize fields: ```typescript { name: 'inject-build-metadata', transformIR: async (ctx, next) => { ctx.manifest = { ...ctx.manifest, description: `[${process.env.CI_COMMIT_SHA?.slice(0, 7) ?? 'local'}] ${ctx.manifest.description}`, } await next() }, } ``` ### `postEmit` — inspect or rewrite emitted files Runs per-target after the adapter has produced its files. `ctx.files` is the mutable list of `{ path, content }` entries: ```typescript { name: 'add-banner', postEmit: async (ctx, next) => { ctx.files = ctx.files.map(f => ({ ...f, content: `// Built by ACME CI — do not edit\n${f.content}`, })) await next() }, } ``` ### `onInstall` — gate or audit install Runs during `agentplugins add`. The built-in security checks run here — pinned `integrity` hash verification, then script policy evaluation. You may add your own checks after them: ```typescript { name: 'org-install-policy', onInstall: async (ctx, next) => { if (ctx.pluginName.startsWith('untrusted-')) { ctx.abort(`Plugin "${ctx.pluginName}" is blocked by org policy`) } await next() }, } ``` ::: warning `onInstall` plugins run in the user's environment, not the plugin author's. Only ship install middleware as part of org-internal tooling, not in public plugins. ::: *** ## Plugin interface reference ```typescript interface Plugin { readonly name: string // Compile adapter?: PlatformAdapter lintRules?: LintRule[] emitters?: Record // Build pipeline middleware preValidate?: Middleware transformIR?: Middleware postEmit?: Middleware // Install pipeline middleware onAudit?: Middleware onInstall?: Middleware } ``` Each field is optional — a plugin may contribute any combination. ## Middleware execution order 1. `validateUniversal()` — structural checks on the manifest 2. `lint()` — with merged lint rules from all plugins 3. All `preValidate` chains run — can abort before compilation 4. All `transformIR` chains run — may mutate `ctx.manifest` 5. Per-target: `validateForPlatform()` → `adapter.compile()` → all `postEmit` chains 6. Files written to `dist/` Install pipeline: 1. All `onInstall` chains run (pinned-integrity and script-policy checks first) 2. Files linked into agent directories ## See also * [Creating Plugins](/guide/creating-plugins) — manifest authoring from scratch * [Adapters reference](/reference/adapters) — built-in adapter output formats * [Linting](/guide/linting) — built-in lint rules --- --- url: /guide/linting.md description: Validate your plugin manifest before publishing --- # Linting `agentplugins lint` checks your manifest against eight rules that catch the most common publishing mistakes. Run it before every release. ```bash agentplugins lint ``` ```text Linting my-plugin@1.0.0 naming ✓ name is kebab-case versioning ✓ version is valid semver description ✓ description is descriptive (42 chars) license ✓ license declared: Apache-2.0 target-hygiene ✓ 7 targets, all recognized hook-coverage ⚠ 2 hooks unsupported on kimi (sessionEnd, userPromptSubmit) handler-safety ✓ all command handlers use ./-prefixed paths secrets ✓ no plaintext secrets detected 7 passed, 1 warning. ``` ## Rules ### `naming` Checks that `name` is kebab-case (`^[a-z][a-z0-9-]*$`), max 64 chars, and not prefixed with `agentplugin`. ```text ✗ naming: name "AgentPlugin" must be kebab-case ✗ naming: name "agentplugin-foo" must not be prefixed with "agentplugin" ``` ### `versioning` Checks that `version` is valid [semver](https://semver.org/). ```text ✗ versioning: version "1.0" is not valid semver (need MAJOR.MINOR.PATCH) ``` ### `description` Checks that `description` is at least 10 characters and not a placeholder. ```text ✗ description: description "todo" is too short (min 10 chars) ✗ description: description matches placeholder pattern ("my plugin") ``` ### `license` Checks that `license` is declared and matches a known [SPDX identifier](https://spdx.org/licenses/). ```text ✗ license: license field is missing ✗ license: "MIT2" is not a recognized SPDX identifier ``` ### `target-hygiene` Checks that every entry in `targets` is a recognized platform and warns on duplicates or empty arrays. ```text ✗ target-hygiene: unknown target "claude2" ⚠ target-hygiene: target list contains duplicates ``` ### `hook-coverage` Warns when a hook you've declared isn't supported by one of your targets. The hook will be silently ignored on that platform. ```text ⚠ hook-coverage: hooks.sessionEnd is not supported by codex — will be ignored ⚠ hook-coverage: hooks.userPromptSubmit is not supported by kimi — will be ignored ``` See the [adapters reference](/reference/adapters) for the per-platform coverage matrix. ### `handler-safety` Checks that every `command` handler uses `./`-prefixed paths or placeholders, and rejects path traversal (`..`). ```text ✗ handler-safety: handler command uses absolute path "/usr/local/bin/foo" ✗ handler-safety: handler command contains ".." traversal ``` ### `secrets` Scans the manifest for common secret patterns (API keys, tokens, private keys) and flags them. Use `${PLUGIN_DATA}` placeholders instead. ```text ✗ secrets: possible AWS access key in mcpServers.github.env.GITHUB_TOKEN ✗ secrets: possible private key in hooks.preToolUse.handler.command ``` ## JSON output Pass `--json` to get machine-readable output for CI: ```bash agentplugins lint --json ``` ```json { "plugin": "my-plugin", "version": "1.0.0", "rules": { "naming": { "status": "pass" }, "versioning": { "status": "pass" }, "description": { "status": "pass" }, "license": { "status": "pass" }, "target-hygiene": { "status": "pass" }, "hook-coverage": { "status": "warn", "warnings": [ "hooks.sessionEnd is not supported by codex — will be ignored", "hooks.userPromptSubmit is not supported by kimi — will be ignored" ] }, "handler-safety": { "status": "pass" }, "secrets": { "status": "pass" } }, "summary": { "passed": 7, "warnings": 1, "failed": 0 } } ``` ## Exit codes | Code | Meaning | |---|---| | `0` | All rules passed (warnings allowed). | | `1` | At least one rule failed. | | `2` | Manifest could not be loaded or parsed. | ## CI integration A typical CI step: ```yaml - name: Lint plugin run: agentplugins lint ``` Fail the build on any error. Treat `hook-coverage` warnings as informational unless the hook is critical to your plugin's behavior — in which case, narrow `targets` to the platforms that support it. ## Next steps * [Creating plugins](/guide/creating-plugins) — the full authoring workflow. * [CLI reference](/reference/commands) — every command and flag. * [Adapters reference](/reference/adapters) — why some hooks are unsupported per target. --- --- url: /guide/ecosystem.md description: >- Community plugins built on the AgentPlugins universal manifest, installable across all Tier-1 harnesses. --- # Ecosystem Community plugins rewritten on the AgentPlugins universal manifest. Each targets functional parity across the four supported harnesses — the same functionality across Claude Code, Codex, OpenCode, and Pi Mono. Install any plugin with a single command: ```bash agentplugins add owner/repo ``` This installs the plugin into `~/.agents/plugins//` and symlinks it to every detected agent on your machine. *** ## Adding your plugin 1. Build on the AgentPlugins universal manifest (see [Creating Plugins](/guide/creating-plugins)) 2. Target all four supported harnesses and confirm functional parity 3. Open a PR or issue to add your plugin to this page See [Rewriting for Tier-1 Parity](/guide/porting) for the step-by-step process. *** ## Available plugins | Plugin | Description | |---|---| | [agentplugins-autoresearch](https://github.com/sigilco/agentplugins-autoresearch) | Autonomous experiment loop that gathers what to optimize and runs iterations until the target improves. | | [agentplugins-goal](https://github.com/sigilco/agentplugins-goal) | Autonomous goal-completion loop — set a goal once, the agent iterates until done across all supported harnesses. | | [agentplugins-ponytail](https://github.com/sigilco/agentplugins-ponytail) | Lazy senior dev mode — forces the minimum solution that works: YAGNI → stdlib → native → one line. | | [agentplugins-caveman](https://github.com/sigilco/agentplugins-caveman) | Ultra-compressed communication mode — cuts ~75% of output tokens while preserving full technical accuracy. | | [agentplugins-btw](https://github.com/sigilco/agentplugins-btw) | Parallel side-thread thoughts — spin up side conversations without interrupting the main agent. | > **Note:** Capability status per plugin lives in each plugin's README. For the harness-level baseline see the [Capability Matrix](/guide/capability-matrix). --- --- url: /reference/commands.md --- # CLI Commands The `agentplugins` CLI exposes 11 commands. Run `agentplugins --help` to see them all, or `agentplugins --help` for details on a specific command. ```text Usage: agentplugins [options] [command] Write AI agent plugins once, ship to any harness. Options: -V, --version output the version number -h, --help display help for command Commands: add Install a plugin from a source remove Remove an installed plugin list List installed plugins update Update one or all plugins info Show details about an installed plugin doctor Diagnose the local install build Compile a plugin to target platforms validate Validate a manifest against the schema init Scaffold a new plugin lint Lint a manifest against the rule set preview Preview the compiled output for a target ``` ## `add` Install a plugin from a source and symlink it into every detected agent. ```bash agentplugins add ``` | Flag | Description | |---|---| | `--target ` | Restrict fan-out to a specific agent (repeatable). | | `--force` | Overwrite an existing install. | | `--no-symlink` | Copy files instead of symlinking. | | `--version ` | Pin a specific version (GitHub sources only). | ### Sources | Source form | Example | |---|---| | `owner/repo` | `agentplugins add user/my-plugin` | | Full GitHub URL | `agentplugins add https://github.com/user/my-plugin` | | GitHub tree URL (monorepo subdir) | `agentplugins add https://github.com/org/repo/tree/main/plugins/my-plugin` | | `owner/repo@version` | `agentplugins add user/my-plugin@1.2.0` | | Local path | `agentplugins add ./my-plugin` | | `gist:` | `agentplugins add gist:abcdef123456` | When a GitHub tree URL points to a subdirectory, only that subdirectory is used as the plugin root — the rest of the repository is cloned but ignored. This lets plugin collections live in a monorepo and be installed individually. ### Examples ```bash # Latest release from GitHub agentplugins add user/my-plugin # Plugin inside a monorepo agentplugins add https://github.com/sigilco/agentplugins-roster/tree/main/plugins/roster # Specific version agentplugins add user/my-plugin@1.2.0 # Local working copy agentplugins add ./my-plugin # Restrict to Claude only agentplugins add user/my-plugin --target claude ``` ## `remove` Remove a plugin from the universal store and delete its symlinks. ```bash agentplugins remove ``` | Flag | Description | |---|---| | `--target ` | Remove the symlink for one agent only. | | `--keep-data` | Preserve `${PLUGIN_DATA}` contents. | ### Examples ```bash agentplugins remove my-plugin agentplugins remove my-plugin --target codex ``` ## `list` List every plugin in the universal store and which agents each is linked into. ```bash agentplugins list ``` | Flag | Description | |---|---| | `--json` | Emit JSON instead of a table. | | `--target ` | Filter to plugins linked into a specific agent. | ### Example ```bash agentplugins list --json ``` ```json [ { "name": "my-plugin", "version": "1.2.0", "targets": ["claude", "codex", "opencode", "copilot"] } ] ``` ## `update` Update one plugin or all plugins to the latest version available from its source. ```bash agentplugins update [name] ``` | Flag | Description | |---|---| | `--all` | Update every installed plugin. | | `--version ` | Pin to a specific version. | | `--dry-run` | Show what would change without writing. | ### Examples ```bash agentplugins update my-plugin agentplugins update --all agentplugins update my-plugin --version 1.3.0 ``` ## `info` Show details about an installed plugin: manifest, declared hooks, skills, MCP servers, and current symlinks. ```bash agentplugins info ``` | Flag | Description | |---|---| | `--json` | Emit JSON instead of formatted text. | ### Example ```bash agentplugins info my-plugin ``` ```text my-plugin@1.2.0 Description: Does awesome things across every agent Author: Jane Doe License: Apache-2.0 Repository: https://github.com/user/my-plugin Hooks: preToolUse matcher=bash command sessionStart inline Skills: my-plugin:my-skill MCP servers: filesystem Symlinks: ~/.claude/skills/my-plugin ~/.codex/skills/my-plugin ~/.config/opencode/skills/my-plugin ~/.copilot/skills/my-plugin ``` ## `doctor` Diagnose the local install: verify the binary, the universal store, the skills compatibility directory, and detected agents. ```bash agentplugins doctor ``` | Flag | Description | |---|---| | `--json` | Emit JSON instead of formatted text. | See the sample output in [Installation](/guide/installation#verify-the-install). ## `build` Compile a plugin manifest into each target platform's native format. ```bash agentplugins build [path] ``` | Flag | Description | |---|---| | `--target ` | Build only one target (repeatable). | | `--out ` | Output directory. Defaults to `dist/`. | | `--watch` | Rebuild on file change. | ### Examples ```bash agentplugins build agentplugins build --target claude --target codex agentplugins build --out build/ ``` ## `validate` Validate a manifest against the JSON Schema and report per-target compatibility issues. ```bash agentplugins validate [path] ``` | Flag | Description | |---|---| | `--target ` | Validate against a specific target's constraints. | | `--json` | Emit JSON instead of formatted text. | ### Example ```bash agentplugins validate ``` ```text 🔍 AgentPlugins Validation claude: ✓ No issues found codex: ⚠ hooks.sessionEnd is not supported by codex — this hook will be ignored ✅ All checks passed! ``` ## `init` Scaffold a new plugin from a template. ```bash agentplugins init [name] ``` | Flag | Description | |---|---| | `--template ` | Template to use: `minimal`, `logger`, `security-guard`, `formatter`. | | `--target ` | Declare a compile target (repeatable). Defaults to all. | | `--no-skill` | Skip creating a root `SKILL.md`. | ### Examples ```bash agentplugins init my-plugin --template security-guard agentplugins init my-plugin --target claude --target opencode ``` ## `lint` Lint a manifest against the eight built-in rules. See the [Linting guide](/guide/linting) for the full rule set. ```bash agentplugins lint [path] ``` | Flag | Description | |---|---| | `--json` | Emit JSON instead of formatted text. | | `--max-warnings ` | Fail when warning count exceeds `n`. | | `--rule ` | Run only one rule (repeatable). | ### Examples ```bash agentplugins lint agentplugins lint --json agentplugins lint --rule secrets --rule naming ``` ## `preview` Preview the compiled output for a specific target without writing files. Useful for debugging adapter behavior. ```bash agentplugins preview --target [path] ``` | Flag | Description | |---|---| | `--target ` | Target to preview (required). | | `--out ` | Write the preview to a file instead of stdout. | ### Example ```bash agentplugins preview --target opencode ``` ```typescript // opencode preview of my-plugin@1.0.0 import type { Plugin } from 'opencode' export default function (): Plugin { return { name: 'my-plugin', hooks: { 'tool.execute.before': async (ctx) => { if (ctx.tool.name === 'bash') { // ...preToolUse handler } }, }, } } ``` ## Global flags These work on every command: | Flag | Description | |---|---| | `-V, --version` | Print the CLI version and exit. | | `-h, --help` | Print help text and exit. | | `--no-color` | Disable colored output. | | `--store ` | Override the universal store path (defaults to `~/.agents/plugins`). | ## Next steps * [Quick start](/guide/quick-start) — a five-command walkthrough. * [Linting](/guide/linting) — the eight rules in depth. * [JSON Schema](/reference/schema) — programmatic validation. --- --- url: /reference/schema.md --- # JSON Schema The AgentPlugins manifest is defined by a published JSON Schema. Use it for editor autocomplete, programmatic validation, and CI checks. ## Locations | Source | URL / package | |---|---| | Raw (GitHub) | `https://raw.githubusercontent.com/sigilco/agentplugins/main/spec/v1/manifest.schema.json` | | npm | `@agentplugins/schema` | | Agent paths schema | `https://raw.githubusercontent.com/sigilco/agentplugins/main/spec/v1/agent-paths.json` | ## Editor autocomplete Add `$schema` to any JSON manifest to get autocomplete, hover docs, and inline validation in VS Code, JetBrains, Zed, and any other JSON-Schema-aware editor: ```json { "$schema": "https://raw.githubusercontent.com/sigilco/agentplugins/main/spec/v1/manifest.schema.json", "name": "my-plugin", "version": "1.0.0", "description": "Does awesome things across every agent" } ``` ## `@agentplugins/schema` package The npm package bundles the JSON Schema, generated TypeScript types, and a ready-to-use [Ajv](https://ajv.js.org/) validator. ```bash npm install @agentplugins/schema ``` ### Validate a manifest ```typescript import { validateManifest } from '@agentplugins/schema' const { valid, errors } = validateManifest(manifest) if (!valid) { for (const error of errors) { console.error(`${error.instancePath}: ${error.message}`) } } ``` `validateManifest` returns `{ valid: boolean, errors: Ajv.ErrorObject[] }`. On a valid manifest, `valid` is `true` and `errors` is empty. ### Types ```typescript import type { Manifest, Hook, Skill, MCPServerConfig } from '@agentplugins/schema' const manifest: Manifest = { name: 'my-plugin', version: '1.0.0', description: 'Does awesome things across every agent', } ``` ## Schema highlights The schema enforces the manifest contract. Highlights: ### `name` ```jsonc { "type": "string", "pattern": "^[a-z][a-z0-9-]*$", "maxLength": 64 } ``` Kebab-case, lowercase, max 64 chars. ### `version` ```jsonc { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-...)?$" } ``` Full [semver](https://semver.org/) regex including pre-release and build metadata. ### `description` ```jsonc { "type": "string", "minLength": 10 } ``` Minimum 10 characters. ### `targets` ```jsonc { "type": "array", "items": { "type": "string", "enum": ["claude", "codex", "copilot", "gemini", "kimi", "opencode", "pimono"] } } ``` Only the seven supported platforms. ### `hooks` An object whose keys are the 19 universal hook names. Each value is a `{ matcher?, handler }` object whose `handler` is a `command`, `http`, or `reference` handler. See the [Hooks guide](/guide/hooks) for details. ### `additionalProperties: false` The root object rejects unknown fields. This catches typos like `discription` or `verson` at validation time. ## Ajv usage example If you'd rather bring your own Ajv instance: ```typescript import Ajv from 'ajv' import addFormats from 'ajv-formats' import schema from '@agentplugins/schema/manifest.schema.json' with { type: 'json' } const ajv = addFormats(new Ajv({ allErrors: true })) const validate = ajv.compile(schema) const ok = validate(manifest) if (!ok) { console.error(validate.errors) } ``` ## Versioning The schema follows SemVer. The `v1` series is backwards-compatible — fields may be added but never removed or renamed in a backwards-incompatible way. Breaking changes ship as `v2`. ::: tip Pin to the raw GitHub URL on `main` to pick up non-breaking additions automatically. Pin to the same URL at a specific commit SHA (`.../raw.githubusercontent.com/sigilco/agentplugins//spec/v1/manifest.schema.json`) if you need absolute reproducibility. ::: ## Next steps * [Manifest reference](/guide/manifest) — what every field does. * [Linting](/guide/linting) — the eight lint rules. * [Adapters](/reference/adapters) — what each target platform emits. --- --- url: /reference/agent-paths.md --- # Agent Paths AgentPlugins scans the local machine for installed agent harnesses and fans plugin symlinks out to each one. This page is the authoritative registry of those paths. ## The universal store Every installed plugin lives in one place: | Path | Purpose | |---|---| | `~/.agents/plugins/` | Universal plugin store — the source of truth. | | `~/.agents/skills/` | Skills compatibility directory (also scanned). | `~/.agents/plugins//` is canonical. Removing a plugin from the store removes every symlink. Updating a plugin in the store updates every agent that links to it. ::: tip Skills placed in `~/.agents/skills/` are picked up and fanned out exactly like plugins in the universal store. Existing setups require no migration. ::: ## Detected agents Seven agent harnesses are supported: | Agent | Display name | Skill path | Binary | Manifest path | |---|---|---|---|---| | Claude Code | Claude Code | `~/.claude/skills` | `claude` | `~/.claude.json` | | Codex CLI | Codex CLI | `~/.codex/skills` | `codex` | `~/.codex/config.json` | | OpenCode | OpenCode | `~/.config/opencode/skills` | `opencode` | `~/.config/opencode/config.json` | | Kimi | Kimi | `~/.kimi/skills` | `kimi` | `~/.kimi/config.json` | | Gemini CLI | Gemini CLI | `~/.gemini/skills` | `gemini` | `~/.gemini/settings.json` | | GitHub Copilot CLI | GitHub Copilot CLI | `~/.copilot/skills` | `copilot` | `~/.copilot/config.json` | | Pi Mono | Pi Mono | `~/.pi/extensions` | `pi` | `~/.pi/config.json` | ## How detection works `agentplugins doctor` and `agentplugins add` detect agents by checking for the binary on `PATH` and the skill path on disk. An agent is considered **installed** if either the binary is resolvable or the skill path exists. ```text AgentPlugins doctor ──────────────────────────────────────── CLI version x.y.z Store path ~/.agents/plugins ✓ Skills path ~/.agents/skills ✓ Detected agents claude ~/.claude/skills ✓ codex ~/.codex/skills ✓ opencode ~/.config/opencode ✓ gemini ~/.gemini/skills ✗ (not installed) copilot ~/.copilot/skills ✓ kimi ~/.kimi/skills ✗ (not installed) pimono ~/.pi/extensions ✗ (not installed) 4 agents detected. ``` ::: warning `agentplugins add` only symlinks into detected agents. If you install a new harness later, run `agentplugins update --all` to fan existing plugins out to the new agent. ::: ## Symlink layout After installing a plugin, the universal store and the per-agent skill paths look like this: ``` ~/.agents/plugins/ └── my-plugin/ # source of truth ├── agentplugins.config.ts ├── SKILL.md └── hooks/ ~/.claude/skills/my-plugin → symlink to ~/.agents/plugins/my-plugin ~/.codex/skills/my-plugin → symlink ~/.config/opencode/skills/my-plugin → symlink ~/.copilot/skills/my-plugin → symlink ~/.gemini/skills/my-plugin → symlink ~/.kimi/skills/my-plugin → symlink ~/.pi/extensions/my-plugin → symlink ``` Each agent reads from its own skill path, unaware that the contents are shared. ## Overriding paths You can override the store path with the `--store` global flag: ```bash agentplugins --store /custom/store add user/my-plugin ``` There is no override for per-agent skill paths — those are determined by each agent's own conventions and are not configurable. ## Registry source The path registry is published as a machine-readable JSON document: | Source | URL | |---|---| | Raw (GitHub) | `https://raw.githubusercontent.com/sigilco/agentplugins/main/spec/v1/agent-paths.json` | ```json { "store": { "path": "~/.agents/plugins" }, "skillsCompat": { "path": "~/.agents/skills" }, "agents": [ { "name": "claude", "displayName": "Claude Code", "skillPath": "~/.claude/skills", "binary": "claude", "manifestPath": "~/.claude.json" } // ... ] } ``` ## Next steps * [Adapters reference](/reference/adapters) — what each agent's adapter emits. * [Skills guide](/guide/skills) — the `SKILL.md` format. * [Installation](/guide/installation) — installing the CLI. --- --- url: /reference/adapters.md --- # Adapters An adapter compiles the universal manifest into one target platform's native format. AgentPlugins ships seven adapters — one per supported agent. This page documents what each emits and the trade-offs. ## Adapter matrix | Adapter | Output type | Native handlers | Hooks supported | `tools[]` | `mcpServers` | |---|---|---|---|:---:|:---:| | `claude` | JSON manifest + `commands.json` | command, http, reference | full | ⚠️ | ✅ | | `codex` | JSON manifest | command | subset | ⚠️ | ✅ | | `copilot` | JSON manifest | command, http, reference | subset | ⚠️ | ❌ | | `gemini` | JSON manifest | command | subset | ⚠️ | ❌ | | `kimi` | JSON manifest | command | subset | ⚠️ | ❌ | | `opencode` | TypeScript plugin + `opencode.json` | inline (reference) | subset | ✅ | ✅ | | `pimono` | TypeScript extension + `package.json` | inline (reference) | subset | ✅ | ⚠️ | ⚠️ = WARN emitted; `tools[]` is not natively emitted — use `mcpServers` for Claude/Codex (Tier-1 universal tool path). Pi Mono has no built-in MCP; `tools[]` is the native tool path. Two families: **JSON-emitting** adapters (claude, codex, copilot, gemini, kimi) produce static manifest files the host reads at startup. **Code-emitting** adapters (opencode, pimono) produce real TypeScript modules the host imports and calls. See the [Capability Matrix](/guide/capability-matrix) for full cross-harness details. ## JSON-emitting adapters ### claude Emits a Claude Code plugin directory: ``` dist/claude/ .claude-plugin/ plugin.json # manifest (name, version, description, ...) commands.json # slash commands hooks/ pre-tool-use.sh # command handlers wrapped as scripts session-start.sh skills/ /SKILL.md ``` * All handler types are supported natively. * Inline/reference handlers are auto-wrapped as shell scripts that invoke the plugin module via the Bun runtime. ### codex Emits a Codex CLI plugin directory: ``` dist/codex/ .codex-plugin/ plugin.json hooks/ pre-tool-use.sh ``` * Supports `command` handlers only. * Unsupported hooks are dropped at build time with a warning. * Exit code `2` from a pre-tool handler blocks the action. ### copilot Emits a GitHub Copilot CLI plugin directory: ``` dist/copilot/ plugin.json hooks.json hooks/ pre-tool-use.sh ``` * Supports `command` and `http` handlers natively. * `preToolUse` is **fail-closed**: if the handler errors, the tool call is blocked. ### gemini Emits a Gemini CLI extension directory: ``` dist/gemini/ gemini-extension.json hooks/ pre-tool-use.sh ``` * Supports `command` handlers only. * Inline handlers are auto-wrapped as command scripts. ### kimi Emits a Kimi (Moonshot) plugin directory: ``` dist/kimi/ kimi.plugin.json hooks/ pre-tool-use.sh ``` * Supports `command` handlers only. * Hooks are **fail-open**: handler errors do not block the action. ## Code-emitting adapters ### opencode Emits a real OpenCode plugin as TypeScript: ``` dist/opencode/ plugin.ts # typed Plugin export with native hooks opencode.json # manifest (skills, mcpServers, commands) ``` * Hooks are mapped to OpenCode's native lifecycle (`tool.execute.before`, `session.start`, etc.). * Inline/reference handlers are emitted as native async functions — no shell wrapping. * Skills and MCP servers are declared in `opencode.json` and picked up by OpenCode's config loader. ::: tip OpenCode runs on Bun, so inline handlers run in-process with no startup overhead. Prefer inline handlers when targeting OpenCode. ::: #### OpenCode registration model OpenCode auto-discovers any `.ts` file dropped in `~/.config/opencode/plugins/` — no `config.json` edits required. Both paths use this: * **Codegen (universal plugins)**: adapter emits `plugin.ts` → linked as `.ts` in the plugins dir. * **Native modules (hand-crafted)**: ship the file as `.ts` (ESM; valid TypeScript). `agentplugins install` symlinks it from the store into the plugins dir, preserving `import.meta.url` so relative `require`/`import` paths resolve correctly. If a native module is shipped as `.mjs`, `agentplugins` links it under a `.ts` name automatically and emits a WARN. Rename the source to `.ts` to silence it. `.js` files are left as-is with a WARN (ambiguous CJS/ESM — not auto-normalized). ### pimono Emits a Pi Mono extension as a TypeScript module plus a `package.json`: ``` dist/pimono/ index.ts # extension entry point package.json # declares the `pi` key with extension metadata ``` * Hooks are mapped to Pi Mono's event system. * Inline/reference handlers are emitted as native functions, loaded via [jiti](https://github.com/unjs/jiti). * The `package.json` declares the extension under the `pi` key. ## Choosing an `emitLanguage` Code-emitting adapters respect the `emitLanguage` field on the manifest: ```typescript { emitLanguage: 'typescript' // default — also: 'javascript', 'go' } ``` | Value | Effect | |---|---| | `typescript` | Emit `.ts` files (default). Best for editor support. | | `javascript` | Emit `.js` files. Skip type checking. | | `go` | Emit `.go` files (Pi Mono only, experimental). | JSON-emitting adapters ignore this field. ## Hook coverage Not every platform supports every universal hook. The build step reports dropped hooks: ```text Building my-plugin@1.0.0 codex: ⚠ hooks.sessionEnd is not supported by codex — will be ignored ⚠ hooks.userPromptSubmit is not supported by codex — will be ignored kimi: ⚠ hooks.userPromptSubmit is not supported by kimi — will be ignored Built 7 targets. ``` Run [`agentplugins lint`](/guide/linting) to catch these before publishing — the `hook-coverage` rule surfaces every mismatch. ## Handler wrapping When a JSON-emitting adapter encounters an `inline` or `reference` handler, it wraps the TypeScript function as a shell script that invokes the plugin module through the Bun runtime: ```bash #!/usr/bin/env bash exec bun "${PLUGIN_ROOT}/dist/handler.js" pre-tool-use "$@" ``` This means inline handlers work everywhere — at the cost of a Bun startup on platforms that don't support them natively. For latency-sensitive hooks on Claude/Codex/Gemini/Kimi, prefer `command` handlers. ## Custom adapters `TargetPlatform` is an open string type — any non-empty target id is valid. Register a custom adapter via the `plugins` field in `defineConfig` and add the target id to `targets`: ```typescript // agentplugins.config.ts import { defineConfig } from '@agentplugins/core' import { myHarnessAdapter } from './src/my-harness-adapter.js' export default defineConfig({ manifest: { name: 'my-plugin', version: '1.0.0', description: '…' }, plugins: [{ name: 'my-harness', adapter: myHarnessAdapter }], targets: ['claude', 'my-harness'], }) ``` ### `PlatformAdapter` interface ```typescript interface PlatformAdapter { readonly name: string // target id readonly displayName: string // human label readonly supportedHooks: UniversalHookName[] // used by lint readonly supportedHandlers: HandlerType[] // used by lint readonly manifestPath: string // primary output path (for install) readonly manifestFormat: 'json' | 'toml' validate(plugin: PluginManifest): ValidationIssue[] compile(plugin: PluginManifest, options?: CompileOptions): AdapterOutput } interface AdapterOutput { files: { path: string; content: string }[] manifest: Record warnings: string[] issues: ValidationIssue[] postInstall?: string[] // commands to run after install nativeCopies?: NativeCopy[] // verbatim file passthrough } ``` `validate()` returns `ValidationIssue[]` — severity `'error'` aborts the build; `'warning'` is printed and continues. `compile()` returns the full set of files to write into `dist//`. Paths are relative to that directory. For a working example, see `plugins/example-custom-adapter/` in the repository. See [Extending the Build Pipeline](/guide/extending) for the full guide including lint rules and middleware hooks. ## Next steps * [Manifest reference](/guide/manifest) — what every field means. * [Hooks](/guide/hooks) — the 19 universal events. * [Extending the Build Pipeline](/guide/extending) — custom adapters and pipeline plugins. * [Agent paths](/reference/agent-paths) — where each adapter writes its output. --- --- url: /reference.md description: >- API reference for AgentPlugins — CLI commands, JSON schema, agent paths, and platform adapters. --- # Reference Technical reference for the AgentPlugins toolchain. For guides, how-to articles, and concept explanations, see the [Guide](/guide/introduction). ## CLI | Command | Description | |---|---| | [`add`](/reference/commands#add) | Install a plugin from GitHub or a local path | | [`remove`](/reference/commands#remove) | Remove a plugin and unlink from all agents | | [`list`](/reference/commands#list) | List installed plugins | | [`update`](/reference/commands#update) | Update plugin(s) from source | | [`info`](/reference/commands#info) | Show plugin metadata and symlink status | | [`doctor`](/reference/commands#doctor) | Diagnose store, symlinks, and agent detection | | [`init`](/reference/commands#init) | Scaffold a new plugin interactively | | [`build`](/reference/commands#build) | Compile plugin for all target platforms | | [`validate`](/reference/commands#validate) | Validate manifest against schema | | [`lint`](/reference/commands#lint) | Static analysis for common issues | | [`preview`](/reference/commands#preview) | Preview compiled output for a target | ## Schema * [JSON Schema](/reference/schema) — manifest schema, TypeScript types, Ajv validator ## Platform * [Agent Paths](/reference/agent-paths) — store layout, per-agent skill paths, symlink layout * [Adapters](/reference/adapters) — what each platform adapter emits ## Manifesto For platform compatibility and capability decisions, see the [Capability Matrix](/guide/capability-matrix) in the Guide.