@khalilgharbaoui/opencode-claude-code-plugin 0.42.3 → 0.44.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khalilgharbaoui/opencode-claude-code-plugin",
3
- "version": "0.42.3",
3
+ "version": "0.44.0",
4
4
  "description": "Claude Code CLI provider plugin for opencode",
5
5
  "homepage": "https://opencode-claude-code-plugin.dev/",
6
6
  "funding": {
@@ -112,14 +112,14 @@ Defaults below describe normal headless opencode use when the key is absent.
112
112
  |---|---|---|---|
113
113
  | `cliPath` | string | `"claude"` | Executable, not a shell command with flags. Use an absolute path for a non-PATH install. The opencode config hook supplies this default; only direct `createClaudeCode()` use falls back to `CLAUDE_CLI_PATH`. Account providers wrap it; never select a generated wrapper yourself. |
114
114
  | `accounts` | string[] | unset | Unset keeps provider `claude-code`. Any array, including `[]`, expands to `claude-code-default` plus normalized, deduplicated names. Non-default accounts use `~/.claude-<name>`; default uses the CLI's normal environment/auth. |
115
- | `accountFailover` | `"ask"` / `"off"` | `"off"` | **Opt-in: only an explicit `"ask"` opens the switch form**, so unset and `"off"` behave identically. `"ask"` ends a usage-limited turn on opencode's native `question` form listing the other configured accounts, and continues the task on the pick inside the same opencode turn. Only ever fires with more than one account configured. The pick is sticky for the LIMITED account until the limit's reset time (or until opencode restarts when the CLI reported none), so it covers every session on that account and subagents follow their parent; child sessions are never shown the form. Leaving it unanswered waits and costs nothing. `stop`, a dismissal, or text that is not one of the offered accounts ends the turn the way a limited turn ends without the form. Triggered only by a rejected `rate_limit_event`, one of the two known account-limit error texts, or one of the five account-level failure kinds the CLI names on its own error reply (`authentication_failed`, `oauth_org_not_allowed`, `account_on_hold`, `verification_required`, `billing_error`); never by a generic failure. Never on compaction turns or the interactive transport. A switch cannot resume the Claude session (transcripts live under the account's own config dir), so the conversation is replayed into a fresh one: it costs input tokens on the new account, and MCP servers configured only in the limited account's Claude profile are gone. With the default `"off"`, a limited turn ends on one `▌ **usage limit:**` note instead (see "When an account runs out of usage"). |
115
+ | `accountFailover` | `"ask"` / `"off"` | `"off"` | **Opt-in: only an explicit `"ask"` opens the switch form**, so unset and `"off"` behave identically. `"ask"` ends a usage-limited turn on opencode's native `question` form listing the other configured accounts, and continues the task on the pick inside the same opencode turn. Only ever fires with more than one account configured. The pick is sticky for the LIMITED account until the limit's reset time (or until opencode restarts when the CLI reported none), so it covers every session on that account and subagents follow their parent; child sessions are never shown the form. Leaving it unanswered waits and costs nothing. `stop`, a dismissal, or text that is not one of the offered accounts ends the turn the way a limited turn ends without the form. Triggered only by a rejected `rate_limit_event`, one of the two known account-limit error texts, or one of the five account-level failure kinds the CLI names on its own error reply (`authentication_failed`, `oauth_org_not_allowed`, `account_on_hold`, `verification_required`, `billing_error`); never by a generic failure. Never on compaction turns; both transports (the TUI's limit reply carries the same `error: "rate_limit"` kind). A switch cannot resume the Claude session (transcripts live under the account's own config dir), so the conversation is replayed into a fresh one: it costs input tokens on the new account, and MCP servers configured only in the limited account's Claude profile are gone. With the default `"off"`, a limited turn ends on one `▌ **usage limit:**` note instead (see "When an account runs out of usage"). |
116
116
  | `failoverAccounts` | string[] | unset/derived | Account expansion supplies the resolved account list so a limited account can offer the others. Do not hand-wire it; set `accounts` instead. |
117
117
  | `baseCliPath` | string | unset/derived | The `cliPath` before the per-account wrapper substitution, so a failover can build another account's wrapper on the same binary. Supplied by the config hook. Do not hand-wire it. |
118
118
  | `defaultSubagentModel` | string | unset | Seed-config default for discovered `mode: subagent` agents without a full `provider/model` pin; `forceModel` takes precedence. Keeps the caller's account. Unknown ids warn and keep the inherited model. Not independently read per expanded account. |
119
119
  | `defaultSubagentCacheTtl` | string | unset | Prompt cache TTL (`5m` / `1h`) for discovered `mode: subagent` agents that declare no `cacheTtl`; the agent's own value takes precedence. Unset leaves the CLI's default (1 hour on a subscription). Unknown values warn and change nothing. Headless and interactive spawns, never compaction. |
120
- | `fallbackModels` | string[] | unset | Ordered models to try when the model a turn would run on is refused. Default for agents declaring no `fallbackModels`; a per-agent list replaces it rather than extending it. Same account throughout, never a switch. Armed only by the CLI refusing the model (`model_not_found`) or by a usage limit when the `accountFailover` form is not taking the turn, which is the case whenever it is `"off"` (its default) or has no other account to offer; with `"ask"` and another account the switch form wins. Entries must be registered model ids, unknown ones warn and are skipped, the current model is dropped from its own chain, each entry is tried at most once per turn, and an exhausted chain surfaces the original error. Never on compaction, title stubs or the interactive transport. Writes a `▌ **model fallback:**` note that transcript rebuilds strip. Not independently read per expanded account. |
120
+ | `fallbackModels` | string[] | unset | Ordered models to try when the model a turn would run on is refused. Default for agents declaring no `fallbackModels`; a per-agent list replaces it rather than extending it. Same account throughout, never a switch. Armed only by the CLI refusing the model (`model_not_found`) or by a usage limit when the `accountFailover` form is not taking the turn, which is the case whenever it is `"off"` (its default) or has no other account to offer; with `"ask"` and another account the switch form wins. Entries must be registered model ids, unknown ones warn and are skipped, the current model is dropped from its own chain, each entry is tried at most once per turn, and an exhausted chain surfaces the original error. Never on compaction or title stubs; both transports. Writes a `▌ **model fallback:**` note that transcript rebuilds strip. Not independently read per expanded account. |
121
121
  | `cwd` | string | automatic | Pin an absolute existing directory. Otherwise: session directory from SDK, usable `process.cwd()`, captured project directory, final `process.cwd()` fallback. Startup diagnostics cannot show the per-call session tier. |
122
- | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to headless Claude, even with proxies enabled. Proxied calls still use opencode permissions, but unproxied CLI tools do not. `false` removes the bypass flag; it does not by itself create human approval prompts. Ignored when `permissionMode` is `"plan"`, which always drops the flag. |
122
+ | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to Claude on both transports (the TUI also gets Claude Code's own `skipDangerousModePermissionPrompt` so it does not stop on that mode's confirmation; CLI 2.1.263+), even with proxies enabled. Proxied calls still use opencode permissions, but unproxied CLI tools do not. `false` removes the bypass flag; it does not by itself create human approval prompts. Ignored when `permissionMode` is `"plan"`, which always drops the flag. |
123
123
  | `permissionMode` | `acceptEdits` / `auto` / `bypassPermissions` / `default` / `dontAsk` / `plan` | unset | Headless `--permission-mode`, not version-gated: verify the installed CLI supports the value. `plan` is enforced: it overrides `skipPermissions: true` and the plugin drops `--dangerously-skip-permissions` for it, so claude cannot edit or run commands. Every other value governs prompting and still passes the skip flag, so `plan` is the only one that makes a run read-only. Headless: nothing releases plan mode mid-session (no headless `ExitPlanMode`), so leaving it means a config change and an opencode restart; the plugin warns once at startup. Interactive transport: forwarded (not `bypassPermissions`), and plan mode CAN be left: the TUI's `ExitPlanMode` dialog is parked and the operator's next message (bare yes approves, anything else is "what to change") or the `planModeQuestion` form answers it; in plan mode there the proxy drops `write`/`edit` so Claude's own Write can write the plan file. |
124
124
  | `permissionPreset` | `"read-only"` | unset | One named posture instead of hand-combining the options around it. Unset changes nothing. An applied preset replaces `permissionMode`, `skipPermissions`, `controlRequestBehavior` and `controlRequestToolBehaviors` outright, filters `proxyTools`, and unions its own names into `extraDisallowedTools`. `read-only` forces `skipPermissions: false` (the CLI exits with `bypassPermissions not supported in restricted mode` if both are passed), replaces any `permissionMode` with `--restricted` (CLI 2.1.258+: no Bash, REPL or other code runners, no WebFetch, file tools confined to the working directories, bypass refused), adds `--permission-prompts none` (CLI 2.1.263+), disallows `Bash`, `Write`, `Edit`, `NotebookEdit`, `REPL`, `JavaScript` and `WebFetch` via `--disallowedTools`, drops `bash`/`write`/`edit`/`webfetch`/`task`/`task_batch` from `proxyTools`, forces `controlRequestBehavior: "deny"` and ignores `controlRequestToolBehaviors` entirely. Every override is logged at NOTICE. An unknown preset name applies nothing and WARNs rather than guessing. On a CLI below either flag gate the preset still holds through `--disallowedTools` plus the plugin's own deny, with a WARN naming what is lost. Reads (`Read`, `Grep`, `Glob`, `WebSearch`) still work; anything else that would prompt, including bridged MCP tools and the `question` proxy, is denied. |
125
125
  | `controlRequestBehavior` | `allow` / `deny` | `allow` | Automatically answer CLI `can_use_tool` requests if emitted. Forced to `deny` by `permissionPreset: "read-only"`. Not an opencode permission prompt or a sandbox; bypass/pre-allowed tools may never ask. `AskUserQuestion` defaults to deny. |
@@ -144,15 +144,16 @@ Defaults below describe normal headless opencode use when the key is absent.
144
144
  | `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` from headless/interactive spawn env, allowing stored auth to be used. Does not log in, change the parent env, or guarantee subscription billing if other CLI/cloud auth is configured. Warns at startup when either nonempty variable is present, regardless of the flag. |
145
145
  | `idleProcessTimeoutMs` | number | unset | Kill a conversation's idle `claude` worker this many ms after a finished turn. The timer starts when a turn completes, reuse cancels it, and a worker found mid-turn when it fires is re-timed rather than killed. The session id is kept, so the next message resumes transparently. Unset or `0` keeps workers until LRU eviction (16 processes, oldest idle first). Values above `2147483647` are ignored. Applies to the interactive transport too (the TUI is closed; the next message resumes it). Deleting a chat in opencode releases its workers and session ids immediately regardless. |
146
146
  | `turnStats` | boolean | `false` | Append one `▌ **stats:**` line to each finished turn: cost, wall duration, CLI turn count, input/output/cache-read/cache-write tokens, and a permission-denial count when the turn had any, taken from the CLI's own `result`. Never on a compaction turn or a turn that ended in error. Its own text part, stripped from transcripts rebuilt for the CLI, so the model never sees it. The same numbers are logged at INFO regardless, and `modelUsage` plus `permission_denials` always reach `providerMetadata`. Reported cost is the CLI's figure, not a billing guarantee. |
147
- | `forkSessions` | boolean | `false` | When a new opencode session turns out to be a fork of one this provider already served, branch the parent's Claude conversation with `claude --resume <parent> --fork-session` instead of re-rendering the whole thread as text into the first message. Measured on CLI 2.1.280 with haiku 4.5 over a ~13k-token thread: 814 cache tokens written and 39,710 read, against 22,355 written and 17,385 read for the replay, so $0.0058 against $0.0467 for that turn; the parent's transcript is byte-identical afterwards. Neither opencode major tells a provider that a session is a fork, so the parent is found by matching this prompt's history against what each sibling session key was last asked to continue. Off by default because a resumed Claude conversation reuses the system prompt recorded on its FIRST request (`--system-prompt-snapshot`, default `on`), so a forked session answers under the parent's appended system prompt rather than this turn's; measured directly, a parent seeded with codename ZEBRA and forked while passing QUAIL answered ZEBRA. Falls back to the replay, unchanged, for: another account, an unknown or released parent session id, a busy parent (live process, proxied call in flight, unanswered plan-mode question), a fork cut mid-conversation, a fork taken mid tool round trip, a different cwd / model / agent / effort / prompt-cache TTL, compaction, the interactive transport, an account-failover switch, and a `claude` whose `--help` does not advertise `--fork-session`. Recording costs nothing while the option is off. |
147
+ | `forkSessions` | boolean | `false` | When a new opencode session turns out to be a fork of one this provider already served, branch the parent's Claude conversation with `claude --resume <parent> --fork-session` instead of re-rendering the whole thread as text into the first message. Measured on CLI 2.1.280 with haiku 4.5 over a ~13k-token thread: 814 cache tokens written and 39,710 read, against 22,355 written and 17,385 read for the replay, so $0.0058 against $0.0467 for that turn; the parent's transcript is byte-identical afterwards. Neither opencode major tells a provider that a session is a fork, so the parent is found by matching this prompt's history against what each sibling session key was last asked to continue. Off by default because a resumed Claude conversation reuses the system prompt recorded on its FIRST request (`--system-prompt-snapshot`, default `on`), so a forked session answers under the parent's appended system prompt rather than this turn's; measured directly, a parent seeded with codename ZEBRA and forked while passing QUAIL answered ZEBRA. Falls back to the replay, unchanged, for: another account, an unknown or released parent session id, a busy parent (live process, proxied call in flight, unanswered plan-mode question), a fork cut mid-conversation, a fork taken mid tool round trip, a different cwd / model / agent / effort / prompt-cache TTL, compaction, an account-failover switch, and a `claude` whose `--help` does not advertise `--fork-session`. Recording costs nothing while the option is off. |
148
148
  | `resumeAfterRestart` | boolean | `true` | After an opencode restart, resume the conversation's Claude session (`--resume`) instead of replaying the thread as text. Persists session id + conversation digest per session key in `$XDG_STATE_HOME/opencode-claude-code-plugin/claude-sessions.json` (0600, 256 entries, 30 days) after each successful turn. Resumes only on the same session key and binary, with the transcript on disk and the history equal to the recorded conversation plus Claude's reply; anything else (edit, revert, compaction, account switch, open tool round trip) replays. Not on compaction. Log: `resuming the claude session from before the restart` (NOTICE) or `not resuming ...` with a reason (INFO). `false` disables reading and writing. |
149
149
  | `bridgeOpencodeSkills` | boolean | `false` | Stage the user's opencode skills for Claude's native Skill tool as `opencode-skills:<name>`, on the headless and interactive spawns (never compaction). Covers every root opencode reads: project `.opencode/`, `.claude/`, `.agents/` walking up, the opencode config dirs (`skill/` and `skills/`), and global `~/.claude/skills` and `~/.agents/skills` under opencode's own `OPENCODE_DISABLE_EXTERNAL_SKILLS` / `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` switches. Requires the CLI's `--help` to advertise `--plugin-dir`; otherwise no-op. Bridged skills are also listed in opencode's forwarded system prompt, so a large skill set costs prompt tokens twice, which is why it is off by default; `true` opts the user's skills in. Bundled skill staging ignores this option, but still requires flag support and successful discovery/staging. |
150
150
  | `bridgeSkipNativeSkills` | boolean | `true` | Leave a skill unbridged when the Claude session already loads it: from `<CLAUDE_CONFIG_DIR>/skills`, the project's `.claude/skills`, or an installed plugin's `skills/`. Matched by resolved directory, by byte-identical SKILL.md, or (user/project scope only, since plugin skills are namespaced `<plugin>:<name>`) by name. A name match means `Skill("<name>")` answers from Claude's copy, not opencode's, so it is logged at WARN with both paths. The plugin scan reads `installed_plugins.json` and does not check whether the plugin is enabled. `false` bridges everything and reinstates the duplicates. |
151
151
  | `transport` | `"auto"` / `"headless"` / `"interactive"` | unset (headless) | Explicit choice wins over `interactive` and its env var. `auto` prefers headless and probes required headless flags with free help/argument parsing and no prompt. Only definitive unsupported flags select the PTY; timeout, auth failure, missing binary and unknown output stay headless. Never retries a submitted request. Explicit or automatic PTY selection requires `Bun.Terminal`, otherwise errors. Title stubs do not probe. `/compact` follows the selected transport. On the PTY, `read-only` is `--restricted` plus `dontAsk` (CLI 2.1.263+, refused below rather than weakened) and plan mode parks `ExitPlanMode`'s dialog for the operator. `/claude-code-doctor usage` reports the free headless check unavailable when those flags are absent; it does not run a PTY inference prompt. |
152
152
  | `interactive` | boolean | unset (headless) | Legacy experimental PTY opt-in when `transport` is unset; explicit boolean wins over `CLAUDE_CODE_INTERACTIVE_TRANSPORT`. Needs `Bun.Terminal`; this legacy setting retains headless fallback without it. Wires the same proxy MCP server, `proxyTools` and `--disallowedTools` as headless (proxied tools, subagent dispatch and `question` run in opencode with its permission prompts); tools the TUI runs itself are pre-allowed by `interactiveAllowTools`. `permissionMode` is forwarded (not `bypassPermissions`); `read-only` and plan mode hold as described under `transport`; MCP hot reload, idle eviction, images and `/btw` (answered by a short-lived `--fork-session` TUI) work. The skill bridge, effort, prompt cache TTL and CLI hygiene env do apply. Stopping a reply sends Esc and keeps the session; a dead or evicted TUI is replaced with `--resume`; folder trust is accepted (moving off the default "No, exit"), a login or first-run screen fails the start with the fix, the usage-limit auto-continue is cancelled. Never enable to bypass a billing/access restriction. |
153
- | `interactiveBypass` | boolean | `false` | Deprecated no-op. The TUI asks for a manual safety confirmation on `bypassPermissions`, so the plugin never passes it. |
154
- | `interactiveAllowTools` | string[] | `["Bash", "Edit", "Write", "Read", "WebFetch"]` | With the interactive transport: replaces the built-in pre-allow list. MCP wildcards from discovered bridge names plus `mcp__opencode_proxy__*` are added even with `[]`. Not a capability denylist; review permissions before enabling. A tool outside the list raises the TUI's permission dialog, which the transport denies with Esc (nobody is there to answer): the turn ends interrupted and the result's `permission_denials` names the tool. |
153
+ | `interactiveBypass` | boolean | `false` | Deprecated no-op: `skipPermissions` governs the interactive transport too. |
154
+ | `interactiveAllowTools` | string[] | `["Bash", "Edit", "Write", "Read", "WebFetch"]` | With the interactive transport when it does not run `--dangerously-skip-permissions` (plan mode, `skipPermissions: false` with `controlRequestBehavior: "deny"`, or a CLI older than 2.1.263): replaces the built-in pre-allow list. MCP wildcards from discovered bridge names plus `mcp__opencode_proxy__*` are added even with `[]`. Not a capability denylist; review permissions before enabling. A tool outside the list raises the TUI's permission dialog, which the transport denies with Esc (nobody is there to answer): the turn ends interrupted and the result's `permission_denials` names the tool. |
155
155
  | `interactiveSystemPrompt` | boolean | `true` | With the interactive transport: append the plugin's own prompt. opencode's forwarded system prompt is deliberately not sent on this transport (it can trip Claude's third-party usage gate). `false` is for diagnostics only. |
156
+ | `interactiveUserInstructions` | boolean | `false` | With the interactive transport: also append, verbatim, the operator's own instruction files that opencode forwarded (each `Instructions from: <path>` block whose local file still matches, trailing whitespace aside; not `CLAUDE.md`, not an `AGENTS.md` already appended) and the active agent's own prompt. Never opencode's generated prompt or environment block. Headless unaffected. If a turn is billed as a third-party app, turn it off. |
156
157
  | `logging` | object | see below | File logging plus how much reaches the operator. |
157
158
  | `name` | string | unset | Low-level `createClaudeCode()` provider identity fallback after `providerID`, not the opencode display-name setting. Display name lives at `provider.<id>.name`; account expansion supplies its own label. Leave this option unset. |
158
159
  | `providerID` | string | derived | Config hook writes the actual provider id (`claude-code` or `claude-code-work`). Do not override manually. |
@@ -205,6 +206,7 @@ their secret values. Arbitrary MCP `{env:NAME}` placeholders are outside this li
205
206
  | `CLAUDE_CODE_DISABLE_FAST_MODE` | CLI-owned kill switch, conventionally `1`; plugin does not interpret it or change picker prices. Use the non-fast id if fast mode is disabled. |
206
207
  | `CLAUDE_CODE_INTERACTIVE_TRANSPORT` | Fallback when both `transport` and `interactive` are absent: `1` enables; empty/`0`/`false`/`no`/`off` disable (case-insensitive). Explicit `transport` or `interactive: false` wins. |
207
208
  | `CLAUDE_CODE_INTERACTIVE_BYPASS` | Deprecated no-op, like `interactiveBypass`. |
209
+ | `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS` | Set to `0` for the interactive TUI unless the operator set it, so a proxied call past 120 s stays in the foreground as on headless. |
208
210
  | `CLAUDE_CODE_START_WATCHDOG_MS` | Positive integer ms before a headless start or proxy-result continuation is considered silent; default 90000 for missing/invalid/nonpositive values. First expiry respawns, second errors. Bookkeeping-only output is not progress. Keep within timer range; do not lower for routine config checks. |
209
211
  | `CLAUDE_CODE_RESULT_FALLBACK_MS` | Positive integer ms of stdout silence, after the CLI has produced output, before the turn is closed with no `result`; default 60000 for missing/invalid/nonpositive values. The close is announced in the reply as a `▌ **stream timeout:**` note, which is stripped from any rebuilt transcript. An aborted turn gets no note. |
210
212
  | `OPENCODE_CLAUDE_CODE_LOG_FILE` | Overrides `logging.file`: trimmed `0/false/no/off` are false; any other nonempty value is true; empty falls back to config. Prefer `1` or `0`. |
@@ -318,8 +320,12 @@ through the TUI's own input queue, as on headless. A turn ends on its stop reaso
318
320
  silence once it has its reply (reported as ended without a stop reason). It reads
319
321
  completed transcript blocks, not token
320
322
  deltas. It currently re-reads the transcript while polling. Normal interactive turns
321
- do not forward opencode's system prompt, and have no account-failover form or
322
- model-fallback chain. `/btw` is answered by a short-lived fork of the conversation
323
+ do not forward opencode's system prompt (on the TUI, Claude Code bills a request
324
+ carrying it as a third-party app's from extra usage; headless does not). The
325
+ account-switch form, the fallback chain, `forkSessions`, the usage-limit and
326
+ account notes and `turnStats` cost and duration all work as on headless; tool
327
+ permissions follow `skipPermissions` and `controlRequestBehavior` as on headless;
328
+ proxied calls stay in the foreground (`CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS=0`). `/btw` is answered by a short-lived fork of the conversation
323
329
  (`--resume <id> --fork-session`, same spawn arguments so the prompt cache is shared,
324
330
  `dontAsk` with nothing pre-approved so it runs no tool), closed and its transcript copy
325
331
  deleted once it answers; the main conversation is never written. MCP hot reload and `idleProcessTimeoutMs` work as on