@khalilgharbaoui/opencode-claude-code-plugin 0.36.5 → 0.37.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/dist/index.js +458 -111
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +33 -3
package/dist/index.js
CHANGED
|
@@ -149,6 +149,7 @@ import path from "path";
|
|
|
149
149
|
import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
|
|
150
150
|
import { homedir } from "os";
|
|
151
151
|
import { dirname, join } from "path";
|
|
152
|
+
import { isMainThread } from "worker_threads";
|
|
152
153
|
var LEVEL_RANK = {
|
|
153
154
|
debug: 0,
|
|
154
155
|
info: 1,
|
|
@@ -241,15 +242,110 @@ function fmt(level, msg, data) {
|
|
|
241
242
|
function shouldEmit(level) {
|
|
242
243
|
return LEVEL_RANK[level] >= LEVEL_RANK[activeConfig.level];
|
|
243
244
|
}
|
|
244
|
-
|
|
245
|
+
var tuiHostOverride;
|
|
246
|
+
function isTuiHost() {
|
|
247
|
+
return tuiHostOverride ?? !isMainThread;
|
|
248
|
+
}
|
|
249
|
+
var MAX_BUFFERED_SINK_ENTRIES = 50;
|
|
250
|
+
var MAX_TOASTED_MESSAGES = 200;
|
|
251
|
+
var toastBurstLimit = 3;
|
|
252
|
+
var toastBurstWindowMs = 2e3;
|
|
253
|
+
var activeSink = null;
|
|
254
|
+
var bufferedEntries = [];
|
|
255
|
+
var droppedBeforeSink = 0;
|
|
256
|
+
var toastedMessages = /* @__PURE__ */ new Set();
|
|
257
|
+
var burstWindowStart = 0;
|
|
258
|
+
var burstCount = 0;
|
|
259
|
+
var burstSuppressed = 0;
|
|
260
|
+
var burstSummaryTimer = null;
|
|
261
|
+
function setLogSink(sink) {
|
|
262
|
+
activeSink = sink;
|
|
263
|
+
if (!sink) return;
|
|
264
|
+
const pending = bufferedEntries;
|
|
265
|
+
const dropped = droppedBeforeSink;
|
|
266
|
+
bufferedEntries = [];
|
|
267
|
+
droppedBeforeSink = 0;
|
|
268
|
+
for (const entry of pending) deliverToSink(entry);
|
|
269
|
+
if (dropped > 0) {
|
|
270
|
+
const message = `${dropped} earlier log line(s) were dropped before opencode's log channel was available`;
|
|
271
|
+
deliverToSink({
|
|
272
|
+
level: "warn",
|
|
273
|
+
message,
|
|
274
|
+
line: fmt("WARN", message)
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function routeToSink(entry) {
|
|
279
|
+
if (!activeSink) {
|
|
280
|
+
if (bufferedEntries.length >= MAX_BUFFERED_SINK_ENTRIES) {
|
|
281
|
+
droppedBeforeSink += 1;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
bufferedEntries.push(entry);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
deliverToSink(entry);
|
|
288
|
+
}
|
|
289
|
+
function deliverToSink(entry) {
|
|
290
|
+
const sink = activeSink;
|
|
291
|
+
if (!sink) return;
|
|
292
|
+
try {
|
|
293
|
+
sink.log(entry);
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
if (entry.level !== "warn" && entry.level !== "error") return;
|
|
297
|
+
if (toastedMessages.has(entry.message)) return;
|
|
298
|
+
if (toastedMessages.size < MAX_TOASTED_MESSAGES) toastedMessages.add(entry.message);
|
|
299
|
+
if (!allowToastNow()) return;
|
|
300
|
+
sendToast(sink, entry);
|
|
301
|
+
}
|
|
302
|
+
function sendToast(sink, entry) {
|
|
303
|
+
try {
|
|
304
|
+
sink.toast(entry);
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function allowToastNow() {
|
|
309
|
+
const now = Date.now();
|
|
310
|
+
if (now - burstWindowStart >= toastBurstWindowMs) {
|
|
311
|
+
burstWindowStart = now;
|
|
312
|
+
burstCount = 0;
|
|
313
|
+
}
|
|
314
|
+
if (burstCount < toastBurstLimit) {
|
|
315
|
+
burstCount += 1;
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
burstSuppressed += 1;
|
|
319
|
+
armBurstSummary();
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
function armBurstSummary() {
|
|
323
|
+
if (burstSummaryTimer) return;
|
|
324
|
+
burstSummaryTimer = setTimeout(() => {
|
|
325
|
+
burstSummaryTimer = null;
|
|
326
|
+
const suppressed = burstSuppressed;
|
|
327
|
+
burstSuppressed = 0;
|
|
328
|
+
if (suppressed <= 0) return;
|
|
329
|
+
const sink = activeSink;
|
|
330
|
+
if (!sink) return;
|
|
331
|
+
const message = `${suppressed} more claude-code warning(s) were not shown; the full text is in opencode's log`;
|
|
332
|
+
sendToast(sink, { level: "warn", message, line: fmt("WARN", message) });
|
|
333
|
+
}, toastBurstWindowMs);
|
|
334
|
+
burstSummaryTimer.unref?.();
|
|
335
|
+
}
|
|
336
|
+
function shouldSurface(level) {
|
|
245
337
|
if (level === "warn" || level === "error") return true;
|
|
246
338
|
return activeConfig.mode === "debug";
|
|
247
339
|
}
|
|
248
340
|
function emit(level, msg, data) {
|
|
249
341
|
if (!shouldEmit(level)) return;
|
|
250
342
|
const line = fmt(level.toUpperCase(), msg, data);
|
|
251
|
-
if (
|
|
252
|
-
|
|
343
|
+
if (shouldSurface(level)) {
|
|
344
|
+
if (isTuiHost()) {
|
|
345
|
+
routeToSink({ level, message: msg, data, line });
|
|
346
|
+
} else {
|
|
347
|
+
console.error(line);
|
|
348
|
+
}
|
|
253
349
|
}
|
|
254
350
|
writeToFile(line);
|
|
255
351
|
}
|
|
@@ -5471,6 +5567,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
5471
5567
|
"skipping remote MCP server with no url",
|
|
5472
5568
|
"stale scratch directory sweep failed",
|
|
5473
5569
|
"task_batch resolving with child results missing",
|
|
5570
|
+
"this opencode process is running an older plugin build than the one on disk",
|
|
5474
5571
|
"timed out pending proxy call",
|
|
5475
5572
|
"turn abort listener threw",
|
|
5476
5573
|
"two different skills share a name; bridging the one the nearest root claimed",
|
|
@@ -5619,6 +5716,10 @@ var BUNDLE_DATA_ALLOWLIST = {
|
|
|
5619
5716
|
toolNames: "name",
|
|
5620
5717
|
cliFloor: "version",
|
|
5621
5718
|
cliVersion: "version",
|
|
5719
|
+
// The two halves of the stale-build WARN: what this process loaded and what
|
|
5720
|
+
// is on disk now. Both are this package's own version strings.
|
|
5721
|
+
loadedVersion: "version",
|
|
5722
|
+
onDiskVersion: "version",
|
|
5622
5723
|
opencode: "version",
|
|
5623
5724
|
plugin: "version",
|
|
5624
5725
|
version: "version",
|
|
@@ -5868,8 +5969,8 @@ function redactLogLine(raw, context) {
|
|
|
5868
5969
|
if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
|
|
5869
5970
|
return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
|
|
5870
5971
|
}
|
|
5871
|
-
function readLogTail(
|
|
5872
|
-
const fd = openSync(
|
|
5972
|
+
function readLogTail(path11, maxBytes = BUNDLE_TAIL_BYTES) {
|
|
5973
|
+
const fd = openSync(path11, "r");
|
|
5873
5974
|
try {
|
|
5874
5975
|
const size = fstatSync(fd).size;
|
|
5875
5976
|
const length = Math.min(size, maxBytes);
|
|
@@ -6059,6 +6160,11 @@ async function fetchPlanUsage(cliPath, options = {}) {
|
|
|
6059
6160
|
}
|
|
6060
6161
|
}
|
|
6061
6162
|
|
|
6163
|
+
// src/stale-build.ts
|
|
6164
|
+
import * as fs5 from "fs";
|
|
6165
|
+
import * as path6 from "path";
|
|
6166
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6167
|
+
|
|
6062
6168
|
// src/startup-diagnostics.ts
|
|
6063
6169
|
import { execFile as execFile3 } from "child_process";
|
|
6064
6170
|
import * as fs4 from "fs";
|
|
@@ -6198,6 +6304,161 @@ function logStartupDiagnostics(providers, opencodeVersion) {
|
|
|
6198
6304
|
})();
|
|
6199
6305
|
}
|
|
6200
6306
|
|
|
6307
|
+
// src/stale-build.ts
|
|
6308
|
+
var STALE_BUILD_MARKER = "\u258C **restart opencode:**";
|
|
6309
|
+
var STALE_BUILD_THROTTLE_MS = 6e4;
|
|
6310
|
+
var STALE_BUILD_SESSION_CAP = 64;
|
|
6311
|
+
function compareBuilds(loaded, onDisk) {
|
|
6312
|
+
if (!onDisk) return null;
|
|
6313
|
+
if (onDisk.version !== loaded.version) {
|
|
6314
|
+
return {
|
|
6315
|
+
kind: "version",
|
|
6316
|
+
loadedVersion: loaded.version,
|
|
6317
|
+
onDiskVersion: onDisk.version,
|
|
6318
|
+
loadedAt: loaded.loadedAt
|
|
6319
|
+
};
|
|
6320
|
+
}
|
|
6321
|
+
if (loaded.mtimeMs === void 0 || loaded.size === void 0) return null;
|
|
6322
|
+
if (onDisk.mtimeMs === loaded.mtimeMs && onDisk.size === loaded.size) return null;
|
|
6323
|
+
return {
|
|
6324
|
+
kind: "rebuilt",
|
|
6325
|
+
loadedVersion: loaded.version,
|
|
6326
|
+
onDiskVersion: onDisk.version,
|
|
6327
|
+
loadedAt: loaded.loadedAt,
|
|
6328
|
+
rebuiltAt: onDisk.mtimeMs
|
|
6329
|
+
};
|
|
6330
|
+
}
|
|
6331
|
+
function formatLocalMinute(ms) {
|
|
6332
|
+
const when = new Date(ms);
|
|
6333
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
6334
|
+
return `${when.getFullYear()}-${pad(when.getMonth() + 1)}-${pad(when.getDate())} ${pad(when.getHours())}:${pad(when.getMinutes())}`;
|
|
6335
|
+
}
|
|
6336
|
+
function formatStaleBuildNote(stale) {
|
|
6337
|
+
const loadedAt = formatLocalMinute(stale.loadedAt);
|
|
6338
|
+
if (stale.kind === "version") {
|
|
6339
|
+
return `${STALE_BUILD_MARKER} plugin ${stale.onDiskVersion} is on disk, but this opencode process still runs ${stale.loadedVersion}, loaded ${loadedAt}. opencode reads a plugin once at startup, so fixes since then are not active here: quit every opencode window and relaunch.`;
|
|
6340
|
+
}
|
|
6341
|
+
const rebuiltAt = stale.rebuiltAt === void 0 ? "since" : `at ${formatLocalMinute(stale.rebuiltAt)}`;
|
|
6342
|
+
return `${STALE_BUILD_MARKER} this opencode process loaded plugin ${stale.loadedVersion} at ${loadedAt}, and that build was rebuilt on disk ${rebuiltAt}. opencode reads a plugin once at startup, so what you just built is not running here: quit every opencode window and relaunch.`;
|
|
6343
|
+
}
|
|
6344
|
+
function describeBuildStatus(status) {
|
|
6345
|
+
const loadedAt = formatLocalMinute(status.loaded.loadedAt);
|
|
6346
|
+
switch (status.verdict) {
|
|
6347
|
+
case "current":
|
|
6348
|
+
return `${status.loaded.version}, loaded ${loadedAt}, current`;
|
|
6349
|
+
case "version":
|
|
6350
|
+
return `${status.loaded.version}, loaded ${loadedAt}; on disk ${status.onDisk?.version ?? "unknown"}. Restart opencode to run it`;
|
|
6351
|
+
case "rebuilt":
|
|
6352
|
+
return `${status.loaded.version}, loaded ${loadedAt}; the same version was rebuilt on disk ${status.onDisk ? `at ${formatLocalMinute(status.onDisk.mtimeMs)}` : "since"}. Restart opencode to run it`;
|
|
6353
|
+
case "unreadable":
|
|
6354
|
+
return `${status.loaded.version}, loaded ${loadedAt}; the build on disk could not be read`;
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
function createStaleBuildWatch(io) {
|
|
6358
|
+
const throttleMs = io.throttleMs ?? STALE_BUILD_THROTTLE_MS;
|
|
6359
|
+
const warn = io.warn ?? ((data) => {
|
|
6360
|
+
log.warn(
|
|
6361
|
+
"this opencode process is running an older plugin build than the one on disk",
|
|
6362
|
+
data
|
|
6363
|
+
);
|
|
6364
|
+
});
|
|
6365
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
6366
|
+
const warned = /* @__PURE__ */ new Set();
|
|
6367
|
+
let checkedAt;
|
|
6368
|
+
let cached = null;
|
|
6369
|
+
const read = () => {
|
|
6370
|
+
let onDisk;
|
|
6371
|
+
try {
|
|
6372
|
+
onDisk = io.readOnDisk();
|
|
6373
|
+
} catch {
|
|
6374
|
+
onDisk = void 0;
|
|
6375
|
+
}
|
|
6376
|
+
const stale = compareBuilds(io.loaded, onDisk);
|
|
6377
|
+
const verdict = !onDisk ? "unreadable" : stale ? stale.kind : "current";
|
|
6378
|
+
return { loaded: io.loaded, onDisk, stale, verdict };
|
|
6379
|
+
};
|
|
6380
|
+
return {
|
|
6381
|
+
get loaded() {
|
|
6382
|
+
return io.loaded;
|
|
6383
|
+
},
|
|
6384
|
+
check() {
|
|
6385
|
+
const now = io.now();
|
|
6386
|
+
if (checkedAt !== void 0 && now - checkedAt < throttleMs) return cached;
|
|
6387
|
+
checkedAt = now;
|
|
6388
|
+
const status = read();
|
|
6389
|
+
cached = status.stale;
|
|
6390
|
+
if (status.stale && status.onDisk) {
|
|
6391
|
+
const identity = `${status.stale.kind}:${status.onDisk.version}:${status.onDisk.mtimeMs}:${status.onDisk.size}`;
|
|
6392
|
+
if (!warned.has(identity)) {
|
|
6393
|
+
warned.add(identity);
|
|
6394
|
+
warn({
|
|
6395
|
+
kind: status.stale.kind,
|
|
6396
|
+
loadedVersion: status.stale.loadedVersion,
|
|
6397
|
+
onDiskVersion: status.stale.onDiskVersion,
|
|
6398
|
+
path: io.loaded.entryPath
|
|
6399
|
+
});
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
return cached;
|
|
6403
|
+
},
|
|
6404
|
+
describe() {
|
|
6405
|
+
return read();
|
|
6406
|
+
},
|
|
6407
|
+
claimSession(sessionId) {
|
|
6408
|
+
if (claimed.has(sessionId)) return false;
|
|
6409
|
+
claimed.add(sessionId);
|
|
6410
|
+
while (claimed.size > STALE_BUILD_SESSION_CAP) {
|
|
6411
|
+
const oldest = claimed.values().next();
|
|
6412
|
+
if (oldest.done) break;
|
|
6413
|
+
claimed.delete(oldest.value);
|
|
6414
|
+
}
|
|
6415
|
+
return true;
|
|
6416
|
+
},
|
|
6417
|
+
claimedSessions() {
|
|
6418
|
+
return [...claimed];
|
|
6419
|
+
}
|
|
6420
|
+
};
|
|
6421
|
+
}
|
|
6422
|
+
function defaultEntryPath() {
|
|
6423
|
+
return fileURLToPath2(import.meta.url);
|
|
6424
|
+
}
|
|
6425
|
+
function readOnDiskBuild(entryPath) {
|
|
6426
|
+
try {
|
|
6427
|
+
const raw = fs5.readFileSync(path6.join(path6.dirname(entryPath), "..", "package.json"), "utf8");
|
|
6428
|
+
const version = JSON.parse(raw).version;
|
|
6429
|
+
if (typeof version !== "string" || version.length === 0) return void 0;
|
|
6430
|
+
const stat = fs5.statSync(entryPath);
|
|
6431
|
+
return { version, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
6432
|
+
} catch {
|
|
6433
|
+
return void 0;
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
function recordLoadedBuild() {
|
|
6437
|
+
const entryPath = defaultEntryPath();
|
|
6438
|
+
let mtimeMs;
|
|
6439
|
+
let size;
|
|
6440
|
+
try {
|
|
6441
|
+
const stat = fs5.statSync(entryPath);
|
|
6442
|
+
mtimeMs = stat.mtimeMs;
|
|
6443
|
+
size = stat.size;
|
|
6444
|
+
} catch {
|
|
6445
|
+
}
|
|
6446
|
+
return { version: pluginVersion(), entryPath, mtimeMs, size, loadedAt: Date.now() };
|
|
6447
|
+
}
|
|
6448
|
+
var loadedAtImport = recordLoadedBuild();
|
|
6449
|
+
var watch;
|
|
6450
|
+
function staleBuildWatch() {
|
|
6451
|
+
if (!watch) {
|
|
6452
|
+
const loaded = loadedAtImport;
|
|
6453
|
+
watch = createStaleBuildWatch({
|
|
6454
|
+
loaded,
|
|
6455
|
+
readOnDisk: () => readOnDiskBuild(loaded.entryPath),
|
|
6456
|
+
now: () => Date.now()
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
return watch;
|
|
6460
|
+
}
|
|
6461
|
+
|
|
6201
6462
|
// src/doctor.ts
|
|
6202
6463
|
var DOCTOR_COMMAND = "claude-code-doctor";
|
|
6203
6464
|
var DOCTOR_COMMAND_DESCRIPTION = "Report what the Claude Code plugin sees: versions, cwd, live processes, pending proxy calls. Add `usage` for plan windows, or `bundle` for a redacted log you can paste into an issue";
|
|
@@ -6269,6 +6530,7 @@ function formatDoctorReport(report) {
|
|
|
6269
6530
|
lines.push("| Field | Value |");
|
|
6270
6531
|
lines.push("|---|---|");
|
|
6271
6532
|
lines.push(`| plugin | ${report.plugin} |`);
|
|
6533
|
+
lines.push(`| plugin build | ${describeBuildStatus(report.build)} |`);
|
|
6272
6534
|
lines.push(`| opencode | ${report.opencode} |`);
|
|
6273
6535
|
lines.push(`| claude CLI | \`${report.claudeCli.path}\` (${report.claudeCli.version}) |`);
|
|
6274
6536
|
lines.push(`| cwd | \`${report.cwd.resolved}\` (${report.cwd.source}) |`);
|
|
@@ -6512,6 +6774,8 @@ async function gatherDoctorReport(options) {
|
|
|
6512
6774
|
}) : { status: "not-requested" };
|
|
6513
6775
|
return {
|
|
6514
6776
|
plugin: base.plugin,
|
|
6777
|
+
// `describe`, not `check`: unthrottled, silent, and it claims no session.
|
|
6778
|
+
build: staleBuildWatch().describe(),
|
|
6515
6779
|
opencode: base.opencode,
|
|
6516
6780
|
claudeCli: { path: cliPath, version: cli?.raw ?? "not detected" },
|
|
6517
6781
|
cwd: base.cwd,
|
|
@@ -6578,6 +6842,7 @@ async function buildDoctorReport(options) {
|
|
|
6578
6842
|
const report = await gatherDoctorReport(options);
|
|
6579
6843
|
log.info("claude-code doctor report", {
|
|
6580
6844
|
plugin: report.plugin,
|
|
6845
|
+
verdict: report.build.verdict,
|
|
6581
6846
|
opencode: report.opencode,
|
|
6582
6847
|
cwd: report.cwd,
|
|
6583
6848
|
processes: report.processes.length,
|
|
@@ -6601,7 +6866,7 @@ Could not build the report: ${message}`;
|
|
|
6601
6866
|
|
|
6602
6867
|
// src/agent-models.ts
|
|
6603
6868
|
import { readFile, readdir } from "fs/promises";
|
|
6604
|
-
import
|
|
6869
|
+
import path7 from "path";
|
|
6605
6870
|
|
|
6606
6871
|
// src/models.ts
|
|
6607
6872
|
var PROVIDER_ID = "claude-code";
|
|
@@ -7104,11 +7369,11 @@ async function readAgentMarkdownRecords(directories) {
|
|
|
7104
7369
|
const name = entry.slice(0, -3);
|
|
7105
7370
|
if (records[name]) continue;
|
|
7106
7371
|
try {
|
|
7107
|
-
const text = await readFile(
|
|
7372
|
+
const text = await readFile(path7.join(directory, entry), "utf8");
|
|
7108
7373
|
records[name] = parseAgentFrontmatter(text);
|
|
7109
7374
|
} catch (err) {
|
|
7110
7375
|
log.debug("failed to read agent markdown", {
|
|
7111
|
-
file:
|
|
7376
|
+
file: path7.join(directory, entry),
|
|
7112
7377
|
error: String(err)
|
|
7113
7378
|
});
|
|
7114
7379
|
}
|
|
@@ -7120,12 +7385,12 @@ function agentDirectories(home, projectDirectory) {
|
|
|
7120
7385
|
const directories = [];
|
|
7121
7386
|
if (projectDirectory) {
|
|
7122
7387
|
for (const name of AGENT_DIR_NAMES) {
|
|
7123
|
-
directories.push(
|
|
7388
|
+
directories.push(path7.join(projectDirectory, ".opencode", name));
|
|
7124
7389
|
}
|
|
7125
7390
|
}
|
|
7126
7391
|
if (home) {
|
|
7127
7392
|
for (const name of AGENT_DIR_NAMES) {
|
|
7128
|
-
directories.push(
|
|
7393
|
+
directories.push(path7.join(home, ".config", "opencode", name));
|
|
7129
7394
|
}
|
|
7130
7395
|
}
|
|
7131
7396
|
return directories;
|
|
@@ -7294,7 +7559,8 @@ var PLUGIN_NOTE_MARKERS = [
|
|
|
7294
7559
|
FAILOVER_MARKER,
|
|
7295
7560
|
ACCOUNT_BLOCK_MARKER,
|
|
7296
7561
|
MODEL_FALLBACK_MARKER,
|
|
7297
|
-
UNATTENDED_REPLAY_MARKER
|
|
7562
|
+
UNATTENDED_REPLAY_MARKER,
|
|
7563
|
+
STALE_BUILD_MARKER
|
|
7298
7564
|
];
|
|
7299
7565
|
function isPluginNote(part) {
|
|
7300
7566
|
if (!part || part.type !== "text" || typeof part.text !== "string") return false;
|
|
@@ -7681,14 +7947,14 @@ Now continuing with the current message:
|
|
|
7681
7947
|
|
|
7682
7948
|
// src/skill-bridge.ts
|
|
7683
7949
|
import * as crypto3 from "crypto";
|
|
7684
|
-
import * as
|
|
7950
|
+
import * as fs6 from "fs";
|
|
7685
7951
|
import * as os3 from "os";
|
|
7686
|
-
import * as
|
|
7687
|
-
import { fileURLToPath as
|
|
7952
|
+
import * as path8 from "path";
|
|
7953
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7688
7954
|
function bundledSkillsDir() {
|
|
7689
7955
|
try {
|
|
7690
|
-
const here =
|
|
7691
|
-
const dir =
|
|
7956
|
+
const here = fileURLToPath3(import.meta.url);
|
|
7957
|
+
const dir = path8.resolve(path8.dirname(here), "..", "skills");
|
|
7692
7958
|
return dirExists2(dir) ? dir : null;
|
|
7693
7959
|
} catch {
|
|
7694
7960
|
return null;
|
|
@@ -7696,16 +7962,16 @@ function bundledSkillsDir() {
|
|
|
7696
7962
|
}
|
|
7697
7963
|
function dirExists2(p) {
|
|
7698
7964
|
try {
|
|
7699
|
-
return
|
|
7965
|
+
return fs6.statSync(p).isDirectory();
|
|
7700
7966
|
} catch {
|
|
7701
7967
|
return false;
|
|
7702
7968
|
}
|
|
7703
7969
|
}
|
|
7704
7970
|
function realPath(p) {
|
|
7705
7971
|
try {
|
|
7706
|
-
return
|
|
7972
|
+
return fs6.realpathSync(p);
|
|
7707
7973
|
} catch {
|
|
7708
|
-
return
|
|
7974
|
+
return path8.resolve(p);
|
|
7709
7975
|
}
|
|
7710
7976
|
}
|
|
7711
7977
|
function envEnabled(name) {
|
|
@@ -7726,10 +7992,10 @@ function declaredSkillName(source) {
|
|
|
7726
7992
|
return SAFE_SKILL_NAME.test(name) ? name : null;
|
|
7727
7993
|
}
|
|
7728
7994
|
function readSkill(root, entry) {
|
|
7729
|
-
const dir =
|
|
7995
|
+
const dir = path8.join(root, entry);
|
|
7730
7996
|
let source;
|
|
7731
7997
|
try {
|
|
7732
|
-
source =
|
|
7998
|
+
source = fs6.readFileSync(path8.join(dir, "SKILL.md"), "utf8");
|
|
7733
7999
|
} catch {
|
|
7734
8000
|
return null;
|
|
7735
8001
|
}
|
|
@@ -7747,47 +8013,47 @@ function skillRoots(cwd) {
|
|
|
7747
8013
|
const roots = [];
|
|
7748
8014
|
const seen = /* @__PURE__ */ new Set();
|
|
7749
8015
|
const push = (p) => {
|
|
7750
|
-
const abs =
|
|
8016
|
+
const abs = path8.resolve(p);
|
|
7751
8017
|
if (seen.has(abs)) return;
|
|
7752
8018
|
seen.add(abs);
|
|
7753
8019
|
if (dirExists2(abs)) roots.push(abs);
|
|
7754
8020
|
};
|
|
7755
8021
|
const home = os3.homedir();
|
|
7756
|
-
const homeDir = home ?
|
|
7757
|
-
let current =
|
|
8022
|
+
const homeDir = home ? path8.resolve(home) : null;
|
|
8023
|
+
let current = path8.resolve(cwd);
|
|
7758
8024
|
while (true) {
|
|
7759
|
-
push(
|
|
8025
|
+
push(path8.join(current, ".opencode", "skills"));
|
|
7760
8026
|
if (current !== homeDir) {
|
|
7761
|
-
push(
|
|
7762
|
-
push(
|
|
8027
|
+
push(path8.join(current, ".claude", "skills"));
|
|
8028
|
+
push(path8.join(current, ".agents", "skills"));
|
|
7763
8029
|
}
|
|
7764
|
-
const parent =
|
|
8030
|
+
const parent = path8.dirname(current);
|
|
7765
8031
|
if (parent === current) break;
|
|
7766
8032
|
current = parent;
|
|
7767
8033
|
}
|
|
7768
|
-
if (homeDir) push(
|
|
8034
|
+
if (homeDir) push(path8.join(homeDir, ".opencode", "skills"));
|
|
7769
8035
|
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
7770
8036
|
if (envDir) {
|
|
7771
|
-
push(
|
|
7772
|
-
push(
|
|
8037
|
+
push(path8.join(envDir, "skills"));
|
|
8038
|
+
push(path8.join(envDir, "skill"));
|
|
7773
8039
|
}
|
|
7774
|
-
const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ?
|
|
8040
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path8.join(homeDir, ".config") : null);
|
|
7775
8041
|
if (xdg) {
|
|
7776
|
-
push(
|
|
7777
|
-
push(
|
|
8042
|
+
push(path8.join(xdg, "opencode", "skills"));
|
|
8043
|
+
push(path8.join(xdg, "opencode", "skill"));
|
|
7778
8044
|
}
|
|
7779
8045
|
if (homeDir && !envEnabled("OPENCODE_DISABLE_EXTERNAL_SKILLS")) {
|
|
7780
8046
|
if (!envEnabled("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")) {
|
|
7781
|
-
push(
|
|
8047
|
+
push(path8.join(homeDir, ".claude", "skills"));
|
|
7782
8048
|
}
|
|
7783
|
-
push(
|
|
8049
|
+
push(path8.join(homeDir, ".agents", "skills"));
|
|
7784
8050
|
}
|
|
7785
8051
|
return roots;
|
|
7786
8052
|
}
|
|
7787
8053
|
function collectSkills(root, claimed, found) {
|
|
7788
8054
|
let entries;
|
|
7789
8055
|
try {
|
|
7790
|
-
entries =
|
|
8056
|
+
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
7791
8057
|
} catch {
|
|
7792
8058
|
return;
|
|
7793
8059
|
}
|
|
@@ -7827,23 +8093,23 @@ function discoverBundledSkills() {
|
|
|
7827
8093
|
}
|
|
7828
8094
|
function claudeConfigDir(configDir) {
|
|
7829
8095
|
const explicit = configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
7830
|
-
if (explicit) return
|
|
8096
|
+
if (explicit) return path8.resolve(expandHome(explicit));
|
|
7831
8097
|
const home = os3.homedir();
|
|
7832
|
-
return home ?
|
|
8098
|
+
return home ? path8.resolve(home, ".claude") : null;
|
|
7833
8099
|
}
|
|
7834
8100
|
function installedPluginSkillRoots(configDir, cwd) {
|
|
7835
8101
|
if (!configDir) return [];
|
|
7836
8102
|
let parsed;
|
|
7837
8103
|
try {
|
|
7838
8104
|
parsed = JSON.parse(
|
|
7839
|
-
|
|
8105
|
+
fs6.readFileSync(path8.join(configDir, "plugins", "installed_plugins.json"), "utf8")
|
|
7840
8106
|
);
|
|
7841
8107
|
} catch {
|
|
7842
8108
|
return [];
|
|
7843
8109
|
}
|
|
7844
8110
|
const plugins = parsed?.plugins;
|
|
7845
8111
|
if (!plugins || typeof plugins !== "object") return [];
|
|
7846
|
-
const workspace =
|
|
8112
|
+
const workspace = path8.resolve(cwd);
|
|
7847
8113
|
const roots = [];
|
|
7848
8114
|
for (const installs of Object.values(plugins)) {
|
|
7849
8115
|
if (!Array.isArray(installs)) continue;
|
|
@@ -7851,28 +8117,28 @@ function installedPluginSkillRoots(configDir, cwd) {
|
|
|
7851
8117
|
const entry = install;
|
|
7852
8118
|
if (typeof entry?.installPath !== "string" || !entry.installPath) continue;
|
|
7853
8119
|
if (entry.scope !== "user" && !coversWorkspace(entry.projectPath, workspace)) continue;
|
|
7854
|
-
roots.push(
|
|
8120
|
+
roots.push(path8.join(entry.installPath, "skills"));
|
|
7855
8121
|
}
|
|
7856
8122
|
}
|
|
7857
8123
|
return roots;
|
|
7858
8124
|
}
|
|
7859
8125
|
function coversWorkspace(projectPath, workspace) {
|
|
7860
8126
|
if (typeof projectPath !== "string" || !projectPath) return false;
|
|
7861
|
-
const root =
|
|
8127
|
+
const root = path8.resolve(projectPath);
|
|
7862
8128
|
if (root === workspace) return true;
|
|
7863
|
-
const rel =
|
|
7864
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
8129
|
+
const rel = path8.relative(root, workspace);
|
|
8130
|
+
return rel !== "" && !rel.startsWith("..") && !path8.isAbsolute(rel);
|
|
7865
8131
|
}
|
|
7866
8132
|
function discoverNativeClaudeSkills(opts) {
|
|
7867
8133
|
const found = [];
|
|
7868
8134
|
const scanned = /* @__PURE__ */ new Set();
|
|
7869
8135
|
const scan = (root, scope) => {
|
|
7870
|
-
const abs =
|
|
8136
|
+
const abs = path8.resolve(root);
|
|
7871
8137
|
if (scanned.has(abs)) return;
|
|
7872
8138
|
scanned.add(abs);
|
|
7873
8139
|
let entries;
|
|
7874
8140
|
try {
|
|
7875
|
-
entries =
|
|
8141
|
+
entries = fs6.readdirSync(abs, { withFileTypes: true });
|
|
7876
8142
|
} catch {
|
|
7877
8143
|
return;
|
|
7878
8144
|
}
|
|
@@ -7884,13 +8150,13 @@ function discoverNativeClaudeSkills(opts) {
|
|
|
7884
8150
|
}
|
|
7885
8151
|
};
|
|
7886
8152
|
const configDir = claudeConfigDir(opts.configDir);
|
|
7887
|
-
if (configDir) scan(
|
|
8153
|
+
if (configDir) scan(path8.join(configDir, "skills"), "user");
|
|
7888
8154
|
const home = os3.homedir();
|
|
7889
|
-
const homeDir = home ?
|
|
7890
|
-
let current =
|
|
8155
|
+
const homeDir = home ? path8.resolve(home) : null;
|
|
8156
|
+
let current = path8.resolve(opts.cwd);
|
|
7891
8157
|
while (true) {
|
|
7892
|
-
if (current !== homeDir) scan(
|
|
7893
|
-
const parent =
|
|
8158
|
+
if (current !== homeDir) scan(path8.join(current, ".claude", "skills"), "project");
|
|
8159
|
+
const parent = path8.dirname(current);
|
|
7894
8160
|
if (parent === current) break;
|
|
7895
8161
|
current = parent;
|
|
7896
8162
|
}
|
|
@@ -7924,10 +8190,10 @@ function dropNativelyLoadedSkills(skills, native) {
|
|
|
7924
8190
|
}
|
|
7925
8191
|
function readBundledSkillInfos() {
|
|
7926
8192
|
return discoverBundledSkills().flatMap((skill) => {
|
|
7927
|
-
const file =
|
|
8193
|
+
const file = path8.join(skill.dir, "SKILL.md");
|
|
7928
8194
|
let text;
|
|
7929
8195
|
try {
|
|
7930
|
-
text =
|
|
8196
|
+
text = fs6.readFileSync(file, "utf8");
|
|
7931
8197
|
} catch {
|
|
7932
8198
|
return [];
|
|
7933
8199
|
}
|
|
@@ -7951,15 +8217,15 @@ function readBundledSkillInfos() {
|
|
|
7951
8217
|
}
|
|
7952
8218
|
function linkSkill(source, target) {
|
|
7953
8219
|
try {
|
|
7954
|
-
|
|
8220
|
+
fs6.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
|
|
7955
8221
|
return;
|
|
7956
8222
|
} catch {
|
|
7957
|
-
|
|
8223
|
+
fs6.cpSync(source, target, { recursive: true, dereference: true });
|
|
7958
8224
|
}
|
|
7959
8225
|
}
|
|
7960
8226
|
function fileExists2(p) {
|
|
7961
8227
|
try {
|
|
7962
|
-
return
|
|
8228
|
+
return fs6.statSync(p).isFile();
|
|
7963
8229
|
} catch {
|
|
7964
8230
|
return false;
|
|
7965
8231
|
}
|
|
@@ -7968,14 +8234,14 @@ function buildSkillPluginDir(skills) {
|
|
|
7968
8234
|
if (skills.length === 0) return null;
|
|
7969
8235
|
const fingerprint = skills.map((s) => `${s.name}\0${s.dir}`).join("\n");
|
|
7970
8236
|
const hash = crypto3.createHash("sha256").update(fingerprint).digest("hex").slice(0, 12);
|
|
7971
|
-
const root =
|
|
7972
|
-
const manifest =
|
|
8237
|
+
const root = path8.join(pluginTmpDir(), `skills-${hash}`);
|
|
8238
|
+
const manifest = path8.join(root, ".claude-plugin", "plugin.json");
|
|
7973
8239
|
if (fileExists2(manifest)) return root;
|
|
7974
8240
|
try {
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
8241
|
+
fs6.rmSync(root, { recursive: true, force: true });
|
|
8242
|
+
fs6.mkdirSync(path8.join(root, ".claude-plugin"), { recursive: true });
|
|
8243
|
+
fs6.mkdirSync(path8.join(root, "skills"), { recursive: true });
|
|
8244
|
+
fs6.writeFileSync(
|
|
7979
8245
|
manifest,
|
|
7980
8246
|
JSON.stringify(
|
|
7981
8247
|
{
|
|
@@ -7988,7 +8254,7 @@ function buildSkillPluginDir(skills) {
|
|
|
7988
8254
|
{ encoding: "utf8", mode: 384 }
|
|
7989
8255
|
);
|
|
7990
8256
|
for (const skill of skills) {
|
|
7991
|
-
linkSkill(skill.dir,
|
|
8257
|
+
linkSkill(skill.dir, path8.join(root, "skills", skill.name));
|
|
7992
8258
|
}
|
|
7993
8259
|
} catch (err) {
|
|
7994
8260
|
log.warn("failed to stage opencode skill plugin dir", {
|
|
@@ -8004,7 +8270,7 @@ function registerBundledSkillPath(config) {
|
|
|
8004
8270
|
if (!dir) return false;
|
|
8005
8271
|
config.skills ??= {};
|
|
8006
8272
|
const paths = config.skills.paths ??= [];
|
|
8007
|
-
if (paths.some((p) =>
|
|
8273
|
+
if (paths.some((p) => path8.resolve(p) === dir)) return false;
|
|
8008
8274
|
paths.push(dir);
|
|
8009
8275
|
return true;
|
|
8010
8276
|
}
|
|
@@ -8055,12 +8321,12 @@ import { unlink as unlink2 } from "fs/promises";
|
|
|
8055
8321
|
|
|
8056
8322
|
// src/claude-session-bun.ts
|
|
8057
8323
|
import * as os4 from "os";
|
|
8058
|
-
import * as
|
|
8059
|
-
import * as
|
|
8324
|
+
import * as fs7 from "fs";
|
|
8325
|
+
import * as path9 from "path";
|
|
8060
8326
|
import { execFileSync } from "child_process";
|
|
8061
8327
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8062
8328
|
function resolveClaude(cmd = "claude") {
|
|
8063
|
-
if (
|
|
8329
|
+
if (path9.isAbsolute(cmd) && fs7.existsSync(cmd)) return cmd;
|
|
8064
8330
|
const viaBun = Bun.which(cmd);
|
|
8065
8331
|
if (viaBun) return viaBun;
|
|
8066
8332
|
const isWin = os4.platform() === "win32";
|
|
@@ -8069,17 +8335,17 @@ function resolveClaude(cmd = "claude") {
|
|
|
8069
8335
|
encoding: "utf8",
|
|
8070
8336
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8071
8337
|
});
|
|
8072
|
-
const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) =>
|
|
8338
|
+
const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs7.existsSync(p));
|
|
8073
8339
|
if (first) return first;
|
|
8074
8340
|
} catch {
|
|
8075
8341
|
}
|
|
8076
8342
|
throw new Error(`Could not resolve command on PATH: ${cmd}`);
|
|
8077
8343
|
}
|
|
8078
8344
|
function encodeCwd(cwd) {
|
|
8079
|
-
const resolved =
|
|
8345
|
+
const resolved = path9.resolve(cwd);
|
|
8080
8346
|
let real = resolved;
|
|
8081
8347
|
try {
|
|
8082
|
-
real =
|
|
8348
|
+
real = fs7.realpathSync.native(resolved);
|
|
8083
8349
|
} catch {
|
|
8084
8350
|
}
|
|
8085
8351
|
return real.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -8164,12 +8430,12 @@ var TERMINAL_STOP = /* @__PURE__ */ new Set(["end_turn", "stop_sequence", "max_t
|
|
|
8164
8430
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
8165
8431
|
function resolveConfigDir(configDir) {
|
|
8166
8432
|
const value = configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
8167
|
-
if (!value) return
|
|
8433
|
+
if (!value) return path9.join(os4.homedir(), ".claude");
|
|
8168
8434
|
if (value === "~") return os4.homedir();
|
|
8169
8435
|
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
8170
|
-
return
|
|
8436
|
+
return path9.join(os4.homedir(), value.slice(2));
|
|
8171
8437
|
}
|
|
8172
|
-
return
|
|
8438
|
+
return path9.resolve(value);
|
|
8173
8439
|
}
|
|
8174
8440
|
var ClaudeSession = class {
|
|
8175
8441
|
sessionId;
|
|
@@ -8187,11 +8453,11 @@ var ClaudeSession = class {
|
|
|
8187
8453
|
signal;
|
|
8188
8454
|
o;
|
|
8189
8455
|
constructor(opts = {}) {
|
|
8190
|
-
this.cwd =
|
|
8456
|
+
this.cwd = path9.resolve(opts.cwd ?? process.cwd());
|
|
8191
8457
|
this.configDir = resolveConfigDir(opts.configDir);
|
|
8192
8458
|
this.signal = opts.signal;
|
|
8193
8459
|
this.sessionId = randomUUID4();
|
|
8194
|
-
this.jsonlPath =
|
|
8460
|
+
this.jsonlPath = path9.join(
|
|
8195
8461
|
this.configDir,
|
|
8196
8462
|
"projects",
|
|
8197
8463
|
encodeCwd(this.cwd),
|
|
@@ -8314,7 +8580,7 @@ var ClaudeSession = class {
|
|
|
8314
8580
|
}
|
|
8315
8581
|
readRawLines() {
|
|
8316
8582
|
try {
|
|
8317
|
-
return
|
|
8583
|
+
return fs7.readFileSync(this.jsonlPath, "utf8").split("\n");
|
|
8318
8584
|
} catch {
|
|
8319
8585
|
return [];
|
|
8320
8586
|
}
|
|
@@ -8711,13 +8977,13 @@ function spawnInteractiveProcess(opts) {
|
|
|
8711
8977
|
}
|
|
8712
8978
|
|
|
8713
8979
|
// src/prompts.ts
|
|
8714
|
-
import { readFileSync as
|
|
8980
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
8715
8981
|
import { homedir as homedir7 } from "os";
|
|
8716
8982
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
8717
|
-
import { dirname as
|
|
8718
|
-
function readPromptFileIfPresent(
|
|
8983
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
8984
|
+
function readPromptFileIfPresent(path11) {
|
|
8719
8985
|
try {
|
|
8720
|
-
const content =
|
|
8986
|
+
const content = readFileSync6(path11, "utf8").trim();
|
|
8721
8987
|
return content || void 0;
|
|
8722
8988
|
} catch {
|
|
8723
8989
|
return void 0;
|
|
@@ -8726,9 +8992,9 @@ function readPromptFileIfPresent(path10) {
|
|
|
8726
8992
|
function nearestWorkspaceAgentsPrompt(cwd) {
|
|
8727
8993
|
let dir = cwd;
|
|
8728
8994
|
while (true) {
|
|
8729
|
-
const content = readPromptFileIfPresent(
|
|
8995
|
+
const content = readPromptFileIfPresent(join9(dir, "AGENTS.md"));
|
|
8730
8996
|
if (content) return content;
|
|
8731
|
-
const parent =
|
|
8997
|
+
const parent = dirname6(dir);
|
|
8732
8998
|
if (parent === dir) return void 0;
|
|
8733
8999
|
dir = parent;
|
|
8734
9000
|
}
|
|
@@ -8835,8 +9101,8 @@ ${options.compressionSummary.trim()}`
|
|
|
8835
9101
|
for (const s of extraSystemContent) {
|
|
8836
9102
|
if (s.trim()) parts.push(s.trim());
|
|
8837
9103
|
}
|
|
8838
|
-
const configRoot = process.env.XDG_CONFIG_HOME ??
|
|
8839
|
-
const globalAgents = readPromptFileIfPresent(
|
|
9104
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join9(homedir7(), ".config");
|
|
9105
|
+
const globalAgents = readPromptFileIfPresent(join9(configRoot, "opencode", "AGENTS.md"));
|
|
8840
9106
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
8841
9107
|
const forwarded = extraSystemContent.join("\n\n");
|
|
8842
9108
|
const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
|
|
@@ -8847,10 +9113,10 @@ ${options.compressionSummary.trim()}`
|
|
|
8847
9113
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
8848
9114
|
const content = parts.join("\n\n");
|
|
8849
9115
|
if (!content) return void 0;
|
|
8850
|
-
const
|
|
9116
|
+
const path11 = join9(pluginTmpDir(), `opencode-cc-sys-${randomUUID5()}.md`);
|
|
8851
9117
|
try {
|
|
8852
|
-
writeFileSync4(
|
|
8853
|
-
return
|
|
9118
|
+
writeFileSync4(path11, content, { encoding: "utf8", mode: 384 });
|
|
9119
|
+
return path11;
|
|
8854
9120
|
} catch (err) {
|
|
8855
9121
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
8856
9122
|
return void 0;
|
|
@@ -11903,6 +12169,11 @@ var ClaudeCodeLanguageModel = class {
|
|
|
11903
12169
|
// the operator is usually not even looking at.
|
|
11904
12170
|
childSession: !!await fetchSessionParentId(affinity)
|
|
11905
12171
|
});
|
|
12172
|
+
let staleBuild = null;
|
|
12173
|
+
if (!compactionMode && mode !== "generate" && affinity !== "default") {
|
|
12174
|
+
staleBuild = staleBuildWatch().check();
|
|
12175
|
+
if (staleBuild && await fetchSessionParentId(affinity)) staleBuild = null;
|
|
12176
|
+
}
|
|
11906
12177
|
log.info("doStream starting", {
|
|
11907
12178
|
cwd,
|
|
11908
12179
|
model: effectiveModelId,
|
|
@@ -12363,6 +12634,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12363
12634
|
if (stoppedBeforeWork()) return;
|
|
12364
12635
|
state.streamStarted = true;
|
|
12365
12636
|
controller.enqueue({ type: "stream-start", warnings });
|
|
12637
|
+
if (staleBuild && staleBuildWatch().claimSession(affinity)) {
|
|
12638
|
+
controller.enqueue({
|
|
12639
|
+
type: "text-delta",
|
|
12640
|
+
id: state.startTextBlock(),
|
|
12641
|
+
delta: formatStaleBuildNote(staleBuild)
|
|
12642
|
+
});
|
|
12643
|
+
state.endTextBlock();
|
|
12644
|
+
}
|
|
12366
12645
|
if (failoverNote) {
|
|
12367
12646
|
controller.enqueue({
|
|
12368
12647
|
type: "text-delta",
|
|
@@ -12810,34 +13089,34 @@ var ClaudeCodeLanguageModel = class {
|
|
|
12810
13089
|
import {
|
|
12811
13090
|
existsSync as existsSync3,
|
|
12812
13091
|
mkdirSync as mkdirSync4,
|
|
12813
|
-
readFileSync as
|
|
13092
|
+
readFileSync as readFileSync7,
|
|
12814
13093
|
realpathSync as realpathSync4,
|
|
12815
13094
|
rmSync as rmSync3,
|
|
12816
13095
|
writeFileSync as writeFileSync5
|
|
12817
13096
|
} from "fs";
|
|
12818
13097
|
import { homedir as homedir8 } from "os";
|
|
12819
|
-
import { dirname as
|
|
12820
|
-
import { fileURLToPath as
|
|
13098
|
+
import { dirname as dirname7, join as join10, resolve as resolve5 } from "path";
|
|
13099
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12821
13100
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
12822
13101
|
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
12823
13102
|
var alreadyRan = false;
|
|
12824
13103
|
function candidateCacheRoots() {
|
|
12825
13104
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
12826
13105
|
return [
|
|
12827
|
-
xdg ?
|
|
12828
|
-
|
|
12829
|
-
|
|
13106
|
+
xdg ? join10(xdg, "opencode") : null,
|
|
13107
|
+
join10(homedir8(), ".cache", "opencode"),
|
|
13108
|
+
join10(homedir8(), "Library", "Caches", "opencode")
|
|
12830
13109
|
].filter((p) => Boolean(p));
|
|
12831
13110
|
}
|
|
12832
13111
|
function userOpencodeJsonPath() {
|
|
12833
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
12834
|
-
return
|
|
13112
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join10(homedir8(), ".config");
|
|
13113
|
+
return join10(xdgConfig, "opencode", "opencode.json");
|
|
12835
13114
|
}
|
|
12836
13115
|
function userIntendsToUseUnscoped() {
|
|
12837
13116
|
const cfg = userOpencodeJsonPath();
|
|
12838
13117
|
if (!existsSync3(cfg)) return false;
|
|
12839
13118
|
try {
|
|
12840
|
-
const json = JSON.parse(
|
|
13119
|
+
const json = JSON.parse(readFileSync7(cfg, "utf8"));
|
|
12841
13120
|
const plugins = json.plugin;
|
|
12842
13121
|
if (!Array.isArray(plugins)) return false;
|
|
12843
13122
|
return plugins.some(
|
|
@@ -12849,7 +13128,7 @@ function userIntendsToUseUnscoped() {
|
|
|
12849
13128
|
}
|
|
12850
13129
|
function ourLoadedDir() {
|
|
12851
13130
|
try {
|
|
12852
|
-
const filePath =
|
|
13131
|
+
const filePath = fileURLToPath4(import.meta.url);
|
|
12853
13132
|
return realpathSync4(resolve5(filePath, "..", ".."));
|
|
12854
13133
|
} catch {
|
|
12855
13134
|
return null;
|
|
@@ -12858,19 +13137,19 @@ function ourLoadedDir() {
|
|
|
12858
13137
|
function ourVersion(dir) {
|
|
12859
13138
|
if (!dir) return null;
|
|
12860
13139
|
try {
|
|
12861
|
-
const pkg = JSON.parse(
|
|
13140
|
+
const pkg = JSON.parse(readFileSync7(join10(dir, "package.json"), "utf8"));
|
|
12862
13141
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
|
|
12863
13142
|
} catch {
|
|
12864
13143
|
return null;
|
|
12865
13144
|
}
|
|
12866
13145
|
}
|
|
12867
13146
|
function markerPath() {
|
|
12868
|
-
const stateRoot = process.env.XDG_STATE_HOME ??
|
|
12869
|
-
return
|
|
13147
|
+
const stateRoot = process.env.XDG_STATE_HOME ?? join10(homedir8(), ".local", "state");
|
|
13148
|
+
return join10(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
|
|
12870
13149
|
}
|
|
12871
13150
|
function markerVersion() {
|
|
12872
13151
|
try {
|
|
12873
|
-
const marker = JSON.parse(
|
|
13152
|
+
const marker = JSON.parse(readFileSync7(markerPath(), "utf8"));
|
|
12874
13153
|
return typeof marker.version === "string" ? marker.version : null;
|
|
12875
13154
|
} catch {
|
|
12876
13155
|
return null;
|
|
@@ -12879,7 +13158,7 @@ function markerVersion() {
|
|
|
12879
13158
|
function writeMarker(version) {
|
|
12880
13159
|
const file = markerPath();
|
|
12881
13160
|
try {
|
|
12882
|
-
mkdirSync4(
|
|
13161
|
+
mkdirSync4(dirname7(file), { recursive: true });
|
|
12883
13162
|
writeFileSync5(
|
|
12884
13163
|
file,
|
|
12885
13164
|
JSON.stringify({ version, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n",
|
|
@@ -12915,7 +13194,7 @@ function cleanupStaleUnscopedInstall() {
|
|
|
12915
13194
|
}
|
|
12916
13195
|
function cleanupOne(cacheRoot, ourDir) {
|
|
12917
13196
|
if (!existsSync3(cacheRoot)) return;
|
|
12918
|
-
const stalePath =
|
|
13197
|
+
const stalePath = join10(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
|
|
12919
13198
|
if (!existsSync3(stalePath)) return;
|
|
12920
13199
|
let realStalePath = stalePath;
|
|
12921
13200
|
try {
|
|
@@ -12923,11 +13202,11 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
12923
13202
|
} catch {
|
|
12924
13203
|
}
|
|
12925
13204
|
if (ourDir && realStalePath === ourDir) return;
|
|
12926
|
-
const pkgJsonPath =
|
|
13205
|
+
const pkgJsonPath = join10(stalePath, "package.json");
|
|
12927
13206
|
if (!existsSync3(pkgJsonPath)) return;
|
|
12928
13207
|
let pkg = {};
|
|
12929
13208
|
try {
|
|
12930
|
-
pkg = JSON.parse(
|
|
13209
|
+
pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
|
|
12931
13210
|
} catch {
|
|
12932
13211
|
return;
|
|
12933
13212
|
}
|
|
@@ -12943,10 +13222,10 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
12943
13222
|
});
|
|
12944
13223
|
return;
|
|
12945
13224
|
}
|
|
12946
|
-
const cachePkgJson =
|
|
13225
|
+
const cachePkgJson = join10(cacheRoot, "package.json");
|
|
12947
13226
|
if (!existsSync3(cachePkgJson)) return;
|
|
12948
13227
|
try {
|
|
12949
|
-
const cfg = JSON.parse(
|
|
13228
|
+
const cfg = JSON.parse(readFileSync7(cachePkgJson, "utf8"));
|
|
12950
13229
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
12951
13230
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
12952
13231
|
writeFileSync5(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
@@ -12959,8 +13238,74 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
12959
13238
|
}
|
|
12960
13239
|
}
|
|
12961
13240
|
|
|
13241
|
+
// src/tui-log-sink.ts
|
|
13242
|
+
var LOG_SERVICE_NAME = "opencode-claude-code";
|
|
13243
|
+
var TOAST_TITLE = "claude-code";
|
|
13244
|
+
var TOAST_DURATION_MS = 1e4;
|
|
13245
|
+
var MAX_TOAST_CHARS = 240;
|
|
13246
|
+
var APP_LOG_LEVEL = {
|
|
13247
|
+
debug: "debug",
|
|
13248
|
+
info: "info",
|
|
13249
|
+
notice: "info",
|
|
13250
|
+
warn: "warn",
|
|
13251
|
+
error: "error"
|
|
13252
|
+
};
|
|
13253
|
+
function toastMessage(message) {
|
|
13254
|
+
const flat = message.replace(/\s+/g, " ").trim();
|
|
13255
|
+
if (flat.length <= MAX_TOAST_CHARS) return flat;
|
|
13256
|
+
return `${flat.slice(0, MAX_TOAST_CHARS - 1)}\u2026`;
|
|
13257
|
+
}
|
|
13258
|
+
function appLogBody(entry) {
|
|
13259
|
+
return {
|
|
13260
|
+
service: LOG_SERVICE_NAME,
|
|
13261
|
+
level: APP_LOG_LEVEL[entry.level],
|
|
13262
|
+
message: entry.message,
|
|
13263
|
+
...entry.data && Object.keys(entry.data).length > 0 ? { extra: entry.data } : {}
|
|
13264
|
+
};
|
|
13265
|
+
}
|
|
13266
|
+
function toastBody(entry) {
|
|
13267
|
+
return {
|
|
13268
|
+
title: TOAST_TITLE,
|
|
13269
|
+
message: toastMessage(entry.message),
|
|
13270
|
+
variant: entry.level === "error" ? "error" : "warning",
|
|
13271
|
+
duration: TOAST_DURATION_MS
|
|
13272
|
+
};
|
|
13273
|
+
}
|
|
13274
|
+
function fireAndForget(call) {
|
|
13275
|
+
try {
|
|
13276
|
+
const result = call();
|
|
13277
|
+
if (result && typeof result.catch === "function") {
|
|
13278
|
+
void result.catch(() => void 0);
|
|
13279
|
+
}
|
|
13280
|
+
} catch {
|
|
13281
|
+
}
|
|
13282
|
+
}
|
|
13283
|
+
function createOpencodeLogSink(client) {
|
|
13284
|
+
if (!client || typeof client !== "object") return null;
|
|
13285
|
+
const sdk = client;
|
|
13286
|
+
const canLog = typeof sdk.app?.log === "function";
|
|
13287
|
+
const canToast = typeof sdk.tui?.showToast === "function";
|
|
13288
|
+
if (!canLog && !canToast) return null;
|
|
13289
|
+
return {
|
|
13290
|
+
log(entry) {
|
|
13291
|
+
if (!canLog) return;
|
|
13292
|
+
fireAndForget(() => sdk.app.log({ body: appLogBody(entry) }));
|
|
13293
|
+
},
|
|
13294
|
+
toast(entry) {
|
|
13295
|
+
if (!canToast) return;
|
|
13296
|
+
fireAndForget(() => sdk.tui.showToast({ body: toastBody(entry) }));
|
|
13297
|
+
}
|
|
13298
|
+
};
|
|
13299
|
+
}
|
|
13300
|
+
function registerOpencodeLogSink(client) {
|
|
13301
|
+
const sink = createOpencodeLogSink(client);
|
|
13302
|
+
if (!sink) return false;
|
|
13303
|
+
setLogSink(sink);
|
|
13304
|
+
return true;
|
|
13305
|
+
}
|
|
13306
|
+
|
|
12962
13307
|
// src/v2-client.ts
|
|
12963
|
-
import
|
|
13308
|
+
import path10 from "path";
|
|
12964
13309
|
function formatAgentTypeList(agents) {
|
|
12965
13310
|
const lines = agents.flatMap((agent) => {
|
|
12966
13311
|
if (agent.hidden === true || agent.mode === "primary") return [];
|
|
@@ -12989,7 +13334,7 @@ function toV1Session(info) {
|
|
|
12989
13334
|
if (!info || typeof info !== "object") return void 0;
|
|
12990
13335
|
const session = info;
|
|
12991
13336
|
const root = session.location?.directory;
|
|
12992
|
-
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ?
|
|
13337
|
+
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path10.resolve(root, session.subpath) : root : void 0;
|
|
12993
13338
|
return { ...info, directory, parentID: session.parentID };
|
|
12994
13339
|
}
|
|
12995
13340
|
function toV1Messages(messages) {
|
|
@@ -13783,7 +14128,9 @@ var server = async (input) => {
|
|
|
13783
14128
|
ensureProcessExitCleanup();
|
|
13784
14129
|
const opencodeVersion = pickOpencodeVersion(input);
|
|
13785
14130
|
if (input && typeof input === "object" && "client" in input) {
|
|
13786
|
-
|
|
14131
|
+
const client = input.client;
|
|
14132
|
+
setOpencodeClient(client);
|
|
14133
|
+
registerOpencodeLogSink(client);
|
|
13787
14134
|
}
|
|
13788
14135
|
setOpencodeProjectDirectory(pickOpencodeDirectory(input));
|
|
13789
14136
|
return {
|