@node9/proxy 2.22.0 → 2.23.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/cli.mjs CHANGED
@@ -1006,6 +1006,7 @@ function normalizeCommandForPolicyImpl(command) {
1006
1006
  const source = command.slice(s, e);
1007
1007
  if (resolved === source) continue;
1008
1008
  if (resolved === "" || /\s/.test(resolved)) continue;
1009
+ if (/^[;&|()<>]+$/.test(resolved)) continue;
1009
1010
  rewrites.push([s, e, resolved]);
1010
1011
  const quoteOnly = source.replace(/['"]/g, "");
1011
1012
  if (quoteOnly !== source) quoteOnlyRewrites.push([s, e, quoteOnly]);
@@ -1355,23 +1356,147 @@ function extractLiteralArgs(callExpr) {
1355
1356
  const args = positionedArgs(words);
1356
1357
  return { name, flags, paths: args.map((a) => a.value), words, args };
1357
1358
  }
1359
+ function payloadKey(payload) {
1360
+ if (!assignmentTable || assignmentTable.size === 0) return payload;
1361
+ const bindings = [];
1362
+ for (const [name, rec] of assignmentTable) {
1363
+ if (rec.value !== null) bindings.push(`${name}=${rec.value}`);
1364
+ }
1365
+ return `${payload}\0${bindings.sort().join("")}`;
1366
+ }
1367
+ function claimPayload(payload) {
1368
+ if (!seenPayloads) return "ok";
1369
+ const key = payloadKey(payload);
1370
+ if (seenPayloads.has(key)) return "seen";
1371
+ if (payloadBudget <= 0) return "exhausted";
1372
+ payloadBudget--;
1373
+ seenPayloads.add(key);
1374
+ return "ok";
1375
+ }
1376
+ function recordTopLevelAssignments(f) {
1377
+ const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
1378
+ for (const stmt of stmts) recordTopLevelStmt(stmt);
1379
+ }
1380
+ function probeBinOp(src) {
1381
+ try {
1382
+ const cmd = syntax.NewParser().Parse(src, "probe")?.Stmts?.[0]?.Cmd;
1383
+ if (cmd && syntax.NodeType(cmd) === "BinaryCmd") return cmd.Op;
1384
+ } catch {
1385
+ }
1386
+ return null;
1387
+ }
1388
+ function recordTopLevelStmt(stmt) {
1389
+ if (!stmt || !stmt.Cmd) return false;
1390
+ const t = syntax.NodeType(stmt.Cmd);
1391
+ if (t === "BinaryCmd") {
1392
+ if (!AND_OR_OPS.has(stmt.Cmd.Op)) return false;
1393
+ if (!recordTopLevelStmt(stmt.Cmd.X)) return false;
1394
+ if (stmt.Cmd.Op === AND_OP) return recordTopLevelStmt(stmt.Cmd.Y);
1395
+ return true;
1396
+ }
1397
+ if (t !== "CallExpr" && t !== "DeclClause") return false;
1398
+ let at = 0;
1399
+ try {
1400
+ at = stmt.Pos().Offset();
1401
+ } catch {
1402
+ at = 0;
1403
+ }
1404
+ recordAssignments(stmt.Cmd, at);
1405
+ if (stmt.Negated) return false;
1406
+ if (t === "DeclClause") return ASSIGNMENT_HEADS.has(stmt.Cmd.Variant?.Value ?? "");
1407
+ return (stmt.Cmd.Args || []).length === 0 && (stmt.Cmd.Assigns || []).length > 0;
1408
+ }
1409
+ function recordAssignments(n, at) {
1410
+ if (!assignmentTable) return;
1411
+ const t = syntax.NodeType(n);
1412
+ let assigns = [];
1413
+ if (t === "CallExpr") {
1414
+ if ((n.Args || []).length > 0) return;
1415
+ assigns = n.Assigns || [];
1416
+ } else if (t === "DeclClause") {
1417
+ if (!ASSIGNMENT_HEADS.has(n.Variant?.Value ?? "")) return;
1418
+ assigns = (n.Args || []).filter((a) => syntax.NodeType(a) === "Assign");
1419
+ } else return;
1420
+ for (const a of assigns) {
1421
+ const name = a?.Name?.Value;
1422
+ if (!name || !a.Value || a.Append) continue;
1423
+ assignmentTable.set(name, { value: resolveWordLiteral(a.Value), at });
1424
+ }
1425
+ }
1426
+ function resolveTrivialSubst(part) {
1427
+ if (syntax.NodeType(part) !== "CmdSubst") return void 0;
1428
+ const stmts = part.Stmts || [];
1429
+ if (stmts.length !== 1) return void 0;
1430
+ const st = stmts[0];
1431
+ if ((st.Redirs || []).length > 0 || st.Negated || st.Background) return void 0;
1432
+ const cmd = st.Cmd;
1433
+ if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return void 0;
1434
+ if ((cmd.Assigns || []).length > 0) return void 0;
1435
+ const words = (cmd.Args || []).map((a) => resolveWordLiteral(a));
1436
+ if (words.length === 0 || words.some((w) => w === null)) return void 0;
1437
+ const head = baseWord(words[0]);
1438
+ const rest = words.slice(1);
1439
+ if (head === "echo") {
1440
+ let i = 0;
1441
+ while (i < rest.length && /^-[neE]+$/.test(rest[i])) i++;
1442
+ return rest.slice(i).join(" ");
1443
+ }
1444
+ if (head === "printf") {
1445
+ if (rest.length !== 2) return void 0;
1446
+ if (!/^%s(\\n)?$/.test(rest[0])) return void 0;
1447
+ return rest[1];
1448
+ }
1449
+ return void 0;
1450
+ }
1451
+ function recordedExpansion(name) {
1452
+ if (!assignmentTable || !name) return void 0;
1453
+ const rec = assignmentTable.get(name);
1454
+ if (rec === void 0 || rec.at >= currentStmtOffset) return void 0;
1455
+ return rec.value;
1456
+ }
1457
+ function isPlainParam(p) {
1458
+ if (syntax.NodeType(p) !== "ParamExp") return false;
1459
+ return !(p.Excl || p.Length || p.Width || p.Index || p.Slice || p.Repl || p.Exp);
1460
+ }
1461
+ function expandPlainParam(p) {
1462
+ if (!assignmentTable) return void 0;
1463
+ if (!isPlainParam(p)) return void 0;
1464
+ const recorded = recordedExpansion(p.Param?.Value);
1465
+ if (recorded !== void 0) return recorded;
1466
+ return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
1467
+ }
1358
1468
  function resolveWordLiteral(w) {
1359
1469
  const parts = w?.Parts || [];
1360
1470
  let s = "";
1361
1471
  for (const p of parts) {
1362
- const t = syntax.NodeType(p);
1363
- if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
1364
- else if (t === "SglQuoted") s += p.Value ?? "";
1365
- else if (t === "DblQuoted") {
1366
- const inner = p.Parts || [];
1367
- if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
1368
- s += inner.map((ip) => ip.Value ?? "").join("");
1369
- } else {
1370
- return null;
1371
- }
1472
+ const piece = resolvePart(p, false);
1473
+ if (piece === void 0 || piece === null) return null;
1474
+ s += piece;
1372
1475
  }
1373
1476
  return s;
1374
1477
  }
1478
+ function resolvePart(p, inQuotes) {
1479
+ const t = syntax.NodeType(p);
1480
+ if (t === "Lit") {
1481
+ const raw = p.Value ?? "";
1482
+ if (!inQuotes) return raw.replace(/\\(.)/g, "$1");
1483
+ return assignmentTable ? raw.replace(/\\([$`"\\])/g, "$1") : raw;
1484
+ }
1485
+ if (t === "SglQuoted") return p.Value ?? "";
1486
+ if (t === "ParamExp") return expandPlainParam(p);
1487
+ if (t === "CmdSubst") return assignmentTable ? resolveTrivialSubst(p) : void 0;
1488
+ if (t === "DblQuoted" && !inQuotes) {
1489
+ const inner = p.Parts || [];
1490
+ let out = "";
1491
+ for (const ip of inner) {
1492
+ const piece = resolvePart(ip, true);
1493
+ if (piece === void 0 || piece === null) return piece;
1494
+ out += piece;
1495
+ }
1496
+ return out;
1497
+ }
1498
+ return void 0;
1499
+ }
1375
1500
  function parseDestHost(token) {
1376
1501
  if (!token) return null;
1377
1502
  let t = token.trim();
@@ -1426,6 +1551,7 @@ function destTokensForBinary(binary, args) {
1426
1551
  case "ssh":
1427
1552
  return positionals.slice(0, 1);
1428
1553
  case "scp":
1554
+ case "rsync":
1429
1555
  return positionals.filter((p) => p.includes(":") || p.includes("@"));
1430
1556
  case "nc":
1431
1557
  case "ncat":
@@ -1615,17 +1741,35 @@ function analyzeFsOperationImpl(command, depth = 0) {
1615
1741
  const f = parseShared(command);
1616
1742
  if (f === PARSE_FAIL) return null;
1617
1743
  let result = null;
1744
+ const outerTable = assignmentTable;
1745
+ const outerOffset = currentStmtOffset;
1746
+ const outerSeen = seenPayloads;
1747
+ const outerBudget = payloadBudget;
1748
+ if (depth === 0) {
1749
+ seenPayloads = /* @__PURE__ */ new Set();
1750
+ payloadBudget = PAYLOAD_BUDGET;
1751
+ }
1752
+ assignmentTable = new Map(
1753
+ [...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
1754
+ );
1755
+ currentStmtOffset = Number.MAX_SAFE_INTEGER;
1618
1756
  try {
1757
+ recordTopLevelAssignments(f);
1619
1758
  syntax.Walk(f, (node) => {
1620
1759
  if (!node || result?.verdict === "block") return false;
1621
1760
  const n = node;
1622
1761
  const nodeType = syntax.NodeType(n);
1623
1762
  if (nodeType === "Stmt") {
1763
+ try {
1764
+ currentStmtOffset = n.Pos().Offset();
1765
+ } catch {
1766
+ currentStmtOffset = Number.MAX_SAFE_INTEGER;
1767
+ }
1624
1768
  result = stricter(result, jailedRedirectRead(n));
1625
1769
  return result?.verdict !== "block";
1626
1770
  }
1627
1771
  if (nodeType !== "CallExpr") return true;
1628
- const { name, flags, paths, words, args } = extractLiteralArgs(n);
1772
+ const { name, flags, paths, words } = extractLiteralArgs(n);
1629
1773
  if (!name) return true;
1630
1774
  if (name === "rm") {
1631
1775
  const flagStr = flags.join("").toLowerCase();
@@ -1654,9 +1798,30 @@ function analyzeFsOperationImpl(command, depth = 0) {
1654
1798
  }
1655
1799
  }
1656
1800
  }
1657
- if (depth < 1) {
1801
+ if (depth < 24 && name === "find") {
1802
+ for (const action of findActions(words, 0)) {
1803
+ const h = unwrapCommandHead(action);
1804
+ const inner = literalShellPayload(action.slice(h), baseWord(action[h]));
1805
+ if (inner === null) continue;
1806
+ const claim = claimPayload(inner);
1807
+ if (claim === "exhausted") {
1808
+ result = stricter(result, UNANALYSABLE_NESTING);
1809
+ continue;
1810
+ }
1811
+ if (claim === "seen") continue;
1812
+ const v = analyzeFsOperationImpl(inner, depth + 1);
1813
+ result = stricter(result, v);
1814
+ if (result?.verdict === "block") return false;
1815
+ }
1816
+ }
1817
+ if (depth < 24) {
1658
1818
  const payload = literalShellPayload(words, name);
1659
- if (payload !== null) {
1819
+ const claim = payload === null ? "seen" : claimPayload(payload);
1820
+ if (claim === "exhausted") {
1821
+ result = stricter(result, UNANALYSABLE_NESTING);
1822
+ return true;
1823
+ }
1824
+ if (payload !== null && claim === "ok") {
1660
1825
  const inner = analyzeFsOperationImpl(payload, depth + 1);
1661
1826
  if (inner) {
1662
1827
  result = inner;
@@ -1665,7 +1830,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1665
1830
  return true;
1666
1831
  }
1667
1832
  }
1668
- const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
1833
+ const readPaths = FS_READ_TOOLS.has(name) ? readerPaths(words, 0) : wrappedReadPaths(words, name);
1669
1834
  if (readPaths) {
1670
1835
  for (const p of readPaths) {
1671
1836
  result = stricter(result, matchSensitivePath2(p));
@@ -1680,6 +1845,11 @@ function analyzeFsOperationImpl(command, depth = 0) {
1680
1845
  return result;
1681
1846
  } catch {
1682
1847
  return null;
1848
+ } finally {
1849
+ assignmentTable = outerTable;
1850
+ currentStmtOffset = outerOffset;
1851
+ seenPayloads = outerSeen;
1852
+ if (depth === 0) payloadBudget = outerBudget;
1683
1853
  }
1684
1854
  }
1685
1855
  function stricter(a, b) {
@@ -1736,6 +1906,18 @@ function resolveCopyShape(words, h) {
1736
1906
  }
1737
1907
  return null;
1738
1908
  }
1909
+ function findAction(words, k) {
1910
+ const end = words.findIndex((w, i) => i > k && (w === ";" || w === "+"));
1911
+ return words.slice(k + 1, end < 0 ? words.length : end);
1912
+ }
1913
+ function findActions(words, h) {
1914
+ const out = [];
1915
+ for (let i = h + 1; i < words.length; i++) {
1916
+ const w = words[i];
1917
+ if (w !== null && FIND_EXEC_FLAGS.has(w)) out.push(findAction(words, i));
1918
+ }
1919
+ return out;
1920
+ }
1739
1921
  function findStartPoints(words, h) {
1740
1922
  const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
1741
1923
  if (k < 0) return { k, starts: [] };
@@ -1751,8 +1933,13 @@ function copySourcePaths(words) {
1751
1933
  if (fi >= 0) {
1752
1934
  const { k, starts } = findStartPoints(words, fi);
1753
1935
  if (k < 0) return [];
1754
- const action = unwrapCommandHead(words.slice(k + 1));
1755
- return resolveCopyShape(words.slice(k + 1), action) ? starts : [];
1936
+ const out = [];
1937
+ for (const action of findActions(words, fi)) {
1938
+ const h2 = unwrapCommandHead(action);
1939
+ if (!resolveCopyShape(action, h2)) continue;
1940
+ out.push(...starts, ...copySourcePaths(action));
1941
+ }
1942
+ return out;
1756
1943
  }
1757
1944
  if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
1758
1945
  const r = resolveCopyShape(words, h);
@@ -1929,6 +2116,15 @@ function flagEffect(token, shape, known) {
1929
2116
  }
1930
2117
  return NONE;
1931
2118
  }
2119
+ function readerPaths(words, h) {
2120
+ const head = baseWord(words[h]);
2121
+ const from = h + 1;
2122
+ const flags = words.slice(from).filter((w) => w !== null && w.startsWith("-"));
2123
+ return [
2124
+ ...readTargets(head, positionedArgs(words, from), flags, words, from),
2125
+ ...flagOperandFiles(head, words, from)
2126
+ ];
2127
+ }
1932
2128
  function readTargets(verb, args, flags, words = [], from = 1) {
1933
2129
  const shape = PATTERN_VERBS[verb];
1934
2130
  if (!shape) return args.map((a) => a.value);
@@ -2010,18 +2206,21 @@ function flagOperandFiles(verb, words, from) {
2010
2206
  function wrappedReadPaths(words, name) {
2011
2207
  if (name === "find") {
2012
2208
  const { k, starts } = findStartPoints(words, 0);
2013
- return k > 0 && isReaderWord(words[k + 1] ?? null) ? starts : null;
2209
+ if (k < 0) return null;
2210
+ const paths = [];
2211
+ let reads = false;
2212
+ for (const action of findActions(words, 0)) {
2213
+ const h2 = unwrapCommandHead(action);
2214
+ if (!isReaderWord(action[h2] ?? null)) continue;
2215
+ reads = true;
2216
+ paths.push(...readerPaths(action, h2));
2217
+ }
2218
+ return reads ? [...starts, ...paths] : paths;
2014
2219
  }
2015
2220
  if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
2016
2221
  const h = unwrapCommandHead(words);
2017
2222
  if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
2018
- const head = baseWord(words[h]);
2019
- const rest = words.slice(h + 1);
2020
- const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
2021
- return [
2022
- ...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
2023
- ...flagOperandFiles(head, words, h + 1)
2024
- ];
2223
+ return readerPaths(words, h);
2025
2224
  }
2026
2225
  function literalShellPayload(words, name) {
2027
2226
  const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
@@ -3784,7 +3983,7 @@ function* stringValues(obj, depth = 0) {
3784
3983
  }
3785
3984
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
3786
3985
  }
3787
- var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, MAX_BLAST_PATH, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, ENGINE_VERSION;
3986
+ var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, MAX_BLAST_PATH, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, ENGINE_VERSION;
3788
3987
  var init_dist = __esm({
3789
3988
  "packages/policy-engine/dist/index.mjs"() {
3790
3989
  "use strict";
@@ -5162,6 +5361,60 @@ var init_dist = __esm({
5162
5361
  "rsync"
5163
5362
  ]);
5164
5363
  VALUE_FLAGS = {
5364
+ // rsync 3.2.7, its own --help: every flag whose operand could be mistaken for
5365
+ // a host. `-e ssh` is the one that matters most (`ssh` is not the destination).
5366
+ rsync: /* @__PURE__ */ new Set([
5367
+ "-e",
5368
+ "--rsh",
5369
+ "-f",
5370
+ "--filter",
5371
+ "-T",
5372
+ "--temp-dir",
5373
+ "-B",
5374
+ "--block-size",
5375
+ "-M",
5376
+ "--remote-option",
5377
+ "--exclude",
5378
+ "--exclude-from",
5379
+ "--include",
5380
+ "--include-from",
5381
+ "--files-from",
5382
+ "--compare-dest",
5383
+ "--copy-dest",
5384
+ "--link-dest",
5385
+ "--partial-dir",
5386
+ "--log-file",
5387
+ "--password-file",
5388
+ "--bwlimit",
5389
+ "--timeout",
5390
+ "--contimeout",
5391
+ "--port",
5392
+ "--sockopts",
5393
+ "--address",
5394
+ "--chmod",
5395
+ "--chown",
5396
+ "--max-size",
5397
+ "--min-size",
5398
+ "--modify-window",
5399
+ "--out-format",
5400
+ "--log-file-format",
5401
+ "--backup-dir",
5402
+ "--suffix",
5403
+ "--iconv",
5404
+ "--max-delete",
5405
+ "--checksum-choice",
5406
+ "--info",
5407
+ "--debug",
5408
+ "--stderr",
5409
+ "--outbuf",
5410
+ "--skip-compress",
5411
+ "--usermap",
5412
+ "--groupmap",
5413
+ "--mkpath",
5414
+ "--write-batch",
5415
+ "--read-batch",
5416
+ "--only-write-batch"
5417
+ ]),
5165
5418
  curl: /* @__PURE__ */ new Set([
5166
5419
  "-d",
5167
5420
  "--data",
@@ -5245,6 +5498,22 @@ var init_dist = __esm({
5245
5498
  ]),
5246
5499
  nc: /* @__PURE__ */ new Set(["-p", "-s", "-w", "-X", "-x", "-e", "-g", "-G", "-i", "-O", "-T", "-q", "-m"])
5247
5500
  };
5501
+ HOME_VARIABLES = /* @__PURE__ */ new Set(["HOME", "USERPROFILE"]);
5502
+ assignmentTable = null;
5503
+ currentStmtOffset = Number.MAX_SAFE_INTEGER;
5504
+ PAYLOAD_BUDGET = 256;
5505
+ payloadBudget = 0;
5506
+ seenPayloads = null;
5507
+ UNANALYSABLE_NESTING = {
5508
+ ruleName: "review-unanalysable-nesting",
5509
+ verdict: "review",
5510
+ reason: "This command nests more wrapped shell payloads than the policy engine will unwrap, so some of what it runs was not read.",
5511
+ path: ""
5512
+ };
5513
+ ASSIGNMENT_HEADS = /* @__PURE__ */ new Set(["export", "declare", "local", "readonly", "typeset"]);
5514
+ AND_OP = probeBinOp("a && b");
5515
+ OR_OP = probeBinOp("a || b");
5516
+ AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
5248
5517
  FS_OP_CACHE_MAX = 5e3;
5249
5518
  fsOpCache = /* @__PURE__ */ new Map();
5250
5519
  stripDotSlash = (p) => p.replace(/^\.\//, "");
@@ -6321,7 +6590,7 @@ var init_dist = __esm({
6321
6590
  { view: "separators-stripped", decoder: "separators", stripped: true }
6322
6591
  ];
6323
6592
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
6324
- CANONICAL_EXTRACTOR_VERSION = "canonical-v17";
6593
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v18";
6325
6594
  DEDUPE_PREVIEW_LEN = 120;
6326
6595
  ENGINE_VERSION = "1.4.0";
6327
6596
  }
@@ -10710,115 +10979,284 @@ var init_core = __esm({
10710
10979
  }
10711
10980
  });
10712
10981
 
10713
- // src/utils/platform-shell.ts
10714
- function locatorCommand() {
10715
- return process.platform === "win32" ? "where" : "which";
10716
- }
10717
- function shellInvocation(command) {
10718
- if (process.platform === "win32") {
10719
- return {
10720
- file: process.env.ComSpec || "cmd.exe",
10721
- args: ["/d", "/s", "/c", command]
10722
- };
10723
- }
10724
- return { file: "/bin/bash", args: ["-c", command] };
10725
- }
10726
- var init_platform_shell = __esm({
10727
- "src/utils/platform-shell.ts"() {
10728
- "use strict";
10729
- }
10730
- });
10731
-
10732
- // src/codex-trust.ts
10982
+ // src/agent-wiring.ts
10733
10983
  import fs16 from "fs";
10734
10984
  import path17 from "path";
10735
10985
  import os13 from "os";
10736
- import { spawnSync } from "child_process";
10986
+ import * as yaml from "yaml";
10737
10987
  import { parse as parseToml } from "smol-toml";
10738
- function readTomlSafe(filePath) {
10988
+ function readJson(filePath) {
10989
+ if (!fs16.existsSync(filePath)) return null;
10739
10990
  try {
10740
- return parseToml(fs16.readFileSync(filePath, "utf-8"));
10991
+ return JSON.parse(fs16.readFileSync(filePath, "utf-8"));
10741
10992
  } catch {
10742
- return null;
10993
+ return "invalid";
10743
10994
  }
10744
10995
  }
10745
- function lastCodexAuditTs(auditLogPath) {
10746
- let text;
10996
+ function matchersHaveNode9Hook(matchers) {
10997
+ return (matchers ?? []).some((m) => (m.hooks ?? []).some((h) => isNode9Hook(h.command)));
10998
+ }
10999
+ function flatHaveNode9Hook(entries) {
11000
+ return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
11001
+ }
11002
+ function readHookRoot(filePath, format) {
11003
+ if (!fs16.existsSync(filePath)) return "absent";
11004
+ let raw;
10747
11005
  try {
10748
- text = fs16.readFileSync(auditLogPath, "utf-8");
11006
+ raw = fs16.readFileSync(filePath, "utf-8");
10749
11007
  } catch {
10750
- return null;
11008
+ return "absent";
10751
11009
  }
10752
- let newest = null;
10753
- for (const line of text.split("\n")) {
10754
- if (!line.includes('"Codex"')) continue;
10755
- try {
10756
- const row = JSON.parse(line);
10757
- if (row.agent !== "Codex" || typeof row.ts !== "string") continue;
10758
- if (newest === null || row.ts > newest) newest = row.ts;
10759
- } catch {
10760
- }
11010
+ try {
11011
+ const parsed = format === "yaml" ? yaml.parse(raw) : JSON.parse(raw);
11012
+ return parsed?.hooks ?? {};
11013
+ } catch {
11014
+ return "invalid";
10761
11015
  }
10762
- return newest;
10763
11016
  }
10764
- function assessCodexTrustFrom(input) {
10765
- const { hooksDisabled, trustEntries, hooksWrittenAt, lastCodexActivityAt } = input;
10766
- let state;
10767
- if (hooksDisabled) state = "disabled";
10768
- else if (trustEntries === 0) state = "never-trusted";
10769
- else if (hooksWrittenAt && lastCodexActivityAt && lastCodexActivityAt > hooksWrittenAt) {
10770
- state = "observed";
10771
- } else state = "unverified";
10772
- return { state, hooksWrittenAt, lastCodexActivityAt, trustEntries };
11017
+ function eventWired(root, ev, format) {
11018
+ const arr = root[ev.key];
11019
+ if (format === "matcher") return matchersHaveNode9Hook(arr);
11020
+ return flatHaveNode9Hook(arr);
10773
11021
  }
10774
- function assessCodexTrust(home = os13.homedir(), auditLogPath = path17.join(home, ".node9", "audit.log")) {
10775
- const hooksPath = path17.join(home, ".codex", "hooks.json");
10776
- const configPath = path17.join(home, ".codex", "config.toml");
10777
- let hooksWrittenAt = null;
11022
+ function detectMcp(servers) {
11023
+ const entries = Object.entries(servers ?? {});
11024
+ const present = entries.some(([, s]) => s?.command === "node9");
11025
+ const wrapped = entries.filter(([, s]) => s?.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
11026
+ return { wrapped, present };
11027
+ }
11028
+ function readMcpServers(filePath, format) {
11029
+ if (!fs16.existsSync(filePath)) return {};
10778
11030
  try {
10779
- hooksWrittenAt = fs16.statSync(hooksPath).mtime.toISOString();
11031
+ if (format === "toml") {
11032
+ const parsed2 = parseToml(fs16.readFileSync(filePath, "utf-8"));
11033
+ return parsed2?.mcp_servers ?? {};
11034
+ }
11035
+ const parsed = readJson(filePath);
11036
+ if (parsed === null || parsed === "invalid") return {};
11037
+ return parsed.mcpServers ?? {};
10780
11038
  } catch {
10781
- hooksWrittenAt = null;
11039
+ return {};
10782
11040
  }
10783
- const config = readTomlSafe(configPath);
10784
- const hooksDisabled = config?.features?.hooks === false || config?.codex_hooks === false;
10785
- const trustEntries = Object.keys(config?.hooks?.state ?? {}).length;
10786
- return assessCodexTrustFrom({
10787
- hooksDisabled,
10788
- trustEntries,
10789
- hooksWrittenAt,
10790
- lastCodexActivityAt: lastCodexAuditTs(auditLogPath)
10791
- });
10792
11041
  }
10793
- function findCodexTui(env = process.env) {
11042
+ function readMcp(filePath, format) {
11043
+ if (!fs16.existsSync(filePath)) return { wrapped: [], present: false };
10794
11044
  try {
10795
- const r = spawnSync(locatorCommand(), ["codex"], { encoding: "utf-8", timeout: 3e3 });
10796
- const first = (r.stdout ?? "").split(/\r?\n/).find((l) => l.trim());
10797
- if (r.status === 0 && first) return "codex";
11045
+ if (format === "toml") {
11046
+ const parsed2 = parseToml(fs16.readFileSync(filePath, "utf-8"));
11047
+ return detectMcp(parsed2?.mcp_servers);
11048
+ }
11049
+ const parsed = readJson(filePath);
11050
+ if (parsed === null || parsed === "invalid") return { wrapped: [], present: false };
11051
+ return detectMcp(parsed.mcpServers);
10798
11052
  } catch {
11053
+ return { wrapped: [], present: false };
10799
11054
  }
10800
- if (process.platform === "win32" && env.LOCALAPPDATA) {
10801
- const binDir = path17.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin");
10802
- try {
10803
- for (const d of fs16.readdirSync(binDir)) {
10804
- const exe = path17.join(binDir, d, "codex.exe");
10805
- if (fs16.existsSync(exe)) return `"${exe}"`;
11055
+ }
11056
+ function getAgentWiring(home = os13.homedir()) {
11057
+ const detected = detectAgents(home);
11058
+ return AGENT_SPECS.map((spec) => {
11059
+ const present = spec.present(home);
11060
+ const pad = spec.labelPad ?? DEFAULT_LABEL_PAD;
11061
+ let hooks;
11062
+ let wireState;
11063
+ let hookLabel;
11064
+ let settingsPath;
11065
+ if (spec.shimFile) {
11066
+ const shimWired = exists(spec.shimFile(home));
11067
+ hooks = [{ label: "node9 plugin (node9 check)", wired: shimWired }];
11068
+ wireState = shimWired ? "wired" : present ? "unwired" : "absent";
11069
+ hookLabel = "node9 plugin";
11070
+ settingsPath = spec.shimFile(home);
11071
+ } else {
11072
+ const root = spec.hookFile ? readHookRoot(spec.hookFile(home), spec.hookFormat) : "absent";
11073
+ const primary = spec.hookEvents[0];
11074
+ const rootPresent = root !== "absent" && root !== "invalid";
11075
+ hooks = spec.hookEvents.map((ev) => ({
11076
+ label: hookLabelOf(ev, pad),
11077
+ wired: rootPresent && eventWired(root, ev, spec.hookFormat)
11078
+ }));
11079
+ if (root === "absent") wireState = "absent";
11080
+ else if (root === "invalid") wireState = "invalid";
11081
+ else wireState = primary && eventWired(root, primary, spec.hookFormat) ? "wired" : "unwired";
11082
+ hookLabel = primary ? `${primary.key} hook` : "MCP proxy";
11083
+ settingsPath = spec.hookFile ? spec.hookFile(home) : spec.mcpFile ? spec.mcpFile(home) : "";
11084
+ }
11085
+ const mcp = spec.mcpFile ? readMcp(spec.mcpFile(home), spec.mcpFormat ?? "json") : null;
11086
+ const anyHookWired = hooks.some((h) => h.wired);
11087
+ return {
11088
+ id: spec.id,
11089
+ label: spec.label,
11090
+ setupCommand: spec.setupCommand,
11091
+ installed: detected[spec.id],
11092
+ present,
11093
+ hooks,
11094
+ wireState,
11095
+ hookLabel,
11096
+ settingsPath,
11097
+ configFormat: spec.hookFormat === "yaml" ? "YAML" : "JSON",
11098
+ mcpServers: mcp ? mcp.wrapped : null,
11099
+ mcpProtected: mcp ? mcp.present : false,
11100
+ isProtected: anyHookWired || (mcp?.present ?? false)
11101
+ };
11102
+ });
11103
+ }
11104
+ var exists, ck, lg, DEFAULT_LABEL_PAD, hookLabelOf, AGENT_SPECS;
11105
+ var init_agent_wiring = __esm({
11106
+ "src/agent-wiring.ts"() {
11107
+ "use strict";
11108
+ init_setup();
11109
+ exists = (p) => {
11110
+ try {
11111
+ return fs16.existsSync(p);
11112
+ } catch {
11113
+ return false;
10806
11114
  }
10807
- } catch {
11115
+ };
11116
+ ck = (key) => ({ key, kind: "check" });
11117
+ lg = (key) => ({ key, kind: "log" });
11118
+ DEFAULT_LABEL_PAD = 11;
11119
+ hookLabelOf = (ev, pad) => `${ev.key.padEnd(pad)} (node9 ${ev.kind})`;
11120
+ AGENT_SPECS = [
11121
+ {
11122
+ id: "claude",
11123
+ label: "Claude Code",
11124
+ setupCommand: "node9 agents add claude",
11125
+ hookFile: (h) => path17.join(h, ".claude", "settings.json"),
11126
+ hookFormat: "matcher",
11127
+ // UserPromptSubmit is prompt DLP. setup.ts has written it for Claude since
11128
+ // that shipped; the spec must name it too, or status/doctor never show the
11129
+ // row and heal (which repairs via setupAgent) has no signal it is missing.
11130
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
11131
+ mcpFile: (h) => path17.join(h, ".claude.json"),
11132
+ present: (h) => exists(path17.join(h, ".claude", "settings.json")) || exists(path17.join(h, ".claude.json"))
11133
+ },
11134
+ {
11135
+ id: "gemini",
11136
+ label: "Gemini CLI",
11137
+ setupCommand: "node9 agents add gemini",
11138
+ hookFile: (h) => path17.join(h, ".gemini", "settings.json"),
11139
+ hookFormat: "matcher",
11140
+ hookEvents: [ck("BeforeTool"), lg("AfterTool")],
11141
+ mcpFile: (h) => path17.join(h, ".gemini", "settings.json"),
11142
+ present: (h) => exists(path17.join(h, ".gemini", "settings.json"))
11143
+ },
11144
+ {
11145
+ id: "codex",
11146
+ label: "Codex",
11147
+ setupCommand: "node9 agents add codex",
11148
+ hookFile: (h) => path17.join(h, ".codex", "hooks.json"),
11149
+ hookFormat: "matcher",
11150
+ hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
11151
+ mcpFile: (h) => path17.join(h, ".codex", "config.toml"),
11152
+ mcpFormat: "toml",
11153
+ present: (h) => exists(path17.join(h, ".codex"))
11154
+ },
11155
+ {
11156
+ id: "antigravity",
11157
+ label: "Antigravity",
11158
+ setupCommand: "node9 agents add antigravity",
11159
+ hookFile: (h) => path17.join(h, ".gemini", "config", "hooks.json"),
11160
+ hookFormat: "matcher",
11161
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
11162
+ mcpFile: (h) => path17.join(h, ".gemini", "config", "mcp_config.json"),
11163
+ present: (h) => exists(path17.join(h, ".gemini", "config", "hooks.json")) || exists(path17.join(h, ".gemini", "antigravity-cli")) || exists(path17.join(h, ".gemini", "antigravity-ide"))
11164
+ },
11165
+ {
11166
+ id: "copilot",
11167
+ label: "GitHub Copilot",
11168
+ setupCommand: "node9 agents add copilot",
11169
+ hookFile: (h) => path17.join(h, ".copilot", "hooks", "node9.json"),
11170
+ hookFormat: "flat",
11171
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
11172
+ mcpFile: (h) => path17.join(h, ".copilot", "mcp-config.json"),
11173
+ present: (h) => exists(path17.join(h, ".copilot"))
11174
+ },
11175
+ {
11176
+ id: "cursor",
11177
+ label: "Cursor",
11178
+ setupCommand: "node9 agents add cursor",
11179
+ // MCP-only — no hook file (see note above).
11180
+ hookFormat: "flat",
11181
+ hookEvents: [],
11182
+ mcpFile: (h) => path17.join(h, ".cursor", "mcp.json"),
11183
+ present: (h) => exists(path17.join(h, ".cursor", "mcp.json"))
11184
+ },
11185
+ {
11186
+ id: "hermes",
11187
+ label: "Hermes Agent",
11188
+ setupCommand: "node9 agents add hermes",
11189
+ hookFile: (h) => hermesConfigPath(h),
11190
+ hookFormat: "yaml",
11191
+ hookEvents: [ck("pre_tool_call"), lg("post_tool_call")],
11192
+ labelPad: 14,
11193
+ // 'post_tool_call' is wider than the default
11194
+ present: (h) => exists(hermesConfigPath(h))
11195
+ },
11196
+ {
11197
+ // Plugin-shim agents — protected by a node9-authored plugin/extension file
11198
+ // (no hooks, no MCP). hookFormat is unused for these (shimFile drives it).
11199
+ id: "opencode",
11200
+ label: "OpenCode",
11201
+ setupCommand: "node9 agents add opencode",
11202
+ hookFormat: "flat",
11203
+ hookEvents: [],
11204
+ shimFile: (h) => path17.join(opencodeConfigDir(h), "plugins", "node9.js"),
11205
+ present: (h) => exists(opencodeConfigDir(h)) || exists(path17.join(opencodeConfigDir(h), "plugins", "node9.js"))
11206
+ },
11207
+ {
11208
+ id: "pi",
11209
+ label: "Pi",
11210
+ setupCommand: "node9 agents add pi",
11211
+ hookFormat: "flat",
11212
+ hookEvents: [],
11213
+ shimFile: (h) => path17.join(h, ".pi", "agent", "extensions", "node9.js"),
11214
+ present: (h) => exists(path17.join(h, ".pi", "agent")) || exists(path17.join(h, ".pi", "agent", "extensions", "node9.js"))
11215
+ }
11216
+ ];
11217
+ }
11218
+ });
11219
+
11220
+ // src/mcp-cmd.ts
11221
+ function tokenize4(cmd) {
11222
+ const tokens = [];
11223
+ let current = "";
11224
+ let inDouble = false;
11225
+ let quoted = false;
11226
+ let i = 0;
11227
+ while (i < cmd.length) {
11228
+ const ch = cmd[i];
11229
+ if (inDouble) {
11230
+ if (ch === '"') inDouble = false;
11231
+ else if (ch === "\\" && i + 1 < cmd.length) current += cmd[++i];
11232
+ else current += ch;
11233
+ } else if (ch === '"') {
11234
+ inDouble = true;
11235
+ quoted = true;
11236
+ } else if (ch === " " || ch === " ") {
11237
+ if (current || quoted) {
11238
+ tokens.push(current);
11239
+ current = "";
11240
+ quoted = false;
11241
+ }
11242
+ } else if (ch === "\\" && i + 1 < cmd.length) {
11243
+ current += cmd[++i];
11244
+ } else {
11245
+ current += ch;
10808
11246
  }
11247
+ i++;
10809
11248
  }
10810
- return null;
11249
+ if (current || quoted && !inDouble) tokens.push(current);
11250
+ return tokens;
10811
11251
  }
10812
- function codexTrustInstruction(tui = findCodexTui()) {
10813
- const where = tui ? `run ${tui} in a terminal` : "run the Codex CLI (`codex`) in a terminal \u2014 the desktop app has no hook review screen";
10814
- return ` \u279C Codex must trust these hooks once: ${where},
10815
- then choose "Trust all and continue" when Codex asks to review them.
10816
- Until then Codex runs unprotected. Every rewrite of hooks.json needs this again.`;
11252
+ function quoteArg(s) {
11253
+ if (s === "") return '""';
11254
+ if (/[\s"\\]/.test(s)) return `"${s.replace(/(["\\])/g, "\\$1")}"`;
11255
+ return s;
10817
11256
  }
10818
- var init_codex_trust = __esm({
10819
- "src/codex-trust.ts"() {
11257
+ var init_mcp_cmd = __esm({
11258
+ "src/mcp-cmd.ts"() {
10820
11259
  "use strict";
10821
- init_platform_shell();
10822
11260
  }
10823
11261
  });
10824
11262
 
@@ -10979,13 +11417,253 @@ var init_mcp_pin = __esm({
10979
11417
  }
10980
11418
  });
10981
11419
 
10982
- // src/daemon/hook-baseline.ts
11420
+ // src/mcp-wrap.ts
10983
11421
  import fs18 from "fs";
10984
- import path19 from "path";
10985
11422
  import os15 from "os";
11423
+ import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
11424
+ function isNode9Command(command) {
11425
+ return /(^|[\\/])node9(\.(exe|cmd|ps1|bat))?$/i.test(command ?? "");
11426
+ }
11427
+ function classifyMcp(s) {
11428
+ if (isNode9Command(s.command)) {
11429
+ return (s.args ?? [])[0] === "mcp-gateway" ? "gatewayed" : "node9-self";
11430
+ }
11431
+ if (typeof s.command !== "string" || s.command.trim() === "") return "remote";
11432
+ return "ungoverned";
11433
+ }
11434
+ function mcpUpstreamString(s) {
11435
+ return [s.command ?? "", ...s.args ?? []].map(quoteArg).join(" ");
11436
+ }
11437
+ function toGateway(s, configName) {
11438
+ const upstream = mcpUpstreamString(s);
11439
+ const nameArgs = configName && !configName.startsWith("-") ? ["--config-name", configName] : [];
11440
+ return {
11441
+ ...s,
11442
+ command: "node9",
11443
+ args: ["mcp-gateway", ...nameArgs, "--upstream", upstream]
11444
+ };
11445
+ }
11446
+ function fromGateway(s) {
11447
+ if (!isNode9Command(s.command) || (s.args ?? [])[0] !== "mcp-gateway") return null;
11448
+ const args = s.args ?? [];
11449
+ const i = args.indexOf("--upstream");
11450
+ if (i < 0 || !args[i + 1]) return null;
11451
+ const [command, ...rest] = tokenize4(args[i + 1]);
11452
+ if (!command) return null;
11453
+ return { ...s, command, args: rest };
11454
+ }
11455
+ function inventoryMcp(home = os15.homedir()) {
11456
+ const out = [];
11457
+ for (const spec of AGENT_SPECS) {
11458
+ if (!spec.mcpFile) continue;
11459
+ const mcpFile = spec.mcpFile(home);
11460
+ const format = spec.mcpFormat ?? "json";
11461
+ const servers = readMcpServers(mcpFile, format);
11462
+ for (const [name, s] of Object.entries(servers)) {
11463
+ if (!s || typeof s !== "object") continue;
11464
+ out.push({
11465
+ agent: String(spec.id),
11466
+ agentLabel: spec.label,
11467
+ mcpFile,
11468
+ format,
11469
+ name,
11470
+ command: s.command ?? "",
11471
+ args: Array.isArray(s.args) ? s.args : [],
11472
+ state: classifyMcp(s),
11473
+ raw: s
11474
+ });
11475
+ }
11476
+ }
11477
+ return out;
11478
+ }
11479
+ function isCorruptedUpstream(upstream) {
11480
+ if (!upstream) return false;
11481
+ if (upstream.includes("\\") || upstream.includes("/")) return false;
11482
+ return /(^|\s)[A-Za-z]:[^\s]/.test(upstream);
11483
+ }
11484
+ function findCorruptedMcpWraps(home = os15.homedir()) {
11485
+ const out = [];
11486
+ for (const e of inventoryMcp(home)) {
11487
+ if (e.state !== "gatewayed") continue;
11488
+ const i = e.args.indexOf("--upstream");
11489
+ const upstream = i >= 0 ? e.args[i + 1] ?? "" : "";
11490
+ if (!isCorruptedUpstream(upstream)) continue;
11491
+ out.push({
11492
+ agent: e.agent,
11493
+ agentLabel: e.agentLabel,
11494
+ mcpFile: e.mcpFile,
11495
+ name: e.name,
11496
+ upstream
11497
+ });
11498
+ }
11499
+ return out;
11500
+ }
11501
+ function inventoryServerKeys(inv) {
11502
+ const keys = /* @__PURE__ */ new Set();
11503
+ for (const e of inv) {
11504
+ if (e.state === "gatewayed") {
11505
+ const i = e.args.indexOf("--upstream");
11506
+ if (i >= 0 && e.args[i + 1]) {
11507
+ keys.add(getServerKey(e.args[i + 1]));
11508
+ }
11509
+ } else if (e.state === "ungoverned") {
11510
+ const cmd = [e.command, ...e.args].map(quoteArg).join(" ");
11511
+ keys.add(getServerKey(cmd));
11512
+ }
11513
+ }
11514
+ return keys;
11515
+ }
11516
+ function writeMcpEntry(mcpFile, format, name, entry) {
11517
+ const key = format === "toml" ? "mcp_servers" : "mcpServers";
11518
+ let root = {};
11519
+ if (fs18.existsSync(mcpFile)) {
11520
+ const raw = fs18.readFileSync(mcpFile, "utf-8");
11521
+ root = format === "toml" ? parseToml2(raw) : JSON.parse(raw);
11522
+ const bak = `${mcpFile}.node9-bak`;
11523
+ try {
11524
+ fs18.writeFileSync(bak, raw, { mode: 384, flag: "wx" });
11525
+ } catch (e) {
11526
+ if (e.code !== "EEXIST") throw e;
11527
+ }
11528
+ }
11529
+ const existing = root[key];
11530
+ const servers = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
11531
+ servers[name] = entry;
11532
+ root[key] = servers;
11533
+ const serialized = format === "toml" ? stringifyToml(root) : JSON.stringify(root, null, 2);
11534
+ const tmp = `${mcpFile}.${process.pid}.tmp`;
11535
+ fs18.writeFileSync(tmp, serialized, { mode: 384 });
11536
+ fs18.renameSync(tmp, mcpFile);
11537
+ }
11538
+ var init_mcp_wrap = __esm({
11539
+ "src/mcp-wrap.ts"() {
11540
+ "use strict";
11541
+ init_agent_wiring();
11542
+ init_mcp_cmd();
11543
+ init_mcp_pin();
11544
+ init_mcp_cmd();
11545
+ }
11546
+ });
11547
+
11548
+ // src/utils/platform-shell.ts
11549
+ function locatorCommand() {
11550
+ return process.platform === "win32" ? "where" : "which";
11551
+ }
11552
+ function shellInvocation(command) {
11553
+ if (process.platform === "win32") {
11554
+ return {
11555
+ file: process.env.ComSpec || "cmd.exe",
11556
+ args: ["/d", "/s", "/c", command]
11557
+ };
11558
+ }
11559
+ return { file: "/bin/bash", args: ["-c", command] };
11560
+ }
11561
+ var init_platform_shell = __esm({
11562
+ "src/utils/platform-shell.ts"() {
11563
+ "use strict";
11564
+ }
11565
+ });
11566
+
11567
+ // src/codex-trust.ts
11568
+ import fs19 from "fs";
11569
+ import path19 from "path";
11570
+ import os16 from "os";
11571
+ import { spawnSync } from "child_process";
11572
+ import { parse as parseToml3 } from "smol-toml";
11573
+ function readTomlSafe(filePath) {
11574
+ try {
11575
+ return parseToml3(fs19.readFileSync(filePath, "utf-8"));
11576
+ } catch {
11577
+ return null;
11578
+ }
11579
+ }
11580
+ function lastCodexAuditTs(auditLogPath) {
11581
+ let text;
11582
+ try {
11583
+ text = fs19.readFileSync(auditLogPath, "utf-8");
11584
+ } catch {
11585
+ return null;
11586
+ }
11587
+ let newest = null;
11588
+ for (const line of text.split("\n")) {
11589
+ if (!line.includes('"Codex"')) continue;
11590
+ try {
11591
+ const row = JSON.parse(line);
11592
+ if (row.agent !== "Codex" || typeof row.ts !== "string") continue;
11593
+ if (newest === null || row.ts > newest) newest = row.ts;
11594
+ } catch {
11595
+ }
11596
+ }
11597
+ return newest;
11598
+ }
11599
+ function assessCodexTrustFrom(input) {
11600
+ const { hooksDisabled, trustEntries, hooksWrittenAt, lastCodexActivityAt } = input;
11601
+ let state;
11602
+ if (hooksDisabled) state = "disabled";
11603
+ else if (trustEntries === 0) state = "never-trusted";
11604
+ else if (hooksWrittenAt && lastCodexActivityAt && lastCodexActivityAt > hooksWrittenAt) {
11605
+ state = "observed";
11606
+ } else state = "unverified";
11607
+ return { state, hooksWrittenAt, lastCodexActivityAt, trustEntries };
11608
+ }
11609
+ function assessCodexTrust(home = os16.homedir(), auditLogPath = path19.join(home, ".node9", "audit.log")) {
11610
+ const hooksPath = path19.join(home, ".codex", "hooks.json");
11611
+ const configPath = path19.join(home, ".codex", "config.toml");
11612
+ let hooksWrittenAt = null;
11613
+ try {
11614
+ hooksWrittenAt = fs19.statSync(hooksPath).mtime.toISOString();
11615
+ } catch {
11616
+ hooksWrittenAt = null;
11617
+ }
11618
+ const config = readTomlSafe(configPath);
11619
+ const hooksDisabled = config?.features?.hooks === false || config?.codex_hooks === false;
11620
+ const trustEntries = Object.keys(config?.hooks?.state ?? {}).length;
11621
+ return assessCodexTrustFrom({
11622
+ hooksDisabled,
11623
+ trustEntries,
11624
+ hooksWrittenAt,
11625
+ lastCodexActivityAt: lastCodexAuditTs(auditLogPath)
11626
+ });
11627
+ }
11628
+ function findCodexTui(env = process.env) {
11629
+ try {
11630
+ const r = spawnSync(locatorCommand(), ["codex"], { encoding: "utf-8", timeout: 3e3 });
11631
+ const first = (r.stdout ?? "").split(/\r?\n/).find((l) => l.trim());
11632
+ if (r.status === 0 && first) return "codex";
11633
+ } catch {
11634
+ }
11635
+ if (process.platform === "win32" && env.LOCALAPPDATA) {
11636
+ const binDir = path19.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin");
11637
+ try {
11638
+ for (const d of fs19.readdirSync(binDir)) {
11639
+ const exe = path19.join(binDir, d, "codex.exe");
11640
+ if (fs19.existsSync(exe)) return `"${exe}"`;
11641
+ }
11642
+ } catch {
11643
+ }
11644
+ }
11645
+ return null;
11646
+ }
11647
+ function codexTrustInstruction(tui = findCodexTui()) {
11648
+ const where = tui ? `run ${tui} in a terminal` : "run the Codex CLI (`codex`) in a terminal \u2014 the desktop app has no hook review screen";
11649
+ return ` \u279C Codex must trust these hooks once: ${where},
11650
+ then choose "Trust all and continue" when Codex asks to review them.
11651
+ Until then Codex runs unprotected. Every rewrite of hooks.json needs this again.`;
11652
+ }
11653
+ var init_codex_trust = __esm({
11654
+ "src/codex-trust.ts"() {
11655
+ "use strict";
11656
+ init_platform_shell();
11657
+ }
11658
+ });
11659
+
11660
+ // src/daemon/hook-baseline.ts
11661
+ import fs20 from "fs";
11662
+ import path20 from "path";
11663
+ import os17 from "os";
10986
11664
  function loadHookBaseline() {
10987
11665
  try {
10988
- const raw = JSON.parse(fs18.readFileSync(BASELINE_FILE, "utf-8"));
11666
+ const raw = JSON.parse(fs20.readFileSync(BASELINE_FILE, "utf-8"));
10989
11667
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
10990
11668
  } catch {
10991
11669
  return {};
@@ -10993,9 +11671,9 @@ function loadHookBaseline() {
10993
11671
  }
10994
11672
  function saveHookBaseline(b) {
10995
11673
  try {
10996
- const dir = path19.dirname(BASELINE_FILE);
10997
- if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
10998
- fs18.writeFileSync(BASELINE_FILE, JSON.stringify(b, null, 2), { mode: 384 });
11674
+ const dir = path20.dirname(BASELINE_FILE);
11675
+ if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
11676
+ fs20.writeFileSync(BASELINE_FILE, JSON.stringify(b, null, 2), { mode: 384 });
10999
11677
  } catch {
11000
11678
  }
11001
11679
  }
@@ -11017,14 +11695,14 @@ function seedHookBaselineIfEmpty(governedNow, now) {
11017
11695
  function clearHookBaseline() {
11018
11696
  for (const f of [BASELINE_FILE, NOTIFIED_FILE]) {
11019
11697
  try {
11020
- fs18.rmSync(f, { force: true });
11698
+ fs20.rmSync(f, { force: true });
11021
11699
  } catch {
11022
11700
  }
11023
11701
  }
11024
11702
  }
11025
11703
  function loadNotified() {
11026
11704
  try {
11027
- const raw = JSON.parse(fs18.readFileSync(NOTIFIED_FILE, "utf-8"));
11705
+ const raw = JSON.parse(fs20.readFileSync(NOTIFIED_FILE, "utf-8"));
11028
11706
  return new Set(Array.isArray(raw) ? raw : []);
11029
11707
  } catch {
11030
11708
  return /* @__PURE__ */ new Set();
@@ -11032,9 +11710,9 @@ function loadNotified() {
11032
11710
  }
11033
11711
  function saveNotified(s) {
11034
11712
  try {
11035
- const dir = path19.dirname(NOTIFIED_FILE);
11036
- if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
11037
- fs18.writeFileSync(NOTIFIED_FILE, JSON.stringify([...s]), { mode: 384 });
11713
+ const dir = path20.dirname(NOTIFIED_FILE);
11714
+ if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
11715
+ fs20.writeFileSync(NOTIFIED_FILE, JSON.stringify([...s]), { mode: 384 });
11038
11716
  } catch {
11039
11717
  }
11040
11718
  }
@@ -11042,8 +11720,8 @@ var BASELINE_FILE, NOTIFIED_FILE;
11042
11720
  var init_hook_baseline = __esm({
11043
11721
  "src/daemon/hook-baseline.ts"() {
11044
11722
  "use strict";
11045
- BASELINE_FILE = path19.join(os15.homedir(), ".node9", "hooks-baseline.json");
11046
- NOTIFIED_FILE = path19.join(os15.homedir(), ".node9", "hook-heal-notified.json");
11723
+ BASELINE_FILE = path20.join(os17.homedir(), ".node9", "hooks-baseline.json");
11724
+ NOTIFIED_FILE = path20.join(os17.homedir(), ".node9", "hook-heal-notified.json");
11047
11725
  }
11048
11726
  });
11049
11727
 
@@ -11396,13 +12074,13 @@ var init_setup_pi_shim = __esm({
11396
12074
  });
11397
12075
 
11398
12076
  // src/setup.ts
11399
- import fs19 from "fs";
11400
- import path20 from "path";
11401
- import os16 from "os";
12077
+ import fs21 from "fs";
12078
+ import path21 from "path";
12079
+ import os18 from "os";
11402
12080
  import chalk from "chalk";
11403
12081
  import { confirm as rawConfirm } from "@inquirer/prompts";
11404
- import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
11405
- import * as yaml from "yaml";
12082
+ import { parse as parseToml4, stringify as stringifyToml2 } from "smol-toml";
12083
+ import * as yaml2 from "yaml";
11406
12084
  function isNonInteractive() {
11407
12085
  return process.env.NODE9_NONINTERACTIVE === "1";
11408
12086
  }
@@ -11458,7 +12136,7 @@ function printInlineAskNotice() {
11458
12136
  )
11459
12137
  );
11460
12138
  }
11461
- function fullPathCommand(subcommand, platform = process.platform, home = os16.homedir()) {
12139
+ function fullPathCommand(subcommand, platform = process.platform, home = os18.homedir()) {
11462
12140
  if (process.env.NODE9_TESTING === "1") return `node9 ${subcommand}`;
11463
12141
  const nodeExec = toForwardSlashes(process.execPath);
11464
12142
  const cliScript = toForwardSlashes(process.argv[1]);
@@ -11470,8 +12148,8 @@ function fullPathCommand(subcommand, platform = process.platform, home = os16.ho
11470
12148
  ensureHookShim(home, nodeExec, cliScript);
11471
12149
  return `"${toForwardSlashes(hookShimPath(home))}" ${subcommand}`;
11472
12150
  }
11473
- function hookShimPath(home = os16.homedir()) {
11474
- return path20.join(home, ".node9", "bin", "hook");
12151
+ function hookShimPath(home = os18.homedir()) {
12152
+ return path21.join(home, ".node9", "bin", "hook");
11475
12153
  }
11476
12154
  function hookShimBody(nodeExec, cliScript) {
11477
12155
  return `#!/bin/sh
@@ -11485,12 +12163,12 @@ function ensureHookShim(home, nodeExec, cliScript) {
11485
12163
  const shim = hookShimPath(home);
11486
12164
  const body = hookShimBody(nodeExec, cliScript);
11487
12165
  try {
11488
- if (fs19.readFileSync(shim, "utf-8") === body) return false;
12166
+ if (fs21.readFileSync(shim, "utf-8") === body) return false;
11489
12167
  } catch {
11490
12168
  }
11491
- fs19.mkdirSync(path20.dirname(shim), { recursive: true });
11492
- fs19.writeFileSync(shim, body, { mode: 493 });
11493
- fs19.chmodSync(shim, 493);
12169
+ fs21.mkdirSync(path21.dirname(shim), { recursive: true });
12170
+ fs21.writeFileSync(shim, body, { mode: 493 });
12171
+ fs21.chmodSync(shim, 493);
11494
12172
  return true;
11495
12173
  }
11496
12174
  function toForwardSlashes(p) {
@@ -11509,7 +12187,7 @@ function isStaleHookCommand(command) {
11509
12187
  while ((m = re.exec(command)) !== null) tokens.push(m[1] ?? m[2] ?? "");
11510
12188
  for (const tok of tokens) {
11511
12189
  if (!tok.startsWith("/") && !/^[A-Za-z]:\//.test(tok)) continue;
11512
- if (!fs19.existsSync(tok)) return true;
12190
+ if (!fs21.existsSync(tok)) return true;
11513
12191
  }
11514
12192
  return false;
11515
12193
  }
@@ -11529,19 +12207,19 @@ function isChurnProneHookForm(command, platform = process.platform) {
11529
12207
  function needsRewrite(command, platform = process.platform) {
11530
12208
  return isStaleHookCommand(command) || isLegacyHookFormat(command) || isWindowsQuoteBrokenHook(command, platform) || isChurnProneHookForm(command, platform);
11531
12209
  }
11532
- function readJson(filePath) {
12210
+ function readJson2(filePath) {
11533
12211
  try {
11534
- if (fs19.existsSync(filePath)) {
11535
- return JSON.parse(fs19.readFileSync(filePath, "utf-8"));
12212
+ if (fs21.existsSync(filePath)) {
12213
+ return JSON.parse(fs21.readFileSync(filePath, "utf-8"));
11536
12214
  }
11537
12215
  } catch {
11538
12216
  }
11539
12217
  return null;
11540
12218
  }
11541
12219
  function writeJson(filePath, data) {
11542
- const dir = path20.dirname(filePath);
11543
- if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
11544
- fs19.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
12220
+ const dir = path21.dirname(filePath);
12221
+ if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
12222
+ fs21.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
11545
12223
  }
11546
12224
  function mcpWrapArgs(upstream) {
11547
12225
  return [MCP_WRAP_SUBCOMMAND, "--upstream", upstream];
@@ -11561,6 +12239,14 @@ function repairLegacyMcpWraps(servers) {
11561
12239
  }
11562
12240
  return repaired;
11563
12241
  }
12242
+ function isCodexAppManagedServer(name, server) {
12243
+ if (!server) return false;
12244
+ const cmd = (server.command ?? "").replace(/\\/g, "/").toLowerCase();
12245
+ if (cmd.includes("/openai/codex/runtimes/")) return true;
12246
+ const envKeys = Object.keys(server.env ?? {});
12247
+ if (envKeys.some((k) => k.startsWith("NODE_REPL_") || k === "CODEX_CLI_PATH")) return true;
12248
+ return name === "node_repl";
12249
+ }
11564
12250
  function isNode9Hook(cmd) {
11565
12251
  if (!cmd) return false;
11566
12252
  return /(?:^|[\s/\\"])node9"? (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js"? (?:check|log)/.test(cmd) || // The shim form: "<home>/.node9/bin/hook" check. Without this alternative a
@@ -11570,11 +12256,11 @@ function isNode9Hook(cmd) {
11570
12256
  /[/\\]\.node9[/\\]bin[/\\]hook"? (?:check|log)/.test(cmd);
11571
12257
  }
11572
12258
  function teardownClaude() {
11573
- const homeDir2 = os16.homedir();
11574
- const hooksPath = path20.join(homeDir2, ".claude", "settings.json");
11575
- const mcpPath = path20.join(homeDir2, ".claude", ".mcp.json");
12259
+ const homeDir2 = os18.homedir();
12260
+ const hooksPath = path21.join(homeDir2, ".claude", "settings.json");
12261
+ const mcpPath = path21.join(homeDir2, ".claude", ".mcp.json");
11576
12262
  let changed = false;
11577
- const settings = readJson(hooksPath);
12263
+ const settings = readJson2(hooksPath);
11578
12264
  if (settings?.hooks) {
11579
12265
  for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
11580
12266
  const before = settings.hooks[event]?.length ?? 0;
@@ -11593,7 +12279,7 @@ function teardownClaude() {
11593
12279
  console.log(chalk.blue(" \u2139\uFE0F No Node9 hooks found in ~/.claude/settings.json"));
11594
12280
  }
11595
12281
  }
11596
- const claudeConfig = readJson(mcpPath);
12282
+ const claudeConfig = readJson2(mcpPath);
11597
12283
  if (claudeConfig?.mcpServers) {
11598
12284
  let mcpChanged = false;
11599
12285
  if (removeNode9McpServer(claudeConfig.mcpServers)) {
@@ -11620,9 +12306,9 @@ function teardownClaude() {
11620
12306
  }
11621
12307
  }
11622
12308
  function teardownGemini() {
11623
- const homeDir2 = os16.homedir();
11624
- const settingsPath = path20.join(homeDir2, ".gemini", "settings.json");
11625
- const settings = readJson(settingsPath);
12309
+ const homeDir2 = os18.homedir();
12310
+ const settingsPath = path21.join(homeDir2, ".gemini", "settings.json");
12311
+ const settings = readJson2(settingsPath);
11626
12312
  if (!settings) {
11627
12313
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
11628
12314
  return;
@@ -11664,9 +12350,9 @@ function teardownGemini() {
11664
12350
  }
11665
12351
  }
11666
12352
  function teardownCursor() {
11667
- const homeDir2 = os16.homedir();
11668
- const mcpPath = path20.join(homeDir2, ".cursor", "mcp.json");
11669
- const mcpConfig = readJson(mcpPath);
12353
+ const homeDir2 = os18.homedir();
12354
+ const mcpPath = path21.join(homeDir2, ".cursor", "mcp.json");
12355
+ const mcpConfig = readJson2(mcpPath);
11670
12356
  if (!mcpConfig?.mcpServers) {
11671
12357
  console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
11672
12358
  return;
@@ -11697,11 +12383,11 @@ function teardownCursor() {
11697
12383
  }
11698
12384
  async function setupClaude() {
11699
12385
  seedMcpPinsIfMissing();
11700
- const homeDir2 = os16.homedir();
11701
- const mcpPath = path20.join(homeDir2, ".claude", ".mcp.json");
11702
- const hooksPath = path20.join(homeDir2, ".claude", "settings.json");
11703
- const claudeConfig = readJson(mcpPath) ?? {};
11704
- const settings = readJson(hooksPath) ?? {};
12386
+ const homeDir2 = os18.homedir();
12387
+ const mcpPath = path21.join(homeDir2, ".claude", ".mcp.json");
12388
+ const hooksPath = path21.join(homeDir2, ".claude", "settings.json");
12389
+ const claudeConfig = readJson2(mcpPath) ?? {};
12390
+ const settings = readJson2(hooksPath) ?? {};
11705
12391
  const servers = claudeConfig.mcpServers ?? {};
11706
12392
  let hooksChanged = false;
11707
12393
  let anythingChanged = false;
@@ -11822,7 +12508,7 @@ async function setupClaude() {
11822
12508
  const serversToWrap = [];
11823
12509
  for (const [name, server] of Object.entries(servers)) {
11824
12510
  if (!server.command || server.command === "node9") continue;
11825
- const upstream = [server.command, ...server.args ?? []].join(" ");
12511
+ const upstream = mcpUpstreamString(server);
11826
12512
  serversToWrap.push({ name, upstream });
11827
12513
  }
11828
12514
  if (serversToWrap.length > 0) {
@@ -11875,9 +12561,9 @@ async function setupGemini() {
11875
12561
  );
11876
12562
  console.log("");
11877
12563
  seedMcpPinsIfMissing();
11878
- const homeDir2 = os16.homedir();
11879
- const settingsPath = path20.join(homeDir2, ".gemini", "settings.json");
11880
- const settings = readJson(settingsPath) ?? {};
12564
+ const homeDir2 = os18.homedir();
12565
+ const settingsPath = path21.join(homeDir2, ".gemini", "settings.json");
12566
+ const settings = readJson2(settingsPath) ?? {};
11881
12567
  const servers = settings.mcpServers ?? {};
11882
12568
  let hooksChanged = false;
11883
12569
  let anythingChanged = false;
@@ -11944,7 +12630,7 @@ async function setupGemini() {
11944
12630
  const serversToWrap = [];
11945
12631
  for (const [name, server] of Object.entries(servers)) {
11946
12632
  if (!server.command || server.command === "node9") continue;
11947
- const upstream = [server.command, ...server.args ?? []].join(" ");
12633
+ const upstream = mcpUpstreamString(server);
11948
12634
  serversToWrap.push({ name, upstream });
11949
12635
  }
11950
12636
  if (serversToWrap.length > 0) {
@@ -11990,11 +12676,11 @@ async function setupGemini() {
11990
12676
  }
11991
12677
  async function setupAntigravity() {
11992
12678
  seedMcpPinsIfMissing();
11993
- const homeDir2 = os16.homedir();
11994
- const hooksPath = path20.join(homeDir2, ".gemini", "config", "hooks.json");
11995
- const mcpPath = path20.join(homeDir2, ".gemini", "config", "mcp_config.json");
11996
- const hooksFile = readJson(hooksPath) ?? {};
11997
- const mcpConfig = readJson(mcpPath) ?? {};
12679
+ const homeDir2 = os18.homedir();
12680
+ const hooksPath = path21.join(homeDir2, ".gemini", "config", "hooks.json");
12681
+ const mcpPath = path21.join(homeDir2, ".gemini", "config", "mcp_config.json");
12682
+ const hooksFile = readJson2(hooksPath) ?? {};
12683
+ const mcpConfig = readJson2(mcpPath) ?? {};
11998
12684
  const servers = mcpConfig.mcpServers ?? {};
11999
12685
  let hooksChanged = false;
12000
12686
  let anythingChanged = false;
@@ -12087,7 +12773,7 @@ async function setupAntigravity() {
12087
12773
  }
12088
12774
  anythingChanged = true;
12089
12775
  }
12090
- const legacySettings = readJson(path20.join(homeDir2, ".gemini", "settings.json"));
12776
+ const legacySettings = readJson2(path21.join(homeDir2, ".gemini", "settings.json"));
12091
12777
  const legacyHasNode9 = ["BeforeTool", "AfterTool"].some(
12092
12778
  (ev) => legacySettings?.hooks?.[ev]?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)))
12093
12779
  );
@@ -12158,11 +12844,11 @@ async function setupAntigravity() {
12158
12844
  }
12159
12845
  }
12160
12846
  function teardownAntigravity() {
12161
- const homeDir2 = os16.homedir();
12162
- const hooksPath = path20.join(homeDir2, ".gemini", "config", "hooks.json");
12163
- const mcpPath = path20.join(homeDir2, ".gemini", "config", "mcp_config.json");
12847
+ const homeDir2 = os18.homedir();
12848
+ const hooksPath = path21.join(homeDir2, ".gemini", "config", "hooks.json");
12849
+ const mcpPath = path21.join(homeDir2, ".gemini", "config", "mcp_config.json");
12164
12850
  let changed = false;
12165
- const hooksFile = readJson(hooksPath);
12851
+ const hooksFile = readJson2(hooksPath);
12166
12852
  if (hooksFile?.hooks) {
12167
12853
  for (const event of ["PreToolUse", "PostToolUse"]) {
12168
12854
  const before = hooksFile.hooks[event]?.length ?? 0;
@@ -12183,7 +12869,7 @@ function teardownAntigravity() {
12183
12869
  } else {
12184
12870
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/config/hooks.json not found \u2014 nothing to remove"));
12185
12871
  }
12186
- const mcpConfig = readJson(mcpPath);
12872
+ const mcpConfig = readJson2(mcpPath);
12187
12873
  if (mcpConfig?.mcpServers) {
12188
12874
  let mcpChanged = false;
12189
12875
  if (removeNode9McpServer(mcpConfig.mcpServers)) {
@@ -12212,13 +12898,13 @@ function teardownAntigravity() {
12212
12898
  }
12213
12899
  async function setupCopilot() {
12214
12900
  seedMcpPinsIfMissing();
12215
- const homeDir2 = os16.homedir();
12216
- const hooksPath = path20.join(homeDir2, ".copilot", "hooks", "node9.json");
12217
- const mcpPath = path20.join(homeDir2, ".copilot", "mcp-config.json");
12218
- const hooksFile = readJson(hooksPath) ?? { version: 1 };
12901
+ const homeDir2 = os18.homedir();
12902
+ const hooksPath = path21.join(homeDir2, ".copilot", "hooks", "node9.json");
12903
+ const mcpPath = path21.join(homeDir2, ".copilot", "mcp-config.json");
12904
+ const hooksFile = readJson2(hooksPath) ?? { version: 1 };
12219
12905
  if (!hooksFile.version) hooksFile.version = 1;
12220
12906
  if (!hooksFile.hooks) hooksFile.hooks = {};
12221
- const mcpConfig = readJson(mcpPath) ?? {};
12907
+ const mcpConfig = readJson2(mcpPath) ?? {};
12222
12908
  const servers = mcpConfig.mcpServers ?? {};
12223
12909
  let hooksChanged = false;
12224
12910
  let anythingChanged = false;
@@ -12315,10 +13001,10 @@ async function setupCopilot() {
12315
13001
  printInlineAskNotice();
12316
13002
  }
12317
13003
  function teardownCopilot() {
12318
- const homeDir2 = os16.homedir();
12319
- const hooksPath = path20.join(homeDir2, ".copilot", "hooks", "node9.json");
12320
- const mcpPath = path20.join(homeDir2, ".copilot", "mcp-config.json");
12321
- const hooksFile = readJson(hooksPath);
13004
+ const homeDir2 = os18.homedir();
13005
+ const hooksPath = path21.join(homeDir2, ".copilot", "hooks", "node9.json");
13006
+ const mcpPath = path21.join(homeDir2, ".copilot", "mcp-config.json");
13007
+ const hooksFile = readJson2(hooksPath);
12322
13008
  let changed = false;
12323
13009
  if (hooksFile?.hooks) {
12324
13010
  for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
@@ -12330,7 +13016,7 @@ function teardownCopilot() {
12330
13016
  if (changed) {
12331
13017
  if (Object.keys(hooksFile.hooks).length === 0) {
12332
13018
  try {
12333
- fs19.unlinkSync(hooksPath);
13019
+ fs21.unlinkSync(hooksPath);
12334
13020
  console.log(chalk.green(" \u2705 Removed ~/.copilot/hooks/node9.json"));
12335
13021
  } catch {
12336
13022
  writeJson(hooksPath, hooksFile);
@@ -12345,7 +13031,7 @@ function teardownCopilot() {
12345
13031
  } else {
12346
13032
  console.log(chalk.blue(" \u2139\uFE0F ~/.copilot/hooks/node9.json not found \u2014 nothing to remove"));
12347
13033
  }
12348
- const mcpConfig = readJson(mcpPath);
13034
+ const mcpConfig = readJson2(mcpPath);
12349
13035
  if (mcpConfig?.mcpServers) {
12350
13036
  let mcpChanged = false;
12351
13037
  if (removeNode9McpServer(mcpConfig.mcpServers)) {
@@ -12372,9 +13058,9 @@ function teardownCopilot() {
12372
13058
  }
12373
13059
  }
12374
13060
  }
12375
- function claudeDesktopConfigPath(homeDir2 = os16.homedir()) {
13061
+ function claudeDesktopConfigPath(homeDir2 = os18.homedir()) {
12376
13062
  if (process.platform === "darwin") {
12377
- return path20.join(
13063
+ return path21.join(
12378
13064
  homeDir2,
12379
13065
  "Library",
12380
13066
  "Application Support",
@@ -12383,47 +13069,47 @@ function claudeDesktopConfigPath(homeDir2 = os16.homedir()) {
12383
13069
  );
12384
13070
  }
12385
13071
  if (process.platform === "linux") {
12386
- return path20.join(homeDir2, ".config", "Claude", "claude_desktop_config.json");
13072
+ return path21.join(homeDir2, ".config", "Claude", "claude_desktop_config.json");
12387
13073
  }
12388
13074
  if (process.platform === "win32") {
12389
- const appData = process.env.APPDATA || path20.join(homeDir2, "AppData", "Roaming");
12390
- return path20.join(appData, "Claude", "claude_desktop_config.json");
13075
+ const appData = process.env.APPDATA || path21.join(homeDir2, "AppData", "Roaming");
13076
+ return path21.join(appData, "Claude", "claude_desktop_config.json");
12391
13077
  }
12392
13078
  return null;
12393
13079
  }
12394
- function opencodeConfigDir(home = os16.homedir()) {
13080
+ function opencodeConfigDir(home = os18.homedir()) {
12395
13081
  const xdg = process.env.XDG_CONFIG_HOME;
12396
- const base = xdg && path20.isAbsolute(xdg) ? xdg : path20.join(home, ".config");
12397
- return path20.join(base, "opencode");
13082
+ const base = xdg && path21.isAbsolute(xdg) ? xdg : path21.join(home, ".config");
13083
+ return path21.join(base, "opencode");
12398
13084
  }
12399
13085
  function commonBinDirs(home) {
12400
13086
  return [
12401
- path20.join(home, ".local", "bin"),
12402
- path20.join(home, ".bun", "bin"),
12403
- path20.join(home, ".npm-global", "bin"),
13087
+ path21.join(home, ".local", "bin"),
13088
+ path21.join(home, ".bun", "bin"),
13089
+ path21.join(home, ".npm-global", "bin"),
12404
13090
  "/usr/local/bin",
12405
13091
  "/opt/homebrew/bin"
12406
13092
  ];
12407
13093
  }
12408
- function binaryInPath(binary, home = os16.homedir()) {
13094
+ function binaryInPath(binary, home = os18.homedir()) {
12409
13095
  const pathEnv = process.env.PATH ?? "";
12410
- const dirs = [...pathEnv.split(path20.delimiter), ...commonBinDirs(home)];
13096
+ const dirs = [...pathEnv.split(path21.delimiter), ...commonBinDirs(home)];
12411
13097
  const seen = /* @__PURE__ */ new Set();
12412
13098
  for (const dir of dirs) {
12413
13099
  if (!dir || seen.has(dir)) continue;
12414
13100
  seen.add(dir);
12415
13101
  try {
12416
- fs19.accessSync(path20.join(dir, binary), fs19.constants.X_OK);
13102
+ fs21.accessSync(path21.join(dir, binary), fs21.constants.X_OK);
12417
13103
  return true;
12418
13104
  } catch {
12419
13105
  }
12420
13106
  }
12421
13107
  return false;
12422
13108
  }
12423
- function detectAgents(homeDir2 = os16.homedir()) {
13109
+ function detectAgents(homeDir2 = os18.homedir()) {
12424
13110
  const exists2 = (p) => {
12425
13111
  try {
12426
- return fs19.existsSync(p);
13112
+ return fs21.existsSync(p);
12427
13113
  } catch (err2) {
12428
13114
  const code = err2.code;
12429
13115
  if (code !== "ENOENT") {
@@ -12435,7 +13121,7 @@ function detectAgents(homeDir2 = os16.homedir()) {
12435
13121
  };
12436
13122
  const desktopPath = claudeDesktopConfigPath(homeDir2);
12437
13123
  return {
12438
- claude: exists2(path20.join(homeDir2, ".claude")) || exists2(path20.join(homeDir2, ".claude.json")),
13124
+ claude: exists2(path21.join(homeDir2, ".claude")) || exists2(path21.join(homeDir2, ".claude.json")),
12439
13125
  // Antigravity (agy) shares the ~/.gemini root, so a bare
12440
13126
  // `exists(~/.gemini)` would report the (EOL'd) Gemini CLI as
12441
13127
  // installed on every agy machine — and `node9 init` would then
@@ -12445,19 +13131,19 @@ function detectAgents(homeDir2 = os16.homedir()) {
12445
13131
  // creates ~/.gemini/settings.json on first run; agy does not touch
12446
13132
  // it (it uses antigravity-cli/settings.json) — that file is the
12447
13133
  // legacy-CLI discriminator.
12448
- gemini: exists2(path20.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini", homeDir2),
13134
+ gemini: exists2(path21.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini", homeDir2),
12449
13135
  // agy creates ~/.gemini/antigravity-cli/ on first launch; the IDE
12450
13136
  // creates antigravity-ide/. PATH fallback covers installed-but-
12451
13137
  // never-launched (same class as opencode #186).
12452
- antigravity: exists2(path20.join(homeDir2, ".gemini", "antigravity-cli")) || exists2(path20.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy", homeDir2),
13138
+ antigravity: exists2(path21.join(homeDir2, ".gemini", "antigravity-cli")) || exists2(path21.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy", homeDir2),
12453
13139
  // GitHub Copilot CLI creates ~/.copilot on first launch; PATH
12454
13140
  // fallback covers installed-but-never-launched (same as opencode #186).
12455
- copilot: exists2(path20.join(homeDir2, ".copilot")) || binaryInPath("copilot", homeDir2),
12456
- cursor: exists2(path20.join(homeDir2, ".cursor")),
12457
- codex: exists2(path20.join(homeDir2, ".codex")),
12458
- windsurf: exists2(path20.join(homeDir2, ".codeium", "windsurf")),
12459
- vscode: exists2(path20.join(homeDir2, ".vscode")),
12460
- claudeDesktop: desktopPath !== null && exists2(path20.dirname(desktopPath)),
13141
+ copilot: exists2(path21.join(homeDir2, ".copilot")) || binaryInPath("copilot", homeDir2),
13142
+ cursor: exists2(path21.join(homeDir2, ".cursor")),
13143
+ codex: exists2(path21.join(homeDir2, ".codex")),
13144
+ windsurf: exists2(path21.join(homeDir2, ".codeium", "windsurf")),
13145
+ vscode: exists2(path21.join(homeDir2, ".vscode")),
13146
+ claudeDesktop: desktopPath !== null && exists2(path21.dirname(desktopPath)),
12461
13147
  // Opencode creates its config dir lazily on first launch — fall back to a
12462
13148
  // PATH lookup so installed-but-never-launched CLIs are still wired. Config
12463
13149
  // dir honors $XDG_CONFIG_HOME via opencodeConfigDir (#186, custom-XDG).
@@ -12468,7 +13154,7 @@ function detectAgents(homeDir2 = os16.homedir()) {
12468
13154
  // dir lazily on first launch — same class of bug as opencode's #186
12469
13155
  // (design R6) — so fall back to PATH lookup for installed-but-never-
12470
13156
  // launched pi.
12471
- pi: exists2(path20.join(homeDir2, ".pi", "agent")) || binaryInPath("pi", homeDir2),
13157
+ pi: exists2(path21.join(homeDir2, ".pi", "agent")) || binaryInPath("pi", homeDir2),
12472
13158
  // Hermes Agent (https://github.com/NousResearch/hermes-agent): home dir
12473
13159
  // is $HERMES_HOME (default ~/.hermes) per hermes_constants.py:30. config.yaml
12474
13160
  // appears after `hermes setup` has run; the directory alone exists from
@@ -12479,9 +13165,9 @@ function detectAgents(homeDir2 = os16.homedir()) {
12479
13165
  }
12480
13166
  async function setupCursor() {
12481
13167
  seedMcpPinsIfMissing();
12482
- const homeDir2 = os16.homedir();
12483
- const mcpPath = path20.join(homeDir2, ".cursor", "mcp.json");
12484
- const mcpConfig = readJson(mcpPath) ?? {};
13168
+ const homeDir2 = os18.homedir();
13169
+ const mcpPath = path21.join(homeDir2, ".cursor", "mcp.json");
13170
+ const mcpConfig = readJson2(mcpPath) ?? {};
12485
13171
  const servers = mcpConfig.mcpServers ?? {};
12486
13172
  let anythingChanged = false;
12487
13173
  const repairedMcpWraps = repairLegacyMcpWraps(
@@ -12507,7 +13193,7 @@ async function setupCursor() {
12507
13193
  const serversToWrap = [];
12508
13194
  for (const [name, server] of Object.entries(servers)) {
12509
13195
  if (!server.command || server.command === "node9") continue;
12510
- const upstream = [server.command, ...server.args ?? []].join(" ");
13196
+ const upstream = mcpUpstreamString(server);
12511
13197
  serversToWrap.push({ name, upstream });
12512
13198
  }
12513
13199
  if (serversToWrap.length > 0) {
@@ -12563,27 +13249,27 @@ async function setupCursor() {
12563
13249
  }
12564
13250
  function readToml(filePath) {
12565
13251
  try {
12566
- if (fs19.existsSync(filePath)) {
12567
- return parseToml2(fs19.readFileSync(filePath, "utf-8"));
13252
+ if (fs21.existsSync(filePath)) {
13253
+ return parseToml4(fs21.readFileSync(filePath, "utf-8"));
12568
13254
  }
12569
13255
  } catch {
12570
13256
  }
12571
13257
  return null;
12572
13258
  }
12573
13259
  function writeToml(filePath, data) {
12574
- const dir = path20.dirname(filePath);
12575
- if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
12576
- fs19.writeFileSync(filePath, stringifyToml(data));
13260
+ const dir = path21.dirname(filePath);
13261
+ if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
13262
+ fs21.writeFileSync(filePath, stringifyToml2(data));
12577
13263
  }
12578
13264
  async function setupCodex() {
12579
13265
  seedMcpPinsIfMissing();
12580
- const homeDir2 = os16.homedir();
12581
- const configPath = path20.join(homeDir2, ".codex", "config.toml");
12582
- const hooksPath = path20.join(homeDir2, ".codex", "hooks.json");
13266
+ const homeDir2 = os18.homedir();
13267
+ const configPath = path21.join(homeDir2, ".codex", "config.toml");
13268
+ const hooksPath = path21.join(homeDir2, ".codex", "hooks.json");
12583
13269
  const config = readToml(configPath) ?? {};
12584
13270
  const servers = config.mcp_servers ?? {};
12585
13271
  let anythingChanged = false;
12586
- const hooksFile = readJson(hooksPath) ?? {};
13272
+ const hooksFile = readJson2(hooksPath) ?? {};
12587
13273
  if (!hooksFile.hooks) hooksFile.hooks = {};
12588
13274
  let hooksChanged = false;
12589
13275
  if (!hooksFile.hooks.PreToolUse) hooksFile.hooks.PreToolUse = [];
@@ -12676,11 +13362,21 @@ async function setupCodex() {
12676
13362
  anythingChanged = true;
12677
13363
  }
12678
13364
  const serversToWrap = [];
13365
+ const appManaged = [];
12679
13366
  for (const [name, server] of Object.entries(servers)) {
12680
13367
  if (!server.command || server.command === "node9") continue;
12681
- const upstream = [server.command, ...server.args ?? []].join(" ");
13368
+ if (isCodexAppManagedServer(name, server)) {
13369
+ appManaged.push(name);
13370
+ continue;
13371
+ }
13372
+ const upstream = mcpUpstreamString(server);
12682
13373
  serversToWrap.push({ name, upstream });
12683
13374
  }
13375
+ if (appManaged.length > 0) {
13376
+ console.log(
13377
+ chalk.gray(` \u2139\uFE0F Managed by the Codex app \u2014 not wrapped: ${appManaged.join(", ")}`)
13378
+ );
13379
+ }
12684
13380
  if (serversToWrap.length > 0) {
12685
13381
  console.log(chalk.bold("The following existing entries will be modified:\n"));
12686
13382
  console.log(chalk.white(` ${configPath}`));
@@ -12745,10 +13441,10 @@ async function setupCodex() {
12745
13441
  }
12746
13442
  }
12747
13443
  function teardownCodex() {
12748
- const homeDir2 = os16.homedir();
12749
- const configPath = path20.join(homeDir2, ".codex", "config.toml");
12750
- const hooksPath = path20.join(homeDir2, ".codex", "hooks.json");
12751
- const hooksFile = readJson(hooksPath);
13444
+ const homeDir2 = os18.homedir();
13445
+ const configPath = path21.join(homeDir2, ".codex", "config.toml");
13446
+ const hooksPath = path21.join(homeDir2, ".codex", "hooks.json");
13447
+ const hooksFile = readJson2(hooksPath);
12752
13448
  if (hooksFile?.hooks) {
12753
13449
  let hooksChanged = false;
12754
13450
  for (const event of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
@@ -12794,9 +13490,9 @@ function teardownCodex() {
12794
13490
  }
12795
13491
  }
12796
13492
  function setupHud() {
12797
- const homeDir2 = os16.homedir();
12798
- const hooksPath = path20.join(homeDir2, ".claude", "settings.json");
12799
- const settings = readJson(hooksPath) ?? {};
13493
+ const homeDir2 = os18.homedir();
13494
+ const hooksPath = path21.join(homeDir2, ".claude", "settings.json");
13495
+ const settings = readJson2(hooksPath) ?? {};
12800
13496
  const hudCommand = fullPathCommand("hud");
12801
13497
  const statusLineObj = { type: "command", command: hudCommand };
12802
13498
  const existing = settings.statusLine;
@@ -12823,9 +13519,9 @@ function setupHud() {
12823
13519
  console.log(chalk.gray(" Restart Claude Code to activate."));
12824
13520
  }
12825
13521
  function teardownHud() {
12826
- const homeDir2 = os16.homedir();
12827
- const hooksPath = path20.join(homeDir2, ".claude", "settings.json");
12828
- const settings = readJson(hooksPath);
13522
+ const homeDir2 = os18.homedir();
13523
+ const hooksPath = path21.join(homeDir2, ".claude", "settings.json");
13524
+ const settings = readJson2(hooksPath);
12829
13525
  if (!settings) {
12830
13526
  console.log(chalk.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
12831
13527
  return;
@@ -12843,9 +13539,9 @@ function teardownHud() {
12843
13539
  }
12844
13540
  async function setupWindsurf() {
12845
13541
  seedMcpPinsIfMissing();
12846
- const homeDir2 = os16.homedir();
12847
- const mcpPath = path20.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
12848
- const mcpConfig = readJson(mcpPath) ?? {};
13542
+ const homeDir2 = os18.homedir();
13543
+ const mcpPath = path21.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
13544
+ const mcpConfig = readJson2(mcpPath) ?? {};
12849
13545
  const servers = mcpConfig.mcpServers ?? {};
12850
13546
  let anythingChanged = false;
12851
13547
  const repairedMcpWraps = repairLegacyMcpWraps(
@@ -12921,9 +13617,9 @@ async function setupWindsurf() {
12921
13617
  }
12922
13618
  }
12923
13619
  function teardownWindsurf() {
12924
- const homeDir2 = os16.homedir();
12925
- const mcpPath = path20.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
12926
- const mcpConfig = readJson(mcpPath);
13620
+ const homeDir2 = os18.homedir();
13621
+ const mcpPath = path21.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
13622
+ const mcpConfig = readJson2(mcpPath);
12927
13623
  if (!mcpConfig?.mcpServers) {
12928
13624
  console.log(
12929
13625
  chalk.blue(" \u2139\uFE0F ~/.codeium/windsurf/mcp_config.json not found \u2014 nothing to remove")
@@ -12964,9 +13660,9 @@ function hasNode9McpServerVSCode(servers) {
12964
13660
  }
12965
13661
  async function setupVSCode() {
12966
13662
  seedMcpPinsIfMissing();
12967
- const homeDir2 = os16.homedir();
12968
- const mcpPath = path20.join(homeDir2, ".vscode", "mcp.json");
12969
- const mcpConfig = readJson(mcpPath) ?? {};
13663
+ const homeDir2 = os18.homedir();
13664
+ const mcpPath = path21.join(homeDir2, ".vscode", "mcp.json");
13665
+ const mcpConfig = readJson2(mcpPath) ?? {};
12970
13666
  const servers = mcpConfig.servers ?? {};
12971
13667
  let anythingChanged = false;
12972
13668
  const repairedMcpWraps = repairLegacyMcpWraps(
@@ -13045,9 +13741,9 @@ async function setupVSCode() {
13045
13741
  }
13046
13742
  }
13047
13743
  function teardownVSCode() {
13048
- const homeDir2 = os16.homedir();
13049
- const mcpPath = path20.join(homeDir2, ".vscode", "mcp.json");
13050
- const mcpConfig = readJson(mcpPath);
13744
+ const homeDir2 = os18.homedir();
13745
+ const mcpPath = path21.join(homeDir2, ".vscode", "mcp.json");
13746
+ const mcpConfig = readJson2(mcpPath);
13051
13747
  if (!mcpConfig?.servers) {
13052
13748
  console.log(chalk.blue(" \u2139\uFE0F ~/.vscode/mcp.json not found \u2014 nothing to remove"));
13053
13749
  return;
@@ -13085,7 +13781,7 @@ async function setupClaudeDesktop() {
13085
13781
  console.log(chalk.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
13086
13782
  return;
13087
13783
  }
13088
- const config = readJson(configPath) ?? {};
13784
+ const config = readJson2(configPath) ?? {};
13089
13785
  const servers = config.mcpServers ?? {};
13090
13786
  let anythingChanged = false;
13091
13787
  const repairedMcpWraps = repairLegacyMcpWraps(
@@ -13166,7 +13862,7 @@ function teardownClaudeDesktop() {
13166
13862
  console.log(chalk.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
13167
13863
  return;
13168
13864
  }
13169
- const config = readJson(configPath);
13865
+ const config = readJson2(configPath);
13170
13866
  if (!config?.mcpServers) {
13171
13867
  console.log(chalk.blue(" \u2139\uFE0F Claude Desktop config not found \u2014 nothing to remove"));
13172
13868
  return;
@@ -13205,7 +13901,7 @@ function node9ArgvForShim() {
13205
13901
  function node9Version() {
13206
13902
  try {
13207
13903
  const pkg = JSON.parse(
13208
- fs19.readFileSync(path20.join(__dirname, "..", "package.json"), "utf-8")
13904
+ fs21.readFileSync(path21.join(__dirname, "..", "package.json"), "utf-8")
13209
13905
  );
13210
13906
  return pkg.version ?? "0.0.0";
13211
13907
  } catch {
@@ -13214,13 +13910,13 @@ function node9Version() {
13214
13910
  }
13215
13911
  async function setupOpencode() {
13216
13912
  seedMcpPinsIfMissing();
13217
- const homeDir2 = os16.homedir();
13913
+ const homeDir2 = os18.homedir();
13218
13914
  const configDir = opencodeConfigDir(homeDir2);
13219
- const pluginsDir = path20.join(configDir, "plugins");
13220
- const configPath = path20.join(configDir, "opencode.json");
13221
- const pluginPath = path20.join(pluginsDir, OPENCODE_PLUGIN_NAME);
13915
+ const pluginsDir = path21.join(configDir, "plugins");
13916
+ const configPath = path21.join(configDir, "opencode.json");
13917
+ const pluginPath = path21.join(pluginsDir, OPENCODE_PLUGIN_NAME);
13222
13918
  try {
13223
- fs19.mkdirSync(pluginsDir, { recursive: true });
13919
+ fs21.mkdirSync(pluginsDir, { recursive: true });
13224
13920
  } catch (err2) {
13225
13921
  const code = err2.code;
13226
13922
  if (code !== "EEXIST") {
@@ -13235,13 +13931,13 @@ async function setupOpencode() {
13235
13931
  let pluginChanged = false;
13236
13932
  const existingShim = (() => {
13237
13933
  try {
13238
- return fs19.readFileSync(pluginPath, "utf-8");
13934
+ return fs21.readFileSync(pluginPath, "utf-8");
13239
13935
  } catch {
13240
13936
  return null;
13241
13937
  }
13242
13938
  })();
13243
13939
  if (existingShim !== shimContent) {
13244
- fs19.writeFileSync(pluginPath, shimContent);
13940
+ fs21.writeFileSync(pluginPath, shimContent);
13245
13941
  pluginChanged = true;
13246
13942
  if (existingShim) {
13247
13943
  console.log(chalk.yellow(" \u{1F527} Opencode plugin shim updated to current version"));
@@ -13251,7 +13947,7 @@ async function setupOpencode() {
13251
13947
  );
13252
13948
  }
13253
13949
  }
13254
- const config = readJson(configPath) ?? {};
13950
+ const config = readJson2(configPath) ?? {};
13255
13951
  const mcp = config.mcp ?? {};
13256
13952
  let configChanged = false;
13257
13953
  const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
@@ -13282,20 +13978,20 @@ async function setupOpencode() {
13282
13978
  }
13283
13979
  }
13284
13980
  function teardownOpencode() {
13285
- const homeDir2 = os16.homedir();
13981
+ const homeDir2 = os18.homedir();
13286
13982
  const configDir = opencodeConfigDir(homeDir2);
13287
- const pluginsDir = path20.join(configDir, "plugins");
13288
- const configPath = path20.join(configDir, "opencode.json");
13289
- const pluginPath = path20.join(pluginsDir, OPENCODE_PLUGIN_NAME);
13983
+ const pluginsDir = path21.join(configDir, "plugins");
13984
+ const configPath = path21.join(configDir, "opencode.json");
13985
+ const pluginPath = path21.join(pluginsDir, OPENCODE_PLUGIN_NAME);
13290
13986
  try {
13291
- if (fs19.existsSync(pluginPath)) {
13292
- fs19.unlinkSync(pluginPath);
13987
+ if (fs21.existsSync(pluginPath)) {
13988
+ fs21.unlinkSync(pluginPath);
13293
13989
  console.log(chalk.green(" \u2705 Removed node9 plugin from ~/.config/opencode/plugins/"));
13294
13990
  }
13295
13991
  } catch (err2) {
13296
13992
  console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
13297
13993
  }
13298
- const config = readJson(configPath);
13994
+ const config = readJson2(configPath);
13299
13995
  if (!config) {
13300
13996
  console.log(chalk.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
13301
13997
  return;
@@ -13318,11 +14014,11 @@ function teardownOpencode() {
13318
14014
  }
13319
14015
  async function setupPi() {
13320
14016
  seedMcpPinsIfMissing();
13321
- const homeDir2 = os16.homedir();
13322
- const extensionsDir = path20.join(homeDir2, ".pi", "agent", "extensions");
13323
- const extensionPath = path20.join(extensionsDir, PI_EXTENSION_NAME);
14017
+ const homeDir2 = os18.homedir();
14018
+ const extensionsDir = path21.join(homeDir2, ".pi", "agent", "extensions");
14019
+ const extensionPath = path21.join(extensionsDir, PI_EXTENSION_NAME);
13324
14020
  try {
13325
- fs19.mkdirSync(extensionsDir, { recursive: true });
14021
+ fs21.mkdirSync(extensionsDir, { recursive: true });
13326
14022
  } catch (err2) {
13327
14023
  const code = err2.code;
13328
14024
  console.log(chalk.yellow(` \u26A0\uFE0F Could not create ${extensionsDir}: ${code ?? String(err2)}`));
@@ -13334,7 +14030,7 @@ async function setupPi() {
13334
14030
  });
13335
14031
  const existingShim = (() => {
13336
14032
  try {
13337
- return fs19.readFileSync(extensionPath, "utf-8");
14033
+ return fs21.readFileSync(extensionPath, "utf-8");
13338
14034
  } catch {
13339
14035
  return null;
13340
14036
  }
@@ -13343,7 +14039,7 @@ async function setupPi() {
13343
14039
  console.log(chalk.blue(" \u2139\uFE0F Node9 is already fully configured for Pi."));
13344
14040
  return;
13345
14041
  }
13346
- fs19.writeFileSync(extensionPath, shimContent);
14042
+ fs21.writeFileSync(extensionPath, shimContent);
13347
14043
  if (existingShim) {
13348
14044
  console.log(chalk.yellow(" \u{1F527} Pi extension shim updated to current version"));
13349
14045
  } else {
@@ -13356,11 +14052,11 @@ async function setupPi() {
13356
14052
  printDaemonTip();
13357
14053
  }
13358
14054
  function teardownPi() {
13359
- const homeDir2 = os16.homedir();
13360
- const extensionPath = path20.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME);
14055
+ const homeDir2 = os18.homedir();
14056
+ const extensionPath = path21.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME);
13361
14057
  try {
13362
- if (fs19.existsSync(extensionPath)) {
13363
- fs19.unlinkSync(extensionPath);
14058
+ if (fs21.existsSync(extensionPath)) {
14059
+ fs21.unlinkSync(extensionPath);
13364
14060
  console.log(chalk.green(" \u2705 Removed node9 extension from ~/.pi/agent/extensions/"));
13365
14061
  } else {
13366
14062
  console.log(chalk.blue(" \u2139\uFE0F No Pi extension installed \u2014 nothing to remove"));
@@ -13369,29 +14065,29 @@ function teardownPi() {
13369
14065
  console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${extensionPath}: ${String(err2)}`));
13370
14066
  }
13371
14067
  }
13372
- function hermesHomeDir(homeDir2 = os16.homedir()) {
14068
+ function hermesHomeDir(homeDir2 = os18.homedir()) {
13373
14069
  const env = process.env.HERMES_HOME?.trim();
13374
- if (env && path20.isAbsolute(env)) return env;
13375
- return path20.join(homeDir2, ".hermes");
14070
+ if (env && path21.isAbsolute(env)) return env;
14071
+ return path21.join(homeDir2, ".hermes");
13376
14072
  }
13377
- function hermesConfigPath(homeDir2 = os16.homedir()) {
13378
- return path20.join(hermesHomeDir(homeDir2), HERMES_CONFIG_FILENAME);
14073
+ function hermesConfigPath(homeDir2 = os18.homedir()) {
14074
+ return path21.join(hermesHomeDir(homeDir2), HERMES_CONFIG_FILENAME);
13379
14075
  }
13380
- function hermesAllowlistPath(homeDir2 = os16.homedir()) {
13381
- return path20.join(hermesHomeDir(homeDir2), HERMES_ALLOWLIST_FILENAME);
14076
+ function hermesAllowlistPath(homeDir2 = os18.homedir()) {
14077
+ return path21.join(hermesHomeDir(homeDir2), HERMES_ALLOWLIST_FILENAME);
13382
14078
  }
13383
14079
  function setupHermes() {
13384
- const homeDir2 = os16.homedir();
14080
+ const homeDir2 = os18.homedir();
13385
14081
  const configPath = hermesConfigPath(homeDir2);
13386
14082
  const allowlistPath = hermesAllowlistPath(homeDir2);
13387
- if (!fs19.existsSync(configPath)) {
14083
+ if (!fs21.existsSync(configPath)) {
13388
14084
  console.log(chalk.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
13389
14085
  console.log(chalk.gray(" Run `hermes setup` first, then re-run node9 agents add hermes."));
13390
14086
  return;
13391
14087
  }
13392
14088
  let anythingChanged = false;
13393
- const raw = fs19.readFileSync(configPath, "utf-8");
13394
- const doc = yaml.parseDocument(raw);
14089
+ const raw = fs21.readFileSync(configPath, "utf-8");
14090
+ const doc = yaml2.parseDocument(raw);
13395
14091
  if (doc.errors.length > 0) {
13396
14092
  console.log(chalk.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
13397
14093
  for (const err2 of doc.errors.slice(0, 3)) {
@@ -13433,9 +14129,9 @@ function setupHermes() {
13433
14129
  atomicWriteSync(configPath, doc.toString());
13434
14130
  }
13435
14131
  let allowlist = {};
13436
- if (fs19.existsSync(allowlistPath)) {
14132
+ if (fs21.existsSync(allowlistPath)) {
13437
14133
  try {
13438
- allowlist = JSON.parse(fs19.readFileSync(allowlistPath, "utf-8"));
14134
+ allowlist = JSON.parse(fs21.readFileSync(allowlistPath, "utf-8"));
13439
14135
  } catch {
13440
14136
  allowlist = {};
13441
14137
  }
@@ -13471,15 +14167,15 @@ function setupHermes() {
13471
14167
  }
13472
14168
  }
13473
14169
  function teardownHermes() {
13474
- const homeDir2 = os16.homedir();
14170
+ const homeDir2 = os18.homedir();
13475
14171
  const configPath = hermesConfigPath(homeDir2);
13476
14172
  const allowlistPath = hermesAllowlistPath(homeDir2);
13477
- if (!fs19.existsSync(configPath)) {
14173
+ if (!fs21.existsSync(configPath)) {
13478
14174
  console.log(chalk.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
13479
14175
  return;
13480
14176
  }
13481
- const raw = fs19.readFileSync(configPath, "utf-8");
13482
- const doc = yaml.parseDocument(raw);
14177
+ const raw = fs21.readFileSync(configPath, "utf-8");
14178
+ const doc = yaml2.parseDocument(raw);
13483
14179
  if (doc.errors.length > 0) {
13484
14180
  console.log(
13485
14181
  chalk.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
@@ -13511,16 +14207,16 @@ function teardownHermesConfigDoc(doc, configPath) {
13511
14207
  anythingChanged = true;
13512
14208
  }
13513
14209
  if (anythingChanged) {
13514
- fs19.writeFileSync(configPath, doc.toString());
14210
+ fs21.writeFileSync(configPath, doc.toString());
13515
14211
  console.log(chalk.green(` \u2705 Removed Node9 hooks from ${configPath}`));
13516
14212
  } else {
13517
14213
  console.log(chalk.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
13518
14214
  }
13519
14215
  }
13520
14216
  function teardownHermesAllowlist(allowlistPath) {
13521
- if (!fs19.existsSync(allowlistPath)) return;
14217
+ if (!fs21.existsSync(allowlistPath)) return;
13522
14218
  try {
13523
- const raw = fs19.readFileSync(allowlistPath, "utf-8");
14219
+ const raw = fs21.readFileSync(allowlistPath, "utf-8");
13524
14220
  const allowlist = JSON.parse(raw);
13525
14221
  if (!Array.isArray(allowlist.approvals)) return;
13526
14222
  const before = allowlist.approvals.length;
@@ -13533,44 +14229,44 @@ function teardownHermesAllowlist(allowlistPath) {
13533
14229
  } catch {
13534
14230
  }
13535
14231
  }
13536
- function getAgentsStatus(homeDir2 = os16.homedir()) {
14232
+ function getAgentsStatus(homeDir2 = os18.homedir()) {
13537
14233
  const detected = detectAgents(homeDir2);
13538
14234
  const claudeWired = (() => {
13539
- const settings = readJson(path20.join(homeDir2, ".claude", "settings.json"));
14235
+ const settings = readJson2(path21.join(homeDir2, ".claude", "settings.json"));
13540
14236
  return !!settings?.hooks?.PreToolUse?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
13541
14237
  })();
13542
14238
  const geminiWired = (() => {
13543
- const settings = readJson(path20.join(homeDir2, ".gemini", "settings.json"));
14239
+ const settings = readJson2(path21.join(homeDir2, ".gemini", "settings.json"));
13544
14240
  return !!settings?.hooks?.BeforeTool?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
13545
14241
  })();
13546
14242
  const antigravityWired = (() => {
13547
- const hooksFile = readJson(
13548
- path20.join(homeDir2, ".gemini", "config", "hooks.json")
14243
+ const hooksFile = readJson2(
14244
+ path21.join(homeDir2, ".gemini", "config", "hooks.json")
13549
14245
  );
13550
14246
  return !!hooksFile?.hooks?.PreToolUse?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
13551
14247
  })();
13552
14248
  const copilotWired = (() => {
13553
- const hooksFile = readJson(
13554
- path20.join(homeDir2, ".copilot", "hooks", "node9.json")
14249
+ const hooksFile = readJson2(
14250
+ path21.join(homeDir2, ".copilot", "hooks", "node9.json")
13555
14251
  );
13556
14252
  return !!hooksFile?.hooks?.PreToolUse?.some((h) => isNode9Hook(h.command));
13557
14253
  })();
13558
14254
  const cursorWired = (() => {
13559
- const cfg = readJson(path20.join(homeDir2, ".cursor", "mcp.json"));
14255
+ const cfg = readJson2(path21.join(homeDir2, ".cursor", "mcp.json"));
13560
14256
  return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
13561
14257
  })();
13562
14258
  const codexWired = (() => {
13563
- const cfg = readToml(path20.join(homeDir2, ".codex", "config.toml"));
14259
+ const cfg = readToml(path21.join(homeDir2, ".codex", "config.toml"));
13564
14260
  return !!(cfg?.mcp_servers && hasNode9McpServer(cfg.mcp_servers));
13565
14261
  })();
13566
14262
  const windsurfWired = (() => {
13567
- const cfg = readJson(
13568
- path20.join(homeDir2, ".codeium", "windsurf", "mcp_config.json")
14263
+ const cfg = readJson2(
14264
+ path21.join(homeDir2, ".codeium", "windsurf", "mcp_config.json")
13569
14265
  );
13570
14266
  return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
13571
14267
  })();
13572
14268
  const vscodeWired = (() => {
13573
- const cfg = readJson(path20.join(homeDir2, ".vscode", "mcp.json"));
14269
+ const cfg = readJson2(path21.join(homeDir2, ".vscode", "mcp.json"));
13574
14270
  return !!(cfg?.servers && hasNode9McpServerVSCode(cfg.servers));
13575
14271
  })();
13576
14272
  return [
@@ -13637,7 +14333,7 @@ function getAgentsStatus(homeDir2 = os16.homedir()) {
13637
14333
  wired: (() => {
13638
14334
  const cfgPath = claudeDesktopConfigPath(homeDir2);
13639
14335
  if (!cfgPath) return false;
13640
- const cfg = readJson(cfgPath);
14336
+ const cfg = readJson2(cfgPath);
13641
14337
  return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
13642
14338
  })(),
13643
14339
  mode: detected.claudeDesktop ? "mcp" : null
@@ -13647,16 +14343,16 @@ function getAgentsStatus(homeDir2 = os16.homedir()) {
13647
14343
  label: "Opencode",
13648
14344
  installed: detected.opencode,
13649
14345
  wired: (() => {
13650
- const pluginPath = path20.join(
14346
+ const pluginPath = path21.join(
13651
14347
  homeDir2,
13652
14348
  ".config",
13653
14349
  "opencode",
13654
14350
  "plugins",
13655
14351
  OPENCODE_PLUGIN_NAME
13656
14352
  );
13657
- if (fs19.existsSync(pluginPath)) return true;
13658
- const cfg = readJson(
13659
- path20.join(homeDir2, ".config", "opencode", "opencode.json")
14353
+ if (fs21.existsSync(pluginPath)) return true;
14354
+ const cfg = readJson2(
14355
+ path21.join(homeDir2, ".config", "opencode", "opencode.json")
13660
14356
  );
13661
14357
  return !!cfg?.mcp?.["node9"];
13662
14358
  })(),
@@ -13668,7 +14364,7 @@ function getAgentsStatus(homeDir2 = os16.homedir()) {
13668
14364
  installed: detected.pi,
13669
14365
  // Pi has no MCP path — only the extension file. "wired" is a
13670
14366
  // simple existence check on the canonical install location.
13671
- wired: fs19.existsSync(path20.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME)),
14367
+ wired: fs21.existsSync(path21.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME)),
13672
14368
  mode: detected.pi ? "hooks" : null
13673
14369
  },
13674
14370
  {
@@ -13680,8 +14376,8 @@ function getAgentsStatus(homeDir2 = os16.homedir()) {
13680
14376
  // Document API for a boolean status check.
13681
14377
  wired: (() => {
13682
14378
  try {
13683
- const raw = fs19.readFileSync(hermesConfigPath(homeDir2), "utf-8");
13684
- const cfg = yaml.parse(raw);
14379
+ const raw = fs21.readFileSync(hermesConfigPath(homeDir2), "utf-8");
14380
+ const cfg = yaml2.parse(raw);
13685
14381
  const pre = cfg?.hooks?.["pre_tool_call"] ?? [];
13686
14382
  return pre.some((e) => typeof e?.command === "string" && isNode9Hook(e.command));
13687
14383
  } catch {
@@ -13696,6 +14392,7 @@ var NODE9_MCP_SERVER_ENTRY, MCP_WRAP_SUBCOMMAND, LEGACY_MCP_WRAP_SUBCOMMAND, COD
13696
14392
  var init_setup = __esm({
13697
14393
  "src/setup.ts"() {
13698
14394
  "use strict";
14395
+ init_mcp_wrap();
13699
14396
  init_codex_trust();
13700
14397
  init_mcp_pin();
13701
14398
  init_hook_baseline();
@@ -13717,244 +14414,6 @@ var init_setup = __esm({
13717
14414
  }
13718
14415
  });
13719
14416
 
13720
- // src/agent-wiring.ts
13721
- import fs20 from "fs";
13722
- import path21 from "path";
13723
- import os17 from "os";
13724
- import * as yaml2 from "yaml";
13725
- import { parse as parseToml3 } from "smol-toml";
13726
- function readJson2(filePath) {
13727
- if (!fs20.existsSync(filePath)) return null;
13728
- try {
13729
- return JSON.parse(fs20.readFileSync(filePath, "utf-8"));
13730
- } catch {
13731
- return "invalid";
13732
- }
13733
- }
13734
- function matchersHaveNode9Hook(matchers) {
13735
- return (matchers ?? []).some((m) => (m.hooks ?? []).some((h) => isNode9Hook(h.command)));
13736
- }
13737
- function flatHaveNode9Hook(entries) {
13738
- return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
13739
- }
13740
- function readHookRoot(filePath, format) {
13741
- if (!fs20.existsSync(filePath)) return "absent";
13742
- let raw;
13743
- try {
13744
- raw = fs20.readFileSync(filePath, "utf-8");
13745
- } catch {
13746
- return "absent";
13747
- }
13748
- try {
13749
- const parsed = format === "yaml" ? yaml2.parse(raw) : JSON.parse(raw);
13750
- return parsed?.hooks ?? {};
13751
- } catch {
13752
- return "invalid";
13753
- }
13754
- }
13755
- function eventWired(root, ev, format) {
13756
- const arr = root[ev.key];
13757
- if (format === "matcher") return matchersHaveNode9Hook(arr);
13758
- return flatHaveNode9Hook(arr);
13759
- }
13760
- function detectMcp(servers) {
13761
- const entries = Object.entries(servers ?? {});
13762
- const present = entries.some(([, s]) => s?.command === "node9");
13763
- const wrapped = entries.filter(([, s]) => s?.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
13764
- return { wrapped, present };
13765
- }
13766
- function readMcpServers(filePath, format) {
13767
- if (!fs20.existsSync(filePath)) return {};
13768
- try {
13769
- if (format === "toml") {
13770
- const parsed2 = parseToml3(fs20.readFileSync(filePath, "utf-8"));
13771
- return parsed2?.mcp_servers ?? {};
13772
- }
13773
- const parsed = readJson2(filePath);
13774
- if (parsed === null || parsed === "invalid") return {};
13775
- return parsed.mcpServers ?? {};
13776
- } catch {
13777
- return {};
13778
- }
13779
- }
13780
- function readMcp(filePath, format) {
13781
- if (!fs20.existsSync(filePath)) return { wrapped: [], present: false };
13782
- try {
13783
- if (format === "toml") {
13784
- const parsed2 = parseToml3(fs20.readFileSync(filePath, "utf-8"));
13785
- return detectMcp(parsed2?.mcp_servers);
13786
- }
13787
- const parsed = readJson2(filePath);
13788
- if (parsed === null || parsed === "invalid") return { wrapped: [], present: false };
13789
- return detectMcp(parsed.mcpServers);
13790
- } catch {
13791
- return { wrapped: [], present: false };
13792
- }
13793
- }
13794
- function getAgentWiring(home = os17.homedir()) {
13795
- const detected = detectAgents(home);
13796
- return AGENT_SPECS.map((spec) => {
13797
- const present = spec.present(home);
13798
- const pad = spec.labelPad ?? DEFAULT_LABEL_PAD;
13799
- let hooks;
13800
- let wireState;
13801
- let hookLabel;
13802
- let settingsPath;
13803
- if (spec.shimFile) {
13804
- const shimWired = exists(spec.shimFile(home));
13805
- hooks = [{ label: "node9 plugin (node9 check)", wired: shimWired }];
13806
- wireState = shimWired ? "wired" : present ? "unwired" : "absent";
13807
- hookLabel = "node9 plugin";
13808
- settingsPath = spec.shimFile(home);
13809
- } else {
13810
- const root = spec.hookFile ? readHookRoot(spec.hookFile(home), spec.hookFormat) : "absent";
13811
- const primary = spec.hookEvents[0];
13812
- const rootPresent = root !== "absent" && root !== "invalid";
13813
- hooks = spec.hookEvents.map((ev) => ({
13814
- label: hookLabelOf(ev, pad),
13815
- wired: rootPresent && eventWired(root, ev, spec.hookFormat)
13816
- }));
13817
- if (root === "absent") wireState = "absent";
13818
- else if (root === "invalid") wireState = "invalid";
13819
- else wireState = primary && eventWired(root, primary, spec.hookFormat) ? "wired" : "unwired";
13820
- hookLabel = primary ? `${primary.key} hook` : "MCP proxy";
13821
- settingsPath = spec.hookFile ? spec.hookFile(home) : spec.mcpFile ? spec.mcpFile(home) : "";
13822
- }
13823
- const mcp = spec.mcpFile ? readMcp(spec.mcpFile(home), spec.mcpFormat ?? "json") : null;
13824
- const anyHookWired = hooks.some((h) => h.wired);
13825
- return {
13826
- id: spec.id,
13827
- label: spec.label,
13828
- setupCommand: spec.setupCommand,
13829
- installed: detected[spec.id],
13830
- present,
13831
- hooks,
13832
- wireState,
13833
- hookLabel,
13834
- settingsPath,
13835
- configFormat: spec.hookFormat === "yaml" ? "YAML" : "JSON",
13836
- mcpServers: mcp ? mcp.wrapped : null,
13837
- mcpProtected: mcp ? mcp.present : false,
13838
- isProtected: anyHookWired || (mcp?.present ?? false)
13839
- };
13840
- });
13841
- }
13842
- var exists, ck, lg, DEFAULT_LABEL_PAD, hookLabelOf, AGENT_SPECS;
13843
- var init_agent_wiring = __esm({
13844
- "src/agent-wiring.ts"() {
13845
- "use strict";
13846
- init_setup();
13847
- exists = (p) => {
13848
- try {
13849
- return fs20.existsSync(p);
13850
- } catch {
13851
- return false;
13852
- }
13853
- };
13854
- ck = (key) => ({ key, kind: "check" });
13855
- lg = (key) => ({ key, kind: "log" });
13856
- DEFAULT_LABEL_PAD = 11;
13857
- hookLabelOf = (ev, pad) => `${ev.key.padEnd(pad)} (node9 ${ev.kind})`;
13858
- AGENT_SPECS = [
13859
- {
13860
- id: "claude",
13861
- label: "Claude Code",
13862
- setupCommand: "node9 agents add claude",
13863
- hookFile: (h) => path21.join(h, ".claude", "settings.json"),
13864
- hookFormat: "matcher",
13865
- // UserPromptSubmit is prompt DLP. setup.ts has written it for Claude since
13866
- // that shipped; the spec must name it too, or status/doctor never show the
13867
- // row and heal (which repairs via setupAgent) has no signal it is missing.
13868
- hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
13869
- mcpFile: (h) => path21.join(h, ".claude.json"),
13870
- present: (h) => exists(path21.join(h, ".claude", "settings.json")) || exists(path21.join(h, ".claude.json"))
13871
- },
13872
- {
13873
- id: "gemini",
13874
- label: "Gemini CLI",
13875
- setupCommand: "node9 agents add gemini",
13876
- hookFile: (h) => path21.join(h, ".gemini", "settings.json"),
13877
- hookFormat: "matcher",
13878
- hookEvents: [ck("BeforeTool"), lg("AfterTool")],
13879
- mcpFile: (h) => path21.join(h, ".gemini", "settings.json"),
13880
- present: (h) => exists(path21.join(h, ".gemini", "settings.json"))
13881
- },
13882
- {
13883
- id: "codex",
13884
- label: "Codex",
13885
- setupCommand: "node9 agents add codex",
13886
- hookFile: (h) => path21.join(h, ".codex", "hooks.json"),
13887
- hookFormat: "matcher",
13888
- hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
13889
- mcpFile: (h) => path21.join(h, ".codex", "config.toml"),
13890
- mcpFormat: "toml",
13891
- present: (h) => exists(path21.join(h, ".codex"))
13892
- },
13893
- {
13894
- id: "antigravity",
13895
- label: "Antigravity",
13896
- setupCommand: "node9 agents add antigravity",
13897
- hookFile: (h) => path21.join(h, ".gemini", "config", "hooks.json"),
13898
- hookFormat: "matcher",
13899
- hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
13900
- mcpFile: (h) => path21.join(h, ".gemini", "config", "mcp_config.json"),
13901
- present: (h) => exists(path21.join(h, ".gemini", "config", "hooks.json")) || exists(path21.join(h, ".gemini", "antigravity-cli")) || exists(path21.join(h, ".gemini", "antigravity-ide"))
13902
- },
13903
- {
13904
- id: "copilot",
13905
- label: "GitHub Copilot",
13906
- setupCommand: "node9 agents add copilot",
13907
- hookFile: (h) => path21.join(h, ".copilot", "hooks", "node9.json"),
13908
- hookFormat: "flat",
13909
- hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
13910
- mcpFile: (h) => path21.join(h, ".copilot", "mcp-config.json"),
13911
- present: (h) => exists(path21.join(h, ".copilot"))
13912
- },
13913
- {
13914
- id: "cursor",
13915
- label: "Cursor",
13916
- setupCommand: "node9 agents add cursor",
13917
- // MCP-only — no hook file (see note above).
13918
- hookFormat: "flat",
13919
- hookEvents: [],
13920
- mcpFile: (h) => path21.join(h, ".cursor", "mcp.json"),
13921
- present: (h) => exists(path21.join(h, ".cursor", "mcp.json"))
13922
- },
13923
- {
13924
- id: "hermes",
13925
- label: "Hermes Agent",
13926
- setupCommand: "node9 agents add hermes",
13927
- hookFile: (h) => hermesConfigPath(h),
13928
- hookFormat: "yaml",
13929
- hookEvents: [ck("pre_tool_call"), lg("post_tool_call")],
13930
- labelPad: 14,
13931
- // 'post_tool_call' is wider than the default
13932
- present: (h) => exists(hermesConfigPath(h))
13933
- },
13934
- {
13935
- // Plugin-shim agents — protected by a node9-authored plugin/extension file
13936
- // (no hooks, no MCP). hookFormat is unused for these (shimFile drives it).
13937
- id: "opencode",
13938
- label: "OpenCode",
13939
- setupCommand: "node9 agents add opencode",
13940
- hookFormat: "flat",
13941
- hookEvents: [],
13942
- shimFile: (h) => path21.join(opencodeConfigDir(h), "plugins", "node9.js"),
13943
- present: (h) => exists(opencodeConfigDir(h)) || exists(path21.join(opencodeConfigDir(h), "plugins", "node9.js"))
13944
- },
13945
- {
13946
- id: "pi",
13947
- label: "Pi",
13948
- setupCommand: "node9 agents add pi",
13949
- hookFormat: "flat",
13950
- hookEvents: [],
13951
- shimFile: (h) => path21.join(h, ".pi", "agent", "extensions", "node9.js"),
13952
- present: (h) => exists(path21.join(h, ".pi", "agent")) || exists(path21.join(h, ".pi", "agent", "extensions", "node9.js"))
13953
- }
13954
- ];
13955
- }
13956
- });
13957
-
13958
14417
  // src/config/keyed-guard.ts
13959
14418
  import chalk2 from "chalk";
13960
14419
  function isKeyedForPolicy() {
@@ -13987,15 +14446,15 @@ var init_keyed_guard = __esm({
13987
14446
  });
13988
14447
 
13989
14448
  // src/pricing/litellm.ts
13990
- import fs22 from "fs";
14449
+ import fs23 from "fs";
13991
14450
  import path23 from "path";
13992
- import os19 from "os";
14451
+ import os20 from "os";
13993
14452
  function normalizeModel(raw) {
13994
14453
  return raw.replace(/-\d{8}$/, "").toLowerCase();
13995
14454
  }
13996
14455
  function readCache(opts) {
13997
14456
  try {
13998
- const raw = JSON.parse(fs22.readFileSync(CACHE_FILE(), "utf-8"));
14457
+ const raw = JSON.parse(fs23.readFileSync(CACHE_FILE(), "utf-8"));
13999
14458
  if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
14000
14459
  return null;
14001
14460
  }
@@ -14011,17 +14470,17 @@ function writeCache(prices) {
14011
14470
  try {
14012
14471
  const target = CACHE_FILE();
14013
14472
  const dir = path23.dirname(target);
14014
- if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
14473
+ if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
14015
14474
  const tmp = target + ".tmp";
14016
14475
  const body = {
14017
14476
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
14018
14477
  prices
14019
14478
  };
14020
- fs22.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
14021
- fs22.renameSync(tmp, target);
14479
+ fs23.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
14480
+ fs23.renameSync(tmp, target);
14022
14481
  } catch (err2) {
14023
14482
  try {
14024
- fs22.appendFileSync(
14483
+ fs23.appendFileSync(
14025
14484
  HOOK_DEBUG_LOG,
14026
14485
  `[pricing] cache write failed: ${err2.message}
14027
14486
  `
@@ -14179,7 +14638,7 @@ var init_litellm = __esm({
14179
14638
  "gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
14180
14639
  "gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
14181
14640
  };
14182
- CACHE_FILE = () => path23.join(os19.homedir(), ".node9", "model-pricing.json");
14641
+ CACHE_FILE = () => path23.join(os20.homedir(), ".node9", "model-pricing.json");
14183
14642
  TTL_MS = 24 * 60 * 60 * 1e3;
14184
14643
  memCache = null;
14185
14644
  memCacheAt = 0;
@@ -14189,11 +14648,11 @@ var init_litellm = __esm({
14189
14648
  });
14190
14649
 
14191
14650
  // src/cost-gemini.ts
14192
- import fs23 from "fs";
14193
- import os20 from "os";
14651
+ import fs24 from "fs";
14652
+ import os21 from "os";
14194
14653
  import path24 from "path";
14195
14654
  function geminiTmpDir() {
14196
- return path24.join(os20.homedir(), ".gemini", "tmp");
14655
+ return path24.join(os21.homedir(), ".gemini", "tmp");
14197
14656
  }
14198
14657
  function geminiPriceFor(model) {
14199
14658
  let tuple = pricingFor(model);
@@ -14208,14 +14667,14 @@ function geminiPriceFor(model) {
14208
14667
  }
14209
14668
  function safeReaddir(dir) {
14210
14669
  try {
14211
- return fs23.readdirSync(dir);
14670
+ return fs24.readdirSync(dir);
14212
14671
  } catch {
14213
14672
  return [];
14214
14673
  }
14215
14674
  }
14216
14675
  function isDir(p) {
14217
14676
  try {
14218
- return fs23.statSync(p).isDirectory();
14677
+ return fs24.statSync(p).isDirectory();
14219
14678
  } catch {
14220
14679
  return false;
14221
14680
  }
@@ -14294,7 +14753,7 @@ var init_cost_gemini = __esm({
14294
14753
  id: "gemini",
14295
14754
  available() {
14296
14755
  try {
14297
- return fs23.existsSync(geminiTmpDir());
14756
+ return fs24.existsSync(geminiTmpDir());
14298
14757
  } catch {
14299
14758
  return false;
14300
14759
  }
@@ -14303,13 +14762,13 @@ var init_cost_gemini = __esm({
14303
14762
  const combined = /* @__PURE__ */ new Map();
14304
14763
  for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
14305
14764
  try {
14306
- if (sinceMs !== void 0 && fs23.statSync(file).mtimeMs < sinceMs) continue;
14765
+ if (sinceMs !== void 0 && fs24.statSync(file).mtimeMs < sinceMs) continue;
14307
14766
  } catch {
14308
14767
  continue;
14309
14768
  }
14310
14769
  let content;
14311
14770
  try {
14312
- content = fs23.readFileSync(file, "utf8");
14771
+ content = fs24.readFileSync(file, "utf8");
14313
14772
  } catch {
14314
14773
  continue;
14315
14774
  }
@@ -14334,11 +14793,11 @@ var init_cost_gemini = __esm({
14334
14793
  });
14335
14794
 
14336
14795
  // src/cost-codex.ts
14337
- import fs24 from "fs";
14338
- import os21 from "os";
14796
+ import fs25 from "fs";
14797
+ import os22 from "os";
14339
14798
  import path25 from "path";
14340
14799
  function codexSessionsDir() {
14341
- return path25.join(process.env.CODEX_HOME?.trim() || path25.join(os21.homedir(), ".codex"), "sessions");
14800
+ return path25.join(process.env.CODEX_HOME?.trim() || path25.join(os22.homedir(), ".codex"), "sessions");
14342
14801
  }
14343
14802
  function codexPriceFor(model) {
14344
14803
  return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
@@ -14370,14 +14829,14 @@ function codexSessionCost(model, tokens, request2) {
14370
14829
  function statAndFirstLine(file) {
14371
14830
  const CAP = 4 * 1024 * 1024;
14372
14831
  const CHUNK = 64 * 1024;
14373
- const fd = fs24.openSync(file, "r");
14832
+ const fd = fs25.openSync(file, "r");
14374
14833
  try {
14375
- const stat = fs24.fstatSync(fd);
14834
+ const stat = fs25.fstatSync(fd);
14376
14835
  const limit = Math.min(stat.size, CAP);
14377
14836
  const parts = [];
14378
14837
  for (let pos = 0; pos < limit; pos += CHUNK) {
14379
14838
  const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
14380
- const read2 = fs24.readSync(fd, buf, 0, buf.length, pos);
14839
+ const read2 = fs25.readSync(fd, buf, 0, buf.length, pos);
14381
14840
  if (read2 <= 0) break;
14382
14841
  const slice = buf.subarray(0, read2);
14383
14842
  const nl = slice.indexOf(10);
@@ -14386,14 +14845,14 @@ function statAndFirstLine(file) {
14386
14845
  }
14387
14846
  return { stat, first: Buffer.concat(parts).toString("utf8") };
14388
14847
  } finally {
14389
- fs24.closeSync(fd);
14848
+ fs25.closeSync(fd);
14390
14849
  }
14391
14850
  }
14392
14851
  function listCodexSessionFiles(base = codexSessionsDir()) {
14393
14852
  const files = [];
14394
14853
  const walk = (dir) => {
14395
14854
  try {
14396
- for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
14855
+ for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
14397
14856
  const file = path25.join(dir, entry.name);
14398
14857
  if (entry.isDirectory()) walk(file);
14399
14858
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file);
@@ -14600,13 +15059,13 @@ var init_cost_codex = __esm({
14600
15059
  CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
14601
15060
  codexSource = {
14602
15061
  id: "codex",
14603
- available: () => fs24.existsSync(codexSessionsDir()) || fs24.existsSync(path25.join(path25.dirname(codexSessionsDir()), "archived_sessions")),
15062
+ available: () => fs25.existsSync(codexSessionsDir()) || fs25.existsSync(path25.join(path25.dirname(codexSessionsDir()), "archived_sessions")),
14604
15063
  collect(sinceMs) {
14605
15064
  const entries = [];
14606
15065
  for (const file of listCodexSessionFiles()) {
14607
15066
  try {
14608
- if (sinceMs !== void 0 && fs24.statSync(file).mtimeMs < sinceMs) continue;
14609
- entries.push(...parseCodexSession(fs24.readFileSync(file, "utf8").split("\n")));
15067
+ if (sinceMs !== void 0 && fs25.statSync(file).mtimeMs < sinceMs) continue;
15068
+ entries.push(...parseCodexSession(fs25.readFileSync(file, "utf8").split("\n")));
14610
15069
  } catch {
14611
15070
  }
14612
15071
  }
@@ -14617,15 +15076,15 @@ var init_cost_codex = __esm({
14617
15076
  });
14618
15077
 
14619
15078
  // src/cost-copilot.ts
14620
- import fs25 from "fs";
14621
- import os22 from "os";
15079
+ import fs26 from "fs";
15080
+ import os23 from "os";
14622
15081
  import path26 from "path";
14623
15082
  function copilotSessionsDir() {
14624
- return path26.join(os22.homedir(), ".copilot", "session-state");
15083
+ return path26.join(os23.homedir(), ".copilot", "session-state");
14625
15084
  }
14626
15085
  function safeReaddir2(dir) {
14627
15086
  try {
14628
- return fs25.readdirSync(dir);
15087
+ return fs26.readdirSync(dir);
14629
15088
  } catch {
14630
15089
  return [];
14631
15090
  }
@@ -14701,7 +15160,7 @@ var init_cost_copilot = __esm({
14701
15160
  id: "copilot",
14702
15161
  available() {
14703
15162
  try {
14704
- return fs25.existsSync(copilotSessionsDir());
15163
+ return fs26.existsSync(copilotSessionsDir());
14705
15164
  } catch {
14706
15165
  return false;
14707
15166
  }
@@ -14712,13 +15171,13 @@ var init_cost_copilot = __esm({
14712
15171
  for (const sid of safeReaddir2(base)) {
14713
15172
  const file = path26.join(base, sid, "events.jsonl");
14714
15173
  try {
14715
- if (sinceMs !== void 0 && fs25.statSync(file).mtimeMs < sinceMs) continue;
15174
+ if (sinceMs !== void 0 && fs26.statSync(file).mtimeMs < sinceMs) continue;
14716
15175
  } catch {
14717
15176
  continue;
14718
15177
  }
14719
15178
  let content;
14720
15179
  try {
14721
- content = fs25.readFileSync(file, "utf8");
15180
+ content = fs26.readFileSync(file, "utf8");
14722
15181
  } catch {
14723
15182
  continue;
14724
15183
  }
@@ -15072,9 +15531,9 @@ var init_scan_summary = __esm({
15072
15531
 
15073
15532
  // src/cli/commands/blast.ts
15074
15533
  import chalk3 from "chalk";
15075
- import fs26 from "fs";
15534
+ import fs27 from "fs";
15076
15535
  import path27 from "path";
15077
- import os23 from "os";
15536
+ import os24 from "os";
15078
15537
  function buildSensitivePaths(home, cwd) {
15079
15538
  return [
15080
15539
  {
@@ -15159,7 +15618,7 @@ function buildSensitivePaths(home, cwd) {
15159
15618
  }
15160
15619
  function isReadable(filePath) {
15161
15620
  try {
15162
- fs26.accessSync(filePath, fs26.constants.R_OK);
15621
+ fs27.accessSync(filePath, fs27.constants.R_OK);
15163
15622
  return true;
15164
15623
  } catch {
15165
15624
  return false;
@@ -15172,13 +15631,13 @@ function scoreLabel(score) {
15172
15631
  return chalk3.red.bold(`${score}/100 Critical`);
15173
15632
  }
15174
15633
  function runBlast() {
15175
- const home = os23.homedir();
15634
+ const home = os24.homedir();
15176
15635
  const cwd = process.cwd();
15177
15636
  const paths = buildSensitivePaths(home, cwd);
15178
15637
  let scoreDeduction = 0;
15179
15638
  const reachable = [];
15180
15639
  for (const p of paths) {
15181
- if (fs26.existsSync(p.full) && isReadable(p.full)) {
15640
+ if (fs27.existsSync(p.full) && isReadable(p.full)) {
15182
15641
  reachable.push(p);
15183
15642
  scoreDeduction += p.score;
15184
15643
  }
@@ -15196,7 +15655,7 @@ function runBlast() {
15196
15655
  }
15197
15656
  function registerBlastCommand(program2) {
15198
15657
  program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
15199
- const home = os23.homedir();
15658
+ const home = os24.homedir();
15200
15659
  const cwd = process.cwd();
15201
15660
  const { reachable, envFindings, score } = runBlast();
15202
15661
  console.log("");
@@ -15385,7 +15844,7 @@ var init_scan_json = __esm({
15385
15844
  });
15386
15845
 
15387
15846
  // src/session-files.ts
15388
- import * as fs27 from "fs";
15847
+ import * as fs28 from "fs";
15389
15848
  import * as path28 from "path";
15390
15849
  function listSessionFiles(dir, maxDepth = 6) {
15391
15850
  const out = [];
@@ -15393,7 +15852,7 @@ function listSessionFiles(dir, maxDepth = 6) {
15393
15852
  if (depth > maxDepth) return;
15394
15853
  let entries;
15395
15854
  try {
15396
- entries = fs27.readdirSync(d, { withFileTypes: true });
15855
+ entries = fs28.readdirSync(d, { withFileTypes: true });
15397
15856
  } catch {
15398
15857
  return;
15399
15858
  }
@@ -15416,17 +15875,17 @@ var init_session_files = __esm({
15416
15875
  });
15417
15876
 
15418
15877
  // src/cli/render/scan-history.ts
15419
- import fs28 from "fs";
15878
+ import fs29 from "fs";
15420
15879
  import path29 from "path";
15421
- import os24 from "os";
15880
+ import os25 from "os";
15422
15881
  function defaultHistoryPath() {
15423
- return path29.join(os24.homedir(), ".node9", "scan-history.json");
15882
+ return path29.join(os25.homedir(), ".node9", "scan-history.json");
15424
15883
  }
15425
15884
  function readPreviousScan(opts = {}) {
15426
15885
  const filePath = opts.path ?? defaultHistoryPath();
15427
15886
  try {
15428
- if (!fs28.existsSync(filePath)) return null;
15429
- const raw = fs28.readFileSync(filePath, "utf8");
15887
+ if (!fs29.existsSync(filePath)) return null;
15888
+ const raw = fs29.readFileSync(filePath, "utf8");
15430
15889
  const parsed = JSON.parse(raw);
15431
15890
  if (!Array.isArray(parsed) || parsed.length === 0) return null;
15432
15891
  const last = parsed[parsed.length - 1];
@@ -15440,11 +15899,11 @@ function appendScanHistory(record2, opts = {}) {
15440
15899
  const filePath = opts.path ?? defaultHistoryPath();
15441
15900
  const cap = opts.cap ?? SCAN_HISTORY_CAP;
15442
15901
  try {
15443
- fs28.mkdirSync(path29.dirname(filePath), { recursive: true });
15902
+ fs29.mkdirSync(path29.dirname(filePath), { recursive: true });
15444
15903
  let history = [];
15445
- if (fs28.existsSync(filePath)) {
15904
+ if (fs29.existsSync(filePath)) {
15446
15905
  try {
15447
- const parsed = JSON.parse(fs28.readFileSync(filePath, "utf8"));
15906
+ const parsed = JSON.parse(fs29.readFileSync(filePath, "utf8"));
15448
15907
  if (Array.isArray(parsed)) {
15449
15908
  history = parsed.filter(isValidRecord);
15450
15909
  }
@@ -15487,9 +15946,9 @@ var init_scan_history = __esm({
15487
15946
  });
15488
15947
 
15489
15948
  // src/costSync.ts
15490
- import fs29 from "fs";
15949
+ import fs30 from "fs";
15491
15950
  import path30 from "path";
15492
- import os25 from "os";
15951
+ import os26 from "os";
15493
15952
  function decodeProjectDirName(dirName) {
15494
15953
  return dirName.replace(/-/g, "/");
15495
15954
  }
@@ -15497,7 +15956,7 @@ function parseJSONLFile(filePath, fallbackWorkingDir) {
15497
15956
  const runId = path30.basename(filePath, ".jsonl");
15498
15957
  let content;
15499
15958
  try {
15500
- content = fs29.readFileSync(filePath, "utf8");
15959
+ content = fs30.readFileSync(filePath, "utf8");
15501
15960
  } catch {
15502
15961
  return /* @__PURE__ */ new Map();
15503
15962
  }
@@ -15597,7 +16056,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
15597
16056
  signal: AbortSignal.timeout(15e3)
15598
16057
  });
15599
16058
  if (!res.ok) {
15600
- fs29.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
16059
+ fs30.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
15601
16060
  `);
15602
16061
  } else {
15603
16062
  let stored;
@@ -15607,7 +16066,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
15607
16066
  } catch {
15608
16067
  }
15609
16068
  if (typeof stored === "number" && stored < batch.length) {
15610
- fs29.appendFileSync(
16069
+ fs30.appendFileSync(
15611
16070
  HOOK_DEBUG_LOG,
15612
16071
  `[cost-sync] dropped ${batch.length - stored} of ${batch.length} rows
15613
16072
  `
@@ -15615,7 +16074,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
15615
16074
  }
15616
16075
  }
15617
16076
  } catch (err2) {
15618
- fs29.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${safeMessage(err2)}
16077
+ fs30.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${safeMessage(err2)}
15619
16078
  `);
15620
16079
  }
15621
16080
  }
@@ -15628,10 +16087,10 @@ async function syncCost() {
15628
16087
  if (entries.length === 0) return;
15629
16088
  let username = "unknown";
15630
16089
  try {
15631
- username = os25.userInfo().username;
16090
+ username = os26.userInfo().username;
15632
16091
  } catch {
15633
16092
  }
15634
- const machineId = `${os25.hostname()}:${username}`;
16093
+ const machineId = `${os26.hostname()}:${username}`;
15635
16094
  await postCostBatches(creds.apiUrl, creds.apiKey, machineId, entries);
15636
16095
  }
15637
16096
  function startCostSync() {
@@ -15659,22 +16118,22 @@ var init_costSync = __esm({
15659
16118
  claudeSource = {
15660
16119
  id: "claude",
15661
16120
  available() {
15662
- return fs29.existsSync(path30.join(os25.homedir(), ".claude", "projects"));
16121
+ return fs30.existsSync(path30.join(os26.homedir(), ".claude", "projects"));
15663
16122
  },
15664
16123
  collect(sinceMs) {
15665
- const projectsDir = path30.join(os25.homedir(), ".claude", "projects");
15666
- if (!fs29.existsSync(projectsDir)) return [];
16124
+ const projectsDir = path30.join(os26.homedir(), ".claude", "projects");
16125
+ if (!fs30.existsSync(projectsDir)) return [];
15667
16126
  const combined = /* @__PURE__ */ new Map();
15668
16127
  let dirs;
15669
16128
  try {
15670
- dirs = fs29.readdirSync(projectsDir);
16129
+ dirs = fs30.readdirSync(projectsDir);
15671
16130
  } catch {
15672
16131
  return [];
15673
16132
  }
15674
16133
  for (const dir of dirs) {
15675
16134
  const dirPath = path30.join(projectsDir, dir);
15676
16135
  try {
15677
- if (!fs29.statSync(dirPath).isDirectory()) continue;
16136
+ if (!fs30.statSync(dirPath).isDirectory()) continue;
15678
16137
  } catch {
15679
16138
  continue;
15680
16139
  }
@@ -15689,7 +16148,7 @@ var init_costSync = __esm({
15689
16148
  const filePath = path30.join(dirPath, file);
15690
16149
  if (sinceMs !== void 0) {
15691
16150
  try {
15692
- if (fs29.statSync(filePath).mtimeMs < sinceMs) continue;
16151
+ if (fs30.statSync(filePath).mtimeMs < sinceMs) continue;
15693
16152
  } catch {
15694
16153
  continue;
15695
16154
  }
@@ -15729,8 +16188,8 @@ __export(scan_watermark_exports, {
15729
16188
  tickForensicBroadcast: () => tickForensicBroadcast,
15730
16189
  tickScanWatcher: () => tickScanWatcher
15731
16190
  });
15732
- import fs30 from "fs";
15733
- import os26 from "os";
16191
+ import fs31 from "fs";
16192
+ import os27 from "os";
15734
16193
  import path31 from "path";
15735
16194
  import readline from "readline";
15736
16195
  function freshWatermark() {
@@ -15744,7 +16203,7 @@ function freshWatermark() {
15744
16203
  function loadWatermark() {
15745
16204
  let raw;
15746
16205
  try {
15747
- raw = fs30.readFileSync(WATERMARK_FILE(), "utf-8");
16206
+ raw = fs31.readFileSync(WATERMARK_FILE(), "utf-8");
15748
16207
  } catch {
15749
16208
  return { status: "fresh", wm: freshWatermark() };
15750
16209
  }
@@ -15797,21 +16256,21 @@ function saveWatermark(wm) {
15797
16256
  if (wm.schemaVersion > WATERMARK_SCHEMA_VERSION) return;
15798
16257
  const target = WATERMARK_FILE();
15799
16258
  const dir = path31.dirname(target);
15800
- if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
16259
+ if (!fs31.existsSync(dir)) fs31.mkdirSync(dir, { recursive: true });
15801
16260
  const tmp = target + ".tmp";
15802
- fs30.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
15803
- fs30.renameSync(tmp, target);
16261
+ fs31.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
16262
+ fs31.renameSync(tmp, target);
15804
16263
  }
15805
16264
  function listJsonlFiles() {
15806
16265
  const root = PROJECTS_DIR();
15807
- if (!fs30.existsSync(root)) return [];
16266
+ if (!fs31.existsSync(root)) return [];
15808
16267
  const out = [];
15809
- for (const entry of fs30.readdirSync(root, { withFileTypes: true })) {
16268
+ for (const entry of fs31.readdirSync(root, { withFileTypes: true })) {
15810
16269
  if (!entry.isDirectory()) continue;
15811
16270
  const projectDir = path31.join(root, entry.name);
15812
16271
  let inner;
15813
16272
  try {
15814
- inner = fs30.readdirSync(projectDir, { withFileTypes: true });
16273
+ inner = fs31.readdirSync(projectDir, { withFileTypes: true });
15815
16274
  } catch {
15816
16275
  continue;
15817
16276
  }
@@ -15825,7 +16284,7 @@ function listJsonlFiles() {
15825
16284
  }
15826
16285
  function fileSize(p) {
15827
16286
  try {
15828
- return fs30.statSync(p).size;
16287
+ return fs31.statSync(p).size;
15829
16288
  } catch {
15830
16289
  return 0;
15831
16290
  }
@@ -15835,7 +16294,7 @@ async function scanDelta(filePath, fromByte, onLine) {
15835
16294
  if (size <= fromByte) return fromByte;
15836
16295
  const lastNl = findLastNewline(filePath, fromByte, size);
15837
16296
  const endsWithNewline = lastNl === size - 1;
15838
- const stream = fs30.createReadStream(filePath, {
16297
+ const stream = fs31.createReadStream(filePath, {
15839
16298
  start: fromByte,
15840
16299
  end: size - 1,
15841
16300
  highWaterMark: 64 * 1024
@@ -15864,7 +16323,7 @@ function findLastNewline(filePath, from, size) {
15864
16323
  const CHUNK = 64 * 1024;
15865
16324
  let fd;
15866
16325
  try {
15867
- fd = fs30.openSync(filePath, "r");
16326
+ fd = fs31.openSync(filePath, "r");
15868
16327
  } catch {
15869
16328
  return -1;
15870
16329
  }
@@ -15873,7 +16332,7 @@ function findLastNewline(filePath, from, size) {
15873
16332
  let end = size;
15874
16333
  while (end > from) {
15875
16334
  const start = Math.max(from, end - CHUNK);
15876
- const n = fs30.readSync(fd, buf, 0, end - start, start);
16335
+ const n = fs31.readSync(fd, buf, 0, end - start, start);
15877
16336
  const idx = buf.subarray(0, n).lastIndexOf(10);
15878
16337
  if (idx !== -1) return start + idx;
15879
16338
  end = start;
@@ -15882,7 +16341,7 @@ function findLastNewline(filePath, from, size) {
15882
16341
  } catch {
15883
16342
  return -1;
15884
16343
  } finally {
15885
- fs30.closeSync(fd);
16344
+ fs31.closeSync(fd);
15886
16345
  }
15887
16346
  }
15888
16347
  function safeCanaryCtxValues() {
@@ -16047,7 +16506,7 @@ function emptyTick(uploadAs) {
16047
16506
  function readRawWatermarkPreservingOffsets() {
16048
16507
  let raw;
16049
16508
  try {
16050
- raw = fs30.readFileSync(WATERMARK_FILE(), "utf-8");
16509
+ raw = fs31.readFileSync(WATERMARK_FILE(), "utf-8");
16051
16510
  } catch {
16052
16511
  return null;
16053
16512
  }
@@ -16082,7 +16541,7 @@ async function runActualTick(wm) {
16082
16541
  if (!known) {
16083
16542
  let mtimeMs = 0;
16084
16543
  try {
16085
- mtimeMs = fs30.statSync(filePath).mtime.getTime();
16544
+ mtimeMs = fs31.statSync(filePath).mtime.getTime();
16086
16545
  } catch {
16087
16546
  continue;
16088
16547
  }
@@ -16141,8 +16600,8 @@ var init_scan_watermark = __esm({
16141
16600
  init_registry();
16142
16601
  init_config();
16143
16602
  init_dist();
16144
- PROJECTS_DIR = () => path31.join(os26.homedir(), ".claude", "projects");
16145
- WATERMARK_FILE = () => path31.join(os26.homedir(), ".node9", "scan-watermark.json");
16603
+ PROJECTS_DIR = () => path31.join(os27.homedir(), ".claude", "projects");
16604
+ WATERMARK_FILE = () => path31.join(os27.homedir(), ".node9", "scan-watermark.json");
16146
16605
  MAX_LINE_BYTES = 2 * 1024 * 1024;
16147
16606
  WATERMARK_SCHEMA_VERSION = 2;
16148
16607
  LONG_OUTPUT_THRESHOLD_BYTES2 = LONG_OUTPUT_THRESHOLD_BYTES;
@@ -16158,9 +16617,9 @@ __export(scan_upload_history_exports, {
16158
16617
  parseSinceCutoff: () => parseSinceCutoff,
16159
16618
  runUploadHistory: () => runUploadHistory
16160
16619
  });
16161
- import fs31 from "fs";
16620
+ import fs32 from "fs";
16162
16621
  import https from "https";
16163
- import os27 from "os";
16622
+ import os28 from "os";
16164
16623
  import path32 from "path";
16165
16624
  import chalk5 from "chalk";
16166
16625
  function emptySignals2() {
@@ -16196,10 +16655,10 @@ function parseSinceCutoff(raw, now = /* @__PURE__ */ new Date()) {
16196
16655
  return now.getTime() - 90 * 864e5;
16197
16656
  }
16198
16657
  function* iterateJsonlFiles(cutoffMs) {
16199
- const projectsDir = path32.join(os27.homedir(), ".claude", "projects");
16658
+ const projectsDir = path32.join(os28.homedir(), ".claude", "projects");
16200
16659
  let dirs;
16201
16660
  try {
16202
- dirs = fs31.readdirSync(projectsDir);
16661
+ dirs = fs32.readdirSync(projectsDir);
16203
16662
  } catch {
16204
16663
  return;
16205
16664
  }
@@ -16207,14 +16666,14 @@ function* iterateJsonlFiles(cutoffMs) {
16207
16666
  const dirPath = path32.join(projectsDir, dir);
16208
16667
  let stats;
16209
16668
  try {
16210
- stats = fs31.statSync(dirPath);
16669
+ stats = fs32.statSync(dirPath);
16211
16670
  } catch {
16212
16671
  continue;
16213
16672
  }
16214
16673
  if (!stats.isDirectory()) continue;
16215
16674
  let files;
16216
16675
  try {
16217
- files = fs31.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
16676
+ files = fs32.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
16218
16677
  } catch {
16219
16678
  continue;
16220
16679
  }
@@ -16222,7 +16681,7 @@ function* iterateJsonlFiles(cutoffMs) {
16222
16681
  const filePath = path32.join(dirPath, file);
16223
16682
  let mtime = 0;
16224
16683
  try {
16225
- mtime = fs31.statSync(filePath).mtimeMs;
16684
+ mtime = fs32.statSync(filePath).mtimeMs;
16226
16685
  } catch {
16227
16686
  continue;
16228
16687
  }
@@ -16297,7 +16756,7 @@ async function runUploadHistory(opts) {
16297
16756
  filesScanned++;
16298
16757
  let content;
16299
16758
  try {
16300
- content = fs31.readFileSync(filePath, "utf8");
16759
+ content = fs32.readFileSync(filePath, "utf8");
16301
16760
  } catch {
16302
16761
  continue;
16303
16762
  }
@@ -16371,10 +16830,10 @@ async function runUploadHistory(opts) {
16371
16830
  const costUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/cost-sync") : `${creds.apiUrl.replace(/\/$/, "")}/cost-sync`;
16372
16831
  let username = "unknown";
16373
16832
  try {
16374
- username = os27.userInfo().username;
16833
+ username = os28.userInfo().username;
16375
16834
  } catch {
16376
16835
  }
16377
- const machineId = `${os27.hostname()}:${username}`;
16836
+ const machineId = `${os28.hostname()}:${username}`;
16378
16837
  await postJson(costUrl, creds.apiKey, {
16379
16838
  machineId,
16380
16839
  entries: dailyEntries
@@ -16448,9 +16907,9 @@ var init_scan_upload_history = __esm({
16448
16907
 
16449
16908
  // src/cli/commands/scan.ts
16450
16909
  import chalk6 from "chalk";
16451
- import fs32 from "fs";
16910
+ import fs33 from "fs";
16452
16911
  import path33 from "path";
16453
- import os28 from "os";
16912
+ import os29 from "os";
16454
16913
  import stringWidth2 from "string-width";
16455
16914
  function claudeModelPrice(model) {
16456
16915
  const t = pricingFor(model);
@@ -16538,7 +16997,7 @@ function findingKey(ruleName, inputPreview, projLabel) {
16538
16997
  return `${ruleName ?? "<unnamed>"}|${inputPreview}|${projLabel}`;
16539
16998
  }
16540
16999
  function displayHome(p) {
16541
- const home = os28.homedir();
17000
+ const home = os29.homedir();
16542
17001
  return p.startsWith(home) ? "~" + p.slice(home.length) : p;
16543
17002
  }
16544
17003
  function dlpKey(patternName, redactedSample, projLabel) {
@@ -16735,14 +17194,14 @@ function buildRuleSources() {
16735
17194
  }
16736
17195
  function countScanFiles() {
16737
17196
  let total = 0;
16738
- const claudeDir = path33.join(os28.homedir(), ".claude", "projects");
16739
- if (fs32.existsSync(claudeDir)) {
17197
+ const claudeDir = path33.join(os29.homedir(), ".claude", "projects");
17198
+ if (fs33.existsSync(claudeDir)) {
16740
17199
  try {
16741
- for (const proj of fs32.readdirSync(claudeDir)) {
17200
+ for (const proj of fs33.readdirSync(claudeDir)) {
16742
17201
  const p = path33.join(claudeDir, proj);
16743
17202
  try {
16744
- if (!fs32.statSync(p).isDirectory()) continue;
16745
- total += fs32.readdirSync(p).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).length;
17203
+ if (!fs33.statSync(p).isDirectory()) continue;
17204
+ total += fs33.readdirSync(p).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).length;
16746
17205
  } catch {
16747
17206
  continue;
16748
17207
  }
@@ -16750,17 +17209,17 @@ function countScanFiles() {
16750
17209
  } catch {
16751
17210
  }
16752
17211
  }
16753
- const geminiDir = path33.join(os28.homedir(), ".gemini", "tmp");
16754
- if (fs32.existsSync(geminiDir)) {
17212
+ const geminiDir = path33.join(os29.homedir(), ".gemini", "tmp");
17213
+ if (fs33.existsSync(geminiDir)) {
16755
17214
  try {
16756
- for (const slug2 of fs32.readdirSync(geminiDir)) {
17215
+ for (const slug2 of fs33.readdirSync(geminiDir)) {
16757
17216
  const p = path33.join(geminiDir, slug2);
16758
17217
  try {
16759
- if (!fs32.statSync(p).isDirectory()) continue;
17218
+ if (!fs33.statSync(p).isDirectory()) continue;
16760
17219
  const chatsDir = path33.join(p, "chats");
16761
- if (fs32.existsSync(chatsDir)) {
17220
+ if (fs33.existsSync(chatsDir)) {
16762
17221
  try {
16763
- total += fs32.readdirSync(chatsDir).filter((f) => f.endsWith(".json")).length;
17222
+ total += fs33.readdirSync(chatsDir).filter((f) => f.endsWith(".json")).length;
16764
17223
  } catch {
16765
17224
  }
16766
17225
  }
@@ -16772,15 +17231,15 @@ function countScanFiles() {
16772
17231
  }
16773
17232
  }
16774
17233
  for (const surface of ["antigravity-cli", "antigravity-ide"]) {
16775
- const brainDir = path33.join(os28.homedir(), ".gemini", surface, "brain");
16776
- if (!fs32.existsSync(brainDir)) continue;
17234
+ const brainDir = path33.join(os29.homedir(), ".gemini", surface, "brain");
17235
+ if (!fs33.existsSync(brainDir)) continue;
16777
17236
  try {
16778
- for (const conv of fs32.readdirSync(brainDir)) {
17237
+ for (const conv of fs33.readdirSync(brainDir)) {
16779
17238
  const convPath = path33.join(brainDir, conv);
16780
17239
  try {
16781
- if (!fs32.statSync(convPath).isDirectory()) continue;
17240
+ if (!fs33.statSync(convPath).isDirectory()) continue;
16782
17241
  const logsDir = path33.join(convPath, ".system_generated", "logs");
16783
- if (fs32.existsSync(path33.join(logsDir, "transcript_full.jsonl")) || fs32.existsSync(path33.join(logsDir, "transcript.jsonl"))) {
17242
+ if (fs33.existsSync(path33.join(logsDir, "transcript_full.jsonl")) || fs33.existsSync(path33.join(logsDir, "transcript.jsonl"))) {
16784
17243
  total += 1;
16785
17244
  }
16786
17245
  } catch {
@@ -16790,11 +17249,11 @@ function countScanFiles() {
16790
17249
  } catch {
16791
17250
  }
16792
17251
  }
16793
- const copilotDir = path33.join(os28.homedir(), ".copilot", "session-state");
16794
- if (fs32.existsSync(copilotDir)) {
17252
+ const copilotDir = path33.join(os29.homedir(), ".copilot", "session-state");
17253
+ if (fs33.existsSync(copilotDir)) {
16795
17254
  try {
16796
- for (const sid of fs32.readdirSync(copilotDir)) {
16797
- if (fs32.existsSync(path33.join(copilotDir, sid, "events.jsonl"))) total += 1;
17255
+ for (const sid of fs33.readdirSync(copilotDir)) {
17256
+ if (fs33.existsSync(path33.join(copilotDir, sid, "events.jsonl"))) total += 1;
16798
17257
  }
16799
17258
  } catch {
16800
17259
  }
@@ -16821,7 +17280,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
16821
17280
  const session = { sessionId, costUSD: 0, toolCalls: 0 };
16822
17281
  let raw;
16823
17282
  try {
16824
- raw = fs32.readFileSync(path33.join(projPath, file), "utf-8");
17283
+ raw = fs33.readFileSync(path33.join(projPath, file), "utf-8");
16825
17284
  } catch {
16826
17285
  return;
16827
17286
  }
@@ -17065,11 +17524,11 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
17065
17524
  function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, canaryVals, onProgress, onLine) {
17066
17525
  const projPath = path33.join(projectsDir, proj);
17067
17526
  try {
17068
- if (!fs32.statSync(projPath).isDirectory()) return;
17527
+ if (!fs33.statSync(projPath).isDirectory()) return;
17069
17528
  } catch {
17070
17529
  return;
17071
17530
  }
17072
- const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os28.homedir(), "~")).slice(
17531
+ const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os29.homedir(), "~")).slice(
17073
17532
  0,
17074
17533
  40
17075
17534
  );
@@ -17112,12 +17571,12 @@ function emptyClaudeScan() {
17112
17571
  };
17113
17572
  }
17114
17573
  function scanClaudeHistory(startDate, onProgress, onLine) {
17115
- const projectsDir = path33.join(os28.homedir(), ".claude", "projects");
17574
+ const projectsDir = path33.join(os29.homedir(), ".claude", "projects");
17116
17575
  const result = emptyClaudeScan();
17117
- if (!fs32.existsSync(projectsDir)) return result;
17576
+ if (!fs33.existsSync(projectsDir)) return result;
17118
17577
  let projDirs;
17119
17578
  try {
17120
- projDirs = fs32.readdirSync(projectsDir);
17579
+ projDirs = fs33.readdirSync(projectsDir);
17121
17580
  } catch {
17122
17581
  return result;
17123
17582
  }
@@ -17141,7 +17600,7 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
17141
17600
  }
17142
17601
  function scanGeminiHistory(startDate, onProgress, onLine) {
17143
17602
  const canaryVals = safeCanaryScanValues();
17144
- const tmpDir = path33.join(os28.homedir(), ".gemini", "tmp");
17603
+ const tmpDir = path33.join(os29.homedir(), ".gemini", "tmp");
17145
17604
  const result = {
17146
17605
  filesScanned: 0,
17147
17606
  sessions: 0,
@@ -17158,10 +17617,10 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
17158
17617
  perSession: []
17159
17618
  };
17160
17619
  const dedup = emptyScanDedup();
17161
- if (!fs32.existsSync(tmpDir)) return result;
17620
+ if (!fs33.existsSync(tmpDir)) return result;
17162
17621
  let slugDirs;
17163
17622
  try {
17164
- slugDirs = fs32.readdirSync(tmpDir);
17623
+ slugDirs = fs33.readdirSync(tmpDir);
17165
17624
  } catch {
17166
17625
  return result;
17167
17626
  }
@@ -17169,22 +17628,22 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
17169
17628
  for (const slug2 of slugDirs) {
17170
17629
  const slugPath = path33.join(tmpDir, slug2);
17171
17630
  try {
17172
- if (!fs32.statSync(slugPath).isDirectory()) continue;
17631
+ if (!fs33.statSync(slugPath).isDirectory()) continue;
17173
17632
  } catch {
17174
17633
  continue;
17175
17634
  }
17176
17635
  let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
17177
17636
  try {
17178
17637
  projLabel = stripTerminalEscapes(
17179
- fs32.readFileSync(path33.join(slugPath, ".project_root"), "utf-8").trim()
17180
- ).replace(os28.homedir(), "~").slice(0, 40);
17638
+ fs33.readFileSync(path33.join(slugPath, ".project_root"), "utf-8").trim()
17639
+ ).replace(os29.homedir(), "~").slice(0, 40);
17181
17640
  } catch {
17182
17641
  }
17183
17642
  const chatsDir = path33.join(slugPath, "chats");
17184
- if (!fs32.existsSync(chatsDir)) continue;
17643
+ if (!fs33.existsSync(chatsDir)) continue;
17185
17644
  let chatFiles;
17186
17645
  try {
17187
- chatFiles = fs32.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
17646
+ chatFiles = fs33.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
17188
17647
  } catch {
17189
17648
  continue;
17190
17649
  }
@@ -17197,7 +17656,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
17197
17656
  onProgress?.(result.filesScanned);
17198
17657
  let raw;
17199
17658
  try {
17200
- raw = fs32.readFileSync(path33.join(chatsDir, chatFile), "utf-8");
17659
+ raw = fs33.readFileSync(path33.join(chatsDir, chatFile), "utf-8");
17201
17660
  } catch {
17202
17661
  continue;
17203
17662
  }
@@ -17392,13 +17851,13 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
17392
17851
  return result;
17393
17852
  }
17394
17853
  function antigravityBrainDirs() {
17395
- return ["antigravity-cli", "antigravity-ide"].map((surface) => path33.join(os28.homedir(), ".gemini", surface, "brain")).filter((p) => fs32.existsSync(p));
17854
+ return ["antigravity-cli", "antigravity-ide"].map((surface) => path33.join(os29.homedir(), ".gemini", surface, "brain")).filter((p) => fs33.existsSync(p));
17396
17855
  }
17397
17856
  function antigravityTranscriptPath(convPath) {
17398
17857
  const logsDir = path33.join(convPath, ".system_generated", "logs");
17399
17858
  for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
17400
17859
  const p = path33.join(logsDir, name);
17401
- if (fs32.existsSync(p)) return p;
17860
+ if (fs33.existsSync(p)) return p;
17402
17861
  }
17403
17862
  return null;
17404
17863
  }
@@ -17427,14 +17886,14 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
17427
17886
  for (const brainDir of brainDirs) {
17428
17887
  let convDirs;
17429
17888
  try {
17430
- convDirs = fs32.readdirSync(brainDir);
17889
+ convDirs = fs33.readdirSync(brainDir);
17431
17890
  } catch {
17432
17891
  continue;
17433
17892
  }
17434
17893
  for (const conv of convDirs) {
17435
17894
  const convPath = path33.join(brainDir, conv);
17436
17895
  try {
17437
- if (!fs32.statSync(convPath).isDirectory()) continue;
17896
+ if (!fs33.statSync(convPath).isDirectory()) continue;
17438
17897
  } catch {
17439
17898
  continue;
17440
17899
  }
@@ -17444,7 +17903,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
17444
17903
  onProgress?.(result.filesScanned);
17445
17904
  let raw;
17446
17905
  try {
17447
- raw = fs32.readFileSync(transcriptFile, "utf-8");
17906
+ raw = fs33.readFileSync(transcriptFile, "utf-8");
17448
17907
  } catch {
17449
17908
  continue;
17450
17909
  }
@@ -17512,7 +17971,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
17512
17971
  result.bashCalls++;
17513
17972
  const cwd = String(input.cwd ?? "");
17514
17973
  if (cwd && projLabel === conv.slice(0, 8)) {
17515
- projLabel = stripTerminalEscapes(cwd).replace(os28.homedir(), "~").slice(0, 40);
17974
+ projLabel = stripTerminalEscapes(cwd).replace(os29.homedir(), "~").slice(0, 40);
17516
17975
  }
17517
17976
  }
17518
17977
  const rawCmd = String(input.command ?? "").trimStart();
@@ -17624,7 +18083,7 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
17624
18083
  }
17625
18084
  function scanCopilotHistory(startDate, onProgress, onLine) {
17626
18085
  const canaryVals = safeCanaryScanValues();
17627
- const sessionDir = path33.join(os28.homedir(), ".copilot", "session-state");
18086
+ const sessionDir = path33.join(os29.homedir(), ".copilot", "session-state");
17628
18087
  const result = {
17629
18088
  filesScanned: 0,
17630
18089
  sessions: 0,
@@ -17642,22 +18101,22 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
17642
18101
  perSession: []
17643
18102
  };
17644
18103
  const dedup = emptyScanDedup();
17645
- if (!fs32.existsSync(sessionDir)) return result;
18104
+ if (!fs33.existsSync(sessionDir)) return result;
17646
18105
  let sessionIds;
17647
18106
  try {
17648
- sessionIds = fs32.readdirSync(sessionDir);
18107
+ sessionIds = fs33.readdirSync(sessionDir);
17649
18108
  } catch {
17650
18109
  return result;
17651
18110
  }
17652
18111
  const ruleSources = buildRuleSources();
17653
18112
  for (const sessionId of sessionIds) {
17654
18113
  const eventsPath = path33.join(sessionDir, sessionId, "events.jsonl");
17655
- if (!fs32.existsSync(eventsPath)) continue;
18114
+ if (!fs33.existsSync(eventsPath)) continue;
17656
18115
  result.filesScanned++;
17657
18116
  onProgress?.(result.filesScanned);
17658
18117
  let raw;
17659
18118
  try {
17660
- raw = fs32.readFileSync(eventsPath, "utf-8");
18119
+ raw = fs33.readFileSync(eventsPath, "utf-8");
17661
18120
  } catch {
17662
18121
  continue;
17663
18122
  }
@@ -17681,7 +18140,7 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
17681
18140
  if (ev.type === "session.start") {
17682
18141
  const cwd = ev.data?.context?.cwd;
17683
18142
  if (typeof cwd === "string" && cwd) {
17684
- projLabel = stripTerminalEscapes(cwd).replace(os28.homedir(), "~").slice(0, 40);
18143
+ projLabel = stripTerminalEscapes(cwd).replace(os29.homedir(), "~").slice(0, 40);
17685
18144
  }
17686
18145
  continue;
17687
18146
  }
@@ -17859,7 +18318,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
17859
18318
  onProgress?.(result.filesScanned);
17860
18319
  let lines;
17861
18320
  try {
17862
- lines = fs32.readFileSync(filePath, "utf-8").split("\n");
18321
+ lines = fs33.readFileSync(filePath, "utf-8").split("\n");
17863
18322
  } catch {
17864
18323
  continue;
17865
18324
  }
@@ -17882,7 +18341,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
17882
18341
  sessionId = String(payload["id"] ?? filePath);
17883
18342
  startTime = String(payload["timestamp"] ?? "");
17884
18343
  const cwd = String(payload["cwd"] ?? "");
17885
- projLabel = stripTerminalEscapes(cwd.replace(os28.homedir(), "~")).slice(0, 40);
18344
+ projLabel = stripTerminalEscapes(cwd.replace(os29.homedir(), "~")).slice(0, 40);
17886
18345
  continue;
17887
18346
  }
17888
18347
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
@@ -18049,17 +18508,17 @@ function scanCodexHistory(startDate, onProgress, onLine) {
18049
18508
  return result;
18050
18509
  }
18051
18510
  function scanShellConfig() {
18052
- const home = os28.homedir();
18511
+ const home = os29.homedir();
18053
18512
  const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
18054
18513
  (f) => path33.join(home, f)
18055
18514
  );
18056
18515
  const findings = [];
18057
18516
  const seen = /* @__PURE__ */ new Set();
18058
18517
  for (const filePath of configFiles) {
18059
- if (!fs32.existsSync(filePath)) continue;
18518
+ if (!fs33.existsSync(filePath)) continue;
18060
18519
  let lines;
18061
18520
  try {
18062
- lines = fs32.readFileSync(filePath, "utf-8").split("\n");
18521
+ lines = fs33.readFileSync(filePath, "utf-8").split("\n");
18063
18522
  } catch {
18064
18523
  continue;
18065
18524
  }
@@ -19289,12 +19748,12 @@ var init_scan = __esm({
19289
19748
  });
19290
19749
 
19291
19750
  // src/daemon/build-id.ts
19292
- import fs33 from "fs";
19751
+ import fs34 from "fs";
19293
19752
  import path34 from "path";
19294
19753
  function readOwnVersion() {
19295
19754
  for (const rel of ["../package.json", "../../package.json"]) {
19296
19755
  try {
19297
- const raw = fs33.readFileSync(path34.join(__dirname, rel), "utf-8");
19756
+ const raw = fs34.readFileSync(path34.join(__dirname, rel), "utf-8");
19298
19757
  const v = JSON.parse(raw).version;
19299
19758
  if (typeof v === "string" && v.length > 0) return v;
19300
19759
  } catch {
@@ -19305,7 +19764,7 @@ function readOwnVersion() {
19305
19764
  function computeBuildId(entry = process.argv[1] ?? "") {
19306
19765
  let mtimeMs = 0;
19307
19766
  try {
19308
- if (entry) mtimeMs = fs33.statSync(entry).mtimeMs;
19767
+ if (entry) mtimeMs = fs34.statSync(entry).mtimeMs;
19309
19768
  } catch {
19310
19769
  }
19311
19770
  return { version: readOwnVersion(), mtimeMs };
@@ -19446,7 +19905,7 @@ var init_suggestion_tracker = __esm({
19446
19905
  });
19447
19906
 
19448
19907
  // src/daemon/taint-store.ts
19449
- import fs34 from "fs";
19908
+ import fs35 from "fs";
19450
19909
  import path35 from "path";
19451
19910
  var DEFAULT_TTL_MS, TaintStore, SESSION_TAINT_TTL_MS, SessionTaintStore;
19452
19911
  var init_taint_store = __esm({
@@ -19517,7 +19976,7 @@ var init_taint_store = __esm({
19517
19976
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
19518
19977
  _resolve(filePath) {
19519
19978
  try {
19520
- return fs34.realpathSync.native(path35.resolve(filePath));
19979
+ return fs35.realpathSync.native(path35.resolve(filePath));
19521
19980
  } catch {
19522
19981
  return path35.resolve(filePath);
19523
19982
  }
@@ -19684,14 +20143,14 @@ var init_session_history = __esm({
19684
20143
 
19685
20144
  // src/daemon/state.ts
19686
20145
  import net2 from "net";
19687
- import fs35 from "fs";
20146
+ import fs36 from "fs";
19688
20147
  import path36 from "path";
19689
- import os29 from "os";
20148
+ import os30 from "os";
19690
20149
  import { randomUUID as randomUUID5 } from "crypto";
19691
20150
  function loadInsightCounts() {
19692
20151
  try {
19693
- if (!fs35.existsSync(INSIGHT_COUNTS_FILE)) return;
19694
- const data = JSON.parse(fs35.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
20152
+ if (!fs36.existsSync(INSIGHT_COUNTS_FILE)) return;
20153
+ const data = JSON.parse(fs36.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
19695
20154
  for (const [tool, count] of Object.entries(data)) {
19696
20155
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
19697
20156
  }
@@ -19748,15 +20207,15 @@ function appendAuditLog(data) {
19748
20207
  source: "daemon"
19749
20208
  };
19750
20209
  const dir = path36.dirname(AUDIT_LOG_FILE);
19751
- if (!fs35.existsSync(dir)) fs35.mkdirSync(dir, { recursive: true });
19752
- fs35.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
20210
+ if (!fs36.existsSync(dir)) fs36.mkdirSync(dir, { recursive: true });
20211
+ fs36.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
19753
20212
  } catch {
19754
20213
  }
19755
20214
  }
19756
20215
  function getAuditHistory(limit = 20) {
19757
20216
  try {
19758
- if (!fs35.existsSync(AUDIT_LOG_FILE)) return [];
19759
- const lines = fs35.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
20217
+ if (!fs36.existsSync(AUDIT_LOG_FILE)) return [];
20218
+ const lines = fs36.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
19760
20219
  if (lines.length === 1 && lines[0] === "") return [];
19761
20220
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
19762
20221
  } catch {
@@ -19765,7 +20224,7 @@ function getAuditHistory(limit = 20) {
19765
20224
  }
19766
20225
  function getOrgName() {
19767
20226
  try {
19768
- if (fs35.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
20227
+ if (fs36.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
19769
20228
  } catch {
19770
20229
  }
19771
20230
  return null;
@@ -19773,8 +20232,8 @@ function getOrgName() {
19773
20232
  function writeGlobalSetting(key, value) {
19774
20233
  let config = {};
19775
20234
  try {
19776
- if (fs35.existsSync(GLOBAL_CONFIG_FILE)) {
19777
- config = JSON.parse(fs35.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
20235
+ if (fs36.existsSync(GLOBAL_CONFIG_FILE)) {
20236
+ config = JSON.parse(fs36.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
19778
20237
  }
19779
20238
  } catch {
19780
20239
  }
@@ -19786,8 +20245,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
19786
20245
  try {
19787
20246
  let trust = { entries: [] };
19788
20247
  try {
19789
- if (fs35.existsSync(TRUST_FILE2))
19790
- trust = JSON.parse(fs35.readFileSync(TRUST_FILE2, "utf-8"));
20248
+ if (fs36.existsSync(TRUST_FILE2))
20249
+ trust = JSON.parse(fs36.readFileSync(TRUST_FILE2, "utf-8"));
19791
20250
  } catch {
19792
20251
  }
19793
20252
  trust.entries = trust.entries.filter(
@@ -19804,8 +20263,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
19804
20263
  }
19805
20264
  function readPersistentDecisions() {
19806
20265
  try {
19807
- if (fs35.existsSync(DECISIONS_FILE)) {
19808
- return JSON.parse(fs35.readFileSync(DECISIONS_FILE, "utf-8"));
20266
+ if (fs36.existsSync(DECISIONS_FILE)) {
20267
+ return JSON.parse(fs36.readFileSync(DECISIONS_FILE, "utf-8"));
19809
20268
  }
19810
20269
  } catch {
19811
20270
  }
@@ -19833,7 +20292,7 @@ function estimateToolCost(tool, args) {
19833
20292
  const filePath = a.file_path ?? a.path;
19834
20293
  if (filePath) {
19835
20294
  try {
19836
- const bytes = fs35.statSync(filePath).size;
20295
+ const bytes = fs36.statSync(filePath).size;
19837
20296
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
19838
20297
  } catch {
19839
20298
  }
@@ -19907,7 +20366,7 @@ function abandonPending() {
19907
20366
  });
19908
20367
  if (autoStarted) {
19909
20368
  try {
19910
- fs35.unlinkSync(DAEMON_PID_FILE);
20369
+ fs36.unlinkSync(DAEMON_PID_FILE);
19911
20370
  } catch {
19912
20371
  }
19913
20372
  setTimeout(() => {
@@ -19918,7 +20377,7 @@ function abandonPending() {
19918
20377
  }
19919
20378
  function logActivitySocket(msg) {
19920
20379
  try {
19921
- fs35.appendFileSync(
20380
+ fs36.appendFileSync(
19922
20381
  path36.join(homeDir, ".node9", "hook-debug.log"),
19923
20382
  `[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
19924
20383
  `
@@ -19941,13 +20400,13 @@ function shouldRebind(now = Date.now()) {
19941
20400
  function startActivitySocket() {
19942
20401
  bindActivitySocket();
19943
20402
  activityHealthInterval = setInterval(() => {
19944
- if (!fs35.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
20403
+ if (!fs36.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
19945
20404
  }, ACTIVITY_HEALTH_PROBE_MS);
19946
20405
  activityHealthInterval.unref();
19947
20406
  process.on("exit", () => {
19948
20407
  if (activityHealthInterval) clearInterval(activityHealthInterval);
19949
20408
  try {
19950
- fs35.unlinkSync(ACTIVITY_SOCKET_PATH2);
20409
+ fs36.unlinkSync(ACTIVITY_SOCKET_PATH2);
19951
20410
  } catch {
19952
20411
  }
19953
20412
  });
@@ -19975,7 +20434,7 @@ function attemptRebind(reason) {
19975
20434
  }
19976
20435
  function bindActivitySocket() {
19977
20436
  try {
19978
- fs35.unlinkSync(ACTIVITY_SOCKET_PATH2);
20437
+ fs36.unlinkSync(ACTIVITY_SOCKET_PATH2);
19979
20438
  } catch {
19980
20439
  }
19981
20440
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -20079,7 +20538,7 @@ var init_state2 = __esm({
20079
20538
  init_session_counters();
20080
20539
  init_session_history();
20081
20540
  init_atomic_write();
20082
- homeDir = os29.homedir();
20541
+ homeDir = os30.homedir();
20083
20542
  DAEMON_PID_FILE = path36.join(homeDir, ".node9", "daemon.pid");
20084
20543
  DECISIONS_FILE = path36.join(homeDir, ".node9", "decisions.json");
20085
20544
  AUDIT_LOG_FILE = path36.join(homeDir, ".node9", "audit.log");
@@ -20104,7 +20563,7 @@ var init_state2 = __esm({
20104
20563
  "2h": 2 * 60 * 6e4
20105
20564
  };
20106
20565
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
20107
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path36.join(os29.tmpdir(), "node9-activity.sock");
20566
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path36.join(os30.tmpdir(), "node9-activity.sock");
20108
20567
  ACTIVITY_RING_SIZE = 100;
20109
20568
  activityRing = [];
20110
20569
  LARGE_RESPONSE_RING_SIZE = 20;
@@ -20143,9 +20602,9 @@ var init_state2 = __esm({
20143
20602
  });
20144
20603
 
20145
20604
  // src/posture/secrets.ts
20146
- import fs36 from "fs";
20605
+ import fs37 from "fs";
20147
20606
  import path37 from "path";
20148
- import os30 from "os";
20607
+ import os31 from "os";
20149
20608
  function displayPath(p, home) {
20150
20609
  if (p === home) return "~";
20151
20610
  const prefix = home.endsWith(path37.sep) ? home : home + path37.sep;
@@ -20160,7 +20619,7 @@ function safeRead(file) {
20160
20619
  function candidateFiles(home, cwd) {
20161
20620
  const files = /* @__PURE__ */ new Set();
20162
20621
  try {
20163
- for (const name of fs36.readdirSync(cwd)) {
20622
+ for (const name of fs37.readdirSync(cwd)) {
20164
20623
  if (name === ".env" || name.startsWith(".env.")) files.add(path37.join(cwd, name));
20165
20624
  }
20166
20625
  } catch {
@@ -20190,7 +20649,7 @@ function plantedDecoyPaths() {
20190
20649
  }
20191
20650
  }
20192
20651
  function checkSecrets(ctx) {
20193
- const home = ctx.home || os30.homedir();
20652
+ const home = ctx.home || os31.homedir();
20194
20653
  const findings = [];
20195
20654
  const planted = plantedDecoyPaths();
20196
20655
  const plaintext = [];
@@ -20226,7 +20685,7 @@ function checkSecrets(ctx) {
20226
20685
  for (const file of credentialMaterial(home)) {
20227
20686
  if (planted.has(file)) continue;
20228
20687
  try {
20229
- if (fs36.statSync(file).isFile()) {
20688
+ if (fs37.statSync(file).isFile()) {
20230
20689
  creds.push(displayPath(file, home));
20231
20690
  credPaths.push(file);
20232
20691
  }
@@ -20395,10 +20854,10 @@ var init_templates = __esm({
20395
20854
  });
20396
20855
 
20397
20856
  // src/posture/egress.ts
20398
- import fs37 from "fs";
20857
+ import fs38 from "fs";
20399
20858
  function sandboxEgressWallActive() {
20400
20859
  try {
20401
- return fs37.existsSync(ALLOWED_DOMAINS_PATH);
20860
+ return fs38.existsSync(ALLOWED_DOMAINS_PATH);
20402
20861
  } catch {
20403
20862
  return false;
20404
20863
  }
@@ -20568,9 +21027,9 @@ var init_gate = __esm({
20568
21027
  });
20569
21028
 
20570
21029
  // src/posture/supply-chain.ts
20571
- import os31 from "os";
21030
+ import os32 from "os";
20572
21031
  import path38 from "path";
20573
- import { parse as parseToml4 } from "smol-toml";
21032
+ import { parse as parseToml5 } from "smol-toml";
20574
21033
  function isNode9Managed(command, args = []) {
20575
21034
  if (!command) return false;
20576
21035
  if (path38.basename(command).toLowerCase() === "node9") return true;
@@ -20584,7 +21043,7 @@ function readServers(file, format, agent) {
20584
21043
  const capped = readCappedText(file, MAX_CONFIG_BYTES);
20585
21044
  if (!capped || capped.truncated) return [];
20586
21045
  const text = capped.text;
20587
- const map = format === "toml" ? parseToml4(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
21046
+ const map = format === "toml" ? parseToml5(text)?.mcp_servers : JSON.parse(text)?.mcpServers;
20588
21047
  if (!map || typeof map !== "object") return [];
20589
21048
  return Object.entries(map).map(([name, v]) => ({
20590
21049
  name,
@@ -20597,7 +21056,7 @@ function readServers(file, format, agent) {
20597
21056
  }
20598
21057
  }
20599
21058
  function checkSupplyChain(ctx) {
20600
- const home = ctx.home || os31.homedir();
21059
+ const home = ctx.home || os32.homedir();
20601
21060
  const servers = [];
20602
21061
  for (const spec of AGENT_SPECS) {
20603
21062
  if (!spec.mcpFile) continue;
@@ -20696,11 +21155,11 @@ var init_privilege = __esm({
20696
21155
  });
20697
21156
 
20698
21157
  // src/posture/containment.ts
20699
- import fs38 from "fs";
21158
+ import fs39 from "fs";
20700
21159
  function inContainer() {
20701
- if (fs38.existsSync("/.dockerenv") || fs38.existsSync("/run/.containerenv")) return true;
21160
+ if (fs39.existsSync("/.dockerenv") || fs39.existsSync("/run/.containerenv")) return true;
20702
21161
  try {
20703
- const cgroup = fs38.readFileSync("/proc/1/cgroup", "utf8");
21162
+ const cgroup = fs39.readFileSync("/proc/1/cgroup", "utf8");
20704
21163
  if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
20705
21164
  } catch {
20706
21165
  }
@@ -20746,7 +21205,7 @@ var init_containment = __esm({
20746
21205
  });
20747
21206
 
20748
21207
  // src/posture/inbound.ts
20749
- import fs39 from "fs";
21208
+ import fs40 from "fs";
20750
21209
  function buildNetworkFix(labels) {
20751
21210
  const shielded = [
20752
21211
  ...new Map(
@@ -20804,7 +21263,7 @@ function collectListeners() {
20804
21263
  const byPort = /* @__PURE__ */ new Map();
20805
21264
  for (const file of ["/proc/net/tcp", "/proc/net/tcp6"]) {
20806
21265
  try {
20807
- for (const l of parseListeners(fs39.readFileSync(file, "utf8"))) {
21266
+ for (const l of parseListeners(fs40.readFileSync(file, "utf8"))) {
20808
21267
  if (!byPort.has(l.port)) byPort.set(l.port, l);
20809
21268
  }
20810
21269
  } catch {
@@ -20816,11 +21275,11 @@ function readProc(pid) {
20816
21275
  let comm = "unknown";
20817
21276
  let cmdline = "";
20818
21277
  try {
20819
- comm = fs39.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
21278
+ comm = fs40.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
20820
21279
  } catch {
20821
21280
  }
20822
21281
  try {
20823
- cmdline = fs39.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
21282
+ cmdline = fs40.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
20824
21283
  } catch {
20825
21284
  }
20826
21285
  return { comm, cmdline };
@@ -20830,21 +21289,21 @@ function resolveProcesses(inodes) {
20830
21289
  if (inodes.size === 0) return map;
20831
21290
  let pids;
20832
21291
  try {
20833
- pids = fs39.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
21292
+ pids = fs40.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
20834
21293
  } catch {
20835
21294
  return map;
20836
21295
  }
20837
21296
  for (const pid of pids) {
20838
21297
  let fds;
20839
21298
  try {
20840
- fds = fs39.readdirSync(`/proc/${pid}/fd`);
21299
+ fds = fs40.readdirSync(`/proc/${pid}/fd`);
20841
21300
  } catch {
20842
21301
  continue;
20843
21302
  }
20844
21303
  for (const fd of fds) {
20845
21304
  let link;
20846
21305
  try {
20847
- link = fs39.readlinkSync(`/proc/${pid}/fd/${fd}`);
21306
+ link = fs40.readlinkSync(`/proc/${pid}/fd/${fd}`);
20848
21307
  } catch {
20849
21308
  continue;
20850
21309
  }
@@ -20949,9 +21408,9 @@ var init_inbound = __esm({
20949
21408
  });
20950
21409
 
20951
21410
  // src/posture/coverage.ts
20952
- import os32 from "os";
21411
+ import os33 from "os";
20953
21412
  function checkCoverage(ctx) {
20954
- const home = ctx.home || os32.homedir();
21413
+ const home = ctx.home || os33.homedir();
20955
21414
  const findings = [];
20956
21415
  const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
20957
21416
  if (protectedAgents.length === 0) {
@@ -21313,7 +21772,7 @@ var init_enforcement = __esm({
21313
21772
  });
21314
21773
 
21315
21774
  // src/posture/index.ts
21316
- import os33 from "os";
21775
+ import os34 from "os";
21317
21776
  function dropEnforcementRedundant(findings) {
21318
21777
  const coveragePresent = findings.some((f) => f.category === "Coverage");
21319
21778
  if (!coveragePresent) return findings;
@@ -21339,7 +21798,7 @@ async function runChecks(checks, ctx) {
21339
21798
  }
21340
21799
  async function runPosture(opts = {}) {
21341
21800
  const ctx = {
21342
- home: opts.home ?? os33.homedir(),
21801
+ home: opts.home ?? os34.homedir(),
21343
21802
  cwd: opts.cwd ?? process.cwd(),
21344
21803
  agent: opts.agent
21345
21804
  };
@@ -21566,53 +22025,10 @@ var init_build2 = __esm({
21566
22025
  }
21567
22026
  });
21568
22027
 
21569
- // src/mcp-cmd.ts
21570
- function tokenize4(cmd) {
21571
- const tokens = [];
21572
- let current = "";
21573
- let inDouble = false;
21574
- let quoted = false;
21575
- let i = 0;
21576
- while (i < cmd.length) {
21577
- const ch = cmd[i];
21578
- if (inDouble) {
21579
- if (ch === '"') inDouble = false;
21580
- else if (ch === "\\" && i + 1 < cmd.length) current += cmd[++i];
21581
- else current += ch;
21582
- } else if (ch === '"') {
21583
- inDouble = true;
21584
- quoted = true;
21585
- } else if (ch === " " || ch === " ") {
21586
- if (current || quoted) {
21587
- tokens.push(current);
21588
- current = "";
21589
- quoted = false;
21590
- }
21591
- } else if (ch === "\\" && i + 1 < cmd.length) {
21592
- current += cmd[++i];
21593
- } else {
21594
- current += ch;
21595
- }
21596
- i++;
21597
- }
21598
- if (current || quoted && !inDouble) tokens.push(current);
21599
- return tokens;
21600
- }
21601
- function quoteArg(s) {
21602
- if (s === "") return '""';
21603
- if (/[\s"\\]/.test(s)) return `"${s.replace(/(["\\])/g, "\\$1")}"`;
21604
- return s;
21605
- }
21606
- var init_mcp_cmd = __esm({
21607
- "src/mcp-cmd.ts"() {
21608
- "use strict";
21609
- }
21610
- });
21611
-
21612
22028
  // src/daemon/mcp-tools.ts
21613
- import fs40 from "fs";
22029
+ import fs41 from "fs";
21614
22030
  import path40 from "path";
21615
- import os34 from "os";
22031
+ import os35 from "os";
21616
22032
  function deriveServerName(cmd) {
21617
22033
  if (!cmd || typeof cmd !== "string") return "MCP Server";
21618
22034
  const strip = (s) => s.replace(/^(mcp-server|server|mcp)-/i, "").replace(/-(mcp-server|server|mcp)$/i, "") || s;
@@ -21638,13 +22054,13 @@ function deriveServerName(cmd) {
21638
22054
  return strip(base) || "MCP Server";
21639
22055
  }
21640
22056
  function getMcpToolsFile() {
21641
- return path40.join(os34.homedir(), ".node9", "mcp-tools.json");
22057
+ return path40.join(os35.homedir(), ".node9", "mcp-tools.json");
21642
22058
  }
21643
22059
  function readMcpToolsConfig() {
21644
22060
  try {
21645
22061
  const file = getMcpToolsFile();
21646
- if (!fs40.existsSync(file)) return {};
21647
- const raw = fs40.readFileSync(file, "utf-8");
22062
+ if (!fs41.existsSync(file)) return {};
22063
+ const raw = fs41.readFileSync(file, "utf-8");
21648
22064
  return JSON.parse(raw);
21649
22065
  } catch {
21650
22066
  return {};
@@ -21654,10 +22070,10 @@ function writeMcpToolsConfig(config) {
21654
22070
  try {
21655
22071
  const file = getMcpToolsFile();
21656
22072
  const dir = path40.dirname(file);
21657
- if (!fs40.existsSync(dir)) fs40.mkdirSync(dir, { recursive: true });
21658
- const tmpPath = `${file}.${os34.hostname()}.${process.pid}.tmp`;
21659
- fs40.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
21660
- fs40.renameSync(tmpPath, file);
22073
+ if (!fs41.existsSync(dir)) fs41.mkdirSync(dir, { recursive: true });
22074
+ const tmpPath = `${file}.${os35.hostname()}.${process.pid}.tmp`;
22075
+ fs41.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
22076
+ fs41.renameSync(tmpPath, file);
21661
22077
  } catch (e) {
21662
22078
  console.error("Failed to write mcp-tools.json", e);
21663
22079
  }
@@ -21711,109 +22127,6 @@ var init_mcp_tools = __esm({
21711
22127
  }
21712
22128
  });
21713
22129
 
21714
- // src/mcp-wrap.ts
21715
- import fs41 from "fs";
21716
- import os35 from "os";
21717
- import { parse as parseToml5, stringify as stringifyToml2 } from "smol-toml";
21718
- function isNode9Command(command) {
21719
- return /(^|[\\/])node9(\.(exe|cmd|ps1|bat))?$/i.test(command ?? "");
21720
- }
21721
- function classifyMcp(s) {
21722
- if (isNode9Command(s.command)) {
21723
- return (s.args ?? [])[0] === "mcp-gateway" ? "gatewayed" : "node9-self";
21724
- }
21725
- if (typeof s.command !== "string" || s.command.trim() === "") return "remote";
21726
- return "ungoverned";
21727
- }
21728
- function toGateway(s, configName) {
21729
- const upstream = [s.command ?? "", ...s.args ?? []].map(quoteArg).join(" ");
21730
- const nameArgs = configName && !configName.startsWith("-") ? ["--config-name", configName] : [];
21731
- return {
21732
- ...s,
21733
- command: "node9",
21734
- args: ["mcp-gateway", ...nameArgs, "--upstream", upstream]
21735
- };
21736
- }
21737
- function fromGateway(s) {
21738
- if (!isNode9Command(s.command) || (s.args ?? [])[0] !== "mcp-gateway") return null;
21739
- const args = s.args ?? [];
21740
- const i = args.indexOf("--upstream");
21741
- if (i < 0 || !args[i + 1]) return null;
21742
- const [command, ...rest] = tokenize4(args[i + 1]);
21743
- if (!command) return null;
21744
- return { ...s, command, args: rest };
21745
- }
21746
- function inventoryMcp(home = os35.homedir()) {
21747
- const out = [];
21748
- for (const spec of AGENT_SPECS) {
21749
- if (!spec.mcpFile) continue;
21750
- const mcpFile = spec.mcpFile(home);
21751
- const format = spec.mcpFormat ?? "json";
21752
- const servers = readMcpServers(mcpFile, format);
21753
- for (const [name, s] of Object.entries(servers)) {
21754
- if (!s || typeof s !== "object") continue;
21755
- out.push({
21756
- agent: String(spec.id),
21757
- agentLabel: spec.label,
21758
- mcpFile,
21759
- format,
21760
- name,
21761
- command: s.command ?? "",
21762
- args: Array.isArray(s.args) ? s.args : [],
21763
- state: classifyMcp(s),
21764
- raw: s
21765
- });
21766
- }
21767
- }
21768
- return out;
21769
- }
21770
- function inventoryServerKeys(inv) {
21771
- const keys = /* @__PURE__ */ new Set();
21772
- for (const e of inv) {
21773
- if (e.state === "gatewayed") {
21774
- const i = e.args.indexOf("--upstream");
21775
- if (i >= 0 && e.args[i + 1]) {
21776
- keys.add(getServerKey(e.args[i + 1]));
21777
- }
21778
- } else if (e.state === "ungoverned") {
21779
- const cmd = [e.command, ...e.args].map(quoteArg).join(" ");
21780
- keys.add(getServerKey(cmd));
21781
- }
21782
- }
21783
- return keys;
21784
- }
21785
- function writeMcpEntry(mcpFile, format, name, entry) {
21786
- const key = format === "toml" ? "mcp_servers" : "mcpServers";
21787
- let root = {};
21788
- if (fs41.existsSync(mcpFile)) {
21789
- const raw = fs41.readFileSync(mcpFile, "utf-8");
21790
- root = format === "toml" ? parseToml5(raw) : JSON.parse(raw);
21791
- const bak = `${mcpFile}.node9-bak`;
21792
- try {
21793
- fs41.writeFileSync(bak, raw, { mode: 384, flag: "wx" });
21794
- } catch (e) {
21795
- if (e.code !== "EEXIST") throw e;
21796
- }
21797
- }
21798
- const existing = root[key];
21799
- const servers = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
21800
- servers[name] = entry;
21801
- root[key] = servers;
21802
- const serialized = format === "toml" ? stringifyToml2(root) : JSON.stringify(root, null, 2);
21803
- const tmp = `${mcpFile}.${process.pid}.tmp`;
21804
- fs41.writeFileSync(tmp, serialized, { mode: 384 });
21805
- fs41.renameSync(tmp, mcpFile);
21806
- }
21807
- var init_mcp_wrap = __esm({
21808
- "src/mcp-wrap.ts"() {
21809
- "use strict";
21810
- init_agent_wiring();
21811
- init_mcp_cmd();
21812
- init_mcp_pin();
21813
- init_mcp_cmd();
21814
- }
21815
- });
21816
-
21817
22130
  // src/mcp-status.ts
21818
22131
  function substituteEnv(input, env = process.env) {
21819
22132
  const missing = [];
@@ -52282,20 +52595,20 @@ init_agent_wiring();
52282
52595
  // src/credentials.ts
52283
52596
  init_config();
52284
52597
  init_api_url();
52285
- import * as fs21 from "fs";
52286
- import * as os18 from "os";
52598
+ import * as fs22 from "fs";
52599
+ import * as os19 from "os";
52287
52600
  import * as path22 from "path";
52288
52601
  function writeCredentialsAndConfig(apiKey, opts = {}) {
52289
52602
  const profileName = opts.profileName || "default";
52290
- const home = opts.homeDir ?? os18.homedir();
52603
+ const home = opts.homeDir ?? os19.homedir();
52291
52604
  const credPath = path22.join(home, ".node9", "credentials.json");
52292
- if (!fs21.existsSync(path22.dirname(credPath))) {
52293
- fs21.mkdirSync(path22.dirname(credPath), { recursive: true });
52605
+ if (!fs22.existsSync(path22.dirname(credPath))) {
52606
+ fs22.mkdirSync(path22.dirname(credPath), { recursive: true });
52294
52607
  }
52295
52608
  let existingCreds = {};
52296
52609
  try {
52297
- if (fs21.existsSync(credPath)) {
52298
- const raw = JSON.parse(fs21.readFileSync(credPath, "utf-8"));
52610
+ if (fs22.existsSync(credPath)) {
52611
+ const raw = JSON.parse(fs22.readFileSync(credPath, "utf-8"));
52299
52612
  existingCreds = raw.apiKey ? { default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL } } : raw;
52300
52613
  }
52301
52614
  } catch {
@@ -52305,7 +52618,7 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
52305
52618
  apiUrl: DEFAULT_API_URL,
52306
52619
  ...opts.isLocal ? { localOnly: true } : {}
52307
52620
  };
52308
- fs21.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), {
52621
+ fs22.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), {
52309
52622
  mode: 384
52310
52623
  });
52311
52624
  let effectiveCloud = null;
@@ -52313,8 +52626,8 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
52313
52626
  const configPath = path22.join(home, ".node9", "config.json");
52314
52627
  let config = {};
52315
52628
  try {
52316
- if (fs21.existsSync(configPath)) {
52317
- config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
52629
+ if (fs22.existsSync(configPath)) {
52630
+ config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
52318
52631
  }
52319
52632
  } catch {
52320
52633
  }
@@ -52329,10 +52642,10 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
52329
52642
  terminal: typeof existing.terminal === "boolean" ? existing.terminal : d.terminal,
52330
52643
  cloud: !opts.isLocal
52331
52644
  };
52332
- if (!fs21.existsSync(path22.dirname(configPath))) {
52333
- fs21.mkdirSync(path22.dirname(configPath), { recursive: true });
52645
+ if (!fs22.existsSync(path22.dirname(configPath))) {
52646
+ fs22.mkdirSync(path22.dirname(configPath), { recursive: true });
52334
52647
  }
52335
- fs21.writeFileSync(configPath, JSON.stringify(config, null, 2), {
52648
+ fs22.writeFileSync(configPath, JSON.stringify(config, null, 2), {
52336
52649
  mode: 384
52337
52650
  });
52338
52651
  effectiveCloud = !opts.isLocal;
@@ -56756,6 +57069,7 @@ function registerInitCommand(program2) {
56756
57069
 
56757
57070
  // src/cli/commands/heal.ts
56758
57071
  init_agent_wiring();
57072
+ init_mcp_wrap();
56759
57073
  init_setup();
56760
57074
  init_hook_baseline();
56761
57075
  import chalk21 from "chalk";
@@ -56767,8 +57081,28 @@ function backupForHeal(file) {
56767
57081
  } catch {
56768
57082
  }
56769
57083
  }
57084
+ function reportCorruptedMcpWraps() {
57085
+ const corrupted = findCorruptedMcpWraps();
57086
+ if (corrupted.length === 0) return;
57087
+ console.log(
57088
+ chalk21.yellow(
57089
+ ` \u26A0\uFE0F ${corrupted.length} MCP server(s) were corrupted by an older node9 and cannot start:`
57090
+ )
57091
+ );
57092
+ for (const c of corrupted) {
57093
+ console.log(chalk21.yellow(` \u2022 ${c.name} (${c.agentLabel})`));
57094
+ console.log(chalk21.gray(` ${c.mcpFile}`));
57095
+ console.log(chalk21.gray(` stored upstream: ${c.upstream}`));
57096
+ }
57097
+ console.log(
57098
+ chalk21.gray(
57099
+ "\n The original command lost its path separators, so node9 cannot recover it.\n Remove and re-add each server in the agent, then run `node9 init` to re-wrap.\n"
57100
+ )
57101
+ );
57102
+ }
56770
57103
  async function runHeal(name) {
56771
57104
  console.log(chalk21.cyan.bold("\n\u{1FA79} Node9 Heal\n"));
57105
+ reportCorruptedMcpWraps();
56772
57106
  const baseline = loadHookBaseline();
56773
57107
  const wiring = getAgentWiring();
56774
57108
  let candidates2 = wiring.filter(
@@ -60535,7 +60869,7 @@ var COLOR = {
60535
60869
  medium: chalk32.yellow,
60536
60870
  advisory: chalk32.gray
60537
60871
  };
60538
- var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security-check?ref=cli_scan_repo";
60872
+ var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security?ref=cli_scan_repo";
60539
60873
  function renderCta(res) {
60540
60874
  const L = [];
60541
60875
  L.push(chalk32.dim(" " + "\u2500".repeat(63)));