@khalilgharbaoui/opencode-claude-code-plugin 0.54.2 → 0.56.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
@@ -18,6 +18,8 @@
18
18
  [![License](https://img.shields.io/github/license/khalilgharbaoui/opencode-claude-code-plugin?style=flat-square&label=license&labelColor=15181E&color=FFC46B)](./LICENSE)
19
19
  [![Buy me a coffee](https://img.shields.io/badge/support-buy%20me%20a%20coffee-FFC46B?style=flat-square&labelColor=15181E&logo=buymeacoffee&logoColor=FFC46B)](https://www.buymeacoffee.com/khalilgharbaoui)
20
20
 
21
+ <a href="https://www.buymeacoffee.com/khalilgharbaoui"><img src="site/public/buy-me-a-coffee.png" alt="Buy me a coffee" width="214" height="60"></a>
22
+
21
23
  **Claude Code is the provider.** This opencode plugin runs Anthropic's Claude models through the official **Claude Code CLI** (`claude`) as a subprocess instead of calling the HTTP API. opencode inherits whatever that CLI is logged in as (a Claude subscription, an API key, Bedrock or Vertex), gets Claude's own tools, MCP servers and skills, and keeps running the tools that touch your machine itself, behind its own permission prompts. One package serves opencode 1.x and 2.x.
22
24
 
23
25
  - **Your CLI's login, untouched.** The plugin never reads, stores or replays a token. Lifting the OAuth session out of the official client is what proxy-style plugins do, and Anthropic disallowed that for third-party Claude use in February 2026. This plugin structurally cannot do it.
@@ -106,10 +108,16 @@ This README used to hold all of the above. Release notes and bookmarks point at
106
108
 
107
109
  Made and maintained by [Khalil Gharbaoui (@khalilgharbaoui)](https://github.com/khalilgharbaoui). This plugin absorbs work from its forks directly, cherry-picked with the original authorship preserved or reimplemented with the author named in the commit. The people behind the features you are using, and what each built, are on the [Credits](./docs/credits.md) page; `git log --author` on this repo shows the preserved authorship.
108
110
 
109
- Free and MIT-licensed. If the plugin saves you time, you can buy [its maintainer](https://github.com/khalilgharbaoui) a coffee:
111
+ ## Support
112
+
113
+ This plugin is maintained, not a side project that gets abandoned when the next Claude Code or opencode release breaks something. Keeping it working means tracking CLI flag changes, opencode plugin API changes, and new model IDs as they ship.
114
+
115
+ If the plugin saves you time, you can keep it maintained here:
110
116
 
111
117
  <a href="https://www.buymeacoffee.com/khalilgharbaoui"><img src="site/public/buy-me-a-coffee.png" alt="Buy me a coffee" width="214" height="60"></a>
112
118
 
119
+ One-off or recurring, both help. Bug reports and PRs are welcome too; see [`AGENTS.md`](./AGENTS.md) for the contribution rules.
120
+
113
121
  ## License
114
122
 
115
123
  MIT. See [LICENSE](./LICENSE). The first version was written by Émilien ([@unixfox](https://github.com/unixfox)); everything since by [Khalil Gharbaoui](https://github.com/khalilgharbaoui) and the people in [Credits](./docs/credits.md).
package/dist/index.d.ts CHANGED
@@ -425,6 +425,8 @@ interface ClaudeCodeConfig {
425
425
  proxyOpencodeTools?: string[];
426
426
  stripContextReminders?: boolean;
427
427
  extraDisallowedTools?: string[];
428
+ /** See `ClaudeCodeProviderSettings.mapReadRules`. Default true. */
429
+ mapReadRules?: boolean;
428
430
  proxyToolTimeoutMs?: Record<string, number>;
429
431
  /**
430
432
  * Route `ExitPlanMode` through opencode's native `question` tool so plan
@@ -458,6 +460,16 @@ interface ClaudeCodeConfig {
458
460
  resumeAcrossModelChanges?: boolean;
459
461
  /** Carry a conversation's Claude transcript over on an account switch. Default true. */
460
462
  crossAccountResume?: boolean;
463
+ /** See `ClaudeCodeProviderSettings.maxBudgetUsd`. Resolved; unset is off. */
464
+ maxBudgetUsd?: number;
465
+ /** See `ClaudeCodeProviderSettings.budgetWarnAt`. Resolved percentages. */
466
+ budgetWarnAt?: number[];
467
+ /** See `ClaudeCodeProviderSettings.maxBackgroundSubagents`. Resolved; unset is off. */
468
+ maxBackgroundSubagents?: number;
469
+ /** Resolved `usageWarnThresholds`, in percent; empty for no notes. */
470
+ usageWarnThresholds?: number[];
471
+ /** Resolved `usageSwitchAt`, in percent, or null for never. */
472
+ usageSwitchAt?: number | null;
461
473
  logging?: LoggingConfig;
462
474
  }
463
475
  interface LoggingConfig {
@@ -490,12 +502,13 @@ interface LoggingConfig {
490
502
  }
491
503
  type WebSearchRouting = "claude" | "disabled" | (string & {});
492
504
  /**
493
- * What happens when the account a conversation runs on is out of usage.
494
- * `"ask"` (default) shows the operator a form listing the other configured
495
- * accounts and applies the pick inside the same turn; `"off"` keeps today's
496
- * behaviour, where the turn ends with the rate-limit error.
505
+ * What happens when the account a conversation is running on is out of usage
506
+ * or cannot serve. `"off"` (the default, also what unset means) ends the turn
507
+ * on one `▌ **usage limit:**` note; `"ask"` ends it on a form listing the
508
+ * other configured accounts and continues on the pick; `"auto"` moves the same
509
+ * request to the next eligible configured account without asking.
497
510
  */
498
- type AccountFailoverMode = "ask" | "off";
511
+ type AccountFailoverMode = "ask" | "off" | "auto";
499
512
  /** `subagentDispatch`. Unset behaves as `"off"`; see the option's own note. */
500
513
  type SubagentDispatchMode = "ask" | "off";
501
514
  interface ClaudeCodeProviderSettings {
@@ -539,11 +552,13 @@ interface ClaudeCodeProviderSettings {
539
552
  */
540
553
  accountInProcess?: boolean;
541
554
  /**
542
- * When this account is out of usage, show the operator a form listing the
543
- * other configured accounts and continue the task on the pick, inside the
544
- * same opencode turn. `"ask"` by default, which only does anything when
545
- * more than one account is configured. `"off"` keeps the plain rate-limit
546
- * error. See README "Account failover".
555
+ * What a usage limit or an account that cannot serve does to the turn.
556
+ * Unset or `"off"` (the default) ends it on one `▌ **usage limit:**` note.
557
+ * `"ask"` shows a form listing the other configured accounts and continues
558
+ * on the pick. `"auto"` continues the same request on the next eligible
559
+ * configured account, in configured order, without asking. Both only do
560
+ * anything when more than one account is configured. See
561
+ * docs/configuration/accounts.md.
547
562
  */
548
563
  accountFailover?: AccountFailoverMode;
549
564
  /**
@@ -618,6 +633,43 @@ interface ClaudeCodeProviderSettings {
618
633
  * the cache they wrote. See `resolveAgentCacheTtl` in `src/agent-models.ts`.
619
634
  */
620
635
  defaultSubagentCacheTtl?: string;
636
+ /**
637
+ * `--max-turns` for discovered `mode: subagent` agents that declare no
638
+ * `maxTurns` of their own. Counted by the CLI per user message, so a reused
639
+ * process is not used up by it (measured on 2.1.293, h #g245). Unset (the
640
+ * default) passes nothing. See `resolveAgentCliCaps` in `src/agent-models.ts`.
641
+ */
642
+ defaultSubagentMaxTurns?: number;
643
+ /**
644
+ * `--max-budget-usd` for discovered `mode: subagent` agents that declare no
645
+ * `maxBudgetUsd` of their own. The CLI counts it over the process's whole
646
+ * life, which is why it is a subagent-only cap (h #g245). Unset (the
647
+ * default) passes nothing.
648
+ */
649
+ defaultSubagentMaxBudgetUsd?: number;
650
+ /**
651
+ * A dollar cap on one opencode conversation: its own turns plus every
652
+ * subagent it dispatched, summed from the CLI's own `total_cost_usd`. Unset
653
+ * (the default) is no cap and no bookkeeping. Notes at `budgetWarnAt`, and
654
+ * once the sum reaches the cap the next turn ends before anything is spawned,
655
+ * with one `▌ **budget:**` note and an error finish. Compaction is exempt.
656
+ * In memory only: see `src/budget-ledger.ts`.
657
+ */
658
+ maxBudgetUsd?: number;
659
+ /**
660
+ * Percentages of `maxBudgetUsd` at which one `▌ **budget:**` note is
661
+ * written, each once per conversation. Default `[50, 80]`; `[]` keeps the
662
+ * stop and drops the notes.
663
+ */
664
+ budgetWarnAt?: number[];
665
+ /**
666
+ * How many background subagents may run at once, counted from the
667
+ * dispatches this opencode process saw opencode accept. A `background: true`
668
+ * `task` or `task_batch` that would go over it is refused as a whole with a
669
+ * tool result naming the cap and the running count. Foreground dispatch is
670
+ * never capped. Unset (the default) is no cap.
671
+ */
672
+ maxBackgroundSubagents?: number;
621
673
  /**
622
674
  * Models to try, in order, when the model a turn would have run on is
623
675
  * refused. The default for every agent that declares no `fallbackModels` of
@@ -778,6 +830,34 @@ interface ClaudeCodeProviderSettings {
778
830
  * the model has to work without it.
779
831
  */
780
832
  extraDisallowedTools?: string[];
833
+ /**
834
+ * Translate opencode's own `read` permission DENY rules into Claude Code
835
+ * deny rules for the Read the CLI runs itself. On by default: it closes a
836
+ * hole operators reasonably assume is already closed.
837
+ *
838
+ * `Read`, and whatever other file readers a CLI ships, are not proxied by
839
+ * default (`proxyTools` is Bash, Edit, Write, WebFetch, Task), so without
840
+ * this a file opencode would refuse to read for its own `read` tool is read
841
+ * by Claude with no prompt. Measured on 2026-10-16 with opencode 1.19.0 and
842
+ * Claude Code 2.1.293: "read .env with your Read tool" returned the planted
843
+ * secret.
844
+ *
845
+ * What is mapped, and what is not:
846
+ * - a `deny` becomes `--disallowedTools "Read(<pattern>)"`, which covers
847
+ * every file-reading tool the CLI has (its own rule validator says
848
+ * Read rules are the only file rules matched);
849
+ * - an `ask` is NOT turned into a deny. A headless spawn runs with the
850
+ * skip flag and has nobody to ask, and opencode's own `*.env` default
851
+ * is an `ask`, so denying it would refuse reads the operator expects to
852
+ * approve. `/claude-code-doctor` lists them as not enforced;
853
+ * - `glob`, `grep`, `list` and `external_directory` rules are not mapped:
854
+ * Claude Code's deny beats its allow, so "outside the working directory"
855
+ * cannot be said without denying the working directory too. Use
856
+ * `permissionPreset: "read-only"` (which sends `--restricted`) for that.
857
+ *
858
+ * Set `false` to send nothing and keep the CLI's own posture.
859
+ */
860
+ mapReadRules?: boolean;
781
861
  /**
782
862
  * Per-tool proxy call timeouts in milliseconds, keyed by the proxy tool
783
863
  * name (`bash`, `edit`, `write`, `webfetch`, `task`, `question` —
@@ -987,6 +1067,23 @@ interface ClaudeCodeProviderSettings {
987
1067
  * verified live. Set `false` to keep the replay.
988
1068
  */
989
1069
  crossAccountResume?: boolean;
1070
+ /**
1071
+ * Usage levels, in percent of a window, at which a served turn ends with one
1072
+ * `▌ **usage:**` note naming the account, the window, how much is used and
1073
+ * when it resets. Default `[50, 75, 90, 95]`; `[]` turns the notes off. Each
1074
+ * threshold is said once per account per window, and again only after the
1075
+ * window resets. Read from Claude Code's own `rate_limit_event`, so the
1076
+ * headless transport only. See docs/configuration/accounts.md.
1077
+ */
1078
+ usageWarnThresholds?: number[];
1079
+ /**
1080
+ * The usage level, in percent, at which work should leave this account
1081
+ * BEFORE it hits the wall. Default `90`; `false` or `0` never. With
1082
+ * `accountFailover: "auto"` and another eligible account, new turns move to
1083
+ * it until the window resets; otherwise the note names the other accounts to
1084
+ * pick a model from. The turn that crossed is never changed: it was served.
1085
+ */
1086
+ usageSwitchAt?: number | false;
990
1087
  /**
991
1088
  * Routing for Claude's built-in `WebSearch` tool.
992
1089
  *
@@ -1400,6 +1497,22 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
1400
1497
  * `⚙ invalid` and wedge the turn, so the plugin keeps the text path.
1401
1498
  */
1402
1499
  private resolvePlanModeQuestion;
1500
+ /**
1501
+ * The ledger this turn keeps under `maxBudgetUsd` (h #g245), or null for
1502
+ * none. The parent lookup runs once per session per process and only with a
1503
+ * cap set, so a default install never pays for it. A lookup that cannot
1504
+ * answer reads as "no parent", which makes the session its own conversation:
1505
+ * its spend is still counted, against its own budget.
1506
+ */
1507
+ private resolveTurnBudget;
1508
+ /**
1509
+ * A turn the session budget stopped before anything was spawned: one
1510
+ * `▌ **budget:**` note (when the turn may write one) and an error finish,
1511
+ * the shape a usage limit has had since h #g214. A child session gets an
1512
+ * `error` part instead of the note, so the parent's `task` result says why
1513
+ * without the note becoming the subagent's answer.
1514
+ */
1515
+ private budgetStopStream;
1403
1516
  /**
1404
1517
  * A dispatch form, as a turn: one synthetic `question` tool call and a
1405
1518
  * `tool-calls` finish, which is how the operator's answer comes back on the
@@ -1489,6 +1602,31 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
1489
1602
  * because which method opencode used is not in them.
1490
1603
  */
1491
1604
  private streamTurn;
1605
+ /**
1606
+ * Run the turn, moving the same request to the next eligible configured
1607
+ * account when the one it ran on is out of usage or cannot serve
1608
+ * (`accountFailover: "auto"`). The single wiring point for the automatic
1609
+ * switch, and outside `runModelChain` on purpose: an account switch starts
1610
+ * that account's own model chain from the top.
1611
+ *
1612
+ * Anything but `"auto"` is `runModelChain` and one `await`, so `"off"` and
1613
+ * `"ask"` are exactly what they were. With `"auto"`, the attempt that failed
1614
+ * decides the switch itself (`completeResult` sets `run.switchTo` and closes
1615
+ * its stream without a `finish`), and the next attempt's prologue makes the
1616
+ * switch the form's pick would have made. This only splices the attempts
1617
+ * into one caller turn: one `stream-start`, and the `finish` of whichever
1618
+ * attempt did not switch.
1619
+ *
1620
+ * The bound is `run.tried`, as it is `tried` for the model chain: every
1621
+ * account spawned or switched to goes in, and none in it is picked again.
1622
+ *
1623
+ * Cancelling the returned stream is a stop, for every attempt: this loop is
1624
+ * the plugin starting more work on the caller's behalf, so a caller that
1625
+ * has gone away must reach all of it. Each attempt gets a signal that the
1626
+ * caller's own `abortSignal` and a `cancel()` both abort, and the loop
1627
+ * starts no further account once either has happened.
1628
+ */
1629
+ private runAccountFailover;
1492
1630
  /**
1493
1631
  * Run the turn, moving to the next model in the fallback chain if the one
1494
1632
  * it started on is refused. The single wiring point for `src/model-fallback.ts`.
@@ -1585,6 +1723,17 @@ type AgentRecord = {
1585
1723
  * and not the CLI's per-agent `experimental.cacheTtl`.
1586
1724
  */
1587
1725
  cacheTtl?: string;
1726
+ /**
1727
+ * `--max-turns` for this agent's own `claude` process. Raw as declared (a
1728
+ * number in opencode.json, a string in frontmatter); `resolveAgentCliCaps`
1729
+ * checks it.
1730
+ */
1731
+ maxTurns?: number | string;
1732
+ /**
1733
+ * `--max-budget-usd` for this agent's own `claude` process. Subagents only;
1734
+ * see `resolveAgentCliCaps` for why.
1735
+ */
1736
+ maxBudgetUsd?: number | string;
1588
1737
  /**
1589
1738
  * Models to try, in order, when the one this agent would have run is
1590
1739
  * refused. Same account throughout; see `src/model-fallback.ts`.