agent-config 0.3.0

Install hooks/integrations into AI coding harnesses (Claude Code, Cursor, Gemini CLI, OpenCode, Codex CLI, Cline, Windsurf, ...) without learning each one's filesystem layout.
Documentation
# OpenCode

ID: `opencode` — `agent_config::by_id("opencode")`

(`sst/opencode` — the open-source AI coding agent.)

## Hooks

### User scope (`Scope::Global`)

| | |
| --- | --- |
| File | `~/.config/opencode/plugins/<tag>.ts` |
| Mechanism | One TypeScript plugin file per consumer |
| Backup | `<file>.bak` (only if the file already existed before install) |

### Project scope (`Scope::Local(<root>)`)

| | |
| --- | --- |
| File | `<root>/.opencode/plugins/<tag>.ts` |
| Mechanism | One TypeScript plugin file per consumer |
| Backup | `<file>.bak` (only if the file already existed before install) |

> OpenCode also supports plugins listed in the `plugin` array of
> `opencode.json`, auto-installed via Bun. Not used here; we drop a single
> file in the plugins directory.

### Format

The default plugin (when `HookSpec::script` is `None`):

```ts
// Generated by agent-config. Edit at your own risk.
// Re-running install will overwrite this file.

import type { Plugin } from "@opencode-ai/plugin";

export const Hook: Plugin = async ({ $ }) => ({
  "tool.execute.before": async (input, output) => {
    if (input.tool !== "bash") return;
    const payload = JSON.stringify({ tool: input.tool, args: output.args });
    await $`echo ${payload} | myapp hook opencode`;
  },
});
```

Pass `ScriptTemplate::TypeScript(body)` on the `HookSpec` to ship a custom
plugin instead. The library writes the body verbatim.

### Event mapping

The `Event` enum is dynamically mapped to OpenCode's hook points:
- `Event::PreToolUse` maps to `"tool.execute.before"`.
- `Event::PostToolUse` maps to `"tool.execute.after"`.
- `Event::Custom(name)` maps directly to the custom hook name `name` (e.g., `"session.idle"`).

For tool events (`tool.execute.before`/`after`), the callback receives `(input, output)` and compiles with tool filtering/payload formatting. For custom non-tool events, the callback receives `(input, output)` and compiles a payload passing the entire input: `const payload = JSON.stringify({ event: input });`.

OpenCode supports 25+ hook points: `tool.execute.before/after`, session/file/
message/shell/command/LSP/permission/server/todo/TUI families.

### Matcher mapping

The `Matcher` enum is dynamically compiled into a guard block in the hook callback:
- `Matcher::All` compiles to no tool filter.
- `Matcher::Bash` compiles to `if (input.tool !== "bash") return;`.
- `Matcher::Exact(tool)` compiles to `if (input.tool !== "<tool>") return;`.
- `Matcher::AnyOf(tools)` compiles to `if (![<tools>].includes(input.tool)) return;`.
- `Matcher::Regex(pattern)` compiles to `if (!new RegExp("<pattern>").test(input.tool)) return;`.

## Prompt instructions

OpenCode reads system instructions from `AGENTS.md`.

| Scope | Host file |
| --- | --- |
| User | `~/.config/opencode/AGENTS.md` |
| Project | `<root>/AGENTS.md` |

When `HookSpec::rules` is present, this integration injects the rules body as a
tagged `AGENT-CONFIG:<tag>` HTML-comment fenced block in that host file.

## Instructions

Standalone instruction bodies installed via `InstructionSurface` use
`InstructionPlacement::InlineBlock`. The body is injected as a tagged
`AGENT-CONFIG-INSTR:<name>` HTML-comment fenced block in OpenCode's `AGENTS.md`
file.

### User scope (`Scope::Global`)

| | |
| --- | --- |
| Host file | `~/.config/opencode/AGENTS.md` |
| Mechanism | Tagged HTML-comment fence (`<!-- BEGIN AGENT-CONFIG-INSTR:<name> -->`) |
| Ledger | `~/.config/opencode/.agent-config-instructions.json` |
| Placement | `InstructionPlacement::InlineBlock` |

### Project scope (`Scope::Local(<root>)`)

| | |
| --- | --- |
| Host file | `<root>/AGENTS.md` |
| Mechanism | Tagged HTML-comment fence (`<!-- BEGIN AGENT-CONFIG-INSTR:<name> -->`) |
| Ledger | `<root>/.opencode/.agent-config-instructions.json` |
| Placement | `InstructionPlacement::InlineBlock` |

## MCP servers

### User scope (`Scope::Global`)

| | |
| --- | --- |
| File | `~/.config/opencode/opencode.json` |
| Key | `mcp` |
| Format | JSONC accepted, written as JSON |

### Project scope (`Scope::Local(<root>)`)

| | |
| --- | --- |
| File | `<root>/opencode.json` |
| Format | JSONC accepted, written as JSON |
| Key | `mcp` (object keyed by server name) |

### Configuration

```json
{
  "mcp": {
    "my-server": {
      "type": "local",
      "command": ["node", "/path/to/server.js"]
    }
  }
}
```

OpenCode automatically handles OAuth for remote servers and supports both local
(command array) and remote (url) transports.

## Skills

| Scope | Path |
| --- | --- |
| User | `~/.config/opencode/skills/<name>/` |
| Project | `.opencode/skills/<name>/` |

OpenCode also scans Claude-compatible and `.agents/skills` locations, but
`agent-config` writes the native OpenCode path for this integration.

## References

- <https://opencode.ai/docs/plugins/>
- <https://opencode.ai/docs/rules/>
- <https://opencode.ai/docs/config/>
- <https://opencode.ai/docs/mcp-servers/>
- <https://opencode.ai/docs/skills>
- <https://github.com/sst/opencode>