@khalilgharbaoui/opencode-claude-code-plugin 0.22.1 → 0.24.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 +33 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.js +1186 -676
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +35 -0
package/README.md
CHANGED
|
@@ -197,6 +197,34 @@ CLAUDE_CONFIG_DIR="$HOME/.claude-work" claude auth login
|
|
|
197
197
|
|
|
198
198
|
The account model IDs are internally suffixed, for example `claude-sonnet-4-6@work`, so long-lived Claude subprocess sessions do not collide across accounts. The generated wrapper strips the suffix before calling `claude --model`.
|
|
199
199
|
|
|
200
|
+
#### Account failover
|
|
201
|
+
|
|
202
|
+
With more than one account configured, an account running out of usage mid-task no longer just ends the turn. The plugin asks, using opencode's own `question` form:
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
Account limit
|
|
206
|
+
The Claude account "work" is out of usage in the five_hour window, which resets at
|
|
207
|
+
2026-09-20T18:00:00.000Z. Continue this task on another configured account?
|
|
208
|
+
Leaving this unanswered waits, at no cost.
|
|
209
|
+
|
|
210
|
+
personal Run on "personal" until 2026-09-20T18:00:00.000Z. …
|
|
211
|
+
default Run on "default" until 2026-09-20T18:00:00.000Z. …
|
|
212
|
+
stop End this turn now and leave the account as it is.
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Pick an account and the task continues on it **inside the same opencode turn**, with no new message from you. This is on by default because the pick is the consent: nothing moves until you choose, and leaving the form open costs nothing.
|
|
216
|
+
|
|
217
|
+
What a pick does, in full:
|
|
218
|
+
|
|
219
|
+
- **It is sticky for the limited account, not for the session.** A usage limit belongs to the account, so one pick governs every session running on `work`, and subagents follow their parent for free. It lasts until the limit's reset time, or until opencode restarts when the CLI did not report one. Child sessions never show the form themselves.
|
|
220
|
+
- **The conversation is replayed, not resumed.** Claude transcripts live under each account's own `CLAUDE_CONFIG_DIR`, so `--resume` cannot cross accounts. The plugin starts a fresh Claude session on the target and replays the thread from opencode's history, then tells it to carry on. That costs input tokens on the new account, and anything the CLI held but opencode did not is gone.
|
|
221
|
+
- **Per-profile MCP servers do not come along.** A server configured only in the limited account's Claude profile is simply absent on the target.
|
|
222
|
+
- **`stop`, dismissing the form, or any answer that is not one of the offered accounts** ends the turn exactly the way the rate-limit error ends it today.
|
|
223
|
+
|
|
224
|
+
Only two things open the form: a `rate_limit_event` the CLI marked `rejected`, and the two known account-limit error texts (`Third-party apps now draw from your extra usage…`, `You've hit your individual spend limit`). A generic 4xx, a timeout or a bad flag never does, deliberately: a transient failure must not quietly move where your usage is billed.
|
|
225
|
+
|
|
226
|
+
Not available on the [interactive transport](#interactive-transport-experimental) (no proxy server, TUI stdin) or on compaction turns. Set `"accountFailover": "off"` to keep the plain error.
|
|
227
|
+
|
|
200
228
|
### Subagents: your account, their model
|
|
201
229
|
|
|
202
230
|
opencode's agent config cannot express "inherit the account, choose the model". A subagent that omits `model` inherits the invoking agent's whole model string; one that pins `model` inherits neither half, so pinning Opus also pins whichever account was written into it. This plugin closes that gap, because it is the piece that knows the account is the *provider* while the model is only a `--model` flag.
|
|
@@ -282,6 +310,7 @@ model: claude-code-work/claude-opus-5@work
|
|
|
282
310
|
|---|---|---|---|
|
|
283
311
|
| `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
312
|
| `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). |
|
|
313
|
+
| `accountFailover` | `"ask"` \| `"off"` | `"ask"` | When this account runs out of usage mid-task, show a form listing the other configured accounts and continue on the one you pick, inside the same turn. Only ever fires when more than one account is configured, so a single-account setup is unaffected. `"off"` keeps the plain rate-limit error. See [Account failover](#account-failover). |
|
|
285
314
|
| `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
315
|
| `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
316
|
| `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. |
|
|
@@ -325,6 +354,7 @@ Every variable the plugin itself reads, in one place. Config is read once at ope
|
|
|
325
354
|
| `CLAUDE_CODE_INTERACTIVE_TRANSPORT` | transport selection | `1` turns on the experimental [interactive transport](#interactive-transport-experimental) for one process, same as `interactive: true`. |
|
|
326
355
|
| `CLAUDE_CODE_INTERACTIVE_BYPASS` | transport selection | Requests `bypassPermissions` in interactive mode. Deliberately ignored, with a warning, for the reason in the `interactiveBypass` row above. |
|
|
327
356
|
| `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. |
|
|
357
|
+
| `CLAUDE_CODE_RESULT_FALLBACK_MS` | wire-inactivity watchdog | Milliseconds a `claude` process that has already produced output may stay silent on stdout before the turn is closed without a `result`. The close is announced in the reply as a `▌ **stream timeout:**` note. Default `60000`; a positive integer is required and anything else falls back to that. Like the start watchdog, mainly a knob for reproducing a hang. |
|
|
328
358
|
| `OPENCODE_CLAUDE_CODE_LOG_FILE` | logger | `1` writes the log file, `0` forces it off even when `logging.file` is `true`. See [Logging](#logging). |
|
|
329
359
|
| `OPENCODE_CLAUDE_CODE_LOG_DIR` | logger | Directory for the log file, overriding `logging.dir`. |
|
|
330
360
|
| `OPENCODE_CLAUDE_CODE_LOG_LEVEL` | logger | Minimum level to emit, overriding `logging.level`. An unrecognised value falls through to config. |
|
|
@@ -334,6 +364,8 @@ Every variable the plugin itself reads, in one place. Config is read once at ope
|
|
|
334
364
|
| `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). |
|
|
335
365
|
| `OPENCODE_VERSION` | startup diagnostics | Reported as the opencode version when set, sparing the plugin a `--version` spawn. Diagnostics only. |
|
|
336
366
|
| `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). |
|
|
367
|
+
| `DISABLE_AUTOUPDATER` | spawn environment | Set to `1` on every `claude` the plugin spawns, **only if you have not set it yourself**. The plugin detects your CLI version once and caches it, and gates `--thinking-display summarized`, `--plugin-dir` and fast mode on the answer, so a CLI that updates itself mid-session would leave those gates describing a binary that is no longer running. Export `DISABLE_AUTOUPDATER=0` to keep the autoupdater; your value is never overwritten, and updating the CLI between opencode restarts works normally either way. |
|
|
368
|
+
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | spawn environment | Set to `1` on every spawned `claude` under the same never-overwrite rule. It suppresses the CLI's non-essential network calls and is a second, independent way Claude Code declines to auto-update. Export it yourself (including as an empty string, which the CLI reads as off) to take control. |
|
|
337
369
|
|
|
338
370
|
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.
|
|
339
371
|
|
|
@@ -624,7 +656,7 @@ Deadlines still exist, as an explicit backstop rather than the mechanism that de
|
|
|
624
656
|
|
|
625
657
|
`question` keeps 30 minutes because it blocks on a human reading a form, and a form nobody answers is not an event. A positive `task` override restores a wall-clock backstop for operators who want one; if it fires, the error tells Claude not to "schedule a wake-up": that is a Claude Code affordance which cannot fire in this headless/proxy context, so deferring silently loses the work.
|
|
626
658
|
|
|
627
|
-
Two watchdogs are a different thing again and are unchanged: the start watchdog (90 s of complete silence after a turn is written, respawn then error, see `CLAUDE_CODE_START_WATCHDOG_MS`) and the wire-inactivity watchdog (60 s of silence after content). Those exist because a process that is alive but wedged emits no event to listen to, and a proxy call is never what they are waiting on: a CLI parked inside a proxied tool is producing nothing on purpose, and both watchdogs know that.
|
|
659
|
+
Two watchdogs are a different thing again and are unchanged: the start watchdog (90 s of complete silence after a turn is written, respawn then error, see `CLAUDE_CODE_START_WATCHDOG_MS`) and the wire-inactivity watchdog (60 s of silence after content, see `CLAUDE_CODE_RESULT_FALLBACK_MS`; when it fires the reply gets a `▌ **stream timeout:**` note so the turn does not just stop). Those exist because a process that is alive but wedged emits no event to listen to, and a proxy call is never what they are waiting on: a CLI parked inside a proxied tool is producing nothing on purpose, and both watchdogs know that.
|
|
628
660
|
|
|
629
661
|
If Claude nevertheless abandons the HTTP call, the plugin preserves narration emitted while opencode was running the tool, renders it on return, and delivers the late completion as a plain-text continuation naming the original call. It tells Claude not to run the tool again. A silent post-tool continuation gets one resumed-process retry, preserving the original model, account, effort, and proxy configuration; a second failure ends with an error rather than an indefinite hang. Buffered narration is capped at 500 lines and 2 MiB, with a warning if output was dropped.
|
|
630
662
|
|
package/dist/index.d.ts
CHANGED
|
@@ -171,6 +171,18 @@ interface ClaudeCodeConfig {
|
|
|
171
171
|
cwd?: string;
|
|
172
172
|
account?: string;
|
|
173
173
|
configDir?: string;
|
|
174
|
+
/**
|
|
175
|
+
* Every account the provider expansion produced, so a limited account can
|
|
176
|
+
* offer the others. Set by `providerConfig`, not by the user.
|
|
177
|
+
*/
|
|
178
|
+
failoverAccounts?: string[];
|
|
179
|
+
/**
|
|
180
|
+
* The CLI path BEFORE the per-account wrapper substitution, so a failover
|
|
181
|
+
* can build another account's wrapper on top of the same binary. Set by
|
|
182
|
+
* `providerConfig`, not by the user.
|
|
183
|
+
*/
|
|
184
|
+
baseCliPath?: string;
|
|
185
|
+
accountFailover?: AccountFailoverMode;
|
|
174
186
|
providerID?: string;
|
|
175
187
|
skipPermissions?: boolean;
|
|
176
188
|
permissionMode?: PermissionMode;
|
|
@@ -237,6 +249,13 @@ interface LoggingConfig {
|
|
|
237
249
|
level?: LogLevel;
|
|
238
250
|
}
|
|
239
251
|
type WebSearchRouting = "claude" | "disabled" | (string & {});
|
|
252
|
+
/**
|
|
253
|
+
* What happens when the account a conversation runs on is out of usage.
|
|
254
|
+
* `"ask"` (default) shows the operator a form listing the other configured
|
|
255
|
+
* accounts and applies the pick inside the same turn; `"off"` keeps today's
|
|
256
|
+
* behaviour, where the turn ends with the rate-limit error.
|
|
257
|
+
*/
|
|
258
|
+
type AccountFailoverMode = "ask" | "off";
|
|
240
259
|
interface ClaudeCodeProviderSettings {
|
|
241
260
|
cliPath?: string;
|
|
242
261
|
/** Drive interactive claude (subscription) instead of headless --print. */
|
|
@@ -255,6 +274,21 @@ interface ClaudeCodeProviderSettings {
|
|
|
255
274
|
account?: string;
|
|
256
275
|
configDir?: string;
|
|
257
276
|
accounts?: string[];
|
|
277
|
+
/**
|
|
278
|
+
* Every account the provider expansion produced. Written by the config
|
|
279
|
+
* hook; setting it by hand only limits what a limited account may offer.
|
|
280
|
+
*/
|
|
281
|
+
failoverAccounts?: string[];
|
|
282
|
+
/** The CLI path before the per-account wrapper substitution. */
|
|
283
|
+
baseCliPath?: string;
|
|
284
|
+
/**
|
|
285
|
+
* When this account is out of usage, show the operator a form listing the
|
|
286
|
+
* other configured accounts and continue the task on the pick, inside the
|
|
287
|
+
* same opencode turn. `"ask"` by default, which only does anything when
|
|
288
|
+
* more than one account is configured. `"off"` keeps the plain rate-limit
|
|
289
|
+
* error. See README "Account failover".
|
|
290
|
+
*/
|
|
291
|
+
accountFailover?: AccountFailoverMode;
|
|
258
292
|
/**
|
|
259
293
|
* Model that subagents run on when their own definition pins nothing.
|
|
260
294
|
* Unset means no implicit override at all, so an agent keeps inheriting the
|