@khalilgharbaoui/opencode-claude-code-plugin 0.64.0 → 0.64.1
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 +9 -5
- package/dist/index.js +16 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +26 -9
package/package.json
CHANGED
|
@@ -23,7 +23,8 @@ Three routes reach Claude from opencode, and they are not interchangeable:
|
|
|
23
23
|
opencode's auth store, pay as you go, everything running in opencode's own runtime.
|
|
24
24
|
2. **This plugin**: the official `claude` CLI authenticates, so opencode inherits
|
|
25
25
|
whatever that CLI holds (subscription login, API key, Bedrock, Vertex). It never
|
|
26
|
-
reads, stores or replays a token
|
|
26
|
+
reads, stores or replays a token. Anthropic has not approved or endorsed it, so
|
|
27
|
+
never describe it as approved or sanctioned. What it
|
|
27
28
|
costs: a `claude` child per conversation, Claude Code's own context compaction and
|
|
28
29
|
clearing happening behind opencode's back, a locally synthesised session title
|
|
29
30
|
instead of a model-written one, `/compact` on its own short-lived spawn, no todo
|
|
@@ -34,6 +35,14 @@ Three routes reach Claude from opencode, and they are not interchangeable:
|
|
|
34
35
|
February 2026, and each of those projects says so in its own README (a "community
|
|
35
36
|
workaround", "no claims regarding compliance", "could be suspended or terminated").
|
|
36
37
|
|
|
38
|
+
A fourth route arrived in opencode 2.2.3: opencode's own **Claude Agent** provider
|
|
39
|
+
(`claude-agent`), which also drives the user's own `claude` (2.1.248+) and never
|
|
40
|
+
reads the login. It is opencode 2 only, runs every tool in opencode, leaves the
|
|
41
|
+
`~/.claude` setup (CLAUDE.md, hooks, skills, MCP servers, memory, auto-compaction)
|
|
42
|
+
out, and serves one login. It is a fair choice for a user on 2.2.3+ with one account
|
|
43
|
+
who wants exactly that; this plugin is the one for opencode 1.x, several accounts,
|
|
44
|
+
the user's own Claude Code setup, fast mode, `/btw`, the sidebar and the doctor.
|
|
45
|
+
|
|
37
46
|
Never recommend route 3, never help migrate a user onto it, and never enable
|
|
38
47
|
`interactive: true` to dodge a billing or access restriction. If a user asks why this
|
|
39
48
|
plugin is slower or heavier than a proxy, the answer is route 2's cost list above, plus
|
|
@@ -159,7 +168,7 @@ Defaults below describe normal headless opencode use when the key is absent.
|
|
|
159
168
|
| `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`. The cost is THIS turn's: the CLI reports the Claude session's running total, and the footer prints the difference from that session's previous total (the INFO line carries both, `costUsd` and `turnCostUsd`). The first turn of a session resumed after an opencode restart, or of a fork, prints no cost, because `--resume` restores the running total and there is nothing to subtract; the next turn has it again. Not a billing guarantee. |
|
|
160
169
|
| `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. |
|
|
161
170
|
| `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. opencode-dcp's `<dcp-message-id>` references (and its `@12@` compact form) are not part of the digest: dcp renumbers them on every opencode restart, so after an undo the same message came back under another number and the conversation was refused as `conversation-changed`. Records carry a digest `version`; one without it (written by an older plugin) is compared the old way. 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. |
|
|
162
|
-
| `resumeAcrossModelChanges` | boolean | `true` | When the operator changes a conversation's model or reasoning effort mid-conversation, continue the same Claude session (`--resume <id> --model <new>`) instead of replaying the thread as text. Both are in the session key, so changing either used to start a fresh Claude conversation (issue #91); neither needs one, because `--model` and `CLAUDE_CODE_EFFORT_LEVEL` are spawn-time and the CLI applies either to a resumed transcript (measured on 2.1.288). The session is MOVED off the old key, so one Claude conversation has exactly one owner. Refused on every term `resumeAfterRestart` is refused on (another account, a transcript that is gone, a conversation that changed) plus two more: a sibling whose transcript may still be written to (that turn is served on the sibling instead and the change applies at the next turn that finds it idle, h #g271), and anything that is not the same cwd, request scope, opencode session
|
|
171
|
+
| `resumeAcrossModelChanges` | boolean | `true` | When the operator changes a conversation's model or reasoning effort mid-conversation, continue the same Claude session (`--resume <id> --model <new>`) instead of replaying the thread as text. Both are in the session key, so changing either used to start a fresh Claude conversation (issue #91); neither needs one, because `--model` and `CLAUDE_CODE_EFFORT_LEVEL` are spawn-time and the CLI applies either to a resumed transcript (measured on 2.1.288). The session is MOVED off the old key, so one Claude conversation has exactly one owner. Refused on every term `resumeAfterRestart` is refused on (another account, a transcript that is gone, a conversation that changed) plus two more: a sibling whose transcript may still be written to (that turn is served on the sibling instead and the change applies at the next turn that finds it idle, h #g271), and anything that is not the same cwd, request scope, opencode session and agent. An agent's prompt-cache TTL, `maxTurns` and `maxBudgetUsd` are spawn settings like the effort, so a change to one carries the conversation too, after a restart as well (h #g275); `--max-budget-usd` counts per process, so the carried process starts the new cap from zero, as any `--resume` does. Not on compaction, not on an account-failover switch. It reads the record `resumeAfterRestart` writes, which is also what carries the first change after an opencode restart (h #g274), so `resumeAfterRestart: false` turns it off too. Log: `continuing this conversation's claude session under the new model or effort` (NOTICE). |
|
|
163
172
|
| `crossAccountResume` | boolean | `true` | On any account switch (a by-hand pick of another account's model in the same session, the switch form, and the switch back when the override expires), and only between accounts in the same `accountGroups` group, continue the same Claude conversation on the other account instead of replaying the thread as text. An account is a `CLAUDE_CONFIG_DIR` and a conversation is one `projects/<encoded cwd>/<id>.jsonl` inside it, so the switch copies that file into the target account's own projects dir (0600, dir 0700) and keeps the session id for the `--resume` both transports already build. Measured on CLI 2.1.288: a copied transcript resumes under another config dir with its context intact, the CLI resolves a session by FILENAME (not the `sessionId` in the records), it appends to the copy it found rather than to the cwd's path, the source comes out byte-identical, and no record holds a user, org or account id. Never overwrites: an older copy already at the target path (what a switch back finds) makes the carry take a fresh session id, and the live copy is the longest one. Refuses on nothing to carry, a non-file at the target path, or a failed copy, and each refusal falls back to the replay. Not on compaction, and inert with one account. Log: `carried this conversation's claude transcript to the other account` or `replaying this conversation as text on the other account` with a reason (both NOTICE). What is NOT verified live is a different Anthropic login answering another's conversation; `false` keeps the replay. |
|
|
164
173
|
| `usageWarnThresholds` | number[] | `[50, 75, 90, 95]` | Percent levels of a usage window at which a SERVED turn ends with one `▌ **usage:**` note (its own text part, stripped from rebuilt transcripts): account, window, utilization, reset in local time. Read from every headless `rate_limit_event` (not only a rejected one), whose `utilization` is a FRACTION (`0.9` is 90%) and is ABSENT on an `allowed` event with headroom, which is unknown, never zero, and crosses nothing. The same event's `unifiedWindows` carries every window's own utilization and reset (measured on CLI 2.1.293), and each listed window is recorded, so the 7-day window has readings and notes while the 5-hour one limits. Readings persist per account per window in `$XDG_STATE_HOME/opencode-claude-code-plugin/account-usage.json` (`0600`, names, numbers and times only), and so does the dedup: each threshold is said once per account per window across reused processes, sessions and restarts, again only after the window's reset. A crossing in a `/compact` turn or a child session is not said there and is said by the next ordinary turn on the account. Never on `doGenerate`, never turns a served turn into a failure. `[]` turns the notes off but keeps the readings. The single-account name is `default`. Interactive transport: no event, so only a limit reply is recorded (`limited`). Log: `claude account usage crossed a warning threshold` (NOTICE). |
|
|
165
174
|
| `usageSwitchAt` | number / `false` | `90` | Percent at which work leaves an account BEFORE the wall, when another `failoverCandidates` account (same `accountGroups` group) exists and is not itself at that level per the store. `accountFailover: "auto"`: sets the existing account override (keyed on the account, until the window's reset; in memory, lost on restart) so the NEXT turn spawns on the other account, carried like a limit switch; the note says `New turns move to the "<account>" account until HH:MM`. `"ask"` / `"off"`: nothing moves and no form (the form only ends a FAILED turn); the note names the other account to pick a model from. The turn that crossed finishes as served. Also a threshold in its own right, so the switch is said exactly once per window. `false` or `0` never. Log: `claude account is near its usage limit; new turns move to another account until the window resets` (WARN). |
|
|
@@ -295,7 +304,9 @@ Same package, same config. 2.x's native key is `plugins` (plural), but it still
|
|
|
295
304
|
{ "plugins": ["@khalilgharbaoui/opencode-claude-code-plugin"] }
|
|
296
305
|
```
|
|
297
306
|
|
|
298
|
-
- Check the major first with `opencode --version`. `plugin` is read by both majors (recorded in `docs/agents-history.md` #g39); `plugins` is read by 2.x only.
|
|
307
|
+
- Check the major first with `opencode --version`. `plugin` is read by both majors (recorded in `docs/agents-history.md` #g39); `plugins` is read by 2.x only. On 2.x, `opencode plugin add @khalilgharbaoui/opencode-claude-code-plugin` writes the `plugins` entry and installs the package.
|
|
308
|
+
- With no `accounts`, 2.x names the provider `Claude Code` (id `claude-code`); 1.x gives it no name and shows the id. Either way a model's full id is `claude-code/<model>`, and `opencode run -m claude-code/claude-haiku-5-5 "reply with ok"` is a one-line check from a terminal.
|
|
309
|
+
- On 2.1.4, `opencode plugin list` prints `No plugins found` even while this plugin is loaded. Never read that as "not installed": check with `/claude-code-doctor` or `opencode models | grep claude-code`.
|
|
299
310
|
- Provider settings, lowest precedence first: `provider.claude-code.options` (1.x's spelling, still read on 2.x), `provider.claude-code.settings`, `providers.claude-code.settings` (2.x's own), and the plugin entry's own `options`, which wins over all three. Any option of this plugin can sit in any of them; the plugin entry is the usual home for `accounts`: `{"package": "@khalilgharbaoui/opencode-claude-code-plugin", "options": {"accounts": ["work"]}}`.
|
|
300
311
|
- A local checkout is loaded by pointing `plugins` at its **`dist`** directory, never the repository root: 2.x resolves a configured plugin path as `<dir>/server` or `<dir>/index`.
|
|
301
312
|
- Known 2.x differences: a `task` call can carry its own `model` (2.x's `subagent` tool takes one; 1.x's `task` does not), there is no todo panel (2.x has no `todowrite` tool, so the sidebar's progress bar is always the time estimate), `bridgeMcpOauthTokens` finds nothing (2.x's plugin API can activate a stored credential but not read one), and Claude's `EnterPlanMode`, `ExitPlanMode` and `NotebookEdit` calls get no tool row (2.x rejects an unknown tool name). Everything else behaves as on 1.x, including `/btw` answered inside the running turn, a permission prompt not counting against a proxied call's deadline, a stopped turn releasing its parked proxied calls, and warnings shown as toasts: the plugin reads busy and idle off opencode 2's event stream (`session.execution.*`) and sends warnings over its plugin RPC channel (measured on 2.0.22 and 2.0.31). Do not set `hostApi`; the 2.x entrypoint sets it, and forcing it on 1.x breaks every proxied tool call.
|
|
@@ -1177,12 +1188,17 @@ which is redacted by allowlist (see "Filing an issue" below).
|
|
|
1177
1188
|
|
|
1178
1189
|
A published version does not reach a running opencode. First distinguish an npm pin,
|
|
1179
1190
|
npm latest resolution, and a local `file://` install. Preserve a pin unless the user
|
|
1180
|
-
requested changing it. An `@latest` install is frozen in opencode's package cache
|
|
1181
|
-
`~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest/`, and a
|
|
1191
|
+
requested changing it. An `@latest` install is frozen in opencode's package cache, and a
|
|
1182
1192
|
plain restart never re-resolves it: removing that one directory and then fully
|
|
1183
|
-
relaunching is what picks a new version up.
|
|
1184
|
-
|
|
1185
|
-
|
|
1193
|
+
relaunching is what picks a new version up. The directory differs per major:
|
|
1194
|
+
`~/.cache/opencode/packages/@khalilgharbaoui/opencode-claude-code-plugin@latest/` on
|
|
1195
|
+
opencode 1.x, `~/.cache/opencode/npm/@khalilgharbaoui/opencode-claude-code-plugin@latest/`
|
|
1196
|
+
on opencode 2.x. On opencode 2.1.4 `opencode plugin check` and `opencode plugin update`
|
|
1197
|
+
do not see this plugin, so they cannot refresh it. A registry proxy or security agent
|
|
1198
|
+
with a minimum package age can hide a new release for hours; opencode then caches the
|
|
1199
|
+
older version it resolved, so remove the entry again once the release is visible.
|
|
1200
|
+
Inspect the actual cache location and package identity and get approval before removing
|
|
1201
|
+
only that stale package directory, never the whole cache or auth/session directories. Respect platform/XDG paths
|
|
1186
1202
|
(`XDG_CACHE_HOME` moves it). Then fully relaunch every opencode window, including serve
|
|
1187
1203
|
and GUI processes. A `file://` install uses the checkout's
|
|
1188
1204
|
`dist/`: rebuild with `npm run build`, not cache deletion. On opencode 2, from plugin
|
|
@@ -1451,7 +1467,8 @@ Prefer the doctor: it needs no logging change and no restart.
|
|
|
1451
1467
|
| 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 |
|
|
1452
1468
|
| A `▌ **restart opencode:**` note at the top of a reply | Correct and acted on by the user, not by you: this opencode process loaded an older plugin build than the one now on disk, so a fix they installed is not running here. opencode reads a plugin once, at process start | Quit every opencode window (serve and GUI processes included) and relaunch. The note appears once per conversation, names the loaded version, the on-disk version and when the build was loaded, and is stripped from any transcript rebuilt for the CLI |
|
|
1453
1469
|
| A bug the user is sure was fixed is back | Same cause as the row above, before they have seen the note: the window answering them predates the fix | The `plugin build` row of `/claude-code-doctor`, which reads the disk unthrottled: `current`, `on disk <version>`, `rebuilt on disk` or `could not be read`. Compare its loaded version with the version they installed before investigating the bug at all |
|
|
1454
|
-
| 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 |
|
|
1470
|
+
| New plugin version or model not in the picker after upgrading | Frozen `@latest` in opencode's package cache (`packages/` on 1.x, `npm/` on 2.x), or a minimum-package-age proxy hiding the release so opencode cached an older one | Remove the cache dir for the major in use (recipe "Upgrade the plugin") and relaunch |
|
|
1471
|
+
| `opencode plugin list` prints `No plugins found` on opencode 2 | Expected on 2.1.4: that command, `plugin check` and `plugin update` do not see this plugin | Check with `/claude-code-doctor` or `opencode models \| grep claude-code`; if either answers, the plugin is loaded |
|
|
1455
1472
|
| No `claude-code` provider or model in the picker at all | The plugin never loaded, or it loaded and the CLI was not usable | Check for a `plugin ready` line first: absent means not loaded (wrong `plugin`/`plugins` key, a 2.x local install not pointing at `dist/`, or no full relaunch), present with `claudeCli.version: not detected` means the binary did not answer `--version`, which also disables every version-gated flag |
|
|
1456
1473
|
| `Model unavailable` for a model id the user typed | The provider id is not what they assumed | Use the id the ready block's `providers` field lists. With no `accounts` configured on opencode 2 the id is `claude-code`, so `claude-code-default/<model>` fails while the plugin is healthy (measured on opencode 2.0.16, 2026-09-27). Declaring `accounts` is what creates `claude-code-default`; on 1.x with accounts the ids are `claude-code-default` / `claude-code-<name>` and never a bare `claude-code` |
|
|
1457
1474
|
| `Tool result name changed`, turn aborts, on opencode 2 | Before 0.28.1 a CLI-executed tool's result reached opencode under a different name than its call, and 2.0.16 aborts the turn on the mismatch, breaking every Claude-side MCP server call | Upgrade to 0.28.1+ **and fully relaunch every opencode window**; plugin code is read once at process start, so upgrading the package under a running window changes nothing |
|