@khalilgharbaoui/opencode-claude-code-plugin 0.3.0 → 0.3.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 +1 -1
- package/dist/index.js +122 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,7 +311,7 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
|
|
|
311
311
|
|
|
312
312
|
- **Empty text blocks are dropped.** Claude sometimes opens a `content_block_start` for text but never sends a delta. The plugin no longer emits the empty block (which was triggering Anthropic 400s like `cache_control cannot be set for empty text blocks`).
|
|
313
313
|
- **`AskUserQuestion`** from the CLI is converted into plain text content rather than forwarded as a tool call.
|
|
314
|
-
- **
|
|
314
|
+
- **Wire-inactivity watchdog.** Once the CLI has produced any content, the stream closes gracefully if stdout goes silent for 60 seconds without a `result` message arriving. Resets on every line received, so long mid-turn pauses (Sonnet between text-end and the next tool_use, for example) are tolerated. On a user-initiated abort, the watchdog shortens to 5 seconds.
|
|
315
315
|
- **Per-iteration usage.** When the CLI internally retries with tools, the plugin only counts the last iteration's usage so opencode's context accounting stays accurate.
|
|
316
316
|
- **Lazy `cwd`.** The working directory is re-resolved at every request, so opencode's project-aware behavior works without restarting the plugin.
|
|
317
317
|
- **Variants survive merge.** opencode recalculates variant lists after the plugin loads; the plugin re-injects defaults into runtime config so your variants don't disappear.
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var log = {
|
|
|
19
19
|
console.error(fmt("NOTICE", msg, data));
|
|
20
20
|
},
|
|
21
21
|
warn(msg, data) {
|
|
22
|
-
|
|
22
|
+
console.error(fmt("WARN", msg, data));
|
|
23
23
|
},
|
|
24
24
|
error(msg, data) {
|
|
25
25
|
console.error(fmt("ERROR", msg, data));
|
|
@@ -381,22 +381,49 @@ Now continuing with the current message:
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
// src/mcp-bridge.ts
|
|
384
|
+
import * as fs2 from "fs";
|
|
385
|
+
import * as path2 from "path";
|
|
386
|
+
import * as os2 from "os";
|
|
387
|
+
import * as crypto from "crypto";
|
|
388
|
+
|
|
389
|
+
// src/tmp.ts
|
|
384
390
|
import * as fs from "fs";
|
|
385
|
-
import * as path from "path";
|
|
386
391
|
import * as os from "os";
|
|
387
|
-
import * as
|
|
392
|
+
import * as path from "path";
|
|
393
|
+
var PLUGIN_TMP_DIR = path.join(
|
|
394
|
+
os.tmpdir(),
|
|
395
|
+
`opencode-claude-code-${process.pid}`
|
|
396
|
+
);
|
|
397
|
+
var registered = false;
|
|
398
|
+
function pluginTmpDir() {
|
|
399
|
+
if (!fs.existsSync(PLUGIN_TMP_DIR)) {
|
|
400
|
+
fs.mkdirSync(PLUGIN_TMP_DIR, { recursive: true });
|
|
401
|
+
}
|
|
402
|
+
if (!registered) {
|
|
403
|
+
registered = true;
|
|
404
|
+
process.on("exit", () => {
|
|
405
|
+
try {
|
|
406
|
+
fs.rmSync(PLUGIN_TMP_DIR, { recursive: true, force: true });
|
|
407
|
+
} catch {
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return PLUGIN_TMP_DIR;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/mcp-bridge.ts
|
|
388
415
|
var FILE_NAMES = ["opencode.jsonc", "opencode.json", "config.json"];
|
|
389
416
|
var PROJECT_FILE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
390
417
|
function fileExists(p) {
|
|
391
418
|
try {
|
|
392
|
-
return
|
|
419
|
+
return fs2.statSync(p).isFile();
|
|
393
420
|
} catch {
|
|
394
421
|
return false;
|
|
395
422
|
}
|
|
396
423
|
}
|
|
397
424
|
function dirExists(p) {
|
|
398
425
|
try {
|
|
399
|
-
return
|
|
426
|
+
return fs2.statSync(p).isDirectory();
|
|
400
427
|
} catch {
|
|
401
428
|
return false;
|
|
402
429
|
}
|
|
@@ -442,7 +469,7 @@ function stripJsonComments(text) {
|
|
|
442
469
|
}
|
|
443
470
|
function readAndParse(file) {
|
|
444
471
|
try {
|
|
445
|
-
const raw =
|
|
472
|
+
const raw = fs2.readFileSync(file, "utf8");
|
|
446
473
|
return JSON.parse(stripJsonComments(raw));
|
|
447
474
|
} catch (e) {
|
|
448
475
|
log.warn("failed to parse opencode config", {
|
|
@@ -470,14 +497,14 @@ function deepMerge(target, source) {
|
|
|
470
497
|
}
|
|
471
498
|
function walkUp(opts) {
|
|
472
499
|
const out = [];
|
|
473
|
-
let current =
|
|
500
|
+
let current = path2.resolve(opts.start);
|
|
474
501
|
while (true) {
|
|
475
502
|
for (const target of opts.targets) {
|
|
476
|
-
const candidate =
|
|
503
|
+
const candidate = path2.join(current, target);
|
|
477
504
|
if (opts.predicate(candidate)) out.push(candidate);
|
|
478
505
|
}
|
|
479
|
-
if (opts.stop && current ===
|
|
480
|
-
const parent =
|
|
506
|
+
if (opts.stop && current === path2.resolve(opts.stop)) break;
|
|
507
|
+
const parent = path2.dirname(current);
|
|
481
508
|
if (parent === current) break;
|
|
482
509
|
current = parent;
|
|
483
510
|
}
|
|
@@ -485,28 +512,28 @@ function walkUp(opts) {
|
|
|
485
512
|
}
|
|
486
513
|
function detectWorktree(cwd) {
|
|
487
514
|
const override = process.env.OPENCODE_WORKTREE;
|
|
488
|
-
if (override) return
|
|
489
|
-
let current =
|
|
515
|
+
if (override) return path2.resolve(override);
|
|
516
|
+
let current = path2.resolve(cwd);
|
|
490
517
|
while (true) {
|
|
491
|
-
const gitPath =
|
|
518
|
+
const gitPath = path2.join(current, ".git");
|
|
492
519
|
try {
|
|
493
|
-
if (
|
|
520
|
+
if (fs2.existsSync(gitPath)) return current;
|
|
494
521
|
} catch {
|
|
495
522
|
}
|
|
496
|
-
const parent =
|
|
523
|
+
const parent = path2.dirname(current);
|
|
497
524
|
if (parent === current) return void 0;
|
|
498
525
|
current = parent;
|
|
499
526
|
}
|
|
500
527
|
}
|
|
501
528
|
function globalConfigDir() {
|
|
502
|
-
const xdg = process.env.XDG_CONFIG_HOME ??
|
|
503
|
-
return
|
|
529
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path2.join(os2.homedir(), ".config");
|
|
530
|
+
return path2.join(xdg, "opencode");
|
|
504
531
|
}
|
|
505
532
|
function loadGlobalConfig() {
|
|
506
533
|
const dir = globalConfigDir();
|
|
507
534
|
let merged = {};
|
|
508
535
|
for (const name of FILE_NAMES.slice().reverse()) {
|
|
509
|
-
const file =
|
|
536
|
+
const file = path2.join(dir, name);
|
|
510
537
|
if (!fileExists(file)) continue;
|
|
511
538
|
const parsed = readAndParse(file);
|
|
512
539
|
if (parsed) merged = deepMerge(merged, parsed);
|
|
@@ -516,7 +543,7 @@ function loadGlobalConfig() {
|
|
|
516
543
|
function loadProjectFilesInDir(dir) {
|
|
517
544
|
let merged = {};
|
|
518
545
|
for (const name of PROJECT_FILE_NAMES) {
|
|
519
|
-
const file =
|
|
546
|
+
const file = path2.join(dir, name);
|
|
520
547
|
if (!fileExists(file)) continue;
|
|
521
548
|
const parsed = readAndParse(file);
|
|
522
549
|
if (parsed) merged = deepMerge(merged, parsed);
|
|
@@ -527,7 +554,7 @@ function dotOpencodeDirs(cwd, worktree) {
|
|
|
527
554
|
const dirs = [];
|
|
528
555
|
const seen = /* @__PURE__ */ new Set();
|
|
529
556
|
const push = (p) => {
|
|
530
|
-
const abs =
|
|
557
|
+
const abs = path2.resolve(p);
|
|
531
558
|
if (!seen.has(abs) && dirExists(abs)) {
|
|
532
559
|
seen.add(abs);
|
|
533
560
|
dirs.push(abs);
|
|
@@ -541,9 +568,9 @@ function dotOpencodeDirs(cwd, worktree) {
|
|
|
541
568
|
})) {
|
|
542
569
|
push(dir);
|
|
543
570
|
}
|
|
544
|
-
const home =
|
|
571
|
+
const home = os2.homedir();
|
|
545
572
|
if (home) {
|
|
546
|
-
const homeDot =
|
|
573
|
+
const homeDot = path2.join(home, ".opencode");
|
|
547
574
|
if (dirExists(homeDot)) push(homeDot);
|
|
548
575
|
}
|
|
549
576
|
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
@@ -628,7 +655,7 @@ function bridgeOpencodeMcp(cwd, runtimeStatus) {
|
|
|
628
655
|
const projectDirs = [];
|
|
629
656
|
const seenProjectDirs = /* @__PURE__ */ new Set();
|
|
630
657
|
for (const f of projectFiles) {
|
|
631
|
-
const d =
|
|
658
|
+
const d = path2.dirname(f);
|
|
632
659
|
if (!seenProjectDirs.has(d)) {
|
|
633
660
|
seenProjectDirs.add(d);
|
|
634
661
|
projectDirs.push(d);
|
|
@@ -658,13 +685,13 @@ function bridgeOpencodeMcp(cwd, runtimeStatus) {
|
|
|
658
685
|
if (Object.keys(servers).length === 0) return null;
|
|
659
686
|
const body = JSON.stringify({ mcpServers: servers }, null, 2);
|
|
660
687
|
const hash = crypto.createHash("sha256").update(body).digest("hex").slice(0, 12);
|
|
661
|
-
const outPath =
|
|
662
|
-
|
|
663
|
-
`
|
|
688
|
+
const outPath = path2.join(
|
|
689
|
+
pluginTmpDir(),
|
|
690
|
+
`mcp-${hash}.json`
|
|
664
691
|
);
|
|
665
692
|
try {
|
|
666
693
|
if (!fileExists(outPath)) {
|
|
667
|
-
|
|
694
|
+
fs2.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
|
|
668
695
|
}
|
|
669
696
|
} catch (e) {
|
|
670
697
|
log.warn("failed to write bridged MCP config", {
|
|
@@ -872,14 +899,14 @@ function sessionKey(cwd, modelId) {
|
|
|
872
899
|
|
|
873
900
|
// src/proxy-mcp.ts
|
|
874
901
|
import { createServer } from "http";
|
|
875
|
-
import * as
|
|
876
|
-
import * as
|
|
877
|
-
import * as os2 from "os";
|
|
902
|
+
import * as fs3 from "fs";
|
|
903
|
+
import * as path3 from "path";
|
|
878
904
|
import * as crypto2 from "crypto";
|
|
879
905
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
880
906
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
881
907
|
var SERVER_NAME = "opencode_proxy";
|
|
882
908
|
var PROXY_TOOL_PREFIX = `mcp__${SERVER_NAME}__`;
|
|
909
|
+
var PROXY_CALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
883
910
|
var DEFAULT_PROXY_TOOLS = [
|
|
884
911
|
{
|
|
885
912
|
name: "bash",
|
|
@@ -1050,6 +1077,7 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1050
1077
|
toolName,
|
|
1051
1078
|
hasInput: input != null
|
|
1052
1079
|
});
|
|
1080
|
+
let timer = null;
|
|
1053
1081
|
const result = await new Promise(
|
|
1054
1082
|
(resolve3, reject) => {
|
|
1055
1083
|
const entry = {
|
|
@@ -1060,9 +1088,24 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1060
1088
|
reject
|
|
1061
1089
|
};
|
|
1062
1090
|
pending.set(callId, entry);
|
|
1091
|
+
timer = setTimeout(() => {
|
|
1092
|
+
if (!pending.has(callId)) return;
|
|
1093
|
+
pending.delete(callId);
|
|
1094
|
+
log.warn("proxy-mcp tool call timed out", {
|
|
1095
|
+
callId,
|
|
1096
|
+
toolName,
|
|
1097
|
+
timeoutMs: PROXY_CALL_TIMEOUT_MS
|
|
1098
|
+
});
|
|
1099
|
+
reject(
|
|
1100
|
+
new Error(
|
|
1101
|
+
`Proxy tool '${toolName}' timed out after ${PROXY_CALL_TIMEOUT_MS}ms waiting for opencode to resolve the call`
|
|
1102
|
+
)
|
|
1103
|
+
);
|
|
1104
|
+
}, PROXY_CALL_TIMEOUT_MS);
|
|
1063
1105
|
calls.emit("call", entry);
|
|
1064
1106
|
}
|
|
1065
1107
|
).finally(() => {
|
|
1108
|
+
if (timer) clearTimeout(timer);
|
|
1066
1109
|
pending.delete(callId);
|
|
1067
1110
|
});
|
|
1068
1111
|
if (result.kind === "error") {
|
|
@@ -1151,11 +1194,11 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1151
1194
|
2
|
|
1152
1195
|
);
|
|
1153
1196
|
const hash = crypto2.createHash("sha256").update(body).digest("hex").slice(0, 12);
|
|
1154
|
-
const outPath =
|
|
1155
|
-
|
|
1156
|
-
`
|
|
1197
|
+
const outPath = path3.join(
|
|
1198
|
+
pluginTmpDir(),
|
|
1199
|
+
`proxy-${hash}.json`
|
|
1157
1200
|
);
|
|
1158
|
-
|
|
1201
|
+
fs3.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
|
|
1159
1202
|
configFilePath = outPath;
|
|
1160
1203
|
return outPath;
|
|
1161
1204
|
},
|
|
@@ -1167,6 +1210,13 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1167
1210
|
await new Promise((resolve3) => {
|
|
1168
1211
|
server2.close(() => resolve3());
|
|
1169
1212
|
});
|
|
1213
|
+
if (configFilePath) {
|
|
1214
|
+
try {
|
|
1215
|
+
fs3.unlinkSync(configFilePath);
|
|
1216
|
+
} catch {
|
|
1217
|
+
}
|
|
1218
|
+
configFilePath = null;
|
|
1219
|
+
}
|
|
1170
1220
|
}
|
|
1171
1221
|
};
|
|
1172
1222
|
return api;
|
|
@@ -1266,9 +1316,9 @@ function resolvePendingProxyCall(sessionKey2, result) {
|
|
|
1266
1316
|
// src/claude-code-language-model.ts
|
|
1267
1317
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1268
1318
|
import { unlink as unlink2 } from "fs/promises";
|
|
1269
|
-
import { homedir as homedir2, tmpdir as
|
|
1319
|
+
import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
|
|
1270
1320
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1271
|
-
import { dirname as dirname2, join as
|
|
1321
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1272
1322
|
function hasNewUserContent(prompt) {
|
|
1273
1323
|
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
1274
1324
|
const msg = prompt[i];
|
|
@@ -1283,14 +1333,15 @@ function hasNewUserContent(prompt) {
|
|
|
1283
1333
|
for (const part of content) {
|
|
1284
1334
|
if (part.type === "text" && part.text && part.text.trim()) return true;
|
|
1285
1335
|
if (part.type === "tool-result") return true;
|
|
1336
|
+
if (part.type === "image" || part.type === "file") return true;
|
|
1286
1337
|
}
|
|
1287
1338
|
}
|
|
1288
1339
|
}
|
|
1289
1340
|
return false;
|
|
1290
1341
|
}
|
|
1291
|
-
function readPromptFileIfPresent(
|
|
1342
|
+
function readPromptFileIfPresent(path5) {
|
|
1292
1343
|
try {
|
|
1293
|
-
const content = readFileSync2(
|
|
1344
|
+
const content = readFileSync2(path5, "utf8").trim();
|
|
1294
1345
|
return content || void 0;
|
|
1295
1346
|
} catch {
|
|
1296
1347
|
return void 0;
|
|
@@ -1299,7 +1350,7 @@ function readPromptFileIfPresent(path4) {
|
|
|
1299
1350
|
function nearestWorkspaceAgentsPrompt(cwd) {
|
|
1300
1351
|
let dir = cwd;
|
|
1301
1352
|
while (true) {
|
|
1302
|
-
const content = readPromptFileIfPresent(
|
|
1353
|
+
const content = readPromptFileIfPresent(join4(dir, "AGENTS.md"));
|
|
1303
1354
|
if (content) return content;
|
|
1304
1355
|
const parent = dirname2(dir);
|
|
1305
1356
|
if (parent === dir) return void 0;
|
|
@@ -1308,17 +1359,17 @@ function nearestWorkspaceAgentsPrompt(cwd) {
|
|
|
1308
1359
|
}
|
|
1309
1360
|
function buildAppendedSystemPrompt(cwd) {
|
|
1310
1361
|
const parts = [];
|
|
1311
|
-
const configRoot = process.env.XDG_CONFIG_HOME ??
|
|
1312
|
-
const globalAgents = readPromptFileIfPresent(
|
|
1362
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join4(homedir2(), ".config");
|
|
1363
|
+
const globalAgents = readPromptFileIfPresent(join4(configRoot, "opencode", "AGENTS.md"));
|
|
1313
1364
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
1314
1365
|
if (globalAgents) parts.push(globalAgents);
|
|
1315
1366
|
if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
|
|
1316
1367
|
const content = parts.join("\n\n");
|
|
1317
1368
|
if (!content) return void 0;
|
|
1318
|
-
const
|
|
1369
|
+
const path5 = join4(tmpdir2(), `opencode-cc-sys-${randomUUID2()}.md`);
|
|
1319
1370
|
try {
|
|
1320
|
-
writeFileSync3(
|
|
1321
|
-
return
|
|
1371
|
+
writeFileSync3(path5, content, "utf8");
|
|
1372
|
+
return path5;
|
|
1322
1373
|
} catch (err) {
|
|
1323
1374
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
1324
1375
|
return void 0;
|
|
@@ -2889,7 +2940,7 @@ var defaultModels = {
|
|
|
2889
2940
|
|
|
2890
2941
|
// src/accounts.ts
|
|
2891
2942
|
import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
|
|
2892
|
-
import
|
|
2943
|
+
import path4 from "path";
|
|
2893
2944
|
var BASE_PROVIDER_ID = "claude-code";
|
|
2894
2945
|
var DEFAULT_ACCOUNT = "default";
|
|
2895
2946
|
var SHARED_CAPABILITY_ITEMS = [
|
|
@@ -2927,7 +2978,7 @@ function expandHome(value) {
|
|
|
2927
2978
|
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
2928
2979
|
if (value === "~") return home ?? value;
|
|
2929
2980
|
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
2930
|
-
return home ?
|
|
2981
|
+
return home ? path4.join(home, value.slice(2)) : value;
|
|
2931
2982
|
}
|
|
2932
2983
|
return value;
|
|
2933
2984
|
}
|
|
@@ -2959,8 +3010,8 @@ async function ensureSharedCapabilities(targetRoot) {
|
|
|
2959
3010
|
}
|
|
2960
3011
|
}
|
|
2961
3012
|
async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
2962
|
-
const source =
|
|
2963
|
-
const target =
|
|
3013
|
+
const source = path4.join(sourceRoot, item);
|
|
3014
|
+
const target = path4.join(targetRoot, item);
|
|
2964
3015
|
let sourceStat;
|
|
2965
3016
|
try {
|
|
2966
3017
|
sourceStat = await lstat(source);
|
|
@@ -2971,8 +3022,8 @@ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
|
2971
3022
|
const targetStat = await lstat(target);
|
|
2972
3023
|
if (targetStat.isSymbolicLink()) {
|
|
2973
3024
|
const current = await readlink(target);
|
|
2974
|
-
const resolvedCurrent =
|
|
2975
|
-
const resolvedSource =
|
|
3025
|
+
const resolvedCurrent = path4.resolve(path4.dirname(target), current);
|
|
3026
|
+
const resolvedSource = path4.resolve(source);
|
|
2976
3027
|
if (resolvedCurrent === resolvedSource) return;
|
|
2977
3028
|
}
|
|
2978
3029
|
log.warn("shared Claude capability already exists; leaving untouched", {
|
|
@@ -2987,11 +3038,11 @@ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
|
2987
3038
|
await symlink(source, target, type);
|
|
2988
3039
|
}
|
|
2989
3040
|
async function writeAccountWrapper(account, baseCliPath, configDir) {
|
|
2990
|
-
const cacheRoot =
|
|
3041
|
+
const cacheRoot = path4.join(
|
|
2991
3042
|
process.env.XDG_CACHE_HOME ?? expandHome("~/.cache"),
|
|
2992
3043
|
"opencode-claude-code-plugin"
|
|
2993
3044
|
);
|
|
2994
|
-
const wrapperPath =
|
|
3045
|
+
const wrapperPath = path4.join(cacheRoot, `claude-${account}`);
|
|
2995
3046
|
const suffix = `@${account}`;
|
|
2996
3047
|
await mkdir(cacheRoot, { recursive: true });
|
|
2997
3048
|
const script = `#!/usr/bin/env bash
|
|
@@ -3031,14 +3082,14 @@ function titleizeAccount(account) {
|
|
|
3031
3082
|
|
|
3032
3083
|
// src/cleanup-stale.ts
|
|
3033
3084
|
import {
|
|
3034
|
-
existsSync as
|
|
3085
|
+
existsSync as existsSync3,
|
|
3035
3086
|
readFileSync as readFileSync3,
|
|
3036
3087
|
realpathSync,
|
|
3037
|
-
rmSync,
|
|
3088
|
+
rmSync as rmSync2,
|
|
3038
3089
|
writeFileSync as writeFileSync4
|
|
3039
3090
|
} from "fs";
|
|
3040
3091
|
import { homedir as homedir3 } from "os";
|
|
3041
|
-
import { join as
|
|
3092
|
+
import { join as join5, resolve as resolve2 } from "path";
|
|
3042
3093
|
import { fileURLToPath } from "url";
|
|
3043
3094
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
3044
3095
|
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
@@ -3046,18 +3097,18 @@ var alreadyRan = false;
|
|
|
3046
3097
|
function candidateCacheRoots() {
|
|
3047
3098
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
3048
3099
|
return [
|
|
3049
|
-
xdg ?
|
|
3050
|
-
|
|
3051
|
-
|
|
3100
|
+
xdg ? join5(xdg, "opencode") : null,
|
|
3101
|
+
join5(homedir3(), ".cache", "opencode"),
|
|
3102
|
+
join5(homedir3(), "Library", "Caches", "opencode")
|
|
3052
3103
|
].filter((p) => Boolean(p));
|
|
3053
3104
|
}
|
|
3054
3105
|
function userOpencodeJsonPath() {
|
|
3055
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
3056
|
-
return
|
|
3106
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join5(homedir3(), ".config");
|
|
3107
|
+
return join5(xdgConfig, "opencode", "opencode.json");
|
|
3057
3108
|
}
|
|
3058
3109
|
function userIntendsToUseUnscoped() {
|
|
3059
3110
|
const cfg = userOpencodeJsonPath();
|
|
3060
|
-
if (!
|
|
3111
|
+
if (!existsSync3(cfg)) return false;
|
|
3061
3112
|
try {
|
|
3062
3113
|
const json = JSON.parse(readFileSync3(cfg, "utf8"));
|
|
3063
3114
|
const plugins = json.plugin;
|
|
@@ -3095,17 +3146,17 @@ function cleanupStaleUnscopedInstall() {
|
|
|
3095
3146
|
}
|
|
3096
3147
|
}
|
|
3097
3148
|
function cleanupOne(cacheRoot, ourDir) {
|
|
3098
|
-
if (!
|
|
3099
|
-
const stalePath =
|
|
3100
|
-
if (!
|
|
3149
|
+
if (!existsSync3(cacheRoot)) return;
|
|
3150
|
+
const stalePath = join5(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
|
|
3151
|
+
if (!existsSync3(stalePath)) return;
|
|
3101
3152
|
let realStalePath = stalePath;
|
|
3102
3153
|
try {
|
|
3103
3154
|
realStalePath = realpathSync(stalePath);
|
|
3104
3155
|
} catch {
|
|
3105
3156
|
}
|
|
3106
3157
|
if (ourDir && realStalePath === ourDir) return;
|
|
3107
|
-
const pkgJsonPath =
|
|
3108
|
-
if (!
|
|
3158
|
+
const pkgJsonPath = join5(stalePath, "package.json");
|
|
3159
|
+
if (!existsSync3(pkgJsonPath)) return;
|
|
3109
3160
|
let pkg = {};
|
|
3110
3161
|
try {
|
|
3111
3162
|
pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
|
|
@@ -3116,7 +3167,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
3116
3167
|
if (!pkg.description?.includes(SUSPECT_DESCRIPTION_TOKEN)) return;
|
|
3117
3168
|
log.info("cleanup-stale: removing unscoped install", { stalePath });
|
|
3118
3169
|
try {
|
|
3119
|
-
|
|
3170
|
+
rmSync2(stalePath, { recursive: true, force: true });
|
|
3120
3171
|
} catch (err) {
|
|
3121
3172
|
log.warn("cleanup-stale: rmSync failed", {
|
|
3122
3173
|
stalePath,
|
|
@@ -3124,8 +3175,8 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
3124
3175
|
});
|
|
3125
3176
|
return;
|
|
3126
3177
|
}
|
|
3127
|
-
const cachePkgJson =
|
|
3128
|
-
if (!
|
|
3178
|
+
const cachePkgJson = join5(cacheRoot, "package.json");
|
|
3179
|
+
if (!existsSync3(cachePkgJson)) return;
|
|
3129
3180
|
try {
|
|
3130
3181
|
const cfg = JSON.parse(readFileSync3(cachePkgJson, "utf8"));
|
|
3131
3182
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
@@ -3342,12 +3393,12 @@ var server = async (input) => {
|
|
|
3342
3393
|
config.provider ??= {};
|
|
3343
3394
|
const expanded = await expandAccountProviders(config);
|
|
3344
3395
|
if (expanded) {
|
|
3345
|
-
const
|
|
3396
|
+
const registered2 = Object.entries(config.provider).filter(([id]) => id === PROVIDER_ID2 || id.startsWith(`${PROVIDER_ID2}-`)).map(([id, p]) => ({
|
|
3346
3397
|
id,
|
|
3347
3398
|
name: p?.name ?? id,
|
|
3348
3399
|
cwd: p?.options?.cwd
|
|
3349
3400
|
}));
|
|
3350
|
-
log.notice("registered claude-code providers", { providers:
|
|
3401
|
+
log.notice("registered claude-code providers", { providers: registered2 });
|
|
3351
3402
|
return;
|
|
3352
3403
|
}
|
|
3353
3404
|
const existing = config.provider[PROVIDER_ID2];
|