@join-pantheon/cli 0.27.0 → 0.29.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/accounts.js +13 -2
- package/dist/agents.js +377 -9
- package/dist/cli.js +419 -182
- package/dist/client.js +91 -8
- package/dist/config.js +15 -0
- package/dist/doctor.js +35 -6
- package/dist/grants.js +16 -1
- package/dist/hooks-install.js +4 -2
- package/dist/host-env.js +51 -6
- package/dist/install.js +216 -16
- package/dist/kiro-files.js +23 -6
- package/dist/ledger.js +8 -2
- package/dist/machine.js +273 -54
- package/dist/mcp-supervisor.js +5 -0
- package/dist/mcp.js +33 -6
- package/dist/network.js +595 -0
- package/dist/pending.js +14 -7
- package/dist/quickstart.js +180 -21
- package/dist/refusal.js +29 -0
- package/dist/relay.js +686 -440
- package/dist/roster-file.js +19 -3
- package/dist/run-failure.js +18 -7
- package/dist/service.js +173 -40
- package/dist/setup-guide.js +21 -4
- package/dist/store.js +9 -3
- package/dist/update-check.js +2 -1
- package/dist/upgrade.js +2 -1
- package/dist/which.js +71 -7
- package/dist/workdir.js +7 -1
- package/dist/worker.js +524 -99
- package/helper/macos/menubar.swift +11 -3
- package/package.json +2 -1
package/dist/install.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import { homedir, platform } from "node:os";
|
|
4
4
|
import { join, dirname, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
6
|
+
import { grantClaudeToolPermissions, grantCursorToolPermissions, claudeSettingsFile, cursorCliConfigFile } from "./grants.js";
|
|
7
7
|
import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES, toolsAllowed } from "./tools.js";
|
|
8
8
|
import { load } from "./config.js";
|
|
9
9
|
import { loadProfile } from "./profile.js";
|
|
@@ -237,8 +237,17 @@ function claudeLocalScope(entry, projectDir) {
|
|
|
237
237
|
return { tool, where: "local scope", status: `failed: ${e.message.split("\n")[0]}` };
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
function
|
|
240
|
+
export function agentPresence(home = homedir(), onPath = onPathNamed) {
|
|
241
|
+
return {
|
|
242
|
+
claude: existsSync(join(home, ".claude")) || onPath("claude"),
|
|
243
|
+
cursor: existsSync(join(home, ".cursor")) || onPath("cursor-agent"),
|
|
244
|
+
codex: existsSync(join(home, ".codex")) || onPath("codex"),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function cursorUserScope(entry, present = true) {
|
|
241
248
|
const file = join(homedir(), ".cursor", "mcp.json");
|
|
249
|
+
if (!present)
|
|
250
|
+
return { tool: "cursor", where: "-", status: "skipped (Cursor not found on this machine)" };
|
|
242
251
|
try {
|
|
243
252
|
return { tool: "cursor", where: file, status: mergeMcpJson(file, entry) };
|
|
244
253
|
}
|
|
@@ -339,10 +348,94 @@ function isOurToolTable(t) {
|
|
|
339
348
|
return keys.length === 1 && /^approval_mode\s*=\s*"prompt"\s*$/.test(keys[0]);
|
|
340
349
|
}
|
|
341
350
|
const toolOf = (t) => (t.name && TOOL_TABLE.exec(t.name)?.[1]) ?? null;
|
|
351
|
+
const ENV_TABLE = `${SERVER_TABLE}.env`;
|
|
352
|
+
const isServerSubTable = (t) => !!t.name && t.name.startsWith(`${SERVER_TABLE}.`) && !toolOf(t) && !t.name.startsWith(`${SERVER_TABLE}.tools.`);
|
|
353
|
+
const DOTTED_ENV = /^\s*env\s*\.\s*(.+?)\s*=\s*(.*)$/;
|
|
354
|
+
function tomlValue(raw) {
|
|
355
|
+
const v = raw.trim();
|
|
356
|
+
const q = /^("(?:[^"\\]|\\.)*"|'[^']*')\s*(#.*)?$/.exec(v);
|
|
357
|
+
if (q)
|
|
358
|
+
return q[1];
|
|
359
|
+
return v.replace(/\s+#.*$/, "").trim();
|
|
360
|
+
}
|
|
361
|
+
function splitInline(body) {
|
|
362
|
+
const out = [];
|
|
363
|
+
let cur = "", quote = null, depth = 0;
|
|
364
|
+
for (let i = 0; i < body.length; i++) {
|
|
365
|
+
const ch = body[i];
|
|
366
|
+
if (quote) {
|
|
367
|
+
cur += ch;
|
|
368
|
+
if (ch === "\\" && quote === '"') {
|
|
369
|
+
cur += body[++i] ?? "";
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (ch === quote)
|
|
373
|
+
quote = null;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (ch === '"' || ch === "'") {
|
|
377
|
+
quote = ch;
|
|
378
|
+
cur += ch;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (ch === "[" || ch === "{")
|
|
382
|
+
depth++;
|
|
383
|
+
if (ch === "]" || ch === "}")
|
|
384
|
+
depth--;
|
|
385
|
+
if (ch === "," && depth === 0) {
|
|
386
|
+
if (cur.trim())
|
|
387
|
+
out.push(cur.trim());
|
|
388
|
+
cur = "";
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
cur += ch;
|
|
392
|
+
}
|
|
393
|
+
if (cur.trim())
|
|
394
|
+
out.push(cur.trim());
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
function hasMultiline(lines) {
|
|
398
|
+
return lines.some((l) => {
|
|
399
|
+
const i = l.indexOf("=");
|
|
400
|
+
if (i <= 0 || l.trim().startsWith("#"))
|
|
401
|
+
return false;
|
|
402
|
+
const v = l.slice(i + 1).trim();
|
|
403
|
+
return ['"""', "'''"].some((d) => v.startsWith(d) && !v.slice(3).includes(d));
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function theirEnv(tables) {
|
|
407
|
+
const out = [];
|
|
408
|
+
for (const t of tables) {
|
|
409
|
+
if (t.name === ENV_TABLE) {
|
|
410
|
+
for (const l of keyLines(t)) {
|
|
411
|
+
const i = l.indexOf("=");
|
|
412
|
+
if (i > 0)
|
|
413
|
+
out.push([l.slice(0, i).trim(), tomlValue(l.slice(i + 1))]);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (t.name === SERVER_TABLE) {
|
|
417
|
+
for (const l of t.lines.slice(1)) {
|
|
418
|
+
if (l.trim().startsWith("#"))
|
|
419
|
+
continue;
|
|
420
|
+
const m = DOTTED_ENV.exec(l);
|
|
421
|
+
if (m)
|
|
422
|
+
out.push([m[1].trim(), tomlValue(m[2])]);
|
|
423
|
+
const inline = /^\s*env\s*=\s*\{(.*)\}\s*(#.*)?$/.exec(l);
|
|
424
|
+
if (inline)
|
|
425
|
+
for (const pair of splitInline(inline[1])) {
|
|
426
|
+
const i = pair.indexOf("=");
|
|
427
|
+
if (i > 0)
|
|
428
|
+
out.push([pair.slice(0, i).trim(), pair.slice(i + 1).trim()]);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return out.filter(([k]) => k.replace(/^["']|["']$/g, "") !== "PANTHEON_AGENT");
|
|
434
|
+
}
|
|
342
435
|
export function codexKept(current, approval = "approve") {
|
|
343
436
|
const tables = parseTables(current);
|
|
344
437
|
const server = tables.find((t) => t.name === SERVER_TABLE);
|
|
345
|
-
const serverKeys = server ? keyLines(server).filter((l) => !OUR_SERVER_KEYS.test(l)).map((l) => l.split("=")[0].trim()) : [];
|
|
438
|
+
const serverKeys = server ? keyLines(server).filter((l) => !OUR_SERVER_KEYS.test(l) && !DOTTED_ENV.test(l)).map((l) => l.split("=")[0].trim()) : [];
|
|
346
439
|
const theirs = tables.filter((t) => toolOf(t) && !isOurToolTable(t));
|
|
347
440
|
const ownApproval = approval === "ask-before-sending"
|
|
348
441
|
? theirs.filter((t) => WRITE_TOOL_NAMES.includes(toolOf(t)) && keyLines(t).some((l) => !marked(l) && /^approval_mode\s*=/.test(l))).map((t) => toolOf(t))
|
|
@@ -352,11 +445,21 @@ export function codexKept(current, approval = "approve") {
|
|
|
352
445
|
export function upsertCodexToml(current, entry, approval = "approve") {
|
|
353
446
|
const tables = parseTables(current);
|
|
354
447
|
const server = tables.find((t) => t.name === SERVER_TABLE);
|
|
355
|
-
const
|
|
448
|
+
const envTable = tables.find((t) => t.name === ENV_TABLE);
|
|
449
|
+
const tableMode = !!envTable && hasMultiline(envTable.lines.slice(1));
|
|
450
|
+
const dottedMode = !tableMode && !!server && hasMultiline(server.lines.slice(1).filter((l) => DOTTED_ENV.test(l)));
|
|
451
|
+
const OUR_DOTTED = /^\s*env\s*\.\s*["']?PANTHEON_AGENT["']?\s*=/;
|
|
452
|
+
const kept = server ? server.lines.slice(1).filter((l) => (dottedMode || l.trim()) && !marked(l) && !OUR_SERVER_KEYS.test(l) && (dottedMode ? !OUR_DOTTED.test(l) : !DOTTED_ENV.test(l))) : [];
|
|
453
|
+
while (kept.length && !kept[kept.length - 1].trim())
|
|
454
|
+
kept.pop();
|
|
356
455
|
const theirsFor = new Map(tables.filter((t) => toolOf(t) && !isOurToolTable(t)).map((t) => [toolOf(t), t]));
|
|
357
456
|
const wantPrompt = approval === "ask-before-sending" ? WRITE_TOOL_NAMES : [];
|
|
457
|
+
const extraEnv = tableMode || dottedMode ? [] : theirEnv(tables);
|
|
458
|
+
const envLine = `env = { ${[`PANTHEON_AGENT = "codex"`, ...extraEnv.map(([k, v]) => `${k} = ${v}`)].join(", ")} }`;
|
|
358
459
|
const ourBlock = () => {
|
|
359
|
-
const
|
|
460
|
+
const block = codexTomlBlock(entry, undefined, "approve").split("\n")
|
|
461
|
+
.flatMap((l) => !/^env = /.test(l) ? [l] : tableMode ? [] : dottedMode ? [`env.PANTHEON_AGENT = "codex" ${OURS_MARK}`] : [envLine]);
|
|
462
|
+
const out = [...block, ...kept];
|
|
360
463
|
for (const t of wantPrompt)
|
|
361
464
|
if (!theirsFor.has(t))
|
|
362
465
|
out.push("", ...ourToolTable(t));
|
|
@@ -385,6 +488,13 @@ export function upsertCodexToml(current, entry, approval = "approve") {
|
|
|
385
488
|
placed = true;
|
|
386
489
|
continue;
|
|
387
490
|
}
|
|
491
|
+
if (t.name === ENV_TABLE) {
|
|
492
|
+
if (!tableMode)
|
|
493
|
+
continue;
|
|
494
|
+
const OUR_KEY = /^\s*["']?PANTHEON_AGENT["']?\s*=/;
|
|
495
|
+
out.push(t.lines[0], `PANTHEON_AGENT = "codex" ${OURS_MARK}`, ...t.lines.slice(1).filter((l) => !marked(l) && !OUR_KEY.test(l)));
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
388
498
|
if (toolOf(t)) {
|
|
389
499
|
if (isOurToolTable(t))
|
|
390
500
|
continue;
|
|
@@ -403,10 +513,16 @@ export function removeCodexToml(current) {
|
|
|
403
513
|
for (const t of parseTables(current)) {
|
|
404
514
|
if (t.name === SERVER_TABLE)
|
|
405
515
|
continue;
|
|
516
|
+
if (isServerSubTable(t))
|
|
517
|
+
continue;
|
|
406
518
|
if (toolOf(t)) {
|
|
407
519
|
if (isOurToolTable(t))
|
|
408
520
|
continue;
|
|
409
|
-
|
|
521
|
+
const lines = t.lines.filter((l) => !marked(l));
|
|
522
|
+
if (!lines.slice(1).some((l) => l.trim() && !l.trim().startsWith("#")))
|
|
523
|
+
continue;
|
|
524
|
+
out.push(`# ${OURS_MARK.slice(2)} leave: kept your setting below, commented out — [mcp_servers.pantheon] is gone, and this table alone would stop Codex starting`);
|
|
525
|
+
out.push(...lines.map((l) => (l.trim() ? `# ${l}` : l)));
|
|
410
526
|
continue;
|
|
411
527
|
}
|
|
412
528
|
out.push(...t.lines);
|
|
@@ -437,8 +553,10 @@ export function codexApprovalState(file = codexConfigFile()) {
|
|
|
437
553
|
return null;
|
|
438
554
|
}
|
|
439
555
|
}
|
|
440
|
-
function codexUserScope(entry, approval) {
|
|
556
|
+
function codexUserScope(entry, approval, present = true) {
|
|
441
557
|
const file = codexConfigFile();
|
|
558
|
+
if (!present)
|
|
559
|
+
return { tool: "codex", where: "-", status: "skipped (Codex not found on this machine)" };
|
|
442
560
|
const said = approval === "ask-before-sending"
|
|
443
561
|
? `${CODEX_APPROVAL_LINE}, with send/open_goal/claim set to prompt`
|
|
444
562
|
: CODEX_APPROVAL_LINE;
|
|
@@ -487,18 +605,100 @@ export function chosenApproval() {
|
|
|
487
605
|
return "approve";
|
|
488
606
|
}
|
|
489
607
|
}
|
|
490
|
-
export function installForUser(approval = chosenApproval()) {
|
|
608
|
+
export function installForUser(approval = chosenApproval(), present = agentPresence()) {
|
|
491
609
|
const entry = userEntry();
|
|
610
|
+
const said = (s) => /^(granted|already granted)/.test(s)
|
|
611
|
+
? `${s} — ${approval === "ask-before-sending" ? "reads run without asking; send, open_goal and claim prompt" : `the ${PANTHEON_TOOL_NAMES.length} pantheon tools run without asking`}`
|
|
612
|
+
: s;
|
|
492
613
|
return [
|
|
493
|
-
claudeUserScope(entry), cursorUserScope(entry), codexUserScope(entry, approval),
|
|
614
|
+
claudeUserScope(entry), cursorUserScope(entry, present.cursor), codexUserScope(entry, approval, present.codex),
|
|
494
615
|
copilotUserScope(entry), vsCodeUserScope(entry), kiroUserScope(entry, approval),
|
|
495
|
-
|
|
496
|
-
tool:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
616
|
+
present.claude
|
|
617
|
+
? { tool: "claude perms", where: claudeSettingsFile(), status: said(grantClaudeToolPermissions(undefined, approval)) }
|
|
618
|
+
: { tool: "claude perms", where: "-", status: "skipped (Claude Code not found on this machine)" },
|
|
619
|
+
present.cursor
|
|
620
|
+
? { tool: "cursor perms", where: cursorCliConfigFile(), status: said(grantCursorToolPermissions(undefined, approval)) }
|
|
621
|
+
: { tool: "cursor perms", where: "-", status: "skipped (Cursor not found on this machine)" },
|
|
622
|
+
];
|
|
623
|
+
}
|
|
624
|
+
export function isOurMcpEntry(e) {
|
|
625
|
+
if (!e || typeof e !== "object")
|
|
626
|
+
return false;
|
|
627
|
+
const { command, args } = e;
|
|
628
|
+
if (typeof command !== "string" || !Array.isArray(args) || args.at(-1) !== "mcp")
|
|
629
|
+
return false;
|
|
630
|
+
const base = command.split(/[\\/]/).pop() ?? "";
|
|
631
|
+
if (/^pantheon(\.cmd|\.exe)?$/i.test(base))
|
|
632
|
+
return true;
|
|
633
|
+
return args.length >= 2 && typeof args[0] === "string" && /[\\/]cli\.js$/.test(args[0]);
|
|
634
|
+
}
|
|
635
|
+
export function removeMcpJsonEntry(file, key = "mcpServers") {
|
|
636
|
+
try {
|
|
637
|
+
if (!existsSync(file))
|
|
638
|
+
return "absent";
|
|
639
|
+
const doc = readConfigJson(file);
|
|
640
|
+
const servers = doc[key];
|
|
641
|
+
if (!servers || !("pantheon" in servers))
|
|
642
|
+
return "absent";
|
|
643
|
+
if (!isOurMcpEntry(servers["pantheon"]))
|
|
644
|
+
return "not ours";
|
|
645
|
+
delete servers["pantheon"];
|
|
646
|
+
writeFileSync(file, JSON.stringify(doc, null, 2) + "\n");
|
|
647
|
+
return "removed";
|
|
648
|
+
}
|
|
649
|
+
catch (e) {
|
|
650
|
+
return `failed: ${e.message}`;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
export function removeUserMcpEntries(deps = {}) {
|
|
654
|
+
const removed = [], left = [];
|
|
655
|
+
const home = deps.home ?? homedir();
|
|
656
|
+
const exec = deps.exec ?? ((c, a, o) => execFileSync(c, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], ...o }) ?? "");
|
|
657
|
+
const os = deps.os ?? platform();
|
|
658
|
+
const claudeBin = deps.claudeBin === undefined ? which("claude") : deps.claudeBin;
|
|
659
|
+
const claudeRemove = (scope, what, cwd) => {
|
|
660
|
+
const rm = agentSpawn(claudeBin, ["mcp", "remove", "pantheon", "-s", scope], os);
|
|
661
|
+
try {
|
|
662
|
+
exec(rm.command, rm.args, { ...rm.options, ...(cwd ? { cwd } : {}) });
|
|
663
|
+
removed.push(what);
|
|
664
|
+
}
|
|
665
|
+
catch (e) {
|
|
666
|
+
const err = e;
|
|
667
|
+
const text = `${err.message} ${String(err.stderr ?? "")}`;
|
|
668
|
+
if (!/not found|no mcp server|does not exist/i.test(text))
|
|
669
|
+
left.push(`${what} (${text.trim().split("\n")[0]}) — remove it: ${cwd ? `in ${cwd}, ` : ""}claude mcp remove pantheon -s ${scope}`);
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
if (claudeBin) {
|
|
673
|
+
claudeRemove("user", "Claude Code's pantheon MCP server (user scope)");
|
|
674
|
+
if (os === "win32" && deps.project)
|
|
675
|
+
claudeRemove("local", `Claude Code's pantheon entry for ${deps.project} (local scope)`, deps.project);
|
|
676
|
+
}
|
|
677
|
+
else if (os === "win32" && deps.project) {
|
|
678
|
+
left.push(`any Claude Code entry for ${deps.project} (local scope) — claude is not on PATH here; once it is: claude mcp remove pantheon -s local, run in that folder`);
|
|
679
|
+
}
|
|
680
|
+
const files = [
|
|
681
|
+
["Cursor", join(home, ".cursor", "mcp.json"), "mcpServers"],
|
|
682
|
+
["Copilot", copilotMcpConfigFile(home), "mcpServers"],
|
|
683
|
+
["Kiro", kiroMcpConfigFile(home), "mcpServers"],
|
|
502
684
|
];
|
|
685
|
+
const vs = vsCodeUserDir(home);
|
|
686
|
+
if (vs)
|
|
687
|
+
files.push(["VS Code", join(vs, "mcp.json"), "servers"]);
|
|
688
|
+
for (const [who, file, key] of files) {
|
|
689
|
+
const r = removeMcpJsonEntry(file, key);
|
|
690
|
+
if (r === "removed")
|
|
691
|
+
removed.push(`${who}'s pantheon MCP entry (${file})`);
|
|
692
|
+
else if (r === "not ours")
|
|
693
|
+
left.push(`an entry named "pantheon" in ${file} that Pantheon did not write — yours to keep or delete`);
|
|
694
|
+
else if (r !== "absent")
|
|
695
|
+
left.push(`${who}'s pantheon MCP entry — ${r}; delete the "pantheon" server from ${file} by hand`);
|
|
696
|
+
}
|
|
697
|
+
const codex = removeCodexUserScope(deps.codexFile ?? join(home, ".codex", "config.toml"));
|
|
698
|
+
if (codex && /removed/.test(codex))
|
|
699
|
+
removed.push(codex.trim().replace(/^removed /, "Codex's "));
|
|
700
|
+
else if (codex)
|
|
701
|
+
left.push(codex.trim());
|
|
702
|
+
return { removed, left };
|
|
503
703
|
}
|
|
504
704
|
//# sourceMappingURL=install.js.map
|
package/dist/kiro-files.js
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
4
5
|
export const kiroDir = (home = homedir(), env = process.env) => env.KIRO_HOME || join(home, ".kiro");
|
|
5
6
|
export const KIRO_AGENT_NAME = "pantheon";
|
|
6
7
|
export const kiroAgentFile = (kiroBase) => join(kiroBase, "agents", `${KIRO_AGENT_NAME}.json`);
|
|
7
8
|
export const isOurKiroHook = (cmd) => !!cmd && /\bhook\s+kiro-(?:prompt|stop)\b/.test(cmd);
|
|
8
9
|
export const kiroAllowed = (tools) => tools.map((t) => `@pantheon/${t}`);
|
|
10
|
+
export const KIRO_AGENT_MARKER = "[written by pantheon: kiro-agent/1]";
|
|
11
|
+
const KIRO_AGENT_DESCRIPTION = "Kiro's default agent, plus Pantheon: its tools run without asking, and a turn is held open while a teammate is waiting on you";
|
|
12
|
+
export function gitTracks(file) {
|
|
13
|
+
try {
|
|
14
|
+
execFileSync("git", ["-C", dirname(file), "ls-files", "--error-unmatch", "--", basename(file)], { stdio: "ignore", timeout: 5_000 });
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
9
21
|
export function kiroAgentDoc(bin, allowed) {
|
|
10
22
|
return {
|
|
11
23
|
name: KIRO_AGENT_NAME,
|
|
12
|
-
description:
|
|
24
|
+
description: `${KIRO_AGENT_DESCRIPTION} ${KIRO_AGENT_MARKER}`,
|
|
13
25
|
tools: ["*"],
|
|
14
26
|
allowedTools: kiroAllowed(allowed),
|
|
15
27
|
includeMcpJson: true,
|
|
@@ -19,16 +31,21 @@ export function kiroAgentDoc(bin, allowed) {
|
|
|
19
31
|
},
|
|
20
32
|
};
|
|
21
33
|
}
|
|
22
|
-
export function readKiroAgent(file) {
|
|
34
|
+
export function readKiroAgent(file, tracked = gitTracks) {
|
|
23
35
|
if (!existsSync(file))
|
|
24
36
|
return null;
|
|
25
37
|
try {
|
|
26
38
|
const doc = JSON.parse(readFileSync(file, "utf8"));
|
|
27
|
-
const
|
|
28
|
-
|
|
39
|
+
const hooks = Object.entries(doc.hooks && typeof doc.hooks === "object" ? doc.hooks : {});
|
|
40
|
+
const events = hooks.filter(([, es]) => Array.isArray(es) && es.some((h) => isOurKiroHook(h?.command))).map(([e]) => e);
|
|
41
|
+
const onlyOurs = hooks.every(([, es]) => Array.isArray(es) && es.every((h) => isOurKiroHook(h?.command)));
|
|
42
|
+
const desc = typeof doc.description === "string" ? doc.description : "";
|
|
43
|
+
const marked = desc.includes(KIRO_AGENT_MARKER) || desc === KIRO_AGENT_DESCRIPTION;
|
|
44
|
+
const isTracked = tracked(file);
|
|
45
|
+
return { doc, ours: marked && onlyOurs && events.length > 0 && doc.name === KIRO_AGENT_NAME && !isTracked, tracked: isTracked, events };
|
|
29
46
|
}
|
|
30
47
|
catch {
|
|
31
|
-
return { doc: {}, ours: false, events: [] };
|
|
48
|
+
return { doc: {}, ours: false, tracked: tracked(file), events: [] };
|
|
32
49
|
}
|
|
33
50
|
}
|
|
34
51
|
export function writeKiroAgent(file, doc) {
|
package/dist/ledger.js
CHANGED
|
@@ -9,7 +9,13 @@ export class Ledger {
|
|
|
9
9
|
this.file = join(dir, `${space}.jsonl`);
|
|
10
10
|
mkdirSync(dirname(this.file), { recursive: true });
|
|
11
11
|
if (existsSync(this.file)) {
|
|
12
|
-
|
|
12
|
+
let text = readFileSync(this.file, "utf8");
|
|
13
|
+
if (text.length && !text.endsWith("\n")) {
|
|
14
|
+
const keep = text.lastIndexOf("\n") + 1;
|
|
15
|
+
text = text.slice(0, keep);
|
|
16
|
+
writeFileSync(this.file, text);
|
|
17
|
+
}
|
|
18
|
+
for (const line of text.split("\n")) {
|
|
13
19
|
if (!line.trim())
|
|
14
20
|
continue;
|
|
15
21
|
try {
|
|
@@ -55,7 +61,7 @@ export class Ledger {
|
|
|
55
61
|
const cut = Math.max(0, this.records.length - opts.keepRecent);
|
|
56
62
|
if (cut === 0)
|
|
57
63
|
return { dropped: 0, kept: this.records.length };
|
|
58
|
-
const kept = this.records.filter((r, i) => i >= cut || opts.isLive(r.goal));
|
|
64
|
+
const kept = this.records.filter((r, i) => i >= cut || opts.isLive(r.goal) || (opts.keep?.(r.id) ?? false));
|
|
59
65
|
const dropped = this.records.length - kept.length;
|
|
60
66
|
if (dropped === 0)
|
|
61
67
|
return { dropped: 0, kept: kept.length };
|