@khalilgharbaoui/opencode-claude-code-plugin 0.18.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,49 +2,65 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@khalilgharbaoui/opencode-claude-code-plugin.svg)](https://www.npmjs.com/package/@khalilgharbaoui/opencode-claude-code-plugin)
4
4
 
5
- An [opencode](https://opencode.ai) plugin that wraps the **Claude Code CLI** (`claude`) and routes model traffic through it instead of the Anthropic HTTP API. You get to use opencode's UI, agents, MCP, and permission system while authenticating and billing through whichever method `claude` is logged into (Pro/Max plan, Bedrock, Vertex, or API key).
5
+ Use Claude models inside [opencode](https://opencode.ai) by driving the official **Claude Code CLI** (`claude`) as a subprocess. opencode therefore inherits whatever authentication that CLI already holds: a Claude subscription login, an API key, Bedrock, or Vertex. This plugin never reads, stores, or replays an OAuth token of its own.
6
+
7
+ - **Your CLI's auth, untouched.** Because `claude` does the authenticating, there is no subscription token here to lift and replay against the Anthropic API. That replay is what proxy-style opencode plugins do, it is a practice Anthropic has disallowed for third-party tools in 2026, and it is structurally not something this plugin can do.
8
+ - **opencode stays in charge of your machine.** Bash, Edit, Write, WebFetch and subagent dispatch are executed by opencode, behind its permission prompts and audit log, rather than by Claude Code. See [Selective tool proxy](#selective-tool-proxy).
9
+ - **Headless by default, which has a billing consequence.** `claude --print` usage on a subscription plan draws from the separate Agent SDK / extra-usage allowance rather than from normal plan usage; API-key authentication is unaffected. See [Billing](#billing).
6
10
 
7
11
  > Maintained fork of [`unixfox/opencode-claude-code-plugin`](https://github.com/unixfox/opencode-claude-code-plugin). Published as `@khalilgharbaoui/opencode-claude-code-plugin` on npm.
8
12
 
9
13
  ---
10
14
 
11
- ## TL;DR
15
+ ## Quickstart
12
16
 
13
- ```bash
14
- # 1. Make sure `claude` is installed and logged in
15
- claude --version
17
+ ### 1. Install and log in the Claude Code CLI
16
18
 
17
- # 2. Add this to your opencode.json
19
+ The plugin drives an existing [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code); it does not bundle one. Check that `claude` is on your `$PATH` and authenticated:
20
+
21
+ ```bash
22
+ claude --version # e.g. 2.1.263 (Claude Code)
23
+ claude auth status # which account you are signed in as
24
+ claude auth login # run this if you are not signed in yet
18
25
  ```
19
26
 
27
+ `login`, `status` and `logout` are the `claude auth` subcommands as of 2.1.263. Run `claude auth --help` if your install differs.
28
+
29
+ ### 2. Add the plugin to your opencode config
30
+
31
+ opencode reads a global config at `~/.config/opencode/opencode.json` (or `$XDG_CONFIG_HOME/opencode/` when that is set). A project-level `opencode.json` in your repo overrides the global one, and `OPENCODE_CONFIG=/path/to/config.json` points opencode at one specific file instead. Put the plugin in the global config so every project gets it:
32
+
20
33
  ```json
21
34
  {
22
35
  "plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"]
23
36
  }
24
37
  ```
25
38
 
26
- That's it. Restart opencode, pick a `claude-code` model, done.
39
+ That package spec is the whole install. Do **not** `npm install` the package yourself: opencode resolves and caches plugin packages on its own. You do not need a `provider` block either, unless you want to change one of the [options](#options-reference).
27
40
 
28
- The plugin self-registers the `claude-code` provider, all current Claude Code models (Haiku 4.5, Sonnet 4.5/4.6/5, Opus 4.5/4.6/4.7/4.8/5, Fable 5/5.1, Mythos 5/5.1) with reasoning variants (`low` / `medium` / `high` / `xhigh` / `max`), and sensible defaults for tool proxying. You don't need to write a `provider` block at all unless you want to override something.
41
+ ### 3. Restart opencode and verify
29
42
 
30
- ---
43
+ Quit opencode fully and relaunch it: plugins are loaded once, at process start, so a reload is not enough.
44
+
45
+ In the model picker you should now see a provider called **Claude Code (Default)** holding entries such as `Claude Haiku 4.5 (1×)`, `Claude Sonnet 5 (3×)` and `Claude Opus 5 (5×)`. The `(N×)` suffix is each model's list price relative to Haiku; see [Models](#models). Pick one and send a message.
31
46
 
32
- ## Prerequisites
47
+ If the provider does not appear, turn on the plugin's log file and look for its one startup line:
48
+
49
+ ```bash
50
+ OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
51
+ grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
52
+ ```
33
53
 
34
- - [opencode](https://opencode.ai) installed
35
- - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude` on your `$PATH`)
36
- - Node 18+ / Bun
54
+ That single `NOTICE: claude-code plugin ready` entry reports the plugin version, the `claude` binary and version it found, the directory it will spawn in, and which providers registered. [Startup diagnostics](#startup-diagnostics) explains every field.
37
55
 
38
- ## Install
56
+ ### Not seeing a version you just upgraded to?
39
57
 
40
- ### From npm (recommended)
58
+ opencode resolves the `@latest` plugin spec once and freezes the concrete version into its own package cache, so restarting never re-resolves the tag. Delete the cache entry and relaunch:
41
59
 
42
60
  ```bash
43
- npm install @khalilgharbaoui/opencode-claude-code-plugin
61
+ rm -rf ~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest
44
62
  ```
45
63
 
46
- Then add it to `opencode.json` as shown in the TL;DR.
47
-
48
64
  ### Local development
49
65
 
50
66
  ```bash
@@ -62,11 +78,13 @@ In your `opencode.json`, point at the local build with a `file://` URL:
62
78
  }
63
79
  ```
64
80
 
81
+ CI installs and builds on **Node 24** (`.github/workflows/publish.yml`), which is the only version this package is built against. `package.json` declares no `engines` range, so older Node versions are untested rather than deliberately unsupported. opencode itself may run under Bun; the [interactive transport](#interactive-transport-experimental) requires that.
82
+
65
83
  ---
66
84
 
67
85
  ## Models
68
86
 
69
- The plugin auto-registers the following. They appear in the model picker without any extra config.
87
+ The plugin auto-registers the following, and they appear in the model picker with no extra config: Haiku 4.5, Sonnet 4.5/4.6/5, Opus 4.5/4.6/4.7/4.8/5 (plus two fast-mode Opus entries), Fable 5/5.1 and Mythos 5/5.1, each except Haiku carrying `low` / `medium` / `high` / `xhigh` / `max` reasoning variants.
70
88
 
71
89
  | ID | Display name | Context | Output | Reasoning variants | Price × |
72
90
  |---|---|---|---|---|---|
@@ -120,8 +138,11 @@ Variants set the underlying reasoning effort. They're regular opencode model var
120
138
 
121
139
  ## Billing
122
140
 
123
- This plugin drives Claude Code headlessly (Agent SDK > `claude --print`)
124
- check out this page for updated information about billing: https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan
141
+ By default this plugin drives Claude Code headlessly (the Agent SDK path, `claude --print`). Since June 2026, headless usage on a Claude subscription plan draws from a separate Agent SDK credit / extra usage rather than from normal plan usage. Authenticating the CLI with an API key is unaffected by that policy and bills as ordinary API usage.
142
+
143
+ Anthropic's own page is the authoritative and current source, including the amounts, which change: <https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan>
144
+
145
+ Two things in this plugin interact with the above. [`ignoreAnthropicApiKey`](#options-reference) stops a stray `ANTHROPIC_API_KEY` in your environment from silently redirecting the CLI onto pay-as-you-go API billing. The experimental [interactive transport](#interactive-transport-experimental) drives the real `claude` TUI instead of `--print`, which bills as normal plan usage.
125
146
 
126
147
  ---
127
148
 
@@ -230,10 +251,10 @@ That beats whatever effort the call arrived with. It has to, because opencode re
230
251
 
231
252
  An agent that declares nothing keeps the inherited effort, so this changes nothing until a file asks for it. An unrecognised level is refused and the inherited one kept, since the CLI rejects a level it does not know. Compaction is exempt: its summary always gets the full budget.
232
253
 
233
- To force an **account** rather than a model, pin the full string. Both halves are needed, because the provider selects the account's config dir and the `@account` marker is what the model was registered under for that provider:
254
+ To force an **account** rather than a model, pin the full string. This only applies if you declared [`accounts`](#multiple-claude-code-accounts) in the first place; with the default single-account setup there is nothing to pin. Both halves are needed, because the provider selects the account's config dir and the `@account` marker is what the model was registered under for that provider:
234
255
 
235
256
  ```yaml
236
- model: claude-code-appical/claude-opus-5@appical
257
+ model: claude-code-work/claude-opus-5@work
237
258
  ```
238
259
 
239
260
  ### Options reference
@@ -259,33 +280,61 @@ model: claude-code-appical/claude-opus-5@appical
259
280
 
260
281
  | Option | Type | Default | Description |
261
282
  |---|---|---|---|
262
- | `cliPath` | string | `process.env.CLAUDE_CLI_PATH ?? "claude"` | Path to the `claude` binary. |
263
- | `accounts` | string[] | – | Optional account list. `default` is implicit. Expands into `Claude Code (Default)`, `Claude Code (Personal)`, etc. |
264
- | `cwd` | string | session directory, then `process.cwd()` | Working directory for the spawned CLI. Resolved **lazily per request**: an explicit value wins, then the opencode session's own `directory` (so `opencode serve` and the web UI spawn in the right project even though one server handles many), then `process.cwd()`. Contributed by [@galvani](https://github.com/galvani). |
265
- | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. Ignored when `proxyTools` is set — the proxy handles permissions through opencode instead. |
266
- | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
283
+ | `cliPath` | string | `"claude"` | Path to the `claude` executable (a binary, not a shell command with flags). opencode's config hook seeds this with `"claude"`, so under opencode this default always applies; `CLAUDE_CLI_PATH` is only consulted when `createClaudeCode()` is called directly and the option is absent. Account providers wrap it with a generated script; never point it at one of those yourself. |
284
+ | `accounts` | string[] | – | **Optional.** Most setups need no accounts at all: with this unset you get a single `Claude Code (Default)` provider on your normal `~/.claude` login. Supply names only to run several Claude logins side by side; `default` stays implicit, so `["work", "personal"]` gives you `Claude Code (Default)`, `Claude Code (Work)` and `Claude Code (Personal)`. See [Multiple Claude Code accounts](#multiple-claude-code-accounts). |
285
+ | `cwd` | string | see description | Working directory for the spawned CLI. Resolved **lazily per request**, first match winning: this explicit value, then the opencode session's own `directory` (so `opencode serve` and the web UI spawn in the right project even though one server handles many), then `process.cwd()` when it is a real directory, then the project directory captured at plugin init (this rescues macOS GUI launches, where `process.cwd()` is `/`), and finally `process.cwd()` regardless. [Startup diagnostics](#startup-diagnostics) reports which tier won. Session tier contributed by [@galvani](https://github.com/galvani). |
286
+ | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. It is still passed when `proxyTools` is set: proxied calls go through opencode's permission system regardless, but unproxied CLI built-ins do not. The one case where the flag is dropped is `permissionMode: "plan"`, because the CLI lets the skip flag override plan mode outright. See [Plan mode](#plan-mode). |
287
+ | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to headless `claude --permission-mode`. `"plan"` also suppresses `--dangerously-skip-permissions` (see the row above). Not version-gated, so check that your installed CLI accepts the value. The [interactive transport](#interactive-transport-experimental) does not forward it. |
288
+ | `defaultSubagentModel` | string | – | Model that plugin-discovered `mode: subagent` agents run on when their own definition pins nothing. The caller's account is kept; only the model name changes. An agent's own `forceModel` wins over it, and an unknown id is refused rather than spawned. Unset means no implicit override at all. See [Subagents: your account, their model](#subagents-your-account-their-model). |
267
289
  | `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Claude built-in tools to route through opencode's executor + permission UI. Opt-in extras: `"Question"`, `"Compress"`. See [Selective tool proxy](#selective-tool-proxy). |
268
290
  | `extraDisallowedTools` | string[] | – | Extra Claude built-ins to switch off with `--disallowedTools`, on top of what `proxyTools` implies. Claude's names, e.g. `["NotebookEdit"]`. See [Closing a tool with no proxy](#closing-a-tool-with-no-proxy). |
269
291
  | `proxyToolTimeoutMs` | `Record<string, number>` | – | Per-tool proxy call deadline in ms, keyed by proxy tool name (`bash`, `task`, …). Defaults: 10 min flat, `task` → 60 min. For `bash`, the call's own `input.timeout` is honoured on top (`max(resolved, input.timeout)`). See [Selective tool proxy](#selective-tool-proxy). |
270
- | `planModeQuestion` | boolean | `false` | Route `ExitPlanMode` approval through opencode's native `question` tool instead of a text "(yes/no)" prompt. Opt-in; verify the form works in your installation first. See [Plan mode](#plan-mode). |
292
+ | `planModeQuestion` | boolean | `false` | Route `ExitPlanMode` approval through opencode's native `question` tool instead of a text "(yes/no)" prompt. Opt-in, and currently unreachable on the default headless transport, which is not offered an `ExitPlanMode` tool at all. See [Plan mode](#plan-mode). |
271
293
  | `controlRequestBehavior` | `allow` \| `deny` | `allow` | Default response when `skipPermissions: false` and Claude sends a `can_use_tool` control request. |
272
294
  | `controlRequestToolBehaviors` | `Record<string, "allow" \| "deny">` | – | Per-tool override for `can_use_tool`. Example: `{ "Bash": "deny", "Read": "allow" }`. |
273
295
  | `controlRequestDenyMessage` | string | built-in message | Message returned to Claude on a deny. |
274
296
  | `bridgeOpencodeMcp` | boolean | `true` | Auto-translate your opencode `mcp` block into Claude's `--mcp-config`. See [MCP bridge](#mcp-bridge). |
275
297
  | `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
276
298
  | `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
299
+ | `hotReloadMcp` | boolean | `true` | With MCP bridging on, compare the merged MCP config and runtime status at the start of each turn and respawn the `claude` process when they drifted, so a server you just enabled or disabled becomes visible without restarting opencode or opening a new chat. Eviction waits for pending proxy calls, never happening mid tool-call, and the session id is preserved for `--resume`. Set `false` to keep a cached subprocess until the chat is reset. It does not reload other provider options and does not watch the contents of files named in `mcpConfig`. |
300
+ | `proxyOpencodeMcpTools` | boolean | `true` | Route the MCP tools discovered from opencode through the in-process `opencode_proxy` server instead of bridging them straight into Claude's `--mcp-config`. With both layers pointed at the same server, direct bridging executes every call twice, once in Claude's own MCP child process and once in opencode; proxying keeps opencode as the single execution site while preserving its permission prompts and tool rows. Falls back to direct bridging when discovery is unavailable, so do not treat it as an exactly-once guarantee for write-capable tools. |
277
301
  | `webSearch` | `"claude"` \| `"disabled"` \| `<tool>` | `"claude"` | Routing for Claude's built-in `WebSearch`. See [WebSearch routing](#websearch-routing). |
278
302
  | `multiStepContinuation` | boolean | `true` | Append a system-prompt hint nudging Claude to chain tool calls within one turn instead of pausing between subtasks. Each opencode turn boundary requires the user to manually press "continue", so for multi-step tasks this reduces friction. Set `false` to disable. |
279
303
  | `autoContinueIncompleteTurns` | boolean \| `"smart"` | `"smart"` | Smartly continue incomplete Claude CLI results inside the same opencode turn. Reduces manual "continue" presses when Claude ends after reasoning/tool activity without a useful final answer. Set `false` to disable. |
280
304
  | `compactionModel` | string | `"claude-haiku-4-5"` | Model used when opencode invokes `/compact`. Override per-process via the `CLAUDE_CODE_COMPACTION_MODEL` env var (env wins over config). See [Compaction](#compaction). |
281
- | `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from every spawned `claude` process so it authenticates with your logged-in subscription instead of pay-as-you-go API billing. The plugin warns once at startup whenever an API key is detected, regardless of this setting. See [Billing](#billing-change-june-15-2026-agent-sdk-credit). |
305
+ | `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from every spawned `claude` process so it authenticates with your logged-in subscription instead of pay-as-you-go API billing. The plugin warns once at startup whenever an API key is detected, regardless of this setting. See [Billing](#billing). |
282
306
  | `idleProcessTimeoutMs` | number | – | Kill a retained headless Claude worker after this many idle milliseconds following a completed turn. The session id is preserved for `--resume`; a new turn cancels the timer. Values above Node's maximum timer delay (`2147483647`) are ignored. Omit or set to `0` to retain workers until LRU eviction. Interactive transport is excluded. Contributed by [@bernardofortes](https://github.com/bernardofortes). |
283
307
  | `bridgeOpencodeSkills` | boolean | `false` | Expose your opencode skills to Claude's native `Skill` tool. See [Skill bridge](#skill-bridge). Written by [@broskees](https://github.com/broskees). |
284
- | `interactive` | boolean | `false` | **Experimental.** Drive the interactive `claude` TUI (subscription billing) instead of headless `--print`. Requires opencode running under Bun with PTY support; silently falls back to headless otherwise. Env: `CLAUDE_CODE_INTERACTIVE_TRANSPORT=1`. See [Interactive transport](#interactive-transport-experimental). |
308
+ | `logging` | object | all defaults | The plugin's own logger, four independent fields: `file` (boolean, default `false`), `dir` (string, default `~/.local/share/opencode-claude-code/`), `mode` (`"silent"` \| `"debug"`, default `"silent"`) and `level` (`"debug"` \| `"info"` \| `"notice"` \| `"warn"` \| `"error"`, default `"info"`). Goes under `provider.claude-code.options` like every other row here. See [Logging](#logging). |
309
+ | `turnStats` | boolean | `false` | Append a one-line cost / duration / cache footer to each finished turn. See [Per-turn stats](#per-turn-stats). |
310
+ | `interactive` | boolean | `false` | **Experimental.** Drive the interactive `claude` TUI (subscription billing) instead of headless `--print`. Requires opencode running under Bun with PTY support; silently falls back to headless otherwise. The tool proxy, `permissionMode` and `/btw` are all unavailable on it, so read [What it does not support](#what-it-does-not-support) before enabling. Env: `CLAUDE_CODE_INTERACTIVE_TRANSPORT=1`. |
285
311
  | `interactiveBypass` | boolean | `false` | Deprecated/no-op with `interactive`: Claude Code's TUI shows a manual safety confirmation for `bypassPermissions`, so the plugin intentionally does not pass it. |
286
312
  | `interactiveAllowTools` | string[] | `["Bash", "Edit", "Write", "Read", "WebFetch"]` | With `interactive`: built-in tools pre-allowed without prompting (replaces the default list). MCP server wildcards (`mcp__<server>__*`) are always added from the bridged config. |
287
313
  | `interactiveSystemPrompt` | boolean | `true` | With `interactive`: append this plugin's CLI/AGENTS/continuation prompt via `--append-system-prompt-file`. The transport intentionally does not forward opencode's own system prompt, because it can trigger Claude Code's third-party-app usage gate on subscription accounts. Set `false` only for diagnostics. |
288
314
 
315
+ ### Environment variables
316
+
317
+ Every variable the plugin itself reads, in one place. Config is read once at opencode startup, so these are the way to change behaviour for a single run without editing `opencode.json`. Claude Code's own variables (`CLAUDE_CODE_DISABLE_THINKING` and friends) are passed through untouched and are listed under [Extended thinking](#extended-thinking).
318
+
319
+ | Variable | Read by | Effect |
320
+ |---|---|---|
321
+ | `CLAUDE_CLI_PATH` | provider factory | Fallback `claude` path when `cliPath` is absent. Under opencode the config hook always supplies `cliPath`, so this only applies to direct `createClaudeCode()` use. |
322
+ | `CLAUDE_CODE_COMPACTION_MODEL` | compaction spawn | Model for `/compact`. Wins over the `compactionModel` option. See [Compaction](#compaction). |
323
+ | `CLAUDE_CODE_INTERACTIVE_TRANSPORT` | transport selection | `1` turns on the experimental [interactive transport](#interactive-transport-experimental) for one process, same as `interactive: true`. |
324
+ | `CLAUDE_CODE_INTERACTIVE_BYPASS` | transport selection | Requests `bypassPermissions` in interactive mode. Deliberately ignored, with a warning, for the reason in the `interactiveBypass` row above. |
325
+ | `CLAUDE_CODE_START_WATCHDOG_MS` | start watchdog | Milliseconds a `claude` process may stay completely silent on stdout after a turn is written, or after a proxy tool result should have resumed it, before the plugin acts. First expiry respawns the process and resumes the session; a second ends the turn with an error rather than hanging. Default `90000`; a positive integer is required and anything else falls back to that. Mainly a knob for reproducing the hang. |
326
+ | `OPENCODE_CLAUDE_CODE_LOG_FILE` | logger | `1` writes the log file, `0` forces it off even when `logging.file` is `true`. See [Logging](#logging). |
327
+ | `OPENCODE_CLAUDE_CODE_LOG_DIR` | logger | Directory for the log file, overriding `logging.dir`. |
328
+ | `OPENCODE_CLAUDE_CODE_LOG_LEVEL` | logger | Minimum level to emit, overriding `logging.level`. An unrecognised value falls through to config. |
329
+ | `DEBUG` | logger | `DEBUG=opencode-claude-code` promotes the logger to `mode: "debug"`, echoing every emitted level to opencode's TUI. |
330
+ | `OPENCODE_CLAUDE_CODE_PLUGIN_NO_CLEANUP` | startup cleanup | `1` skips the one-time removal of a stale **unscoped** `opencode-claude-code-plugin` install from opencode's plugin cache. That old package is a different artifact that shadows this scoped one when both are present; set this if you are deliberately keeping it. |
331
+ | `OPENCODE_WORKTREE` | MCP bridge | Overrides worktree-root detection, which otherwise walks up from the working directory looking for a `.git` entry. |
332
+ | `OPENCODE_CONFIG` / `OPENCODE_CONFIG_DIR` | config discovery | Where the plugin looks for your opencode config when bridging MCP and skills. See [Discovery order](#discovery-order-highest-to-lowest-priority). |
333
+ | `OPENCODE_VERSION` | startup diagnostics | Reported as the opencode version when set, sparing the plugin a `--version` spawn. Diagnostics only. |
334
+ | `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` | spawn environment | Not set by the plugin: these are yours, and Claude Code authenticates with them in preference to your subscription login when present. `ignoreAnthropicApiKey: true` strips them from the spawn. See [Billing](#billing). |
335
+
336
+ The plugin also honours the usual path conventions rather than defining its own: `XDG_CONFIG_HOME` and `XDG_CACHE_HOME` (falling back to `~/.config` and `~/.cache`), `HOME` / `USERPROFILE`, and Claude Code's `CLAUDE_CONFIG_DIR` when the interactive transport needs to find the session transcript. Account providers set `CLAUDE_CONFIG_DIR` themselves for the process they spawn.
337
+
289
338
  ### Overriding model metadata
290
339
 
291
340
  To rename a model, change a limit, or add a custom one:
@@ -312,7 +361,7 @@ Anything you supply is merged on top of the defaults; you don't need to redeclar
312
361
 
313
362
  ## Interactive transport (experimental)
314
363
 
315
- By default the plugin spawns `claude --print` (headless). From **June 15, 2026** that usage bills against the separate [Agent SDK credit](#billing-change-june-15-2026-agent-sdk-credit) on subscription plans. The interactive transport instead drives the real interactive `claude` TUI — which bills as **normal plan usage** — under a native PTY inside opencode's Bun runtime, types your prompt into it, and streams the session transcript (`~/.claude/projects/<cwd>/<session-id>.jsonl`) back through the same pipeline the headless transport uses.
364
+ By default the plugin spawns `claude --print` (headless). From **June 15, 2026** that usage bills against the separate [Agent SDK credit](#billing) on subscription plans. The interactive transport instead drives the real interactive `claude` TUI — which bills as **normal plan usage** — under a native PTY inside opencode's Bun runtime, types your prompt into it, and streams the session transcript (`~/.claude/projects/<cwd>/<session-id>.jsonl`) back through the same pipeline the headless transport uses.
316
365
 
317
366
  ```json
318
367
  "options": { "interactive": true }
@@ -333,12 +382,21 @@ Or per-process: `CLAUDE_CODE_INTERACTIVE_TRANSPORT=1`.
333
382
 
334
383
  Set `interactiveSystemPrompt: false` only for diagnostics. While disabled, the interactive session will not receive the plugin's CLI context, AGENTS.md guidance, or continuation hints.
335
384
 
336
- ### What's different
385
+ ### What it does not support
386
+
387
+ This is the part to read before turning it on. Three whole features of this plugin are simply absent on the interactive transport:
388
+
389
+ - **No tool proxy.** The interactive spawn starts no proxy MCP server at all, so `mcp__opencode_proxy__bash`, `edit`, `write`, `webfetch`, `task`, `task_batch`, `question` and `compress` do not exist for that session. Claude uses its own built-in tools directly, which means opencode does not execute them, does not prompt for them, and does not log them. Everything in [Selective tool proxy](#selective-tool-proxy) applies to the headless transport only.
390
+ - **No `permissionMode`.** The interactive spawn never passes your `permissionMode` to the CLI, so `"plan"` and the rest have no effect there. Permission handling is the pre-allow list described below and nothing else.
391
+ - **No [`/btw`](#side-questions-with-btw).** Side questions ride Claude Code's `side_question` control protocol over the headless process's stdio. Asking one in an interactive session returns an error telling you so.
392
+
393
+ ### What else is different
337
394
 
338
395
  - **Permissions:** the interactive TUI has no `can_use_tool` control channel, so tools can't be approved per-call through opencode. Built-in tools are pre-allowed via a settings allow list (default `Bash, Edit, Write, Read, WebFetch`; override with `interactiveAllowTools`). `bypassPermissions` is intentionally not used here because Claude Code shows a manual safety confirmation in the TUI and defaults to exit.
339
396
  - **Input is text-only:** images and other non-text blocks are dropped (with a logged warning); tool results are rendered as labeled text.
340
397
  - **Output granularity:** text arrives per transcript record, not token-by-token, so it can feel chunkier than headless streaming.
341
398
  - **Turn timeout:** a turn that produces no terminal stop within 30 minutes is reported honestly as an error result (visible truncation), not silently ended.
399
+ - **No idle eviction:** `idleProcessTimeoutMs` does not apply to interactive sessions.
342
400
  - `/compact` always uses the headless transport regardless of this setting.
343
401
 
344
402
  ---
@@ -431,6 +489,8 @@ It is the one proxy tool opencode never sees. The call is answered inside the pl
431
489
 
432
490
  Without it, the appended system prompt tells the model that `compress` is unavailable and to ignore instructions that ask for it, which is the right answer when nothing implements it.
433
491
 
492
+ The store, the interceptor and the two prompt variants are covered by tests, but the full "model calls compress, the next turn really is a fresh process carrying only the summary" round-trip has not been verified against a live CLI. Treat it as working-but-unproven and check the plugin log the first time you rely on it.
493
+
434
494
  Only those seven values are actually proxied; anything else you put in `proxyTools` is ignored. Proxying `Edit` also disables `MultiEdit` — opencode has no batched-edit equivalent, so Claude is forced to fan out into single `Edit` calls that each flow through the permission UI. The `"Question"` proxy is version-gated on opencode's built-in `question` tool: on builds that lack the registry entry the def is silently dropped (a forwarded call would otherwise render as `⚙ invalid`), so add it only on opencode versions that ship the `question` tool.
435
495
 
436
496
  Without `"Task"` in `proxyTools`, Claude's built-in `Agent` tool stays enabled and Claude orchestrates subagents internally with no opencode child-session visibility. To opt out of all proxying, including Task, use an explicit empty list:
@@ -483,7 +543,7 @@ sqlite3 ~/.local/share/opencode/opencode.db \
483
543
 
484
544
  ### What you get with proxying on
485
545
 
486
- - opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call (the default `claude --dangerously-skip-permissions` is NOT applied to proxied tools).
546
+ - opencode's **permission prompts** for every Bash/Edit/Write/WebFetch call. The default `--dangerously-skip-permissions` is still passed to `claude`, but it only governs Claude's own built-in tools; a proxied call is executed by opencode and answers to opencode's rules instead. Built-ins that are neither proxied nor listed in `extraDisallowedTools` do run under that flag.
487
547
  - opencode's **audit log** captures the calls.
488
548
  - Per-tool **policy rules** in opencode apply.
489
549
 
@@ -553,6 +613,50 @@ Notes:
553
613
 
554
614
  Fully restart opencode after upgrading to load the command and runtime changes. Other providers do not gain Claude's native side-question behavior from this command.
555
615
 
616
+ ## Plugin health with /claude-code-doctor
617
+
618
+ ```text
619
+ /claude-code-doctor
620
+ ```
621
+
622
+ Prints, in the chat, what the plugin currently thinks is happening. The plugin answers it itself: no model is called, nothing is billed, and the reply reports 0 tokens. It is the thing to paste into a bug report.
623
+
624
+ It carries the startup-diagnostics fields (plugin version, opencode version, `claude` path and version, the working directory and which resolution tier picked it, providers, accounts, `proxyTools`, the on-disk MCP servers, transport, whether an `ANTHROPIC_API_KEY` is present) plus the live runtime state the startup block cannot know:
625
+
626
+ - every live `claude` child, by opencode session id and model, with its pid, whether a turn is in flight, how long it has been up, and the effort it was spawned at,
627
+ - every pending proxy call, with the tool, the call id, how long it has waited, and its deadline,
628
+ - each proxy server's URL with one unauthenticated `initialize` posted to it: `401, good` is the patched behaviour, and anything else is flagged unsafe with the fix (restart every opencode window, since a window opened before 0.13.2 keeps serving an open port). See [Proxy endpoint security](#proxy-endpoint-security).
629
+
630
+ Nothing secret goes in it: not the proxy bearer token, not the value of `ANTHROPIC_API_KEY`, not the system prompt, not a pending call's arguments. A `claude-code-doctor` command you defined yourself is never overwritten. The name has no space in it because opencode reads everything after the first space as the command's arguments. The whole exchange is kept out of any transcript replayed to the CLI, like a `/btw` pair.
631
+
632
+ ## Per-turn stats
633
+
634
+ Off by default. With `turnStats: true`:
635
+
636
+ ```text
637
+ ▌ **stats:** $0.0123 · 4.2 s · 2 CLI turns · in 1.2k · out 812 · cache read 45.1k · cache write 2.0k
638
+ ```
639
+
640
+ One line at the end of a finished turn, from the numbers the CLI already reports on its `result`. Notes:
641
+
642
+ - Never on a `/compact` turn (the footer would be appended to what opencode stores as the summary) and never on a turn that ended in error, where the error is the thing to read.
643
+ - It is its own text part led by `▌ **stats:**`, and the plugin strips it again if the conversation is ever replayed into a fresh Claude Code process. The model never reads its own accounting.
644
+ - Token counts are the turn's totals, which is what matches the cost. They are deliberately not the same numbers opencode's context gauge shows, which use the last tool-use iteration so the window is not inflated.
645
+ - The cost is what the CLI reported for the turn, not a billing guarantee.
646
+
647
+ The same numbers are logged at INFO whatever this option is set to, and `total_cost_usd`, `duration_ms`, `duration_api_ms`, `num_turns`, `usage`, `modelUsage` and `permission_denials` always reach `providerMetadata` (denials by tool name and id only, never their inputs).
648
+
649
+ ## Things the CLI says that are no longer silent
650
+
651
+ Four Claude Code stream events used to reach nothing but a debug log:
652
+
653
+ - **A rate-limit rejection.** When the CLI reports `status: "rejected"` (or a rejected extra-usage state), the turn now carries a `▌ **rate limit:**` line naming the window, the reason extra usage is unavailable, when it resets, and the four things that can be done about it. Warned once per identity per process. See [Billing](#billing-change-june-15-2026-agent-sdk-credit).
654
+ - **A context compaction Claude Code did on its own.** A `▌ **context compacted:**` note says so, with the before and after token counts, so an answer that suddenly forgets the start of the conversation has a visible cause.
655
+ - **A `result` whose subtype is not `success`** (`error_max_turns`, `error_during_execution`, …). The subtype is named in the transcript and the turn finishes as an error instead of an ordinary reply.
656
+ - **A CLI-executed tool that failed.** Its result is forwarded with the AI SDK's error flag, so opencode renders the row as failed rather than as a success whose output happens to be an error message.
657
+
658
+ At session start the plugin also warns once per process for each MCP server Claude Code could not connect (its tools are simply absent otherwise) and once when the CLI's own `apiKeySource` says an API key is in effect, which is the field that tells you pay-as-you-go billing is happening. See [`ignoreAnthropicApiKey`](#options-reference).
659
+
556
660
  ## Configuration skill
557
661
 
558
662
  The package includes a `claude-code-plugin` skill so your agent can configure it without asking you to navigate all its options. Ask, for example:
@@ -652,7 +756,8 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
652
756
  - **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
653
757
  - **Abort (Esc / Ctrl+C)** → the plugin sends the Claude CLI a stream-json `interrupt` control request, so the CLI actually stops generating and running tools instead of finishing the abandoned turn on your bill. The process stays alive for the next message in that chat. If a turn is somehow still running when the next one starts, it is interrupted first (5 s cap). Contributed by [@broskees](https://github.com/broskees).
654
758
  - **Idle timeout** → when `idleProcessTimeoutMs` is configured, a completed headless turn arms an eviction timer; reuse cancels it, and eviction preserves the session id for `--resume`.
655
- - **Cap**: 16 active processes, LRU eviction.
759
+ - **Cap**: 16 active processes, LRU eviction. A process that is mid-turn is never the victim: eviction takes the oldest **idle** one, and when every process is busy it evicts nothing and warns instead, so a running answer is never truncated to make room.
760
+ - **Crash** → if the CLI dies mid-turn (no terminal `result` line), the turn ends with a visible error naming the exit code or signal and the last stderr the CLI wrote, not a silent `stop` that reads as a short but finished answer. An abort you asked for is not reported this way.
656
761
 
657
762
  ---
658
763
 
@@ -769,7 +874,7 @@ What you see is a **summary** of the model's thinking, not the raw chain-of-thou
769
874
 
770
875
  ### Reasoning effort
771
876
 
772
- Each model exposes `low` / `medium` / `high` / `xhigh` / `max` variants, and an agent can set `reasoningEffort` in its own frontmatter (`minimal` is also accepted and maps to the CLI's `low`). The plugin hands the level to the CLI as `CLAUDE_CODE_EFFORT_LEVEL` at spawn, which Claude Code treats as the session-wide override: it beats the `effortLevel` in that account's `settings.json` and a shell export of the same variable. Effort is fixed for the life of a `claude` process, so it is part of the session key. Changing effort retires the previous effort's process and remembered transcript ID before replaying the conversation into a fresh process. Switching back cannot resume stale context; same-effort streaming turns still reuse their process. This reset is scoped to the same directory, model, provider/account, agent, and conversation. If the previous effort still has pending work (including tool results, plan approval, recovery, or `/btw`), the switch is rejected: finish that work at its original effort first. Title, compaction, and `/btw` calls do not trigger effort resets.
877
+ Each model exposes five picker variants, `low` / `medium` / `high` / `xhigh` / `max`. An agent's own `reasoningEffort` frontmatter accepts six values: those five plus `minimal`, which maps to the CLI's `low`. The plugin hands the level to the CLI as `CLAUDE_CODE_EFFORT_LEVEL` at spawn, which Claude Code treats as the session-wide override: it beats the `effortLevel` in that account's `settings.json` and a shell export of the same variable. Effort is fixed for the life of a `claude` process, so it is part of the session key. Changing effort retires the previous effort's process and remembered transcript ID before replaying the conversation into a fresh process. Switching back cannot resume stale context; same-effort streaming turns still reuse their process. This reset is scoped to the same directory, model, provider/account, agent, and conversation. If the previous effort still has pending work (including tool results, plan approval, recovery, or `/btw`), the switch is rejected: finish that work at its original effort first. Title, compaction, and `/btw` calls do not trigger effort resets.
773
878
 
774
879
  Earlier versions injected a thinking keyword such as `(ultrathink)` into the user message instead. Claude Code stopped recognising every keyword except `ultrathink`, so that path is gone and nothing is appended to your messages any more. Compaction skips request and agent effort overrides, but still inherits a shell-level `CLAUDE_CODE_EFFORT_LEVEL` when set.
775
880
 
@@ -815,14 +920,24 @@ to file only and lets WARN/ERROR bubble in the TUI (they always do).
815
920
  `mode: "debug"` additionally echoes every emitted level to the TUI (which
816
921
  opencode surfaces as warning bubbles).
817
922
 
923
+ `logging` is an ordinary provider option, so it goes under `provider.claude-code.options` like every other one. Keying it on the package name instead is the common mistake: opencode accepts that config without complaint and the plugin never reads it, so you get no log and no error.
924
+
818
925
  **Recommended dev setup** — capture audit trail to disk, keep TUI quiet:
819
926
 
820
927
  ```jsonc
821
- "@khalilgharbaoui/opencode-claude-code-plugin": {
822
- "logging": { "file": true }
928
+ {
929
+ "provider": {
930
+ "claude-code": {
931
+ "options": {
932
+ "logging": { "file": true }
933
+ }
934
+ }
935
+ }
823
936
  }
824
937
  ```
825
938
 
939
+ The snippets below abbreviate to the `logging` value alone; each one belongs at that same path.
940
+
826
941
  **Full firehose for deep debugging** (every DEBUG stream event captured):
827
942
 
828
943
  ```jsonc
@@ -881,9 +996,11 @@ grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
881
996
  Reading it:
882
997
 
883
998
  - **`cwd.source`** is which rule picked the working directory Claude will be
884
- spawned in — `configured` (you pinned `options.cwd`), `process` (normal),
999
+ spawned in: `configured` (you pinned `options.cwd`), `process` (normal),
885
1000
  `captured` (`process.cwd()` was unusable and opencode's project directory
886
1001
  rescued it, the macOS GUI-launch case), or `unresolved` (neither worked).
1002
+ The per-session tier that `opencode serve` uses is resolved per call and so
1003
+ cannot appear here; this line mirrors the synchronous order only.
887
1004
  - **`claudeCli.version`** reading `not detected` means the `claude` binary at
888
1005
  that path didn't answer `--version`, which also disables version-gated
889
1006
  flags like `--thinking-display`.
@@ -893,6 +1010,10 @@ Reading it:
893
1010
  opencode still does not hand its version to plugins. It reads `unknown` when
894
1011
  opencode is run from source rather than as the packaged binary.
895
1012
 
1013
+ This block is logged once, to a file that is off by default. For the same
1014
+ fields plus live process and proxy state, without enabling logging, run
1015
+ [`/claude-code-doctor`](#plugin-health-with-claude-code-doctor) in the session.
1016
+
896
1017
  ### Default behavior (no config, no env)
897
1018
 
898
1019
  Nothing persists; only WARN and ERROR bubble in the TUI. The plugin
@@ -904,7 +1025,7 @@ plugin internals.
904
1025
 
905
1026
  ### [opencode-dcp](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning) (Dynamic Context Pruning)
906
1027
 
907
- Partial support since v0.5.1. DCP runs in a useful degraded mode: automatic strategies and slash commands work, autonomous model-driven compression does not.
1028
+ Partial support since v0.5.1. DCP runs in a useful degraded mode: its automatic strategies and slash commands work, while its own model-facing tools do not reach the model. Model-driven compression is still available, through this plugin's opt-in [`compress` proxy](#context-compression) rather than DCP's tool.
908
1029
 
909
1030
  | DCP feature | Status | Notes |
910
1031
  |---|---|---|
@@ -912,15 +1033,16 @@ Partial support since v0.5.1. DCP runs in a useful degraded mode: automatic stra
912
1033
  | `experimental.chat.system.transform` (context-limit nudges, iteration reminders) | ✅ Works in headless | Headless spawns forward system-role content via `--append-system-prompt-file`. Interactive mode intentionally omits opencode's forwarded system prompt and keeps only this plugin's CLI/AGENTS/continuation prompt. |
913
1034
  | `/dcp compress`, `/dcp sweep`, `/dcp manual`, `/dcp context`, `/dcp stats` slash commands | ✅ Works | Handled by opencode's `command.execute.before` hook, not the model. |
914
1035
  | Automatic `deduplication` + `purgeErrors` strategies | ✅ Works | Message-transform only, no model tool calls. |
915
- | Autonomous model-driven `compress` tool calls | ❌ Not supported | DCP registers `compress` as an opencode-native tool. Claude CLI only sees its own built-ins and MCP-bridged servers, so the model never sees `compress`. The plugin prepends a runtime note instructing Claude to ignore any system instruction that asks it to call `compress`/`distill`/`prune`. |
1036
+ | DCP's own autonomous `compress` / `distill` / `prune` tool calls | ❌ Not supported | DCP registers those as opencode-native tools. Claude CLI only ever sees its own built-ins and MCP-bridged servers, so the model never sees them. |
1037
+ | Model-driven compression through this plugin's `compress` proxy | ⚠️ Opt-in | Add `"Compress"` to `proxyTools` and the plugin exposes `mcp__opencode_proxy__compress`, which gives the model a working way to compress its own context. It is not DCP's tool and does not use DCP's strategies. See [Context compression](#context-compression). |
916
1038
 
917
- Workaround for autonomous compression: trigger it manually with `/dcp compress` whenever you'd want the model to call it. Full autonomous support would require exposing `compress` as an MCP-bridged tool, which is upstream of this plugin.
1039
+ So autonomous compression is available, just not DCP's implementation of it. Two routes: add `"Compress"` to `proxyTools` so the model can compress its own context through this plugin, or leave it off and trigger DCP manually with `/dcp compress` whenever you would have wanted the model to call it. With `"Compress"` absent, the plugin's appended system prompt tells Claude that no such tool exists and to ignore instructions asking for it, which is the correct answer in that case.
918
1040
 
919
1041
  ---
920
1042
 
921
1043
  ## Known limitations
922
1044
 
923
- - No streaming of tool inputs as they're being constructed (Anthropic's `input_json_delta`); the plugin emits them once complete.
1045
+ - Tool inputs stream as they are constructed (Anthropic's `input_json_delta` is forwarded as `tool-input-delta`), but only for tool calls opencode actually sees. Calls the plugin deliberately does not forward, meaning proxy tools, CLI-internal `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, the todo-ledger `Task*` family and Claude's other internal tools, have their deltas suppressed, because a delta for a tool opencode never saw start renders as a permanently pending `⚙ unknown` row.
924
1046
  - Raw chain-of-thought is not available. Claude 4 family models ship summarized thinking only. See [Extended thinking](#extended-thinking) for the full picture.
925
1047
  - Recommended Claude Code CLI: **2.1.142+**. Older CLIs work for everything else but skip the `--thinking-display` flag, so Claude Opus 4.7 turns may render empty Thinking rows. If something breaks after a Claude Code update, the CLI version is the first thing to check.
926
1048
  - **Foreground Task calls have a 60-minute proxy deadline** (configurable via [`proxyToolTimeoutMs`](#per-tool-proxy-timeouts)). A ceiling covering the longest configured deadline is written into Claude's generated HTTP MCP configuration so long-running opencode subagents are not cut off by Claude's 60-second default. For independent longer work, use `background: true` after enabling opencode's experimental background-subagent flag.
@@ -960,7 +1082,7 @@ src/
960
1082
  opencode-types.ts # mirrored opencode types
961
1083
  ```
962
1084
 
963
- For runtime gotchas, the v1.15.0 audit waterline, and the release flow, see [`AGENTS.md`](./AGENTS.md).
1085
+ For runtime gotchas, the release flow, and the compatibility audit (last taken against **opencode 1.18.29**), see [`AGENTS.md`](./AGENTS.md).
964
1086
 
965
1087
  ## Publishing (maintainers)
966
1088
 
@@ -969,7 +1091,7 @@ npm version patch # or minor/major — bumps package.json + creates the tag
969
1091
  git push origin master --follow-tags
970
1092
  ```
971
1093
 
972
- The GitHub Actions workflow at `.github/workflows/publish.yml` runs `npm publish --access public` on tag push (requires `NPM_TOKEN` secret in the repo settings — use a classic automation token so 2FA isn't required at workflow time).
1094
+ The GitHub Actions workflow at `.github/workflows/publish.yml` runs `npm publish --access public` on tag push. Since v0.6.2 it authenticates with **npm trusted publishing (OIDC)**, not a token: the job holds `id-token: write`, upgrades npm first because OIDC needs npm 11.5.1 or newer, and passes no `NODE_AUTH_TOKEN`. The trusted publisher is configured on npmjs.com against this repository and the `publish.yml` workflow filename, so a publish that fails on auth means that configuration, not an expired secret. There is no `NPM_TOKEN` in the workflow.
973
1095
 
974
1096
  ## Star History
975
1097
 
package/dist/index.d.ts CHANGED
@@ -186,9 +186,9 @@ interface ClaudeCodeConfig {
186
186
  /**
187
187
  * Route `ExitPlanMode` through opencode's native `question` tool so plan
188
188
  * approval is a real form instead of a "(yes/no)" line the operator has to
189
- * answer in prose. Off by default: opencode's question form is currently
190
- * broken upstream, so enabling this trades a working text prompt for a
191
- * silent hang. See the plan-mode gotcha in AGENTS.md.
189
+ * answer in prose. Off by default because it cannot currently fire: headless
190
+ * `--print` is not offered an `ExitPlanMode` tool at all, and this bridge
191
+ * keys on that tool call. See the plan-mode gotcha in AGENTS.md.
192
192
  */
193
193
  planModeQuestion?: boolean;
194
194
  webSearch?: WebSearchRouting;
@@ -202,6 +202,8 @@ interface ClaudeCodeConfig {
202
202
  idleProcessTimeoutMs?: number;
203
203
  /** Stage opencode skills as a `--plugin-dir` so Claude's Skill tool can run them. */
204
204
  bridgeOpencodeSkills?: boolean;
205
+ /** Append a one-line cost / duration / cache footer to each finished turn. */
206
+ turnStats?: boolean;
205
207
  logging?: LoggingConfig;
206
208
  }
207
209
  interface LoggingConfig {
@@ -355,11 +357,15 @@ interface ClaudeCodeProviderSettings {
355
357
  * real form; the answer is fed back to the CLI as the `tool_result` for
356
358
  * the original `ExitPlanMode` call, which is what unlocks plan mode.
357
359
  *
358
- * Two reasons it is opt-in. opencode's `question` form does not currently
359
- * render (upstream anomalyco/opencode#36604), so an enabled bridge hangs
360
- * the turn until the operator interrupts; and older opencode builds have
361
- * no `question` registry entry at all, in which case the plugin silently
362
- * keeps the text path. See the plan-mode gotcha in AGENTS.md.
360
+ * Opt-in, and currently dormant. The delivery surface works: opencode's
361
+ * `question` form renders and round-trips (verified 2026-09-06, correcting
362
+ * an earlier claim here that it was broken upstream). What does not work is
363
+ * the trigger: headless `--print` does not offer the model an
364
+ * `ExitPlanMode` tool, measured on CLI 2.1.258, so the bridge has nothing
365
+ * to key on and the text path is what you get. Older opencode builds also
366
+ * have no `question` registry entry, in which case the plugin silently
367
+ * keeps the text path. Re-run the probes in AGENTS.md on a newer CLI before
368
+ * assuming the bridge is reachable.
363
369
  */
364
370
  planModeQuestion?: boolean;
365
371
  /**
@@ -390,6 +396,20 @@ interface ClaudeCodeProviderSettings {
390
396
  * `Unknown skill`. No-op on CLIs without `--plugin-dir`.
391
397
  */
392
398
  bridgeOpencodeSkills?: boolean;
399
+ /**
400
+ * Append one compact line to the end of every finished (non-compaction,
401
+ * non-error) turn with what that turn cost: dollars, wall duration, how many
402
+ * internal CLI turns it took, and input / output / cache-read / cache-write
403
+ * tokens. It is rendered as its own text part led by `▌ **stats:**` and is
404
+ * stripped again from any transcript rebuilt for the CLI, so the model never
405
+ * reads its own accounting.
406
+ *
407
+ * Off by default, because a cost line under every reply is a preference.
408
+ * The same numbers are logged at INFO regardless of this setting, and
409
+ * `total_cost_usd`, `duration_ms`, `usage`, `modelUsage` and
410
+ * `permission_denials` always reach `providerMetadata`.
411
+ */
412
+ turnStats?: boolean;
393
413
  /**
394
414
  * Routing for Claude's built-in `WebSearch` tool.
395
415
  *
@@ -510,8 +530,22 @@ interface ClaudeStreamMessage {
510
530
  text?: string;
511
531
  }>;
512
532
  thinking?: string;
533
+ /** On a `tool_result` block: the CLI-executed tool failed. */
534
+ is_error?: boolean;
513
535
  }>;
514
536
  };
537
+ apiKeySource?: string;
538
+ permissionMode?: string;
539
+ model?: string;
540
+ claude_code_version?: string;
541
+ tools?: string[];
542
+ mcp_servers?: Array<{
543
+ name?: string;
544
+ status?: string;
545
+ }>;
546
+ compact_metadata?: Record<string, unknown>;
547
+ compactMetadata?: Record<string, unknown>;
548
+ rate_limit_info?: Record<string, unknown>;
515
549
  tool?: {
516
550
  name?: string;
517
551
  id?: string;
@@ -533,6 +567,24 @@ interface ClaudeStreamMessage {
533
567
  result?: string;
534
568
  is_error?: boolean;
535
569
  num_turns?: number;
570
+ stop_reason?: string | null;
571
+ /**
572
+ * Per-model totals on `result`, keyed by model id: `inputTokens`,
573
+ * `outputTokens`, `cacheReadInputTokens`, `cacheCreationInputTokens`,
574
+ * `webSearchRequests`, `costUSD`. All numeric, which is what makes it safe
575
+ * to forward whole into `providerMetadata`.
576
+ */
577
+ modelUsage?: Record<string, Record<string, number>>;
578
+ /**
579
+ * Tool calls the CLI's permission layer refused during the turn. Each entry
580
+ * also carries a `tool_input` on the wire; it is deliberately not declared
581
+ * here, because it can be a whole file's contents and must not be copied
582
+ * into provider metadata.
583
+ */
584
+ permission_denials?: Array<{
585
+ tool_name?: string;
586
+ tool_use_id?: string;
587
+ }>;
536
588
  usage?: {
537
589
  input_tokens?: number;
538
590
  output_tokens?: number;
@@ -768,6 +820,17 @@ declare const DEFAULT_PROXY_TOOL_NAMES: string[];
768
820
  * so a user-defined command keeps opencode's normal behaviour end to end.
769
821
  */
770
822
  declare function registerSideQuestionCommand(config: OpenCodeConfig): boolean;
823
+ /**
824
+ * Registers `/claude-code-doctor` unless the user defined their own command of
825
+ * that name. Unlike `/btw` there is no hook to guard: the command is a plain
826
+ * template and the language model answers the message it produces, so leaving
827
+ * a user definition alone here is the whole guard.
828
+ *
829
+ * The name carries no slash. opencode invokes a command as `/<key>` and takes
830
+ * everything after the first space as `$ARGUMENTS`, so `claude-code doctor`
831
+ * would be the command `claude-code` with the argument `doctor`.
832
+ */
833
+ declare function registerDoctorCommand(config: OpenCodeConfig): boolean;
771
834
  declare function _resetPlanModeWarningForTests(): void;
772
835
  declare function warnIfPlanModeCannotExit(permissionMode: string | undefined): void;
773
836
  declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): ClaudeCodeProvider;
@@ -788,4 +851,4 @@ declare const _default: {
788
851
  server: OpenCodePlugin;
789
852
  };
790
853
 
791
- export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, _resetPlanModeWarningForTests, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, getAgentRegistry, getDefaultSubagentModel, registerSideQuestionCommand, resolveAgentModel, warnIfPlanModeCannotExit };
854
+ export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, _resetPlanModeWarningForTests, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, getAgentRegistry, getDefaultSubagentModel, registerDoctorCommand, registerSideQuestionCommand, resolveAgentModel, warnIfPlanModeCannotExit };