@khalilgharbaoui/opencode-claude-code-plugin 0.9.0 → 0.9.2
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 +12 -8
- package/dist/index.d.ts +12 -0
- package/dist/index.js +39 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @khalilgharbaoui/opencode-claude-code-plugin
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@khalilgharbaoui/opencode-claude-code-plugin)
|
|
4
|
+
|
|
3
5
|
An [opencode](https://opencode.ai) plugin that wraps the **Claude Code CLI** (`claude`) and routes model traffic through it instead of the Anthropic HTTP API. You get to use opencode's UI, agents, MCP, and permission system while authenticating and billing through whichever method `claude` is logged into (Pro/Max plan, Bedrock, Vertex, or API key).
|
|
4
6
|
|
|
5
7
|
> Maintained fork of [`unixfox/opencode-claude-code-plugin`](https://github.com/unixfox/opencode-claude-code-plugin). Published as `@khalilgharbaoui/opencode-claude-code-plugin` on npm.
|
|
@@ -101,16 +103,21 @@ This plugin drives Claude Code headlessly (`claude --print`), which Anthropic bi
|
|
|
101
103
|
| Pro | $20 |
|
|
102
104
|
| Max 5x | $100 |
|
|
103
105
|
| Max 20x | $200 |
|
|
104
|
-
| Team Standard | $20
|
|
105
|
-
| Team Premium | $100
|
|
106
|
-
| Enterprise (
|
|
106
|
+
| Team (Standard seats) | $20 |
|
|
107
|
+
| Team (Premium seats) | $100 |
|
|
108
|
+
| Enterprise (usage-based) | $20 |
|
|
109
|
+
| Enterprise (seat-based Premium seats) | $200 |
|
|
110
|
+
|
|
111
|
+
Credits are **per user, not pooled** across a team, and Standard seats on seat-based Enterprise plans aren't eligible. See Anthropic's [Agent SDK credit article](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) for the authoritative table.
|
|
107
112
|
|
|
108
113
|
What this means for plugin users:
|
|
109
114
|
|
|
110
|
-
- **Claim the credit once.** It's a one-time opt-in via your Claude account
|
|
115
|
+
- **Claim the credit once.** It's a one-time opt-in via your Claude account; eligible users get an email with claim instructions before June 15, 2026. After that it refreshes every billing cycle, and unused credit does not roll over.
|
|
116
|
+
- **Agent SDK usage drains the credit first**, before any other source.
|
|
111
117
|
- **When the credit runs out, plugin requests stop** until the next billing cycle — unless you enable usage credits in your Claude account, in which case overflow is billed at standard API rates.
|
|
112
118
|
- **The credit is denominated in dollars at standard API rates**, so the Price × column above maps directly to how fast each model drains it — Fable 5 / Mythos 5 burn it 10× faster than Haiku, 2× faster than Opus 4.8.
|
|
113
119
|
- **API-key auth is unaffected.** If your `claude` CLI authenticates with an Anthropic API key / Console billing instead of a subscription, nothing changes — pay-as-you-go as before.
|
|
120
|
+
- **Watch for a stray `ANTHROPIC_API_KEY`.** If that variable (or `ANTHROPIC_AUTH_TOKEN`) is present in your environment, Claude Code uses it and bills pay-as-you-go — silently bypassing the subscription credit even when `claude` is logged into a plan. The plugin logs a one-time warning when it detects a key. To force subscription auth, set `ignoreAnthropicApiKey: true`, which strips the key from the `claude` spawn environment.
|
|
114
121
|
- **Interactive Claude Code in your terminal is unaffected.** The change targets programmatic usage only: the Agent SDK, `claude -p`, Claude Code GitHub Actions, and third-party apps like this plugin.
|
|
115
122
|
|
|
116
123
|
Two related dates:
|
|
@@ -209,6 +216,7 @@ The account model IDs are internally suffixed, for example `claude-sonnet-4-6@wo
|
|
|
209
216
|
| `multiStepContinuation` | boolean | `true` | Append a system-prompt hint nudging Claude to chain tool calls within one turn instead of pausing between subtasks. Each opencode turn boundary requires the user to manually press "continue", so for multi-step tasks this reduces friction. Set `false` to disable. |
|
|
210
217
|
| `autoContinueIncompleteTurns` | boolean \| `"smart"` | `"smart"` | Smartly continue incomplete Claude CLI results inside the same opencode turn. Reduces manual "continue" presses when Claude ends after reasoning/tool activity without a useful final answer. Set `false` to disable. |
|
|
211
218
|
| `compactionModel` | string | `"claude-haiku-4-5"` | Model used when opencode invokes `/compact`. Override per-process via the `CLAUDE_CODE_COMPACTION_MODEL` env var (env wins over config). See [Compaction](#compaction). |
|
|
219
|
+
| `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from every spawned `claude` process so it authenticates with your logged-in subscription instead of pay-as-you-go API billing. The plugin warns once at startup whenever an API key is detected, regardless of this setting. See [Billing](#billing-change-june-15-2026-agent-sdk-credit). |
|
|
212
220
|
| `interactive` | boolean | `false` | **Experimental.** Drive the interactive `claude` TUI (subscription billing) instead of headless `--print`. Requires opencode running under Bun with PTY support; silently falls back to headless otherwise. Env: `CLAUDE_CODE_INTERACTIVE_TRANSPORT=1`. See [Interactive transport](#interactive-transport-experimental). |
|
|
213
221
|
| `interactiveBypass` | boolean | `false` | Deprecated/no-op with `interactive`: Claude Code's TUI shows a manual safety confirmation for `bypassPermissions`, so the plugin intentionally does not pass it. |
|
|
214
222
|
| `interactiveAllowTools` | string[] | `["Bash", "Edit", "Write", "Read", "WebFetch"]` | With `interactive`: built-in tools pre-allowed without prompting (replaces the default list). MCP server wildcards (`mcp__<server>__*`) are always added from the bridged config. |
|
|
@@ -269,10 +277,6 @@ Set `interactiveSystemPrompt: false` only for diagnostics. While disabled, the i
|
|
|
269
277
|
- **Turn timeout:** a turn that produces no terminal stop within 30 minutes is reported honestly as an error result (visible truncation), not silently ended.
|
|
270
278
|
- `/compact` always uses the headless transport regardless of this setting.
|
|
271
279
|
|
|
272
|
-
### Known issue
|
|
273
|
-
|
|
274
|
-
- **Fresh sessions can hang at startup.** With `interactive: true`, starting a brand-new opencode session (under Bun) can leave the TUI blank and unresponsive before you can type. Resuming an existing session (`opencode --continue`) works, and once a session is running the transport is stable. Until this is fixed, leave `interactive` unset (headless default) if you hit it. Tracked for a follow-up release.
|
|
275
|
-
|
|
276
280
|
---
|
|
277
281
|
|
|
278
282
|
## Selective tool proxy
|
package/dist/index.d.ts
CHANGED
|
@@ -167,6 +167,7 @@ interface ClaudeCodeConfig {
|
|
|
167
167
|
multiStepContinuation?: boolean;
|
|
168
168
|
autoContinueIncompleteTurns?: boolean | "smart";
|
|
169
169
|
compactionModel?: string;
|
|
170
|
+
ignoreAnthropicApiKey?: boolean;
|
|
170
171
|
logging?: LoggingConfig;
|
|
171
172
|
}
|
|
172
173
|
interface LoggingConfig {
|
|
@@ -267,6 +268,17 @@ interface ClaudeCodeProviderSettings {
|
|
|
267
268
|
* (see opencode's agent docs).
|
|
268
269
|
*/
|
|
269
270
|
proxyTools?: string[];
|
|
271
|
+
/**
|
|
272
|
+
* Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from the environment of
|
|
273
|
+
* every spawned `claude` process. When an API key is present, Claude Code
|
|
274
|
+
* authenticates with it (pay-as-you-go Console billing) instead of the
|
|
275
|
+
* logged-in Pro/Max subscription — silently bypassing the Agent SDK plan
|
|
276
|
+
* credit. Set this to `true` to force the CLI to fall back to its stored
|
|
277
|
+
* subscription auth. Defaults to `false` (the key is passed through, so
|
|
278
|
+
* deliberate API-key users are unaffected). Regardless of this setting, the
|
|
279
|
+
* plugin logs a one-time warning at startup when an API key is detected.
|
|
280
|
+
*/
|
|
281
|
+
ignoreAnthropicApiKey?: boolean;
|
|
270
282
|
/**
|
|
271
283
|
* Routing for Claude's built-in `WebSearch` tool.
|
|
272
284
|
*
|
package/dist/index.js
CHANGED
|
@@ -1252,11 +1252,15 @@ function envFlagEnabled(value) {
|
|
|
1252
1252
|
function isClaudeThinkingDisabled() {
|
|
1253
1253
|
return envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_THINKING) || envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING);
|
|
1254
1254
|
}
|
|
1255
|
-
function claudeSpawnEnv() {
|
|
1255
|
+
function claudeSpawnEnv(opts) {
|
|
1256
1256
|
const env = {
|
|
1257
1257
|
...process.env,
|
|
1258
1258
|
TERM: "xterm-256color"
|
|
1259
1259
|
};
|
|
1260
|
+
if (opts?.ignoreAnthropicApiKey) {
|
|
1261
|
+
delete env.ANTHROPIC_API_KEY;
|
|
1262
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
1263
|
+
}
|
|
1260
1264
|
if (!isClaudeThinkingDisabled() && process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0) {
|
|
1261
1265
|
env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES = "1";
|
|
1262
1266
|
}
|
|
@@ -1304,13 +1308,13 @@ function deleteClaudeSessionId(key) {
|
|
|
1304
1308
|
if (claudeSessionId) clearLedger(claudeSessionId);
|
|
1305
1309
|
claudeSessions.delete(key);
|
|
1306
1310
|
}
|
|
1307
|
-
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile) {
|
|
1311
|
+
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey) {
|
|
1308
1312
|
evictIfNeeded();
|
|
1309
1313
|
log.info("spawning new claude process", { cliPath, cliArgs, cwd, sessionKey: sessionKey2 });
|
|
1310
1314
|
const proc = spawn(cliPath, cliArgs, {
|
|
1311
1315
|
cwd,
|
|
1312
1316
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1313
|
-
env: claudeSpawnEnv(),
|
|
1317
|
+
env: claudeSpawnEnv({ ignoreAnthropicApiKey }),
|
|
1314
1318
|
shell: process.platform === "win32"
|
|
1315
1319
|
});
|
|
1316
1320
|
const lineEmitter = new EventEmitter();
|
|
@@ -1501,6 +1505,7 @@ var ClaudeSession = class {
|
|
|
1501
1505
|
model: opts.model,
|
|
1502
1506
|
settingSources: opts.settingSources,
|
|
1503
1507
|
extraArgs: opts.extraArgs ?? [],
|
|
1508
|
+
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey,
|
|
1504
1509
|
cols: opts.cols ?? 200,
|
|
1505
1510
|
rows: opts.rows ?? 50,
|
|
1506
1511
|
bootMinMs: opts.bootMinMs ?? 3e3,
|
|
@@ -1544,7 +1549,8 @@ var ClaudeSession = class {
|
|
|
1544
1549
|
env: {
|
|
1545
1550
|
...process.env,
|
|
1546
1551
|
CLAUDE_CONFIG_DIR: this.o.configDir,
|
|
1547
|
-
TERM: "xterm-256color"
|
|
1552
|
+
TERM: "xterm-256color",
|
|
1553
|
+
...this.o.ignoreAnthropicApiKey ? { ANTHROPIC_API_KEY: void 0, ANTHROPIC_AUTH_TOKEN: void 0 } : {}
|
|
1548
1554
|
},
|
|
1549
1555
|
terminal: {
|
|
1550
1556
|
cols: this.o.cols,
|
|
@@ -1868,7 +1874,8 @@ function spawnInteractiveProcess(opts) {
|
|
|
1868
1874
|
// Default null = normal CLAUDE.md + settings load, matching what the
|
|
1869
1875
|
// headless spawn does. "" (skip everything) is for fast e2e runs only.
|
|
1870
1876
|
settingSources: opts.settingSources === void 0 ? null : opts.settingSources,
|
|
1871
|
-
extraArgs
|
|
1877
|
+
extraArgs,
|
|
1878
|
+
ignoreAnthropicApiKey: opts.ignoreAnthropicApiKey
|
|
1872
1879
|
});
|
|
1873
1880
|
log.info("prepared interactive claude session", {
|
|
1874
1881
|
cwd: opts.cwd,
|
|
@@ -2576,7 +2583,7 @@ function isAskUserQuestionTool(name) {
|
|
|
2576
2583
|
const n = name.toLowerCase();
|
|
2577
2584
|
return n === "askuserquestion" || n === "ask_user_question";
|
|
2578
2585
|
}
|
|
2579
|
-
var ASK_USER_QUESTION_DENY_MESSAGE = "Your question and its options have already been presented to the operator verbatim. Stop now: end your turn without calling any more tools and without answering the question yourself.
|
|
2586
|
+
var ASK_USER_QUESTION_DENY_MESSAGE = "Your question and its options have already been presented to the operator verbatim. This is NOT a cancellation or a refusal \u2014 the operator simply has not answered yet. Stop now: end your turn without calling any more tools and without answering the question yourself. Do not say the question was cancelled, skipped, or declined, and do not guess, assume, or proceed on their behalf. Wait for the operator's reply, which arrives as the next user message.";
|
|
2580
2587
|
function denyMessageForTool(toolName, configuredDenyMessage) {
|
|
2581
2588
|
if (isAskUserQuestionTool(toolName)) return ASK_USER_QUESTION_DENY_MESSAGE;
|
|
2582
2589
|
return configuredDenyMessage ?? `Denied by opencode-claude-code policy for tool ${toolName}`;
|
|
@@ -2652,6 +2659,7 @@ function shouldAutoContinueIncompleteTurn(state, snapshot) {
|
|
|
2652
2659
|
if (state.enabled === false) return { continue: false, reason: "disabled" };
|
|
2653
2660
|
if (snapshot.isError) return { continue: false, reason: "error" };
|
|
2654
2661
|
if (state.aborted) return { continue: false, reason: "aborted" };
|
|
2662
|
+
if (state.sawAskUserQuestion) return { continue: false, reason: "question" };
|
|
2655
2663
|
if (snapshot.stopReason) {
|
|
2656
2664
|
return {
|
|
2657
2665
|
continue: false,
|
|
@@ -3307,7 +3315,9 @@ var ClaudeCodeLanguageModel = class {
|
|
|
3307
3315
|
const proc = spawn2(this.config.cliPath, cliArgs, {
|
|
3308
3316
|
cwd,
|
|
3309
3317
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3310
|
-
env: claudeSpawnEnv(
|
|
3318
|
+
env: claudeSpawnEnv({
|
|
3319
|
+
ignoreAnthropicApiKey: this.config.ignoreAnthropicApiKey
|
|
3320
|
+
}),
|
|
3311
3321
|
shell: process.platform === "win32"
|
|
3312
3322
|
});
|
|
3313
3323
|
if (systemPromptFile) {
|
|
@@ -3718,7 +3728,8 @@ ${plan}
|
|
|
3718
3728
|
model: effectiveModelId,
|
|
3719
3729
|
mcpConfigPaths: mcp.paths,
|
|
3720
3730
|
permissionsAllow: allow,
|
|
3721
|
-
systemPromptFile
|
|
3731
|
+
systemPromptFile,
|
|
3732
|
+
ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey
|
|
3722
3733
|
});
|
|
3723
3734
|
ap.mcpHash = mcp.bridgedHash;
|
|
3724
3735
|
setActiveProcess(sk, ap);
|
|
@@ -3803,7 +3814,8 @@ ${plan}
|
|
|
3803
3814
|
sk,
|
|
3804
3815
|
spawnProxyServer,
|
|
3805
3816
|
spawnMcpHash,
|
|
3806
|
-
spawnSystemPromptFile
|
|
3817
|
+
spawnSystemPromptFile,
|
|
3818
|
+
self.config.ignoreAnthropicApiKey
|
|
3807
3819
|
);
|
|
3808
3820
|
proc = ap.proc;
|
|
3809
3821
|
lineEmitter = ap.lineEmitter;
|
|
@@ -4103,6 +4115,7 @@ ${plan}
|
|
|
4103
4115
|
} catch {
|
|
4104
4116
|
}
|
|
4105
4117
|
if (isAskUserQuestionTool(tc.name)) {
|
|
4118
|
+
autoContinueState.sawAskUserQuestion = true;
|
|
4106
4119
|
const askId = startTextBlock();
|
|
4107
4120
|
controller.enqueue({
|
|
4108
4121
|
type: "text-delta",
|
|
@@ -5199,6 +5212,21 @@ function pickOpencodeDirectory(input) {
|
|
|
5199
5212
|
if (isUsableDirectory(ctx.worktree)) return ctx.worktree;
|
|
5200
5213
|
return void 0;
|
|
5201
5214
|
}
|
|
5215
|
+
var warnedAnthropicApiKey = false;
|
|
5216
|
+
function warnIfAnthropicApiKey(ignore) {
|
|
5217
|
+
if (warnedAnthropicApiKey) return;
|
|
5218
|
+
if (!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_AUTH_TOKEN) return;
|
|
5219
|
+
warnedAnthropicApiKey = true;
|
|
5220
|
+
if (ignore) {
|
|
5221
|
+
log.warn(
|
|
5222
|
+
"ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN detected; stripping it from claude spawns (ignoreAnthropicApiKey) so requests use your subscription auth, not pay-as-you-go API billing."
|
|
5223
|
+
);
|
|
5224
|
+
} else {
|
|
5225
|
+
log.warn(
|
|
5226
|
+
"ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN detected; claude may bill as pay-as-you-go API usage instead of your subscription / Agent SDK credit. Set provider option `ignoreAnthropicApiKey: true` to force subscription auth."
|
|
5227
|
+
);
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5202
5230
|
function createClaudeCode(settings = {}) {
|
|
5203
5231
|
if (settings.logging) {
|
|
5204
5232
|
configureLogger({
|
|
@@ -5208,6 +5236,7 @@ function createClaudeCode(settings = {}) {
|
|
|
5208
5236
|
level: settings.logging.level ?? "info"
|
|
5209
5237
|
});
|
|
5210
5238
|
}
|
|
5239
|
+
warnIfAnthropicApiKey(settings.ignoreAnthropicApiKey);
|
|
5211
5240
|
const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
|
|
5212
5241
|
const providerName = settings.providerID ?? settings.name ?? "claude-code";
|
|
5213
5242
|
const proxyTools = settings.proxyTools ?? ["Bash", "Edit", "Write", "WebFetch"];
|
|
@@ -5234,6 +5263,7 @@ function createClaudeCode(settings = {}) {
|
|
|
5234
5263
|
multiStepContinuation: settings.multiStepContinuation ?? true,
|
|
5235
5264
|
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart",
|
|
5236
5265
|
compactionModel: settings.compactionModel,
|
|
5266
|
+
ignoreAnthropicApiKey: settings.ignoreAnthropicApiKey,
|
|
5237
5267
|
interactive: settings.interactive,
|
|
5238
5268
|
interactiveBypass: settings.interactiveBypass,
|
|
5239
5269
|
interactiveAllowTools: settings.interactiveAllowTools,
|