@khalilgharbaoui/opencode-claude-code-plugin 0.40.0 → 0.41.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/dist/index.d.ts +5 -0
- package/dist/index.js +98 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +51 -8
package/dist/index.d.ts
CHANGED
|
@@ -338,6 +338,7 @@ type OpenCodePlugin = (input: unknown, options?: Record<string, unknown>) => Pro
|
|
|
338
338
|
type LogLevel = "debug" | "info" | "notice" | "warn" | "error";
|
|
339
339
|
type LogMode = "silent" | "debug";
|
|
340
340
|
|
|
341
|
+
type ClaudeCodeTransport = "auto" | "headless" | "interactive";
|
|
341
342
|
interface ClaudeCodeConfig {
|
|
342
343
|
provider: string;
|
|
343
344
|
cliPath: string;
|
|
@@ -349,6 +350,8 @@ interface ClaudeCodeConfig {
|
|
|
349
350
|
* so nothing process-wide may decide it.
|
|
350
351
|
*/
|
|
351
352
|
hostApi?: "v1" | "v2";
|
|
353
|
+
/** Explicit transport selection; auto switches only on proven missing headless flags. */
|
|
354
|
+
transport?: ClaudeCodeTransport;
|
|
352
355
|
/** Drive interactive claude (subscription) instead of headless --print. */
|
|
353
356
|
interactive?: boolean;
|
|
354
357
|
/** Deprecated/no-op with interactive: Claude Code's TUI requires manual confirmation for bypassPermissions. */
|
|
@@ -466,6 +469,8 @@ interface ClaudeCodeProviderSettings {
|
|
|
466
469
|
cliPath?: string;
|
|
467
470
|
/** Internal: set by the opencode 2 entrypoint. See `ClaudeCodeConfig.hostApi`. */
|
|
468
471
|
hostApi?: "v1" | "v2";
|
|
472
|
+
/** Wins over the legacy interactive option and its environment fallback. */
|
|
473
|
+
transport?: ClaudeCodeTransport;
|
|
469
474
|
/** Drive interactive claude (subscription) instead of headless --print. */
|
|
470
475
|
interactive?: boolean;
|
|
471
476
|
/** Deprecated/no-op with interactive: Claude Code's TUI requires manual confirmation for bypassPermissions. */
|
package/dist/index.js
CHANGED
|
@@ -1728,6 +1728,43 @@ function cliSupportsThinking(v) {
|
|
|
1728
1728
|
return gte(v, { major: 2, minor: 0, patch: 0 });
|
|
1729
1729
|
}
|
|
1730
1730
|
var flagSupport = /* @__PURE__ */ new Map();
|
|
1731
|
+
var headlessSupport = /* @__PURE__ */ new Map();
|
|
1732
|
+
function detectHeadlessSupport(cliPath) {
|
|
1733
|
+
const key = `${cliPath}\0headless`;
|
|
1734
|
+
const cached = headlessSupport.get(key);
|
|
1735
|
+
if (cached) return cached;
|
|
1736
|
+
let deadlineKill = false;
|
|
1737
|
+
const promise = (async () => {
|
|
1738
|
+
try {
|
|
1739
|
+
const execution = execFileAsync(cliPath, [
|
|
1740
|
+
"--print",
|
|
1741
|
+
"--input-format",
|
|
1742
|
+
"stream-json",
|
|
1743
|
+
"--output-format",
|
|
1744
|
+
"stream-json",
|
|
1745
|
+
"--help"
|
|
1746
|
+
], { timeout: probeTimeoutMs, killSignal: "SIGKILL", maxBuffer: 4 * 1024 * 1024 });
|
|
1747
|
+
execution.child.stdin?.end();
|
|
1748
|
+
const { stdout } = await execution;
|
|
1749
|
+
if (!/^\s*Usage:/im.test(stdout) || !/^\s*Options:/im.test(stdout)) return "unknown";
|
|
1750
|
+
const flags = ["--print", "--input-format", "--output-format"];
|
|
1751
|
+
if (!flags.every((flag) => new RegExp(`^\\s+(?:-[A-Za-z],\\s+)?${flag}(?:\\s|,|$)`, "m").test(stdout))) {
|
|
1752
|
+
return "unsupported";
|
|
1753
|
+
}
|
|
1754
|
+
return stdout.includes("stream-json") ? "supported" : "unknown";
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
deadlineKill = killedByDeadline(error);
|
|
1757
|
+
const stderr = error?.stderr;
|
|
1758
|
+
if (typeof stderr === "string" && /^(?:error: )?(?:unknown|unrecognized|unsupported) option ['"]?--(?:print|input-format|output-format)(?:['"\s]|$)/im.test(stderr)) {
|
|
1759
|
+
return "unsupported";
|
|
1760
|
+
}
|
|
1761
|
+
return "unknown";
|
|
1762
|
+
}
|
|
1763
|
+
})();
|
|
1764
|
+
headlessSupport.set(key, promise);
|
|
1765
|
+
forgetDeadlineKill(headlessSupport, key, promise, () => deadlineKill);
|
|
1766
|
+
return promise;
|
|
1767
|
+
}
|
|
1731
1768
|
function detectCliSupportsFlag(cliPath, flag) {
|
|
1732
1769
|
const key = `${cliPath}\0${flag}`;
|
|
1733
1770
|
const cached = flagSupport.get(key);
|
|
@@ -6336,6 +6373,10 @@ async function fetchPlanUsage(cliPath, options = {}) {
|
|
|
6336
6373
|
const timeoutMs = options.timeoutMs ?? 2e4;
|
|
6337
6374
|
const run = options.runImpl ?? runCli;
|
|
6338
6375
|
try {
|
|
6376
|
+
const support = options.headlessSupportImpl ?? (options.runImpl ? void 0 : detectHeadlessSupport);
|
|
6377
|
+
if (support && await support(cliPath) === "unsupported") {
|
|
6378
|
+
return { status: "failed", error: "Plan usage is unavailable: this Claude CLI does not support headless output. Check usage in the Claude CLI directly." };
|
|
6379
|
+
}
|
|
6339
6380
|
const env = claudeSpawnEnv({ ignoreAnthropicApiKey: options.ignoreAnthropicApiKey });
|
|
6340
6381
|
const stdout = await run(
|
|
6341
6382
|
cliPath,
|
|
@@ -8055,6 +8096,9 @@ The complete prior conversation appears above. The synthesis instructions follow
|
|
|
8055
8096
|
`
|
|
8056
8097
|
});
|
|
8057
8098
|
}
|
|
8099
|
+
if (opts.compactionInstructions?.trim()) {
|
|
8100
|
+
content.push({ type: "text", text: opts.compactionInstructions });
|
|
8101
|
+
}
|
|
8058
8102
|
} else if (includeHistoryContext) {
|
|
8059
8103
|
const historyContext = compactConversationHistory(prompt);
|
|
8060
8104
|
if (historyContext) {
|
|
@@ -9350,6 +9394,7 @@ ${text}`
|
|
|
9350
9394
|
}
|
|
9351
9395
|
function interactiveExtraArgs(opts) {
|
|
9352
9396
|
const extraArgs = [];
|
|
9397
|
+
if (opts.tools) extraArgs.push("--tools", opts.tools.join(","));
|
|
9353
9398
|
if (opts.mcpConfigPaths && opts.mcpConfigPaths.length > 0) {
|
|
9354
9399
|
extraArgs.push(
|
|
9355
9400
|
"--mcp-config",
|
|
@@ -9681,6 +9726,32 @@ function spawnInteractiveProcess(opts) {
|
|
|
9681
9726
|
return ap;
|
|
9682
9727
|
}
|
|
9683
9728
|
|
|
9729
|
+
// src/transport.ts
|
|
9730
|
+
function requestedTransport(config, env = process.env.CLAUDE_CODE_INTERACTIVE_TRANSPORT) {
|
|
9731
|
+
if (config.transport !== void 0) {
|
|
9732
|
+
if (!["auto", "headless", "interactive"].includes(config.transport)) {
|
|
9733
|
+
throw new Error(`Unknown Claude transport: ${config.transport}`);
|
|
9734
|
+
}
|
|
9735
|
+
return config.transport;
|
|
9736
|
+
}
|
|
9737
|
+
const enabled = config.interactive ?? (env !== void 0 && !["", "0", "false", "no", "off"].includes(env.trim().toLowerCase()));
|
|
9738
|
+
return enabled ? "interactive" : "headless";
|
|
9739
|
+
}
|
|
9740
|
+
function hasInteractiveTransport() {
|
|
9741
|
+
const runtime = globalThis;
|
|
9742
|
+
return typeof runtime.Bun?.Terminal === "function";
|
|
9743
|
+
}
|
|
9744
|
+
async function selectTransport(requested, cliPath, available = hasInteractiveTransport()) {
|
|
9745
|
+
if (requested === "headless") return "headless";
|
|
9746
|
+
if (requested === "interactive") {
|
|
9747
|
+
if (!available) throw new Error("Interactive Claude transport requires Bun.Terminal. Run opencode under a Bun runtime with PTY support.");
|
|
9748
|
+
return "interactive";
|
|
9749
|
+
}
|
|
9750
|
+
if (await detectHeadlessSupport(cliPath) !== "unsupported") return "headless";
|
|
9751
|
+
if (!available) throw new Error("Claude no longer supports the headless flags, and Bun.Terminal is unavailable. Run opencode under a Bun runtime with PTY support.");
|
|
9752
|
+
return "interactive";
|
|
9753
|
+
}
|
|
9754
|
+
|
|
9684
9755
|
// src/prompts.ts
|
|
9685
9756
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
9686
9757
|
import { homedir as homedir8 } from "os";
|
|
@@ -12549,8 +12620,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12549
12620
|
const toFinishReason2 = this.toFinishReason.bind(this);
|
|
12550
12621
|
const handleControlRequest2 = this.handleControlRequest.bind(this);
|
|
12551
12622
|
const flagOn = (v) => v !== void 0 && !["", "0", "false", "no", "off"].includes(v.trim().toLowerCase());
|
|
12552
|
-
const
|
|
12553
|
-
const
|
|
12623
|
+
const transport = requestedTransport(this.config);
|
|
12624
|
+
const doctor = !compactionMode && scope !== "no-tools" ? parseDoctorCommand(options.prompt) : null;
|
|
12625
|
+
const selectedTransport = doctor ? transport === "interactive" ? "interactive" : "headless" : this.config.transport === void 0 && !hasInteractiveTransport() ? "headless" : this.isTitleRequest(scope, options) && !compactionMode ? "headless" : await Promise.race([selectTransport(transport, this.config.cliPath), turnAbort.whenAborted]);
|
|
12626
|
+
if (turnAbort.aborted) return abortedBeforeWork("selecting the transport");
|
|
12627
|
+
const useInteractive = selectedTransport === "interactive";
|
|
12628
|
+
if (useInteractive && !compactionMode && !doctor && (this.config.permissionPreset === "read-only" || this.config.permissionMode === "plan")) {
|
|
12629
|
+
throw new Error("Interactive Claude transport cannot enforce the read-only preset or plan mode. Use the headless transport for this permission posture.");
|
|
12630
|
+
}
|
|
12554
12631
|
const interactiveBypassRequested = this.config.interactiveBypass ?? flagOn(process.env.CLAUDE_CODE_INTERACTIVE_BYPASS);
|
|
12555
12632
|
const modelFallbackArmed = attempt?.armed === true && !compactionMode && !useInteractive;
|
|
12556
12633
|
const sourceAccount = normalizeAccountName(
|
|
@@ -12566,7 +12643,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12566
12643
|
let cliPath = failover.cliPath;
|
|
12567
12644
|
if (turnAbort.aborted) return abortedBeforeWork("resolving the spawn cwd and account");
|
|
12568
12645
|
const asideTransportRef = { cliPath, interactive: !!useInteractive };
|
|
12569
|
-
const doctor = !compactionMode && scope !== "no-tools" ? parseDoctorCommand(options.prompt) : null;
|
|
12570
12646
|
if (doctor) {
|
|
12571
12647
|
const doctorOptions = {
|
|
12572
12648
|
cliPath,
|
|
@@ -12686,7 +12762,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12686
12762
|
request: { body: { text: "" } }
|
|
12687
12763
|
};
|
|
12688
12764
|
}
|
|
12689
|
-
if (!hasNewUserContent(options.prompt)) {
|
|
12765
|
+
if (!compactionMode && !hasNewUserContent(options.prompt)) {
|
|
12690
12766
|
log.info("doStream short-circuit: no new user content");
|
|
12691
12767
|
const stream2 = new ReadableStream({
|
|
12692
12768
|
start(controller) {
|
|
@@ -12859,6 +12935,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12859
12935
|
const effectivePrompt = failoverAnswer?.kind === "switch" ? buildFailoverContinuationPrompt(options.prompt, failoverAnswer.target) : options.prompt;
|
|
12860
12936
|
const userMsg = exitPlanModeQuestionResult ?? getClaudeUserMessage(effectivePrompt, includeHistoryContext, {
|
|
12861
12937
|
compactionMode,
|
|
12938
|
+
compactionInstructions: useInteractive && compactionMode ? extractSystemMessages(effectivePrompt).join("\n\n") : void 0,
|
|
12862
12939
|
cliToolCallIds: new Set(previousPendingProxyCalls.map((c) => c.toolCallId)),
|
|
12863
12940
|
stripContextReminders: this.stripContextRemindersEnabled()
|
|
12864
12941
|
});
|
|
@@ -13222,7 +13299,21 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13222
13299
|
allDisallowed
|
|
13223
13300
|
};
|
|
13224
13301
|
};
|
|
13225
|
-
if (useInteractive &&
|
|
13302
|
+
if (useInteractive && compactionMode) {
|
|
13303
|
+
const ap = spawnInteractiveProcess({
|
|
13304
|
+
cwd,
|
|
13305
|
+
cliPath,
|
|
13306
|
+
configDir: self.config.configDir,
|
|
13307
|
+
model: spawnModelId,
|
|
13308
|
+
fastMode,
|
|
13309
|
+
tools: [],
|
|
13310
|
+
mcpConfigPaths: ['{"mcpServers":{}}'],
|
|
13311
|
+
env: claudeSpawnEnv({ ignoreAnthropicApiKey: self.config.ignoreAnthropicApiKey })
|
|
13312
|
+
});
|
|
13313
|
+
state.proc = ap.proc;
|
|
13314
|
+
state.lineEmitter = ap.lineEmitter;
|
|
13315
|
+
state.activeProcess = ap;
|
|
13316
|
+
} else if (useInteractive) {
|
|
13226
13317
|
if (state.activeProcess) {
|
|
13227
13318
|
state.proc = state.activeProcess.proc;
|
|
13228
13319
|
state.lineEmitter = state.activeProcess.lineEmitter;
|
|
@@ -13706,6 +13797,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13706
13797
|
state.asideSinkUnregister?.();
|
|
13707
13798
|
state.asideSinkUnregister = null;
|
|
13708
13799
|
state.proc.off("error", procErrorHandler);
|
|
13800
|
+
if (compactionMode && useInteractive) state.proc.kill();
|
|
13709
13801
|
};
|
|
13710
13802
|
const procErrorHandler = (err) => {
|
|
13711
13803
|
log.error("process error", { error: err.message });
|
|
@@ -14719,6 +14811,7 @@ function createClaudeCode(settings = {}) {
|
|
|
14719
14811
|
turnStats: settings.turnStats === true,
|
|
14720
14812
|
forkSessions: settings.forkSessions === true,
|
|
14721
14813
|
resumeAfterRestart: settings.resumeAfterRestart !== false,
|
|
14814
|
+
transport: settings.transport,
|
|
14722
14815
|
interactive: settings.interactive,
|
|
14723
14816
|
interactiveBypass: settings.interactiveBypass,
|
|
14724
14817
|
interactiveAllowTools: settings.interactiveAllowTools,
|