@khalilgharbaoui/opencode-claude-code-plugin 0.10.0 → 0.11.1
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 +41 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +180 -29
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -531,6 +531,47 @@ Boolean env vars accept `1/true/on/yes` for on and `0/false/no/off` for
|
|
|
531
531
|
off; empty / unset falls through to config. Invalid `level` values fall
|
|
532
532
|
through to config.
|
|
533
533
|
|
|
534
|
+
### Startup diagnostics
|
|
535
|
+
|
|
536
|
+
Once per process, right after the provider(s) register, the plugin logs a
|
|
537
|
+
single `NOTICE: claude-code plugin ready` line summarizing everything worth
|
|
538
|
+
knowing before you start debugging anything else:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
|
|
542
|
+
grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"plugin": "0.11.1",
|
|
548
|
+
"opencode": "1.18.5",
|
|
549
|
+
"cwd": { "resolved": "/Users/you/code/app", "source": "process" },
|
|
550
|
+
"providers": ["claude-code-default", "claude-code-work"],
|
|
551
|
+
"accounts": ["default", "work"],
|
|
552
|
+
"proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task"],
|
|
553
|
+
"mcpServers": ["github", "slack"],
|
|
554
|
+
"interactiveTransport": false,
|
|
555
|
+
"anthropicApiKeyInEnv": false,
|
|
556
|
+
"claudeCli": { "path": "claude", "version": "2.1.211 (Claude Code)" }
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Reading it:
|
|
561
|
+
|
|
562
|
+
- **`cwd.source`** is which rule picked the working directory Claude will be
|
|
563
|
+
spawned in — `configured` (you pinned `options.cwd`), `process` (normal),
|
|
564
|
+
`captured` (`process.cwd()` was unusable and opencode's project directory
|
|
565
|
+
rescued it, the macOS GUI-launch case), or `unresolved` (neither worked).
|
|
566
|
+
- **`claudeCli.version`** reading `not detected` means the `claude` binary at
|
|
567
|
+
that path didn't answer `--version`, which also disables version-gated
|
|
568
|
+
flags like `--thinking-display`.
|
|
569
|
+
- **`mcpServers`** is the on-disk merge, before opencode's runtime toggles
|
|
570
|
+
are applied (those aren't settled yet at startup).
|
|
571
|
+
- **`opencode`** is read from the running opencode binary (`--version`), since
|
|
572
|
+
opencode still does not hand its version to plugins. It reads `unknown` when
|
|
573
|
+
opencode is run from source rather than as the packaged binary.
|
|
574
|
+
|
|
534
575
|
### Default behavior (no config, no env)
|
|
535
576
|
|
|
536
577
|
Nothing persists; only WARN and ERROR bubble in the TUI. The plugin
|
package/dist/index.d.ts
CHANGED
|
@@ -467,6 +467,11 @@ interface ClaudeStreamMessage {
|
|
|
467
467
|
index?: number;
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
+
interface DiagnosticsProviderEntry {
|
|
471
|
+
name?: string;
|
|
472
|
+
options?: Record<string, unknown>;
|
|
473
|
+
}
|
|
474
|
+
|
|
470
475
|
declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
471
476
|
readonly specificationVersion = "v3";
|
|
472
477
|
readonly modelId: string;
|
|
@@ -598,9 +603,15 @@ declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): Claude
|
|
|
598
603
|
* so the config-path provider loader parses them correctly.
|
|
599
604
|
*/
|
|
600
605
|
declare function configModelsForProvider(providerModels: OpenCodeProvider["models"], providerID: string, modelSuffix?: string): Record<string, Record<string, unknown>>;
|
|
606
|
+
/**
|
|
607
|
+
* Narrow opencode's full provider map down to the ones this plugin owns
|
|
608
|
+
* (`claude-code` plus every `claude-code-<account>` expansion) so startup
|
|
609
|
+
* diagnostics never report another provider's options.
|
|
610
|
+
*/
|
|
611
|
+
declare function claudeCodeProviders(providers: Record<string, DiagnosticsProviderEntry> | undefined): Record<string, DiagnosticsProviderEntry>;
|
|
601
612
|
declare const _default: {
|
|
602
613
|
id: string;
|
|
603
614
|
server: OpenCodePlugin;
|
|
604
615
|
};
|
|
605
616
|
|
|
606
|
-
export { type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, configModelsForProvider, createClaudeCode, _default as default, defaultModels };
|
|
617
|
+
export { type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels };
|
package/dist/index.js
CHANGED
|
@@ -974,6 +974,31 @@ function mergeMcp(target, source) {
|
|
|
974
974
|
return out;
|
|
975
975
|
}
|
|
976
976
|
function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers) {
|
|
977
|
+
const {
|
|
978
|
+
servers: merged,
|
|
979
|
+
enabledServerNames: allEnabledServerNames,
|
|
980
|
+
hash
|
|
981
|
+
} = mergeOpencodeMcp(cwd, runtimeStatus);
|
|
982
|
+
const servers = {};
|
|
983
|
+
const bridgedServerNames = [];
|
|
984
|
+
for (const [name, spec] of Object.entries(merged)) {
|
|
985
|
+
if (!spec || typeof spec !== "object") continue;
|
|
986
|
+
if (excludeServers?.has(name)) continue;
|
|
987
|
+
const translated = translateServer(name, spec);
|
|
988
|
+
if (translated) {
|
|
989
|
+
servers[name] = translated;
|
|
990
|
+
bridgedServerNames.push(name);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return finishBridge({
|
|
994
|
+
servers,
|
|
995
|
+
bridgedServerNames,
|
|
996
|
+
allEnabledServerNames,
|
|
997
|
+
hash,
|
|
998
|
+
excludeServers
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
function mergeOpencodeMcp(cwd, runtimeStatus) {
|
|
977
1002
|
const worktree = detectWorktree(cwd);
|
|
978
1003
|
let merged = {};
|
|
979
1004
|
merged = mergeMcp(merged, extractMcpBlock(loadGlobalConfig()));
|
|
@@ -1012,26 +1037,19 @@ function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers) {
|
|
|
1012
1037
|
merged[name] = { ...base, enabled: status === "connected" };
|
|
1013
1038
|
}
|
|
1014
1039
|
}
|
|
1015
|
-
const
|
|
1040
|
+
const enabledServerNames = [];
|
|
1016
1041
|
for (const [name, spec] of Object.entries(merged)) {
|
|
1017
1042
|
if (!spec || typeof spec !== "object") continue;
|
|
1018
1043
|
const enabled = spec.enabled;
|
|
1019
1044
|
if (enabled === false) continue;
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
const servers = {};
|
|
1023
|
-
const bridgedServerNames = [];
|
|
1024
|
-
for (const [name, spec] of Object.entries(merged)) {
|
|
1025
|
-
if (!spec || typeof spec !== "object") continue;
|
|
1026
|
-
if (excludeServers?.has(name)) continue;
|
|
1027
|
-
const translated = translateServer(name, spec);
|
|
1028
|
-
if (translated) {
|
|
1029
|
-
servers[name] = translated;
|
|
1030
|
-
bridgedServerNames.push(name);
|
|
1031
|
-
}
|
|
1045
|
+
enabledServerNames.push(name);
|
|
1032
1046
|
}
|
|
1033
1047
|
const mergedBody = JSON.stringify({ mcpServers: merged }, null, 2);
|
|
1034
1048
|
const hash = crypto.createHash("sha256").update(mergedBody).digest("hex").slice(0, 12);
|
|
1049
|
+
return { servers: merged, enabledServerNames, hash };
|
|
1050
|
+
}
|
|
1051
|
+
function finishBridge(input) {
|
|
1052
|
+
const { servers, bridgedServerNames, allEnabledServerNames, hash, excludeServers } = input;
|
|
1035
1053
|
if (Object.keys(servers).length === 0) {
|
|
1036
1054
|
const allEnabledServersExcluded = excludeServers && allEnabledServerNames.length > 0 && allEnabledServerNames.every((name) => excludeServers.has(name));
|
|
1037
1055
|
if (!allEnabledServersExcluded) return null;
|
|
@@ -1082,6 +1100,9 @@ var opencodeProjectDirectory;
|
|
|
1082
1100
|
function setOpencodeProjectDirectory(dir) {
|
|
1083
1101
|
opencodeProjectDirectory = dir;
|
|
1084
1102
|
}
|
|
1103
|
+
function getOpencodeProjectDirectory() {
|
|
1104
|
+
return opencodeProjectDirectory;
|
|
1105
|
+
}
|
|
1085
1106
|
function isUsableDirectory(d) {
|
|
1086
1107
|
return typeof d === "string" && d.length > 1 && d !== "/";
|
|
1087
1108
|
}
|
|
@@ -2832,9 +2853,9 @@ function makeAutoContinueMessage() {
|
|
|
2832
2853
|
}
|
|
2833
2854
|
});
|
|
2834
2855
|
}
|
|
2835
|
-
function readPromptFileIfPresent(
|
|
2856
|
+
function readPromptFileIfPresent(path7) {
|
|
2836
2857
|
try {
|
|
2837
|
-
const content = readFileSync3(
|
|
2858
|
+
const content = readFileSync3(path7, "utf8").trim();
|
|
2838
2859
|
return content || void 0;
|
|
2839
2860
|
} catch {
|
|
2840
2861
|
return void 0;
|
|
@@ -2903,10 +2924,10 @@ function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystem
|
|
|
2903
2924
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
2904
2925
|
const content = parts.join("\n\n");
|
|
2905
2926
|
if (!content) return void 0;
|
|
2906
|
-
const
|
|
2927
|
+
const path7 = join6(tmpdir2(), `opencode-cc-sys-${randomUUID3()}.md`);
|
|
2907
2928
|
try {
|
|
2908
|
-
writeFileSync3(
|
|
2909
|
-
return
|
|
2929
|
+
writeFileSync3(path7, content, "utf8");
|
|
2930
|
+
return path7;
|
|
2910
2931
|
} catch (err) {
|
|
2911
2932
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
2912
2933
|
return void 0;
|
|
@@ -5515,6 +5536,130 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
5515
5536
|
}
|
|
5516
5537
|
}
|
|
5517
5538
|
|
|
5539
|
+
// src/startup-diagnostics.ts
|
|
5540
|
+
import { execFile as execFile2 } from "child_process";
|
|
5541
|
+
import * as fs5 from "fs";
|
|
5542
|
+
import * as path6 from "path";
|
|
5543
|
+
import { promisify as promisify2 } from "util";
|
|
5544
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5545
|
+
var cachedPluginVersion;
|
|
5546
|
+
function pluginVersion() {
|
|
5547
|
+
if (cachedPluginVersion) return cachedPluginVersion;
|
|
5548
|
+
try {
|
|
5549
|
+
const here = path6.dirname(fileURLToPath2(import.meta.url));
|
|
5550
|
+
const raw = fs5.readFileSync(path6.join(here, "..", "package.json"), "utf8");
|
|
5551
|
+
const version = JSON.parse(raw).version;
|
|
5552
|
+
cachedPluginVersion = typeof version === "string" ? version : "unknown";
|
|
5553
|
+
} catch {
|
|
5554
|
+
cachedPluginVersion = "unknown";
|
|
5555
|
+
}
|
|
5556
|
+
return cachedPluginVersion;
|
|
5557
|
+
}
|
|
5558
|
+
function pickOpencodeVersion(input) {
|
|
5559
|
+
if (!input || typeof input !== "object") return void 0;
|
|
5560
|
+
const app = input.app;
|
|
5561
|
+
if (app && typeof app === "object") {
|
|
5562
|
+
const version = app.version;
|
|
5563
|
+
if (typeof version === "string" && version.length > 0) return version;
|
|
5564
|
+
}
|
|
5565
|
+
const direct = input.version;
|
|
5566
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
5567
|
+
return void 0;
|
|
5568
|
+
}
|
|
5569
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
5570
|
+
var opencodeVersionProbe;
|
|
5571
|
+
function detectOpencodeVersion(execPath = process.execPath) {
|
|
5572
|
+
if (opencodeVersionProbe) return opencodeVersionProbe;
|
|
5573
|
+
opencodeVersionProbe = (async () => {
|
|
5574
|
+
if (!path6.basename(execPath).toLowerCase().includes("opencode")) {
|
|
5575
|
+
log.debug("skipping opencode version probe: execPath is not opencode", { execPath });
|
|
5576
|
+
return void 0;
|
|
5577
|
+
}
|
|
5578
|
+
try {
|
|
5579
|
+
const { stdout } = await execFileAsync2(execPath, ["--version"], { timeout: 5e3 });
|
|
5580
|
+
const match = /\d+\.\d+\.\d+\S*/.exec(stdout.trim());
|
|
5581
|
+
return match ? match[0] : void 0;
|
|
5582
|
+
} catch (err) {
|
|
5583
|
+
log.debug("opencode version probe failed", {
|
|
5584
|
+
execPath,
|
|
5585
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5586
|
+
});
|
|
5587
|
+
return void 0;
|
|
5588
|
+
}
|
|
5589
|
+
})();
|
|
5590
|
+
return opencodeVersionProbe;
|
|
5591
|
+
}
|
|
5592
|
+
function describeSpawnCwd(configured, live = process.cwd(), captured = getOpencodeProjectDirectory()) {
|
|
5593
|
+
if (typeof configured === "string" && configured.length > 0) {
|
|
5594
|
+
return { resolved: configured, source: "configured" };
|
|
5595
|
+
}
|
|
5596
|
+
if (isUsableDirectory(live)) return { resolved: live, source: "process" };
|
|
5597
|
+
if (isUsableDirectory(captured)) return { resolved: captured, source: "captured" };
|
|
5598
|
+
return { resolved: live, source: "unresolved" };
|
|
5599
|
+
}
|
|
5600
|
+
function stringList(value) {
|
|
5601
|
+
if (!Array.isArray(value)) return [];
|
|
5602
|
+
return value.filter((entry) => typeof entry === "string");
|
|
5603
|
+
}
|
|
5604
|
+
function firstOption(providers, key) {
|
|
5605
|
+
for (const entry of Object.values(providers)) {
|
|
5606
|
+
const value = entry?.options?.[key];
|
|
5607
|
+
if (value !== void 0) return value;
|
|
5608
|
+
}
|
|
5609
|
+
return void 0;
|
|
5610
|
+
}
|
|
5611
|
+
function collectStartupDiagnostics(providers, opencodeVersion) {
|
|
5612
|
+
const accounts = [];
|
|
5613
|
+
for (const entry of Object.values(providers)) {
|
|
5614
|
+
const account = entry?.options?.account;
|
|
5615
|
+
if (typeof account === "string" && account.length > 0) accounts.push(account);
|
|
5616
|
+
}
|
|
5617
|
+
const cwd = describeSpawnCwd(firstOption(providers, "cwd"));
|
|
5618
|
+
let mcpServers = [];
|
|
5619
|
+
try {
|
|
5620
|
+
mcpServers = mergeOpencodeMcp(cwd.resolved).enabledServerNames;
|
|
5621
|
+
} catch (err) {
|
|
5622
|
+
log.debug("startup diagnostics could not read MCP config", {
|
|
5623
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5624
|
+
});
|
|
5625
|
+
}
|
|
5626
|
+
return {
|
|
5627
|
+
plugin: pluginVersion(),
|
|
5628
|
+
opencode: opencodeVersion ?? process.env.OPENCODE_VERSION ?? "unknown",
|
|
5629
|
+
claudeCliPath: String(firstOption(providers, "cliPath") ?? "claude"),
|
|
5630
|
+
cwd,
|
|
5631
|
+
providers: Object.keys(providers),
|
|
5632
|
+
accounts,
|
|
5633
|
+
proxyTools: stringList(firstOption(providers, "proxyTools")),
|
|
5634
|
+
mcpServers,
|
|
5635
|
+
interactiveTransport: firstOption(providers, "interactive") === true || process.env.CLAUDE_CODE_INTERACTIVE_TRANSPORT === "1",
|
|
5636
|
+
anthropicApiKeyInEnv: Boolean(
|
|
5637
|
+
process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN
|
|
5638
|
+
)
|
|
5639
|
+
};
|
|
5640
|
+
}
|
|
5641
|
+
var logged = false;
|
|
5642
|
+
function logStartupDiagnostics(providers, opencodeVersion) {
|
|
5643
|
+
if (logged) return;
|
|
5644
|
+
logged = true;
|
|
5645
|
+
void (async () => {
|
|
5646
|
+
try {
|
|
5647
|
+
const version = opencodeVersion ?? process.env.OPENCODE_VERSION ?? await detectOpencodeVersion();
|
|
5648
|
+
const { claudeCliPath, ...rest } = collectStartupDiagnostics(providers, version);
|
|
5649
|
+
const cli = await detectCliVersion(claudeCliPath);
|
|
5650
|
+
const diagnostics = {
|
|
5651
|
+
...rest,
|
|
5652
|
+
claudeCli: { path: claudeCliPath, version: cli?.raw ?? "not detected" }
|
|
5653
|
+
};
|
|
5654
|
+
log.notice("claude-code plugin ready", { ...diagnostics });
|
|
5655
|
+
} catch (err) {
|
|
5656
|
+
log.debug("startup diagnostics failed", {
|
|
5657
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5658
|
+
});
|
|
5659
|
+
}
|
|
5660
|
+
})();
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5518
5663
|
// src/index.ts
|
|
5519
5664
|
function pickOpencodeDirectory(input) {
|
|
5520
5665
|
if (!input || typeof input !== "object") return void 0;
|
|
@@ -5690,6 +5835,13 @@ async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefault
|
|
|
5690
5835
|
// opencode's config-path loader parses (and merges user variants).
|
|
5691
5836
|
};
|
|
5692
5837
|
}
|
|
5838
|
+
function claudeCodeProviders(providers) {
|
|
5839
|
+
const out = {};
|
|
5840
|
+
for (const [id, entry] of Object.entries(providers ?? {})) {
|
|
5841
|
+
if (id === PROVIDER_ID2 || id.startsWith(`${PROVIDER_ID2}-`)) out[id] = entry;
|
|
5842
|
+
}
|
|
5843
|
+
return out;
|
|
5844
|
+
}
|
|
5693
5845
|
async function expandAccountProviders(config) {
|
|
5694
5846
|
const seed = config.provider?.[PROVIDER_ID2];
|
|
5695
5847
|
const accounts = resolveAccounts(seed?.options?.accounts);
|
|
@@ -5735,6 +5887,7 @@ async function expandAccountProviders(config) {
|
|
|
5735
5887
|
}
|
|
5736
5888
|
var server = async (input) => {
|
|
5737
5889
|
cleanupStaleUnscopedInstall();
|
|
5890
|
+
const opencodeVersion = pickOpencodeVersion(input);
|
|
5738
5891
|
if (input && typeof input === "object" && "client" in input) {
|
|
5739
5892
|
setOpencodeClient(input.client);
|
|
5740
5893
|
}
|
|
@@ -5744,12 +5897,10 @@ var server = async (input) => {
|
|
|
5744
5897
|
config.provider ??= {};
|
|
5745
5898
|
const expanded = await expandAccountProviders(config);
|
|
5746
5899
|
if (expanded) {
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
}));
|
|
5752
|
-
log.notice("registered claude-code providers", { providers: registered2 });
|
|
5900
|
+
logStartupDiagnostics(
|
|
5901
|
+
claudeCodeProviders(config.provider),
|
|
5902
|
+
opencodeVersion
|
|
5903
|
+
);
|
|
5753
5904
|
return;
|
|
5754
5905
|
}
|
|
5755
5906
|
const existing = config.provider[PROVIDER_ID2];
|
|
@@ -5761,11 +5912,10 @@ var server = async (input) => {
|
|
|
5761
5912
|
PROVIDER_ID2
|
|
5762
5913
|
)
|
|
5763
5914
|
};
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
});
|
|
5915
|
+
logStartupDiagnostics(
|
|
5916
|
+
claudeCodeProviders(config.provider),
|
|
5917
|
+
opencodeVersion
|
|
5918
|
+
);
|
|
5769
5919
|
},
|
|
5770
5920
|
// No `event` hook: MCP config drift is detected at turn start by the
|
|
5771
5921
|
// hot-reload check in `claude-code-language-model.ts`, which respawns
|
|
@@ -5810,6 +5960,7 @@ var index_default = {
|
|
|
5810
5960
|
export {
|
|
5811
5961
|
ClaudeCodeLanguageModel,
|
|
5812
5962
|
bridgeOpencodeMcp,
|
|
5963
|
+
claudeCodeProviders,
|
|
5813
5964
|
configModelsForProvider,
|
|
5814
5965
|
createClaudeCode,
|
|
5815
5966
|
index_default as default,
|