@khalilgharbaoui/opencode-claude-code-plugin 0.64.0 → 0.65.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 +9 -5
- package/dist/index.d.ts +162 -0
- package/dist/index.js +3891 -2537
- package/dist/index.js.map +1 -1
- package/dist/tui.js +3 -3
- package/dist/tui.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +57 -21
package/README.md
CHANGED
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
[](./LICENSE)
|
|
19
19
|
[](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
|
-
|
|
23
21
|
**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.
|
|
24
22
|
|
|
25
23
|
- **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.
|
|
@@ -46,9 +44,13 @@
|
|
|
46
44
|
}
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
`~/.config/opencode/opencode.json
|
|
47
|
+
`~/.config/opencode/opencode.json`, on opencode 1.x and 2.x alike. opencode 2's native key is `plugins`, and `opencode plugin add @khalilgharbaoui/opencode-claude-code-plugin` writes that entry and installs the package for you.
|
|
48
|
+
|
|
49
|
+
3. **Quit opencode fully and relaunch.** Plugins load once, at process start. The model picker now has a **Claude Code** provider (opencode 1.x labels it with its id, `claude-code`) with entries such as `Claude Sonnet 5.5 (2×)` and `Claude Opus 5 (5×)`; the suffix is each model's list price relative to Haiku. A model's full id is `claude-code/<model>`, so one line checks it from a terminal:
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
```bash
|
|
52
|
+
opencode run -m claude-code/claude-haiku-5-5 "reply with ok"
|
|
53
|
+
```
|
|
52
54
|
|
|
53
55
|
Nothing in the picker? [Troubleshooting](./docs/troubleshooting/symptoms.md) is keyed on the first thing you see. `/claude-code-doctor` in any session prints what the plugin thinks is happening, without calling a model.
|
|
54
56
|
|
|
@@ -70,12 +72,14 @@ Nothing in the picker? [Troubleshooting](./docs/troubleshooting/symptoms.md) is
|
|
|
70
72
|
| | opencode's native `anthropic` provider | This plugin | Proxy and token-reuse plugins |
|
|
71
73
|
|---|---|---|---|
|
|
72
74
|
| **Authentication** | A Platform API key in opencode's auth store. | Whatever the official `claude` CLI holds. No token is read, stored or replayed. | The Claude OAuth session, used outside the official client. |
|
|
73
|
-
| **Terms-of-service status** | The ordinary API route. |
|
|
75
|
+
| **Terms-of-service status** | The ordinary API route. | Uses the official client: Claude Code does the authenticating, the same as running `claude -p` yourself. Anthropic's [legal page](https://code.claude.com/docs/en/legal-and-compliance) allows signing in to the unmodified binary; it has not approved or endorsed this plugin. | Disallowed. Anthropic disallowed reusing subscription authentication for third-party Claude use in February 2026, and each of those projects carries its own disclaimer. |
|
|
74
76
|
| **Who runs Bash, Edit, Write** | opencode. | opencode, by default; `Read`, `Grep`, `Glob` run in Claude Code. | opencode. |
|
|
75
77
|
| **What it costs you** | Baseline. | A `claude` child per conversation under a cap of 16; Claude Code may compact its own context behind opencode's back. | One more moving part, plus the account risk above. |
|
|
76
78
|
|
|
77
79
|
The [full comparison](./docs/comparison.md) has nine rows, names the projects in the third column, and quotes their own READMEs.
|
|
78
80
|
|
|
81
|
+
On opencode 2.2.3 or later with one Claude login, also look at opencode's own **Claude Agent** provider: it drives your own `claude` too, runs every tool in opencode, and leaves your `~/.claude` setup out. The comparison has [a section on it](./docs/comparison.md#opencodes-built-in-claude-agent-provider), including what this plugin still adds (opencode 1.x, several accounts, your Claude Code setup, fast mode, `/btw`, the sidebar).
|
|
82
|
+
|
|
79
83
|
## Built from measurements
|
|
80
84
|
|
|
81
85
|
A dozen test files drive a fake `claude` through real turns: the stream parser, the proxy broker, both watchdogs, abort, respawn, account failover and the model fallback chain are exercised end to end. Every rule in [`AGENTS.md`](./AGENTS.md) names the probe, the version and the number that produced it, and the evidence lives in [`docs/agents-history.md`](./docs/agents-history.md). Two runtime dependencies. The [site](https://opencode-claude-code-plugin.dev/) reports the live numbers: it rebuilds itself after every release, on every push that moves a figure, and every six hours.
|
package/dist/index.d.ts
CHANGED
|
@@ -113,6 +113,81 @@ interface V2ModelRequestEvent {
|
|
|
113
113
|
interface V2ModelHookOptions {
|
|
114
114
|
readonly providerID?: string;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* `@opencode/plugin` 2.2.2 `SessionRetry`, the `session.hook("retry")` event:
|
|
118
|
+
* opencode asks before it runs a failed turn again (h #g277).
|
|
119
|
+
*/
|
|
120
|
+
interface V2SessionRetryEvent {
|
|
121
|
+
readonly sessionID: string;
|
|
122
|
+
readonly model: {
|
|
123
|
+
readonly id: string;
|
|
124
|
+
readonly providerID: string;
|
|
125
|
+
};
|
|
126
|
+
readonly error: {
|
|
127
|
+
readonly type?: string;
|
|
128
|
+
readonly message?: string;
|
|
129
|
+
};
|
|
130
|
+
readonly attempt: number;
|
|
131
|
+
decision: {
|
|
132
|
+
retry: false;
|
|
133
|
+
} | {
|
|
134
|
+
retry: true;
|
|
135
|
+
delay: number;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** One way to connect an integration (`IntegrationMethod`); only `type` is always there. */
|
|
139
|
+
interface V2IntegrationMethod {
|
|
140
|
+
readonly type: string;
|
|
141
|
+
readonly id?: string;
|
|
142
|
+
readonly label?: string;
|
|
143
|
+
}
|
|
144
|
+
/** `@opencode/plugin` 2.2.2 `IntegrationEditor`, the calls we make. */
|
|
145
|
+
interface V2IntegrationEditor {
|
|
146
|
+
get(id: string): {
|
|
147
|
+
id: string;
|
|
148
|
+
name: string;
|
|
149
|
+
} | undefined;
|
|
150
|
+
update(id: string, update: (integration: {
|
|
151
|
+
id: string;
|
|
152
|
+
name: string;
|
|
153
|
+
}) => void): void;
|
|
154
|
+
readonly method: {
|
|
155
|
+
list(integrationID: string): readonly V2IntegrationMethod[];
|
|
156
|
+
update(input: {
|
|
157
|
+
integrationID: string;
|
|
158
|
+
method: {
|
|
159
|
+
id: string;
|
|
160
|
+
type: "external";
|
|
161
|
+
label: string;
|
|
162
|
+
};
|
|
163
|
+
}): void;
|
|
164
|
+
remove(integrationID: string, method: V2IntegrationMethod): void;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** `@opencode/client` `ConnectionInfo`, passed back to opencode untouched. */
|
|
168
|
+
interface V2ConnectionInfo {
|
|
169
|
+
readonly type: string;
|
|
170
|
+
readonly id?: string;
|
|
171
|
+
readonly label?: string;
|
|
172
|
+
}
|
|
173
|
+
/** `@opencode/schema/connection` `Connection.Status`. */
|
|
174
|
+
interface V2ConnectionStatus {
|
|
175
|
+
status: "needs_auth";
|
|
176
|
+
message: string;
|
|
177
|
+
url?: string;
|
|
178
|
+
}
|
|
179
|
+
/** `@opencode/plugin` 2.2.2 `IntegrationDomain`, the slice `/connect` needs. */
|
|
180
|
+
interface V2IntegrationDomain {
|
|
181
|
+
transform(callback: (editor: V2IntegrationEditor) => void): Promise<V2Registration>;
|
|
182
|
+
readonly connection: {
|
|
183
|
+
active(integrationID: string): Promise<V2ConnectionInfo | undefined>;
|
|
184
|
+
status(input: {
|
|
185
|
+
integrationID: string;
|
|
186
|
+
connection: V2ConnectionInfo;
|
|
187
|
+
status: V2ConnectionStatus | undefined;
|
|
188
|
+
}): Promise<void>;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
116
191
|
/** `@opencode/schema/prompt-input` `Prompt`: we only rewrite `text`. */
|
|
117
192
|
type V2Prompt = {
|
|
118
193
|
text?: string;
|
|
@@ -158,6 +233,8 @@ interface V2Context {
|
|
|
158
233
|
readonly options: Record<string, unknown>;
|
|
159
234
|
readonly provider: {
|
|
160
235
|
transform(callback: (editor: V2ProviderEditor) => void): Promise<V2Registration>;
|
|
236
|
+
/** Re-runs the transforms. In `@opencode/plugin` 2.2.5; optional because older hosts may lack it. */
|
|
237
|
+
reload?(): Promise<void>;
|
|
161
238
|
};
|
|
162
239
|
readonly aisdk: {
|
|
163
240
|
hook(name: "sdk", callback: (event: V2AISDKSdkEvent) => Promise<void> | void, options?: V2ModelHookOptions): Promise<V2Registration>;
|
|
@@ -165,6 +242,7 @@ interface V2Context {
|
|
|
165
242
|
};
|
|
166
243
|
readonly session: {
|
|
167
244
|
hook(name: "model.request", callback: (event: V2ModelRequestEvent) => Promise<void> | void, options?: V2ModelHookOptions): Promise<V2Registration>;
|
|
245
|
+
hook(name: "retry", callback: (event: V2SessionRetryEvent) => Promise<void> | void, options?: V2ModelHookOptions): Promise<V2Registration>;
|
|
168
246
|
prompt?(input: V2Prompt & {
|
|
169
247
|
sessionID: string;
|
|
170
248
|
delivery?: V2Delivery;
|
|
@@ -180,6 +258,8 @@ interface V2Context {
|
|
|
180
258
|
signal?: AbortSignal;
|
|
181
259
|
}): AsyncIterable<V2Event>;
|
|
182
260
|
};
|
|
261
|
+
/** `/connect`'s entry for each provider (h #g277). Optional: early 2.0 releases lack parts of it. */
|
|
262
|
+
readonly integration?: V2IntegrationDomain;
|
|
183
263
|
/**
|
|
184
264
|
* Absent before 2.0.31's measured shape: the definition is plain JSON
|
|
185
265
|
* Schema, which `Rpc.PortableDefinition` accepts, and the handlers object is
|
|
@@ -444,7 +524,11 @@ interface ClaudeCodeConfig {
|
|
|
444
524
|
controlRequestDenyMessage?: string;
|
|
445
525
|
proxyTools?: string[];
|
|
446
526
|
proxyOpencodeTools?: string[];
|
|
527
|
+
/** See `ClaudeCodeProviderSettings.alwaysLoadProxyTools`. Default bash, edit, write. */
|
|
528
|
+
alwaysLoadProxyTools?: boolean | string[];
|
|
447
529
|
stripContextReminders?: boolean;
|
|
530
|
+
/** See `ClaudeCodeProviderSettings.verbatimPrompts`. Default true. */
|
|
531
|
+
verbatimPrompts?: boolean;
|
|
448
532
|
extraDisallowedTools?: string[];
|
|
449
533
|
/** See `ClaudeCodeProviderSettings.mapReadRules`. Default true. */
|
|
450
534
|
mapReadRules?: boolean;
|
|
@@ -481,6 +565,8 @@ interface ClaudeCodeConfig {
|
|
|
481
565
|
resumeAfterRestart?: boolean;
|
|
482
566
|
/** Carry a conversation's Claude session over when its model or effort changes. Default true. */
|
|
483
567
|
resumeAcrossModelChanges?: boolean;
|
|
568
|
+
/** Apply a model or effort change to the live `claude` process. Default true. */
|
|
569
|
+
liveModelSwitch?: boolean;
|
|
484
570
|
/** Carry a conversation's Claude transcript over on an account switch. Default true. */
|
|
485
571
|
crossAccountResume?: boolean;
|
|
486
572
|
/** See `ClaudeCodeProviderSettings.maxBudgetUsd`. Resolved; unset is off. */
|
|
@@ -536,6 +622,26 @@ type AccountFailoverMode = "ask" | "off" | "auto";
|
|
|
536
622
|
type SubagentDispatchMode = "ask" | "off";
|
|
537
623
|
interface ClaudeCodeProviderSettings {
|
|
538
624
|
cliPath?: string;
|
|
625
|
+
/**
|
|
626
|
+
* Register the model catalog as the installed Claude Code describes it.
|
|
627
|
+
* Default `true`.
|
|
628
|
+
*
|
|
629
|
+
* A background probe asks the CLI, once per CLI version, which models it
|
|
630
|
+
* knows, the context window it runs each in and the effort levels it
|
|
631
|
+
* accepts, and keeps the answer in `cli-models.json` under
|
|
632
|
+
* `$XDG_STATE_HOME`. Providers are registered from that file: windows and
|
|
633
|
+
* effort variants follow the CLI, and a model the CLI's own picker offers
|
|
634
|
+
* that the catalog lacks is added at an unknown (zero) price. Costs and the
|
|
635
|
+
* `(N×)` labels stay the catalog's, because the CLI carries no prices.
|
|
636
|
+
*
|
|
637
|
+
* Free: each probe is a `--bare` process with no credential, sent
|
|
638
|
+
* `initialize` and `get_context_usage` and never a message (measured: no
|
|
639
|
+
* API request, h #g279). Registration never waits for it, so a fresh answer
|
|
640
|
+
* applies at once on opencode 2 (a provider reload) and from the next
|
|
641
|
+
* start on opencode 1. `false` registers the built-in catalog as it ships
|
|
642
|
+
* and runs no probe.
|
|
643
|
+
*/
|
|
644
|
+
cliModelCatalog?: boolean;
|
|
539
645
|
/** Internal: set by the opencode 2 entrypoint. See `ClaudeCodeConfig.hostApi`. */
|
|
540
646
|
hostApi?: "v1" | "v2";
|
|
541
647
|
/** Wins over the legacy interactive option and its environment fallback. */
|
|
@@ -839,6 +945,39 @@ interface ClaudeCodeProviderSettings {
|
|
|
839
945
|
* reminder is then something the model can actually act on.
|
|
840
946
|
*/
|
|
841
947
|
stripContextReminders?: boolean;
|
|
948
|
+
/**
|
|
949
|
+
* Which proxy tools Claude Code loads into every request instead of
|
|
950
|
+
* deferring them behind its `ToolSearch` tool. Default `["bash", "edit",
|
|
951
|
+
* "write"]`.
|
|
952
|
+
*
|
|
953
|
+
* Claude Code defers every MCP tool, the proxy's included, so the first
|
|
954
|
+
* proxied call in a `claude` process costs one extra API request to find
|
|
955
|
+
* the tool. A listed tool is served with `_meta["anthropic/alwaysLoad"]`,
|
|
956
|
+
* which skips that request: measured on Claude Code 2.1.314, a turn that
|
|
957
|
+
* ran a proxied `bash` took 2 API calls instead of 3, with 32% fewer prompt
|
|
958
|
+
* tokens and 41% lower cost, while a turn that called no tool carried 531
|
|
959
|
+
* more prompt tokens. `true` loads every tool the proxy serves (the MCP
|
|
960
|
+
* tools `proxyOpencodeMcpTools` adds included), `false` defers them all as
|
|
961
|
+
* before. Names are the proxy's lowercase tool names. An older Claude Code
|
|
962
|
+
* ignores the field.
|
|
963
|
+
*/
|
|
964
|
+
alwaysLoadProxyTools?: boolean | string[];
|
|
965
|
+
/**
|
|
966
|
+
* Deliver the text of every message as written. Default true.
|
|
967
|
+
*
|
|
968
|
+
* Claude Code reads the last text block of a headless user message the way
|
|
969
|
+
* it reads typing: a leading `/` runs one of its commands and an `@path`
|
|
970
|
+
* attaches that file. Measured on Claude Code 2.1.314, a message `/model
|
|
971
|
+
* claude-sonnet-5-5` switched the running process to Sonnet without a
|
|
972
|
+
* model call, and a message starting with a path such as `/etc/hosts` lost
|
|
973
|
+
* the environment, deferred-tool, agent and skill context of its turn. On,
|
|
974
|
+
* each envelope carries `client_composed: true`, the field the Agent SDK's
|
|
975
|
+
* `verbatimPrompts` sets, and the text reaches Claude unchanged; hooks,
|
|
976
|
+
* CLAUDE.md and the skill listing still arrive. The one thing lost is the
|
|
977
|
+
* `ultrathink` keyword's reminder: use an effort variant instead. Headless
|
|
978
|
+
* transport only; an older Claude Code ignores the field.
|
|
979
|
+
*/
|
|
980
|
+
verbatimPrompts?: boolean;
|
|
842
981
|
/**
|
|
843
982
|
* Extra Claude Code built-ins to switch off with `--disallowedTools`,
|
|
844
983
|
* on top of the ones implied by `proxyTools`.
|
|
@@ -1079,6 +1218,29 @@ interface ClaudeCodeProviderSettings {
|
|
|
1079
1218
|
* false` turns this off too.
|
|
1080
1219
|
*/
|
|
1081
1220
|
resumeAcrossModelChanges?: boolean;
|
|
1221
|
+
/**
|
|
1222
|
+
* Apply a model or reasoning effort change to the conversation's running
|
|
1223
|
+
* `claude` process, with the CLI's own `set_model` and `apply_flag_settings`
|
|
1224
|
+
* control requests, instead of stopping it and starting another with
|
|
1225
|
+
* `--resume`. Default `true`.
|
|
1226
|
+
*
|
|
1227
|
+
* Measured on Claude Code 2.1.288 and 2.1.314 (h #g278): the conversation is
|
|
1228
|
+
* kept, the next request goes out under the new model or effort, and the
|
|
1229
|
+
* prompt cache reads and writes are the same as the respawn's. What it saves
|
|
1230
|
+
* is the respawn itself, 4.6 to 6.6 seconds to `system`/`init` per change on
|
|
1231
|
+
* a real install, against about 25 ms for an effort change and 0.5 to 0.7 s
|
|
1232
|
+
* for a model change. To make an effort change possible, a headless spawn
|
|
1233
|
+
* carries its effort as `--effort` and drops CLAUDE_CODE_EFFORT_LEVEL: the
|
|
1234
|
+
* variable beats a live change silently.
|
|
1235
|
+
*
|
|
1236
|
+
* Only between two models on the same account and in the same fast mode,
|
|
1237
|
+
* between two explicit efforts, on an idle child (a change that lands
|
|
1238
|
+
* mid-turn still finishes that turn on the old settings, h #g271), and on
|
|
1239
|
+
* the headless transport: the interactive one has no control channel and
|
|
1240
|
+
* keeps respawning. Any refusal or timeout falls back to the respawn. Needs
|
|
1241
|
+
* `resumeAcrossModelChanges`; `false` here restores the respawn exactly.
|
|
1242
|
+
*/
|
|
1243
|
+
liveModelSwitch?: boolean;
|
|
1082
1244
|
/**
|
|
1083
1245
|
* Continue the same Claude conversation when it moves to another account,
|
|
1084
1246
|
* instead of replaying the whole thread as text into a fresh session there.
|