@khalilgharbaoui/opencode-claude-code-plugin 0.19.0 → 0.21.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 +92 -22
- package/dist/index.d.ts +76 -14
- package/dist/index.js +389 -84
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +66 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khalilgharbaoui/opencode-claude-code-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Claude Code CLI provider plugin for opencode",
|
|
5
5
|
"author": "Khalil Gharbaoui",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"build": "tsup",
|
|
22
22
|
"dev": "tsup --watch",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
|
-
"test": "tsx --test test-bridge.ts test-broker.ts test-proxy-mcp.ts test-proxy-task.ts test-auto-continue.ts test-has-new-user-content.ts test-get-claude-user-message.ts test-logger.ts test-cli-args.ts test-session-manager.ts test-compaction-model.ts test-tool-mapping.ts test-cwd-resolution.ts test-todo-ledger.ts test-session-affinity.ts test-config-models.ts test-ask-user-question.ts test-claude-session-wrapper.ts test-spawn-env.ts test-respawn.ts test-startup-diagnostics.ts test-subagent-hint.ts test-exit-plan-mode-question.ts test-compress-tool.ts test-agent-models.ts test-side-question.ts test-btw-command.ts test-effort-sessions.ts test-tool-block-index.ts test-skill-bridge.ts test-turn-stats.ts test-cli-events.ts test-cli-events-stream.ts test-doctor.ts test-configure-skill.ts"
|
|
24
|
+
"test": "tsx --test test-bridge.ts test-broker.ts test-proxy-mcp.ts test-proxy-task.ts test-auto-continue.ts test-has-new-user-content.ts test-get-claude-user-message.ts test-logger.ts test-cli-args.ts test-session-manager.ts test-compaction-model.ts test-tool-mapping.ts test-cwd-resolution.ts test-todo-ledger.ts test-session-affinity.ts test-config-models.ts test-ask-user-question.ts test-claude-session-wrapper.ts test-spawn-env.ts test-respawn.ts test-startup-diagnostics.ts test-subagent-hint.ts test-exit-plan-mode-question.ts test-compress-tool.ts test-agent-models.ts test-side-question.ts test-btw-command.ts test-effort-sessions.ts test-tool-block-index.ts test-skill-bridge.ts test-turn-stats.ts test-cli-events.ts test-cli-events-stream.ts test-doctor.ts test-configure-skill.ts test-unattended-replay.ts test-process-lifecycle.ts"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@ai-sdk/provider": "^3.0.8",
|
|
@@ -88,22 +88,24 @@ Defaults below describe normal headless opencode use when the key is absent.
|
|
|
88
88
|
| `controlRequestDenyMessage` | string | built-in text | Override ordinary deny text. `AskUserQuestion` always uses its own stop-and-wait message. |
|
|
89
89
|
| `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Case-insensitive replacement list, not additive and not a capability allowlist. Known entries expose `mcp__opencode_proxy__<name>`; omitted/unknown tools are not disabled. `Task` also brings `task_batch`; `[]` disables this list, not MCP proxying. See the proxy table for exceptions. |
|
|
90
90
|
| `extraDisallowedTools` | string[] | unset | Claude built-ins to switch off outright with `--disallowedTools`, for tools that have no proxy (`["NotebookEdit"]`). Removes the capability rather than routing it. |
|
|
91
|
-
| `proxyToolTimeoutMs` | object of proxy tool name to ms | unset |
|
|
91
|
+
| `proxyToolTimeoutMs` | object of proxy tool name to ms | unset | Optional wall-clock backstop per tool, in ms, case-insensitive keys. A proxied call normally ends on an event the plugin listens for, not on a timer: opencode's result, an abort (the CLI is interrupted), the next user message (calls the previous turn left pending are rejected as orphaned), the `claude` process exiting, the chat being deleted, or opencode exiting. Fallback 10 min (including dynamic MCP tools); `task` and `task_batch` have no deadline, so a subagent runs to completion and a chat parked in one holds its worker until one of those events; `question` 30 min. Set both task keys to cover both. A positive value replaces the default, `0` removes that tool's deadline, negative or non-numeric values are ignored, and values above 2147483647 are clamped. Bash `input.timeout` raises the resolved deadline (and restores one after `bash: 0`); executor ceilings still apply. The generated MCP client timeout is the largest effective deadline, or the CLI's maximum while any tool has none. `compress` is intercepted without a deadline. |
|
|
92
92
|
| `planModeQuestion` | boolean | `false` | Bridge `ExitPlanMode` approval to opencode's `question` and return a real CLI tool result. Requires a live question registry entry; otherwise keeps text fallback. Cannot fire on the headless transport: CLI 2.1.258 does not offer `ExitPlanMode` under `--print`, measured directly and through a full plugin probe, so the text path is what runs. Prose yes/no is not a verified CLI plan-mode unlock. |
|
|
93
93
|
| `webSearch` | `"claude"` / `"disabled"` / `"<opencode tool name>"` | `"claude"` | Default: CLI search with the query rendered as text. Custom target forwards a tool call to an existing opencode tool accepting `query`; this is mapping, not the authenticated proxy replacement, so do not assume CLI search is suppressed. `"disabled"` disallows headless `WebSearch`. |
|
|
94
94
|
| `bridgeOpencodeMcp` | boolean | `true` | Discover/translate disk MCP config plus runtime enabled status. False stops this bridge, not explicit `mcpConfig`, the built-in-tool proxy, or Claude's own MCP settings. Only bridge trusted servers. |
|
|
95
95
|
| `mcpConfig` | string or string[] | unset | Extra `--mcp-config` paths or inline JSON passed alongside the bridged config. |
|
|
96
96
|
| `strictMcpConfig` | boolean | `false` | Headless `--strict-mcp-config`: use only explicitly supplied MCP configs, ignoring other MCP sources, not all settings/credentials/hooks. The interactive wrapper adds it whenever it passes MCP paths, independently of this option. |
|
|
97
97
|
| `hotReloadMcp` | boolean | `true` | With bridging on, compare merged MCP config/status at turn start and respawn on drift after pending proxy calls resolve. Keeps the session via headless `--resume`. Does not reload arbitrary provider options or watch explicit `mcpConfig` contents. |
|
|
98
|
-
| `proxyOpencodeMcpTools` | boolean | `true` |
|
|
98
|
+
| `proxyOpencodeMcpTools` | boolean | `true` | Measured inert on opencode 1.18.31: discovery returns no MCP-backed tools (only built-ins and plugin-declared ones) even with servers connected, so nothing is routed and MCP calls reach Claude through the direct bridge instead. Do not tell a user this option gives them opencode permission prompts for MCP tools until it is re-verified on their version. Intended behaviour, when discovery succeeds: route discovered MCP tools through opencode's executor. Disabled/unavailable discovery falls back to direct CLI bridging. Do not promise exactly-once side effects across failures/retries or opencode versions; verify routing before using write-capable tools. |
|
|
99
|
+
| `proxyOpencodeTools` | string[] | `[]` | Forward named opencode tools through the proxy by registry id (`client.tool.list()`, matched case-insensitively). Covers tools another opencode plugin declares directly, which belong to no MCP server and so are invisible to `proxyOpencodeMcpTools`: opencode-dcp's `compress` is the motivating case. Same broker as every other proxy tool, so the same events release the call. Unknown name is skipped with a warning; a name a proxy def already holds is dropped with a warning and the existing tool keeps it. Explicit allowlist only, because a forwarded tool runs in opencode with the calling agent's permissions. |
|
|
100
|
+
| `stripContextReminders` | boolean | `false` | Strip opencode-dcp `<dcp-system-reminder>` blocks from user/assistant message text, including the fresh-session rebuild. Only when no `compress` is proxied via `proxyTools` or `proxyOpencodeTools`; reachable compress makes it inert. Resolved from config, so a configured-but-unregistered name still counts as reachable. Leaves opencode's own `<system-reminder>` blocks alone. |
|
|
99
101
|
| `multiStepContinuation` | boolean | `true` | Append a system-prompt hint to chain tool calls in one turn instead of stopping between subtasks. |
|
|
100
102
|
| `autoContinueIncompleteTurns` | boolean or `"smart"` | `"smart"` | `true`/`"smart"` continue a turn truncated at `max_tokens`, bounded by 8 attempts and 10 minutes, and otherwise run the keyword heuristic only when stop reason is missing. Every other stop reason, plus error, abort or latched question, stops it. Current measured CLIs always report a reason, so truncation is the only case that resumes in practice. |
|
|
101
103
|
| `compactionModel` | string | `"claude-haiku-4-5"` | `/compact` uses a fresh short-lived headless process without the usual bridge/proxy/skill wiring. Nonblank `CLAUDE_CODE_COMPACTION_MODEL` wins. This is inference and can be billed. |
|
|
102
104
|
| `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. |
|
|
103
|
-
| `idleProcessTimeoutMs` | number | unset | Kill a conversation's idle `claude` worker this many ms after a finished turn. The session id is kept, so the next message resumes transparently. `0`
|
|
105
|
+
| `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. Not applied to the interactive transport. Deleting a chat in opencode releases its workers and session ids immediately regardless. |
|
|
104
106
|
| `turnStats` | boolean | `false` | Append one `▌ **stats:**` line to each finished turn: cost, wall duration, CLI turn count, and input/output/cache-read/cache-write tokens, 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. |
|
|
105
|
-
| `bridgeOpencodeSkills` | boolean | `false` |
|
|
106
|
-
| `interactive` | boolean | unset (headless) | Experimental PTY transport; explicit boolean wins over `CLAUDE_CODE_INTERACTIVE_TRANSPORT`. Needs `Bun.Terminal`; otherwise headless fallback. Compaction stays headless. Does not wire the headless proxy server
|
|
107
|
+
| `bridgeOpencodeSkills` | boolean | `false` | Stage the user's opencode skills for Claude's native Skill tool as `opencode-skills:<name>`, on headless, interactive and direct `doGenerate` spawns (never compaction). 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. |
|
|
108
|
+
| `interactive` | boolean | unset (headless) | Experimental PTY transport; explicit boolean wins over `CLAUDE_CODE_INTERACTIVE_TRANSPORT`. Needs `Bun.Terminal`; otherwise headless fallback. Compaction stays headless. Does not wire the headless proxy server or disallowed-tools controls; no equivalent opencode permission guarantee or `/btw`. The skill bridge does apply. Never enable to bypass a billing/access restriction. |
|
|
107
109
|
| `interactiveBypass` | boolean | `false` | Deprecated no-op. The TUI asks for a manual safety confirmation on `bypassPermissions`, so the plugin never passes it. |
|
|
108
110
|
| `interactiveAllowTools` | string[] | `["Bash", "Edit", "Write", "Read", "WebFetch"]` | With `interactive`: 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. |
|
|
109
111
|
| `interactiveSystemPrompt` | boolean | `true` | With `interactive`: 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. |
|
|
@@ -251,6 +253,29 @@ The proxy's loopback endpoint has bearer, Host, Origin and Content-Type guards.
|
|
|
251
253
|
Never weaken them, publish its token or relax the generated MCP file's `0600` mode.
|
|
252
254
|
Restart all old processes after a security upgrade; changing files cannot patch them.
|
|
253
255
|
|
|
256
|
+
### Let the model satisfy an opencode-dcp compress nudge
|
|
257
|
+
|
|
258
|
+
DCP injects "MAX CONTEXT LIMIT REACHED ... You MUST use the `compress` tool now"
|
|
259
|
+
reminders. DCP declares `compress` directly rather than through an MCP server, so
|
|
260
|
+
automatic MCP routing never offers it and the model cannot obey. Two choices, and
|
|
261
|
+
they are different tools, so choose one rather than both:
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{ "proxyOpencodeTools": ["compress"] }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
forwards DCP's real tool, which compresses opencode's transcript with DCP's
|
|
268
|
+
strategies. The live `claude` process keeps its own context until it restarts.
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{ "proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task", "Compress"] }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
uses this plugin's tool instead, which resets the Claude session and carries a
|
|
275
|
+
summary forward. Setting both leaves this one holding the `compress` name and logs
|
|
276
|
+
`proxyOpencodeTools entry dropped`. If neither is wanted, `stripContextReminders: true`
|
|
277
|
+
removes the reminders the model cannot act on.
|
|
278
|
+
|
|
254
279
|
### Proxy tool names
|
|
255
280
|
|
|
256
281
|
Names below become `mcp__opencode_proxy__<name>`; input config is case-insensitive.
|
|
@@ -261,10 +286,30 @@ Names below become `mcp__opencode_proxy__<name>`; input config is case-insensiti
|
|
|
261
286
|
| `edit` | `"Edit"`, default; replaces CLI Edit. |
|
|
262
287
|
| `write` | `"Write"`, default; replaces CLI Write. |
|
|
263
288
|
| `webfetch` | `"WebFetch"`, default; replaces CLI WebFetch. |
|
|
264
|
-
| `task` | `"Task"`, default; disables CLI Agent and dispatches opencode subagents under its permissions. |
|
|
289
|
+
| `task` | `"Task"`, default; disables CLI Agent and dispatches opencode subagents under its permissions. No proxy deadline by default; a positive `proxyToolTimeoutMs` entry adds one. |
|
|
265
290
|
| `task_batch` | Included with Task; one MCP call fans out two or more independent task inputs concurrently. Separate task calls were measured serial on CLI 2.1.258. |
|
|
266
291
|
| `question` | `"Question"`, opt-in; replaces AskUserQuestion only if the live opencode registry has question. Round-trip verified on plugin 0.18.0 / CLI 2.1.258 / opencode 1.18.29, headless and as a real TUI form, with no `permission` block; grant `permission.question` only if a subagent's form is refused. Opt-in because it disables Claude's own AskUserQuestion. |
|
|
267
|
-
| `compress` | `"Compress"`, opt-in; in-process summary/reset interceptor, no opencode permission prompt and no built-in replacement. Discards prior CLI detail on a later eligible turn, retaining the summary, not the full transcript. Keep off unless explicitly requested
|
|
292
|
+
| `compress` | `"Compress"`, opt-in; in-process summary/reset interceptor, no opencode permission prompt and no built-in replacement. Discards prior CLI detail on a later eligible turn, retaining the summary, not the full transcript. Keep off unless explicitly requested. Reset round-trip verified live on CLI 2.1.263 / opencode 1.18.31. Not the same tool as a forwarded opencode `compress` (see `proxyOpencodeTools`): this one resets the Claude session, that one compresses opencode's transcript. Enabling both leaves this one holding the name. |
|
|
293
|
+
|
|
294
|
+
A proxied call is held open until an event ends it, and the plugin listens to the
|
|
295
|
+
`claude` process, the stream and the control protocol for those events rather than
|
|
296
|
+
inferring failure from elapsed time: opencode's result resolves the call; an abort
|
|
297
|
+
interrupts the CLI and rejects the turn's pending calls, even when it lands while
|
|
298
|
+
opencode is running the tool; the next user message rejects what the previous turn left pending
|
|
299
|
+
and tells the CLI; the process exiting, the chat being deleted, or opencode exiting
|
|
300
|
+
rejects the rest. That is why `task` and `task_batch` carry no default deadline and a
|
|
301
|
+
subagent runs to completion. Three timers remain and are distinct from that: the
|
|
302
|
+
optional per-tool deadlines above (a backstop the user chooses), the start and
|
|
303
|
+
inactivity watchdogs (for a process that is alive but silent, which emits nothing to
|
|
304
|
+
listen to; a CLI parked in a proxied call is exempt), and the connection keepalives
|
|
305
|
+
(SSE comments or JSON whitespace every 15 s, so the CLI's HTTP client does not give up
|
|
306
|
+
on a long call; they never extend a deadline). Do not present a raised deadline as the
|
|
307
|
+
fix for a long subagent; the default already waits for it. A deadline-free call is not
|
|
308
|
+
silent while it waits: it logs `proxy call still waiting, no deadline` at WARN after
|
|
309
|
+
five minutes and every five minutes after, with tool, call id and elapsed time. That
|
|
310
|
+
line is a status report, never a failure; it does not end the call and a call with a
|
|
311
|
+
deadline never emits it. Use it, or `/claude-code-doctor`, to tell a working subagent
|
|
312
|
+
from a wedged one before suggesting any timeout change.
|
|
268
313
|
|
|
269
314
|
### Let Claude load the user's opencode skills
|
|
270
315
|
|
|
@@ -272,14 +317,14 @@ Names below become `mcp__opencode_proxy__<name>`; input config is case-insensiti
|
|
|
272
317
|
{ "bridgeOpencodeSkills": true }
|
|
273
318
|
```
|
|
274
319
|
|
|
275
|
-
|
|
276
|
-
|
|
320
|
+
The bridge is off by default. With it on, `Skill("<name>")` works for any skill opencode
|
|
321
|
+
advertises. Bridged names are `opencode-skills:<name>`, including this bundled skill as
|
|
277
322
|
`opencode-skills:claude-code-plugin`. The package also registers its skill directory
|
|
278
323
|
with opencode's `skills.paths`; older opencode versions may not support that surface.
|
|
279
|
-
The native Claude bridge needs `--plugin-dir` support and is wired into
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
324
|
+
The native Claude bridge needs `--plugin-dir` support and is wired into headless
|
|
325
|
+
streaming, interactive and direct `doGenerate` spawns, never compaction. Set `true`
|
|
326
|
+
only when the user asks for it, since a large skill set costs prompt tokens twice; the
|
|
327
|
+
bundled skill is staged either way. Reusing a process does not load a new skill catalog.
|
|
283
328
|
|
|
284
329
|
User roots: `.opencode/skills` walking from cwd to filesystem root, home `.opencode/skills`,
|
|
285
330
|
`OPENCODE_CONFIG_DIR/skills`, then `XDG_CONFIG_HOME/opencode/skills` (home `.config`
|
|
@@ -289,14 +334,16 @@ singular `skill/`, `~/.agents/skills` and `~/.claude/skills` are not scanned by
|
|
|
289
334
|
bridge; Claude can already discover its own skills independently. Broad bridging can
|
|
290
335
|
duplicate advertised skill context and exposes every discovered skill, not just one.
|
|
291
336
|
|
|
292
|
-
###
|
|
337
|
+
### Change when idle workers are freed
|
|
293
338
|
|
|
294
339
|
```json
|
|
295
340
|
{ "idleProcessTimeoutMs": 900000 }
|
|
296
341
|
```
|
|
297
342
|
|
|
298
|
-
|
|
299
|
-
process exits
|
|
343
|
+
The default is thirty minutes: that long after a turn ends with no new message, the
|
|
344
|
+
conversation's `claude` process exits, and the next message resumes the same
|
|
345
|
+
conversation. This example shortens it to fifteen; `0` keeps workers until the
|
|
346
|
+
8-process LRU cap evicts the oldest idle one. Neither ever kills a worker mid-turn.
|
|
300
347
|
|
|
301
348
|
### Different `/compact` model
|
|
302
349
|
|
|
@@ -431,7 +478,7 @@ commands are preserved. Do not use it as an automatic diagnostic probe.
|
|
|
431
478
|
| A config change did nothing | Options are read at startup; another opencode window is still running the old process | Fully quit every opencode window and relaunch |
|
|
432
479
|
| New plugin version or model not in the picker after upgrading | Frozen `@latest` in opencode's package cache | Remove the cache dir (recipe "Upgrade the plugin") and relaunch |
|
|
433
480
|
| `/btw` shows "Queued" or "requires an idle Claude Code session" | Plugin older than 0.15.2, or a window started before the current build | Upgrade and restart. `/btw` also needs Claude Code 2.1.258+ |
|
|
434
|
-
| Model calls `Skill("x")` and gets `Unknown skill` | Wrong namespace,
|
|
481
|
+
| Model calls `Skill("x")` and gets `Unknown skill` | Wrong namespace (`opencode-skills:x`), a CLI without `--plugin-dir`, an unscanned root, a compaction turn, or `bridgeOpencodeSkills: false` | Check the namespace, `claude --help` and the skill root; remove the `false` only with approval |
|
|
435
482
|
| `Subagent failed (task_id …): Tool execution aborted` while the child finished fine | Bug fixed in 0.15.1 | Upgrade |
|
|
436
483
|
| A `subtask: true` command's subagent output is "lost" | Bug fixed in 0.15.4 | Upgrade |
|
|
437
484
|
| Two subagents run one after another | The CLI serialises MCP calls | Plugin 0.17.0+; the model must use `mcp__opencode_proxy__task_batch` |
|
|
@@ -452,7 +499,8 @@ commands are preserved. Do not use it as an automatic diagnostic probe.
|
|
|
452
499
|
| Claude "forgot" the earlier part of a long conversation | Claude Code compacted its own context | Look for the `▌ **context compacted:**` note in the transcript |
|
|
453
500
|
| Wanting the per-turn cost in the chat | Not shown by default | Set `turnStats: true` and restart opencode |
|
|
454
501
|
| Turn ends with an error naming an exit code or signal and a stderr tail | The `claude` child died mid-turn without emitting its terminal `result` | Read the quoted stderr; that is the CLI's own reason. Older builds reported this as a normal stop, so a truncated answer looked finished |
|
|
455
|
-
| An answer is cut off with no error, in a window with many open chats | Plugin older than this fix: LRU eviction could kill a process mid-turn | Upgrade. Eviction now takes the oldest idle process and skips the round when all
|
|
502
|
+
| An answer is cut off with no error, in a window with many open chats | Plugin older than this fix: LRU eviction could kill a process mid-turn | Upgrade. Eviction now takes the oldest idle process and skips the round when all 8 are busy; the 30-minute idle timer spares a busy worker too |
|
|
503
|
+
| A `claude` worker lingers after its chat was deleted, or after opencode quit | Plugin older than this release | Upgrade. Deleting a chat now releases its workers; every retained worker is killed when opencode exits |
|
|
456
504
|
|
|
457
505
|
## Do not
|
|
458
506
|
|