@node9/proxy 1.3.1 → 1.3.2

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.js CHANGED
@@ -114,8 +114,8 @@ function sanitizeConfig(raw) {
114
114
  }
115
115
  }
116
116
  const lines = result.error.issues.map((issue) => {
117
- const path22 = issue.path.length > 0 ? issue.path.join(".") : "root";
118
- return ` \u2022 ${path22}: ${issue.message}`;
117
+ const path24 = issue.path.length > 0 ? issue.path.join(".") : "root";
118
+ return ` \u2022 ${path24}: ${issue.message}`;
119
119
  });
120
120
  return {
121
121
  sanitized,
@@ -1178,8 +1178,440 @@ var init_dlp = __esm({
1178
1178
  }
1179
1179
  });
1180
1180
 
1181
+ // src/utils/provenance.ts
1182
+ function findInPath(cmd) {
1183
+ if (import_path5.default.posix.isAbsolute(cmd)) return cmd;
1184
+ const pathEnv = process.env.PATH ?? "";
1185
+ for (const dir of pathEnv.split(import_path5.default.delimiter)) {
1186
+ if (!dir) continue;
1187
+ const full = import_path5.default.join(dir, cmd);
1188
+ try {
1189
+ import_fs5.default.accessSync(full, import_fs5.default.constants.X_OK);
1190
+ return full;
1191
+ } catch {
1192
+ }
1193
+ }
1194
+ return null;
1195
+ }
1196
+ function _classifyPath(resolved, cwd) {
1197
+ if (cwd && resolved.startsWith(cwd + "/")) {
1198
+ return { trustLevel: "user", reason: "binary in project directory" };
1199
+ }
1200
+ const osTmp = import_os4.default.tmpdir();
1201
+ const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
1202
+ if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1203
+ return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
1204
+ }
1205
+ if (SYSTEM_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1206
+ return { trustLevel: "system", reason: "" };
1207
+ }
1208
+ if (MANAGED_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1209
+ return { trustLevel: "managed", reason: "" };
1210
+ }
1211
+ if (USER_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1212
+ return { trustLevel: "user", reason: "" };
1213
+ }
1214
+ return { trustLevel: "unknown", reason: "binary in unrecognized location" };
1215
+ }
1216
+ function checkProvenance(cmd, cwd) {
1217
+ const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
1218
+ if (import_path5.default.posix.isAbsolute(bare)) {
1219
+ const early = _classifyPath(bare, cwd);
1220
+ if (early.trustLevel === "suspect") {
1221
+ return { resolvedPath: bare, ...early };
1222
+ }
1223
+ }
1224
+ let resolved;
1225
+ try {
1226
+ const found = findInPath(bare);
1227
+ if (!found) {
1228
+ return {
1229
+ resolvedPath: cmd,
1230
+ trustLevel: "unknown",
1231
+ reason: "binary not found in PATH"
1232
+ };
1233
+ }
1234
+ resolved = import_fs5.default.realpathSync(found);
1235
+ } catch {
1236
+ return {
1237
+ resolvedPath: cmd,
1238
+ trustLevel: "unknown",
1239
+ reason: "binary not found in PATH"
1240
+ };
1241
+ }
1242
+ try {
1243
+ const stat = import_fs5.default.statSync(resolved);
1244
+ if (stat.mode & 2) {
1245
+ return {
1246
+ resolvedPath: resolved,
1247
+ trustLevel: "suspect",
1248
+ reason: "binary is world-writable"
1249
+ };
1250
+ }
1251
+ } catch {
1252
+ return {
1253
+ resolvedPath: resolved,
1254
+ trustLevel: "unknown",
1255
+ reason: "could not stat binary"
1256
+ };
1257
+ }
1258
+ const classify = _classifyPath(resolved, cwd);
1259
+ return { resolvedPath: resolved, ...classify };
1260
+ }
1261
+ var import_fs5, import_path5, import_os4, SYSTEM_PREFIXES, MANAGED_PREFIXES, USER_PREFIXES, SUSPECT_PREFIXES;
1262
+ var init_provenance = __esm({
1263
+ "src/utils/provenance.ts"() {
1264
+ "use strict";
1265
+ import_fs5 = __toESM(require("fs"));
1266
+ import_path5 = __toESM(require("path"));
1267
+ import_os4 = __toESM(require("os"));
1268
+ SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
1269
+ MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
1270
+ USER_PREFIXES = [
1271
+ import_path5.default.join(import_os4.default.homedir(), "bin"),
1272
+ import_path5.default.join(import_os4.default.homedir(), ".local", "bin"),
1273
+ import_path5.default.join(import_os4.default.homedir(), ".cargo", "bin"),
1274
+ import_path5.default.join(import_os4.default.homedir(), ".npm-global", "bin"),
1275
+ import_path5.default.join(import_os4.default.homedir(), ".volta", "bin")
1276
+ ];
1277
+ SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
1278
+ }
1279
+ });
1280
+
1281
+ // src/policy/pipe-chain.ts
1282
+ function isSensitivePath(p) {
1283
+ return SENSITIVE_PATTERNS.some((re) => re.test(p));
1284
+ }
1285
+ function splitOnPipe(cmd) {
1286
+ const segments = [];
1287
+ let current = "";
1288
+ let inSingle = false;
1289
+ let inDouble = false;
1290
+ for (let i = 0; i < cmd.length; i++) {
1291
+ const ch = cmd[i];
1292
+ if (ch === "'" && !inDouble) {
1293
+ inSingle = !inSingle;
1294
+ current += ch;
1295
+ } else if (ch === '"' && !inSingle) {
1296
+ inDouble = !inDouble;
1297
+ current += ch;
1298
+ } else if (ch === "|" && !inSingle && !inDouble && cmd[i + 1] !== "|" && (i === 0 || cmd[i - 1] !== "|")) {
1299
+ segments.push(current.trim());
1300
+ current = "";
1301
+ } else {
1302
+ current += ch;
1303
+ }
1304
+ }
1305
+ if (current.trim()) segments.push(current.trim());
1306
+ return segments.filter(Boolean);
1307
+ }
1308
+ function positionalTokens(segment) {
1309
+ return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1310
+ }
1311
+ function analyzePipeChain(command) {
1312
+ const segments = splitOnPipe(command);
1313
+ if (segments.length < 2) {
1314
+ return {
1315
+ isPipeline: false,
1316
+ hasSensitiveSource: false,
1317
+ hasExternalSink: false,
1318
+ hasObfuscation: false,
1319
+ sourceFiles: [],
1320
+ sinkTargets: [],
1321
+ risk: "none"
1322
+ };
1323
+ }
1324
+ const sourceFiles = [];
1325
+ const sinkTargets = [];
1326
+ let hasSensitiveSource = false;
1327
+ let hasExternalSink = false;
1328
+ let hasObfuscation = false;
1329
+ for (const segment of segments) {
1330
+ const tokens = segment.split(/\s+/).filter(Boolean);
1331
+ if (tokens.length === 0) continue;
1332
+ const binary = tokens[0].toLowerCase();
1333
+ const args = positionalTokens(segment);
1334
+ if (SOURCE_COMMANDS.has(binary)) {
1335
+ sourceFiles.push(...args);
1336
+ if (args.some(isSensitivePath)) hasSensitiveSource = true;
1337
+ }
1338
+ if (OBFUSCATORS.has(binary)) hasObfuscation = true;
1339
+ if (SINK_COMMANDS.has(binary)) {
1340
+ const targets = args.filter(
1341
+ (a) => a.includes(".") || a.includes("://") || /^\d+\.\d+/.test(a)
1342
+ );
1343
+ sinkTargets.push(...targets);
1344
+ if (targets.length > 0) hasExternalSink = true;
1345
+ }
1346
+ }
1347
+ const fullCmd = command.toLowerCase();
1348
+ if (!hasSensitiveSource) {
1349
+ const redirMatch = fullCmd.match(/<\s*(\S+)/);
1350
+ if (redirMatch && isSensitivePath(redirMatch[1])) {
1351
+ hasSensitiveSource = true;
1352
+ sourceFiles.push(redirMatch[1]);
1353
+ }
1354
+ }
1355
+ const risk = hasSensitiveSource && hasExternalSink && hasObfuscation ? "critical" : hasSensitiveSource && hasExternalSink ? "high" : hasExternalSink ? "medium" : "none";
1356
+ return {
1357
+ isPipeline: true,
1358
+ hasSensitiveSource,
1359
+ hasExternalSink,
1360
+ hasObfuscation,
1361
+ sourceFiles,
1362
+ sinkTargets,
1363
+ risk
1364
+ };
1365
+ }
1366
+ var SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS;
1367
+ var init_pipe_chain = __esm({
1368
+ "src/policy/pipe-chain.ts"() {
1369
+ "use strict";
1370
+ SOURCE_COMMANDS = /* @__PURE__ */ new Set([
1371
+ "cat",
1372
+ "head",
1373
+ "tail",
1374
+ "grep",
1375
+ "awk",
1376
+ "sed",
1377
+ "cut",
1378
+ "sort",
1379
+ "tee",
1380
+ "less",
1381
+ "more",
1382
+ "strings",
1383
+ "xxd"
1384
+ ]);
1385
+ SINK_COMMANDS = /* @__PURE__ */ new Set([
1386
+ "curl",
1387
+ "wget",
1388
+ "nc",
1389
+ "ncat",
1390
+ "netcat",
1391
+ "ssh",
1392
+ "scp",
1393
+ "rsync",
1394
+ "socat",
1395
+ "ftp",
1396
+ "sftp",
1397
+ "telnet"
1398
+ ]);
1399
+ OBFUSCATORS = /* @__PURE__ */ new Set([
1400
+ "base64",
1401
+ "gzip",
1402
+ "gunzip",
1403
+ "bzip2",
1404
+ "xz",
1405
+ "zstd",
1406
+ "openssl",
1407
+ "gpg",
1408
+ "python",
1409
+ "python3",
1410
+ "perl",
1411
+ "ruby",
1412
+ "node"
1413
+ ]);
1414
+ SENSITIVE_PATTERNS = [
1415
+ /(?:^|\/)\.env(?:\.|$)/i,
1416
+ // .env, .env.local, .env.production
1417
+ /id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
1418
+ // SSH private keys
1419
+ /\.pem$|\.key$|\.p12$|\.pfx$/i,
1420
+ // certificate files
1421
+ /(?:^|\/)\.ssh\//i,
1422
+ // ~/.ssh/ directory
1423
+ /(?:^|\/)\.aws\/credentials/i,
1424
+ // AWS credentials
1425
+ /(?:^|\/)\.netrc$/i,
1426
+ // netrc (stores HTTP credentials)
1427
+ /(?:^|\/)(passwd|shadow|sudoers)$/i,
1428
+ // /etc/passwd, /etc/shadow
1429
+ /(?:^|\/)credentials(?:\.json)?$/i
1430
+ // generic credentials files
1431
+ ];
1432
+ }
1433
+ });
1434
+
1435
+ // src/policy/flag-tables.ts
1436
+ function extractPositionalArgs(tokens, binary) {
1437
+ const binaryName = import_path6.default.basename(binary).replace(/\.exe$/i, "");
1438
+ const flagsWithValues = FLAGS_WITH_VALUES[binaryName] ?? /* @__PURE__ */ new Set();
1439
+ const positional = [];
1440
+ let skipNext = false;
1441
+ for (const token of tokens) {
1442
+ if (skipNext) {
1443
+ skipNext = false;
1444
+ continue;
1445
+ }
1446
+ if (token.startsWith("--") && token.includes("=")) continue;
1447
+ if (token.startsWith("-") && token.length === 2 && flagsWithValues.has(token)) {
1448
+ skipNext = true;
1449
+ continue;
1450
+ }
1451
+ if (token.startsWith("--") && flagsWithValues.has(token)) {
1452
+ skipNext = true;
1453
+ continue;
1454
+ }
1455
+ const shortFlag = token.slice(0, 2);
1456
+ if (token.startsWith("-") && token.length > 2 && flagsWithValues.has(shortFlag)) continue;
1457
+ if (token.startsWith("-")) continue;
1458
+ if (token.startsWith("@")) continue;
1459
+ positional.push(token);
1460
+ }
1461
+ return positional;
1462
+ }
1463
+ function extractNetworkTargets(tokens, binary) {
1464
+ return extractPositionalArgs(tokens, binary).map((t) => t.includes("@") ? t.split("@")[1] : t).map((t) => {
1465
+ const colonIdx = t.indexOf(":");
1466
+ if (colonIdx === -1) return t;
1467
+ const afterColon = t.slice(colonIdx + 1);
1468
+ if (/^\d+$/.test(afterColon)) return t.slice(0, colonIdx);
1469
+ return t;
1470
+ }).filter(Boolean);
1471
+ }
1472
+ var import_path6, FLAGS_WITH_VALUES;
1473
+ var init_flag_tables = __esm({
1474
+ "src/policy/flag-tables.ts"() {
1475
+ "use strict";
1476
+ import_path6 = __toESM(require("path"));
1477
+ FLAGS_WITH_VALUES = {
1478
+ curl: /* @__PURE__ */ new Set([
1479
+ "-H",
1480
+ "--header",
1481
+ "-A",
1482
+ "--user-agent",
1483
+ "-e",
1484
+ "--referer",
1485
+ "-x",
1486
+ "--proxy",
1487
+ "-u",
1488
+ "--user",
1489
+ "-d",
1490
+ "--data",
1491
+ "--data-raw",
1492
+ "--data-binary",
1493
+ "-o",
1494
+ "--output",
1495
+ "-F",
1496
+ "--form",
1497
+ "--connect-to",
1498
+ "--resolve",
1499
+ "--cacert",
1500
+ "--cert",
1501
+ "--key",
1502
+ "-m",
1503
+ "--max-time"
1504
+ ]),
1505
+ wget: /* @__PURE__ */ new Set([
1506
+ "-O",
1507
+ "--output-document",
1508
+ "-P",
1509
+ "--directory-prefix",
1510
+ "-U",
1511
+ "--user-agent",
1512
+ "-e",
1513
+ "--execute",
1514
+ "--proxy",
1515
+ "--ca-certificate"
1516
+ ]),
1517
+ nc: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w", "-W", "-I", "-O"]),
1518
+ ncat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "--proxy", "--proxy-auth", "-w", "--wait"]),
1519
+ netcat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w"]),
1520
+ ssh: /* @__PURE__ */ new Set([
1521
+ "-i",
1522
+ "-l",
1523
+ "-p",
1524
+ "-o",
1525
+ "-E",
1526
+ "-F",
1527
+ "-J",
1528
+ "-L",
1529
+ "-R",
1530
+ "-W",
1531
+ "-b",
1532
+ "-c",
1533
+ "-D",
1534
+ "-e",
1535
+ "-I",
1536
+ "-S"
1537
+ ]),
1538
+ scp: /* @__PURE__ */ new Set(["-i", "-o", "-P", "-S"]),
1539
+ rsync: /* @__PURE__ */ new Set(["-e", "--rsh", "--rsync-path", "--password-file", "--log-file"]),
1540
+ socat: /* @__PURE__ */ new Set([])
1541
+ // socat uses address syntax, not flags — no value-flags
1542
+ };
1543
+ }
1544
+ });
1545
+
1546
+ // src/policy/ssh-parser.ts
1547
+ function tokenize(cmd) {
1548
+ const tokens = [];
1549
+ let current = "";
1550
+ let inSingle = false;
1551
+ let inDouble = false;
1552
+ for (const ch of cmd) {
1553
+ if (ch === "'" && !inDouble) {
1554
+ inSingle = !inSingle;
1555
+ } else if (ch === '"' && !inSingle) {
1556
+ inDouble = !inDouble;
1557
+ } else if ((ch === " " || ch === " ") && !inSingle && !inDouble) {
1558
+ if (current) {
1559
+ tokens.push(current);
1560
+ current = "";
1561
+ }
1562
+ } else {
1563
+ current += ch;
1564
+ }
1565
+ }
1566
+ if (current) tokens.push(current);
1567
+ return tokens;
1568
+ }
1569
+ function parseHost(raw) {
1570
+ return raw.split("@").pop().split(":")[0];
1571
+ }
1572
+ function extractAllSshHosts(tokens) {
1573
+ const hosts = /* @__PURE__ */ new Set();
1574
+ for (let i = 0; i < tokens.length; i++) {
1575
+ const t = tokens[i];
1576
+ if (t === "-J" && tokens[i + 1]) {
1577
+ for (const hop of tokens[++i].split(",")) {
1578
+ const h = parseHost(hop);
1579
+ if (h) hosts.add(h);
1580
+ }
1581
+ continue;
1582
+ }
1583
+ if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxyjump=")) {
1584
+ const val = tokens[++i].split("=").slice(1).join("=");
1585
+ for (const hop of val.split(",")) {
1586
+ const h = parseHost(hop);
1587
+ if (h) hosts.add(h);
1588
+ }
1589
+ continue;
1590
+ }
1591
+ if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxycommand=")) {
1592
+ const raw = tokens[++i].split("=").slice(1).join("=").replace(/^['"]|['"]$/g, "");
1593
+ const subTokens = tokenize(raw);
1594
+ const binary = subTokens[0] ?? "";
1595
+ extractNetworkTargets(subTokens.slice(1), binary).forEach((h) => hosts.add(h));
1596
+ extractAllSshHosts(subTokens.slice(1)).forEach((h) => hosts.add(h));
1597
+ continue;
1598
+ }
1599
+ if (!t.startsWith("-")) {
1600
+ const h = parseHost(t);
1601
+ if (h) hosts.add(h);
1602
+ }
1603
+ }
1604
+ return [...hosts].filter(Boolean);
1605
+ }
1606
+ var init_ssh_parser = __esm({
1607
+ "src/policy/ssh-parser.ts"() {
1608
+ "use strict";
1609
+ init_flag_tables();
1610
+ }
1611
+ });
1612
+
1181
1613
  // src/policy/index.ts
1182
- function tokenize(toolName) {
1614
+ function tokenize2(toolName) {
1183
1615
  return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
1184
1616
  }
1185
1617
  function matchesPattern(text, patterns) {
@@ -1192,9 +1624,9 @@ function matchesPattern(text, patterns) {
1192
1624
  const withoutDotSlash = text.replace(/^\.\//, "");
1193
1625
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1194
1626
  }
1195
- function getNestedValue(obj, path22) {
1627
+ function getNestedValue(obj, path24) {
1196
1628
  if (!obj || typeof obj !== "object") return null;
1197
- return path22.split(".").reduce((prev, curr) => prev?.[curr], obj);
1629
+ return path24.split(".").reduce((prev, curr) => prev?.[curr], obj);
1198
1630
  }
1199
1631
  function shouldSnapshot(toolName, args, config) {
1200
1632
  if (!config.settings.enableUndo) return false;
@@ -1329,7 +1761,7 @@ async function analyzeShellCommand(command) {
1329
1761
  }
1330
1762
  return { actions, paths, allTokens };
1331
1763
  }
1332
- async function evaluatePolicy(toolName, args, agent) {
1764
+ async function evaluatePolicy(toolName, args, agent, cwd) {
1333
1765
  const config = getConfig();
1334
1766
  if (matchesPattern(toolName, config.policy.ignoredTools)) return { decision: "allow" };
1335
1767
  if (config.policy.smartRules.length > 0) {
@@ -1359,11 +1791,55 @@ async function evaluatePolicy(toolName, args, agent) {
1359
1791
  if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
1360
1792
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
1361
1793
  }
1794
+ const pipeAnalysis = analyzePipeChain(shellCommand);
1795
+ if (pipeAnalysis.isPipeline) {
1796
+ if (pipeAnalysis.risk === "critical") {
1797
+ return {
1798
+ decision: "block",
1799
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
1800
+ reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1801
+ tier: 3
1802
+ };
1803
+ }
1804
+ if (pipeAnalysis.risk === "high") {
1805
+ return {
1806
+ decision: "review",
1807
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1808
+ reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1809
+ tier: 3
1810
+ };
1811
+ }
1812
+ }
1813
+ const firstToken = analyzed.actions[0] ?? "";
1814
+ if (["ssh", "scp", "rsync"].includes(firstToken)) {
1815
+ const rawTokens = shellCommand.trim().split(/\s+/);
1816
+ const sshHosts = extractAllSshHosts(rawTokens.slice(1));
1817
+ allTokens.push(...sshHosts);
1818
+ }
1819
+ if (firstToken && import_path7.default.posix.isAbsolute(firstToken)) {
1820
+ const prov = checkProvenance(firstToken, cwd);
1821
+ if (prov.trustLevel === "suspect") {
1822
+ return {
1823
+ decision: config.settings.mode === "strict" ? "block" : "review",
1824
+ blockedByLabel: "Node9: Suspect Binary",
1825
+ reason: `Binary "${firstToken}" resolved to ${prov.resolvedPath} \u2014 ${prov.reason}`,
1826
+ tier: 3
1827
+ };
1828
+ }
1829
+ if (prov.trustLevel === "unknown" && config.settings.mode === "strict") {
1830
+ return {
1831
+ decision: "review",
1832
+ blockedByLabel: "Node9: Unknown Binary (strict mode)",
1833
+ reason: `Binary "${firstToken}" \u2014 ${prov.reason}`,
1834
+ tier: 3
1835
+ };
1836
+ }
1837
+ }
1362
1838
  if (isSqlTool(toolName, config.policy.toolInspection)) {
1363
1839
  allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
1364
1840
  }
1365
1841
  } else {
1366
- allTokens = tokenize(toolName);
1842
+ allTokens = tokenize2(toolName);
1367
1843
  if (args && typeof args === "object") {
1368
1844
  const flattenedArgs = JSON.stringify(args).toLowerCase();
1369
1845
  const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
@@ -1437,9 +1913,9 @@ async function evaluatePolicy(toolName, args, agent) {
1437
1913
  }
1438
1914
  async function explainPolicy(toolName, args) {
1439
1915
  const steps = [];
1440
- const globalPath = import_path5.default.join(import_os4.default.homedir(), ".node9", "config.json");
1441
- const projectPath = import_path5.default.join(process.cwd(), "node9.config.json");
1442
- const credsPath = import_path5.default.join(import_os4.default.homedir(), ".node9", "credentials.json");
1916
+ const globalPath = import_path7.default.join(import_os5.default.homedir(), ".node9", "config.json");
1917
+ const projectPath = import_path7.default.join(process.cwd(), "node9.config.json");
1918
+ const credsPath = import_path7.default.join(import_os5.default.homedir(), ".node9", "credentials.json");
1443
1919
  const waterfall = [
1444
1920
  {
1445
1921
  tier: 1,
@@ -1450,19 +1926,19 @@ async function explainPolicy(toolName, args) {
1450
1926
  {
1451
1927
  tier: 2,
1452
1928
  label: "Cloud policy",
1453
- status: import_fs5.default.existsSync(credsPath) ? "active" : "missing",
1454
- note: import_fs5.default.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
1929
+ status: import_fs6.default.existsSync(credsPath) ? "active" : "missing",
1930
+ note: import_fs6.default.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
1455
1931
  },
1456
1932
  {
1457
1933
  tier: 3,
1458
1934
  label: "Project config",
1459
- status: import_fs5.default.existsSync(projectPath) ? "active" : "missing",
1935
+ status: import_fs6.default.existsSync(projectPath) ? "active" : "missing",
1460
1936
  path: projectPath
1461
1937
  },
1462
1938
  {
1463
1939
  tier: 4,
1464
1940
  label: "Global config",
1465
- status: import_fs5.default.existsSync(globalPath) ? "active" : "missing",
1941
+ status: import_fs6.default.existsSync(globalPath) ? "active" : "missing",
1466
1942
  path: globalPath
1467
1943
  },
1468
1944
  {
@@ -1594,7 +2070,7 @@ async function explainPolicy(toolName, args) {
1594
2070
  });
1595
2071
  }
1596
2072
  } else {
1597
- allTokens = tokenize(toolName);
2073
+ allTokens = tokenize2(toolName);
1598
2074
  let detail = `No toolInspection match for "${toolName}" \u2014 tokens: [${allTokens.join(", ")}]`;
1599
2075
  if (args && typeof args === "object") {
1600
2076
  const flattenedArgs = JSON.stringify(args).toLowerCase();
@@ -1699,18 +2175,21 @@ function isIgnoredTool(toolName) {
1699
2175
  const config = getConfig();
1700
2176
  return matchesPattern(toolName, config.policy.ignoredTools);
1701
2177
  }
1702
- var import_fs5, import_path5, import_os4, import_picomatch, import_sh_syntax, SQL_DML_KEYWORDS;
2178
+ var import_fs6, import_path7, import_os5, import_picomatch, import_sh_syntax, SQL_DML_KEYWORDS;
1703
2179
  var init_policy = __esm({
1704
2180
  "src/policy/index.ts"() {
1705
2181
  "use strict";
1706
- import_fs5 = __toESM(require("fs"));
1707
- import_path5 = __toESM(require("path"));
1708
- import_os4 = __toESM(require("os"));
2182
+ import_fs6 = __toESM(require("fs"));
2183
+ import_path7 = __toESM(require("path"));
2184
+ import_os5 = __toESM(require("os"));
1709
2185
  import_picomatch = __toESM(require("picomatch"));
1710
2186
  import_sh_syntax = require("sh-syntax");
1711
2187
  init_dlp();
1712
2188
  init_config();
1713
2189
  init_regex();
2190
+ init_provenance();
2191
+ init_pipe_chain();
2192
+ init_ssh_parser();
1714
2193
  SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
1715
2194
  }
1716
2195
  });
@@ -1718,11 +2197,11 @@ var init_policy = __esm({
1718
2197
  // src/auth/state.ts
1719
2198
  function checkPause() {
1720
2199
  try {
1721
- if (!import_fs6.default.existsSync(PAUSED_FILE)) return { paused: false };
1722
- const state = JSON.parse(import_fs6.default.readFileSync(PAUSED_FILE, "utf-8"));
2200
+ if (!import_fs7.default.existsSync(PAUSED_FILE)) return { paused: false };
2201
+ const state = JSON.parse(import_fs7.default.readFileSync(PAUSED_FILE, "utf-8"));
1723
2202
  if (state.expiry > 0 && Date.now() >= state.expiry) {
1724
2203
  try {
1725
- import_fs6.default.unlinkSync(PAUSED_FILE);
2204
+ import_fs7.default.unlinkSync(PAUSED_FILE);
1726
2205
  } catch {
1727
2206
  }
1728
2207
  return { paused: false };
@@ -1733,11 +2212,11 @@ function checkPause() {
1733
2212
  }
1734
2213
  }
1735
2214
  function atomicWriteSync(filePath, data, options) {
1736
- const dir = import_path6.default.dirname(filePath);
1737
- if (!import_fs6.default.existsSync(dir)) import_fs6.default.mkdirSync(dir, { recursive: true });
1738
- const tmpPath = `${filePath}.${import_os5.default.hostname()}.${process.pid}.tmp`;
1739
- import_fs6.default.writeFileSync(tmpPath, data, options);
1740
- import_fs6.default.renameSync(tmpPath, filePath);
2215
+ const dir = import_path8.default.dirname(filePath);
2216
+ if (!import_fs7.default.existsSync(dir)) import_fs7.default.mkdirSync(dir, { recursive: true });
2217
+ const tmpPath = `${filePath}.${import_os6.default.hostname()}.${process.pid}.tmp`;
2218
+ import_fs7.default.writeFileSync(tmpPath, data, options);
2219
+ import_fs7.default.renameSync(tmpPath, filePath);
1741
2220
  }
1742
2221
  function pauseNode9(durationMs, durationStr) {
1743
2222
  const state = { expiry: Date.now() + durationMs, duration: durationStr };
@@ -1745,18 +2224,18 @@ function pauseNode9(durationMs, durationStr) {
1745
2224
  }
1746
2225
  function resumeNode9() {
1747
2226
  try {
1748
- if (import_fs6.default.existsSync(PAUSED_FILE)) import_fs6.default.unlinkSync(PAUSED_FILE);
2227
+ if (import_fs7.default.existsSync(PAUSED_FILE)) import_fs7.default.unlinkSync(PAUSED_FILE);
1749
2228
  } catch {
1750
2229
  }
1751
2230
  }
1752
2231
  function getActiveTrustSession(toolName) {
1753
2232
  try {
1754
- if (!import_fs6.default.existsSync(TRUST_FILE)) return false;
1755
- const trust = JSON.parse(import_fs6.default.readFileSync(TRUST_FILE, "utf-8"));
2233
+ if (!import_fs7.default.existsSync(TRUST_FILE)) return false;
2234
+ const trust = JSON.parse(import_fs7.default.readFileSync(TRUST_FILE, "utf-8"));
1756
2235
  const now = Date.now();
1757
2236
  const active = trust.entries.filter((e) => e.expiry > now);
1758
2237
  if (active.length !== trust.entries.length) {
1759
- import_fs6.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
2238
+ import_fs7.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1760
2239
  }
1761
2240
  return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
1762
2241
  } catch {
@@ -1767,8 +2246,8 @@ function writeTrustSession(toolName, durationMs) {
1767
2246
  try {
1768
2247
  let trust = { entries: [] };
1769
2248
  try {
1770
- if (import_fs6.default.existsSync(TRUST_FILE)) {
1771
- trust = JSON.parse(import_fs6.default.readFileSync(TRUST_FILE, "utf-8"));
2249
+ if (import_fs7.default.existsSync(TRUST_FILE)) {
2250
+ trust = JSON.parse(import_fs7.default.readFileSync(TRUST_FILE, "utf-8"));
1772
2251
  }
1773
2252
  } catch {
1774
2253
  }
@@ -1784,34 +2263,34 @@ function writeTrustSession(toolName, durationMs) {
1784
2263
  }
1785
2264
  function getPersistentDecision(toolName) {
1786
2265
  try {
1787
- const file = import_path6.default.join(import_os5.default.homedir(), ".node9", "decisions.json");
1788
- if (!import_fs6.default.existsSync(file)) return null;
1789
- const decisions = JSON.parse(import_fs6.default.readFileSync(file, "utf-8"));
2266
+ const file = import_path8.default.join(import_os6.default.homedir(), ".node9", "decisions.json");
2267
+ if (!import_fs7.default.existsSync(file)) return null;
2268
+ const decisions = JSON.parse(import_fs7.default.readFileSync(file, "utf-8"));
1790
2269
  const d = decisions[toolName];
1791
2270
  if (d === "allow" || d === "deny") return d;
1792
2271
  } catch {
1793
2272
  }
1794
2273
  return null;
1795
2274
  }
1796
- var import_fs6, import_path6, import_os5, PAUSED_FILE, TRUST_FILE;
2275
+ var import_fs7, import_path8, import_os6, PAUSED_FILE, TRUST_FILE;
1797
2276
  var init_state = __esm({
1798
2277
  "src/auth/state.ts"() {
1799
2278
  "use strict";
1800
- import_fs6 = __toESM(require("fs"));
1801
- import_path6 = __toESM(require("path"));
1802
- import_os5 = __toESM(require("os"));
2279
+ import_fs7 = __toESM(require("fs"));
2280
+ import_path8 = __toESM(require("path"));
2281
+ import_os6 = __toESM(require("os"));
1803
2282
  init_policy();
1804
- PAUSED_FILE = import_path6.default.join(import_os5.default.homedir(), ".node9", "PAUSED");
1805
- TRUST_FILE = import_path6.default.join(import_os5.default.homedir(), ".node9", "trust.json");
2283
+ PAUSED_FILE = import_path8.default.join(import_os6.default.homedir(), ".node9", "PAUSED");
2284
+ TRUST_FILE = import_path8.default.join(import_os6.default.homedir(), ".node9", "trust.json");
1806
2285
  }
1807
2286
  });
1808
2287
 
1809
2288
  // src/auth/daemon.ts
1810
2289
  function getInternalToken() {
1811
2290
  try {
1812
- const pidFile = import_path7.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
1813
- if (!import_fs7.default.existsSync(pidFile)) return null;
1814
- const data = JSON.parse(import_fs7.default.readFileSync(pidFile, "utf-8"));
2291
+ const pidFile = import_path9.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
2292
+ if (!import_fs8.default.existsSync(pidFile)) return null;
2293
+ const data = JSON.parse(import_fs8.default.readFileSync(pidFile, "utf-8"));
1815
2294
  process.kill(data.pid, 0);
1816
2295
  return data.internalToken ?? null;
1817
2296
  } catch {
@@ -1819,10 +2298,10 @@ function getInternalToken() {
1819
2298
  }
1820
2299
  }
1821
2300
  function isDaemonRunning() {
1822
- const pidFile = import_path7.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
1823
- if (import_fs7.default.existsSync(pidFile)) {
2301
+ const pidFile = import_path9.default.join(import_os7.default.homedir(), ".node9", "daemon.pid");
2302
+ if (import_fs8.default.existsSync(pidFile)) {
1824
2303
  try {
1825
- const { pid, port } = JSON.parse(import_fs7.default.readFileSync(pidFile, "utf-8"));
2304
+ const { pid, port } = JSON.parse(import_fs8.default.readFileSync(pidFile, "utf-8"));
1826
2305
  if (port !== DAEMON_PORT) return false;
1827
2306
  process.kill(pid, 0);
1828
2307
  return true;
@@ -1915,13 +2394,13 @@ async function resolveViaDaemon(id, decision, internalToken) {
1915
2394
  signal: AbortSignal.timeout(3e3)
1916
2395
  });
1917
2396
  }
1918
- var import_fs7, import_path7, import_os6, import_child_process, DAEMON_PORT, DAEMON_HOST;
2397
+ var import_fs8, import_path9, import_os7, import_child_process, DAEMON_PORT, DAEMON_HOST;
1919
2398
  var init_daemon = __esm({
1920
2399
  "src/auth/daemon.ts"() {
1921
2400
  "use strict";
1922
- import_fs7 = __toESM(require("fs"));
1923
- import_path7 = __toESM(require("path"));
1924
- import_os6 = __toESM(require("os"));
2401
+ import_fs8 = __toESM(require("fs"));
2402
+ import_path9 = __toESM(require("path"));
2403
+ import_os7 = __toESM(require("os"));
1925
2404
  import_child_process = require("child_process");
1926
2405
  DAEMON_PORT = 7391;
1927
2406
  DAEMON_HOST = "127.0.0.1";
@@ -1980,7 +2459,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
1980
2459
  intent = "EDIT";
1981
2460
  if (obj.file_path) {
1982
2461
  editFilePath = String(obj.file_path);
1983
- editFileName = import_path8.default.basename(editFilePath);
2462
+ editFileName = import_path10.default.basename(editFilePath);
1984
2463
  }
1985
2464
  const result = extractContext(String(obj.new_string), matchedWord);
1986
2465
  contextSnippet = result.snippet;
@@ -2012,11 +2491,11 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
2012
2491
  ...ruleName && { ruleName }
2013
2492
  };
2014
2493
  }
2015
- var import_path8, CODE_KEYS;
2494
+ var import_path10, CODE_KEYS;
2016
2495
  var init_context_sniper = __esm({
2017
2496
  "src/context-sniper.ts"() {
2018
2497
  "use strict";
2019
- import_path8 = __toESM(require("path"));
2498
+ import_path10 = __toESM(require("path"));
2020
2499
  CODE_KEYS = [
2021
2500
  "command",
2022
2501
  "cmd",
@@ -2055,7 +2534,7 @@ function formatArgs(args, matchedField, matchedWord) {
2055
2534
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
2056
2535
  const obj = parsed;
2057
2536
  if (obj.old_string !== void 0 && obj.new_string !== void 0) {
2058
- const file = obj.file_path ? import_path9.default.basename(String(obj.file_path)) : "file";
2537
+ const file = obj.file_path ? import_path11.default.basename(String(obj.file_path)) : "file";
2059
2538
  const oldPreview = smartTruncate(String(obj.old_string), 120);
2060
2539
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
2061
2540
  return {
@@ -2228,12 +2707,12 @@ end run`;
2228
2707
  }
2229
2708
  });
2230
2709
  }
2231
- var import_child_process2, import_path9, isTestEnv;
2710
+ var import_child_process2, import_path11, isTestEnv;
2232
2711
  var init_native = __esm({
2233
2712
  "src/ui/native.ts"() {
2234
2713
  "use strict";
2235
2714
  import_child_process2 = require("child_process");
2236
- import_path9 = __toESM(require("path"));
2715
+ import_path11 = __toESM(require("path"));
2237
2716
  init_context_sniper();
2238
2717
  isTestEnv = () => {
2239
2718
  return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || !!process.env.VITEST || process.env.CI === "true" || !!process.env.CI || process.env.NODE9_TESTING === "1";
@@ -2253,9 +2732,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2253
2732
  context: {
2254
2733
  agent: meta?.agent,
2255
2734
  mcpServer: meta?.mcpServer,
2256
- hostname: import_os7.default.hostname(),
2735
+ hostname: import_os8.default.hostname(),
2257
2736
  cwd: process.cwd(),
2258
- platform: import_os7.default.platform()
2737
+ platform: import_os8.default.platform()
2259
2738
  }
2260
2739
  }),
2261
2740
  signal: AbortSignal.timeout(5e3)
@@ -2276,9 +2755,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2276
2755
  context: {
2277
2756
  agent: meta?.agent,
2278
2757
  mcpServer: meta?.mcpServer,
2279
- hostname: import_os7.default.hostname(),
2758
+ hostname: import_os8.default.hostname(),
2280
2759
  cwd: process.cwd(),
2281
- platform: import_os7.default.platform()
2760
+ platform: import_os8.default.platform()
2282
2761
  },
2283
2762
  ...riskMetadata && { riskMetadata }
2284
2763
  }),
@@ -2334,26 +2813,26 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2334
2813
  });
2335
2814
  clearTimeout(timer);
2336
2815
  if (!res.ok) {
2337
- import_fs8.default.appendFileSync(
2816
+ import_fs9.default.appendFileSync(
2338
2817
  HOOK_DEBUG_LOG,
2339
2818
  `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
2340
2819
  `
2341
2820
  );
2342
2821
  }
2343
2822
  } catch (err) {
2344
- import_fs8.default.appendFileSync(
2823
+ import_fs9.default.appendFileSync(
2345
2824
  HOOK_DEBUG_LOG,
2346
2825
  `[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
2347
2826
  `
2348
2827
  );
2349
2828
  }
2350
2829
  }
2351
- var import_fs8, import_os7;
2830
+ var import_fs9, import_os8;
2352
2831
  var init_cloud = __esm({
2353
2832
  "src/auth/cloud.ts"() {
2354
2833
  "use strict";
2355
- import_fs8 = __toESM(require("fs"));
2356
- import_os7 = __toESM(require("os"));
2834
+ import_fs9 = __toESM(require("fs"));
2835
+ import_os8 = __toESM(require("os"));
2357
2836
  init_audit();
2358
2837
  }
2359
2838
  });
@@ -2440,7 +2919,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2440
2919
  }
2441
2920
  if (config.settings.mode === "audit") {
2442
2921
  if (!isIgnoredTool(toolName)) {
2443
- const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
2922
+ const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
2444
2923
  if (policyResult.decision === "review") {
2445
2924
  appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
2446
2925
  if (approvers.cloud && creds?.apiKey) {
@@ -2705,13 +3184,13 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
2705
3184
  }
2706
3185
  return finalResult;
2707
3186
  }
2708
- var import_net, import_path10, import_os8, import_crypto2, ACTIVITY_SOCKET_PATH;
3187
+ var import_net, import_path12, import_os9, import_crypto2, ACTIVITY_SOCKET_PATH;
2709
3188
  var init_orchestrator = __esm({
2710
3189
  "src/auth/orchestrator.ts"() {
2711
3190
  "use strict";
2712
3191
  import_net = __toESM(require("net"));
2713
- import_path10 = __toESM(require("path"));
2714
- import_os8 = __toESM(require("os"));
3192
+ import_path12 = __toESM(require("path"));
3193
+ import_os9 = __toESM(require("os"));
2715
3194
  import_crypto2 = require("crypto");
2716
3195
  init_native();
2717
3196
  init_context_sniper();
@@ -2722,7 +3201,7 @@ var init_orchestrator = __esm({
2722
3201
  init_state();
2723
3202
  init_daemon();
2724
3203
  init_cloud();
2725
- ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path10.default.join(import_os8.default.tmpdir(), "node9-activity.sock");
3204
+ ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path12.default.join(import_os9.default.tmpdir(), "node9-activity.sock");
2726
3205
  }
2727
3206
  });
2728
3207
 
@@ -4215,11 +4694,11 @@ function markRejectionHandlerRegistered() {
4215
4694
  daemonRejectionHandlerRegistered = true;
4216
4695
  }
4217
4696
  function atomicWriteSync2(filePath, data, options) {
4218
- const dir = import_path12.default.dirname(filePath);
4219
- if (!import_fs10.default.existsSync(dir)) import_fs10.default.mkdirSync(dir, { recursive: true });
4697
+ const dir = import_path14.default.dirname(filePath);
4698
+ if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
4220
4699
  const tmpPath = `${filePath}.${(0, import_crypto3.randomUUID)()}.tmp`;
4221
- import_fs10.default.writeFileSync(tmpPath, data, options);
4222
- import_fs10.default.renameSync(tmpPath, filePath);
4700
+ import_fs11.default.writeFileSync(tmpPath, data, options);
4701
+ import_fs11.default.renameSync(tmpPath, filePath);
4223
4702
  }
4224
4703
  function redactArgs(value) {
4225
4704
  if (!value || typeof value !== "object") return value;
@@ -4239,16 +4718,16 @@ function appendAuditLog(data) {
4239
4718
  decision: data.decision,
4240
4719
  source: "daemon"
4241
4720
  };
4242
- const dir = import_path12.default.dirname(AUDIT_LOG_FILE);
4243
- if (!import_fs10.default.existsSync(dir)) import_fs10.default.mkdirSync(dir, { recursive: true });
4244
- import_fs10.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
4721
+ const dir = import_path14.default.dirname(AUDIT_LOG_FILE);
4722
+ if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
4723
+ import_fs11.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
4245
4724
  } catch {
4246
4725
  }
4247
4726
  }
4248
4727
  function getAuditHistory(limit = 20) {
4249
4728
  try {
4250
- if (!import_fs10.default.existsSync(AUDIT_LOG_FILE)) return [];
4251
- const lines = import_fs10.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
4729
+ if (!import_fs11.default.existsSync(AUDIT_LOG_FILE)) return [];
4730
+ const lines = import_fs11.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
4252
4731
  if (lines.length === 1 && lines[0] === "") return [];
4253
4732
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
4254
4733
  } catch {
@@ -4257,19 +4736,19 @@ function getAuditHistory(limit = 20) {
4257
4736
  }
4258
4737
  function getOrgName() {
4259
4738
  try {
4260
- if (import_fs10.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
4739
+ if (import_fs11.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
4261
4740
  } catch {
4262
4741
  }
4263
4742
  return null;
4264
4743
  }
4265
4744
  function hasStoredSlackKey() {
4266
- return import_fs10.default.existsSync(CREDENTIALS_FILE);
4745
+ return import_fs11.default.existsSync(CREDENTIALS_FILE);
4267
4746
  }
4268
4747
  function writeGlobalSetting(key, value) {
4269
4748
  let config = {};
4270
4749
  try {
4271
- if (import_fs10.default.existsSync(GLOBAL_CONFIG_FILE)) {
4272
- config = JSON.parse(import_fs10.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
4750
+ if (import_fs11.default.existsSync(GLOBAL_CONFIG_FILE)) {
4751
+ config = JSON.parse(import_fs11.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
4273
4752
  }
4274
4753
  } catch {
4275
4754
  }
@@ -4281,8 +4760,8 @@ function writeTrustEntry(toolName, durationMs) {
4281
4760
  try {
4282
4761
  let trust = { entries: [] };
4283
4762
  try {
4284
- if (import_fs10.default.existsSync(TRUST_FILE2))
4285
- trust = JSON.parse(import_fs10.default.readFileSync(TRUST_FILE2, "utf-8"));
4763
+ if (import_fs11.default.existsSync(TRUST_FILE2))
4764
+ trust = JSON.parse(import_fs11.default.readFileSync(TRUST_FILE2, "utf-8"));
4286
4765
  } catch {
4287
4766
  }
4288
4767
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -4293,8 +4772,8 @@ function writeTrustEntry(toolName, durationMs) {
4293
4772
  }
4294
4773
  function readPersistentDecisions() {
4295
4774
  try {
4296
- if (import_fs10.default.existsSync(DECISIONS_FILE)) {
4297
- return JSON.parse(import_fs10.default.readFileSync(DECISIONS_FILE, "utf-8"));
4775
+ if (import_fs11.default.existsSync(DECISIONS_FILE)) {
4776
+ return JSON.parse(import_fs11.default.readFileSync(DECISIONS_FILE, "utf-8"));
4298
4777
  }
4299
4778
  } catch {
4300
4779
  }
@@ -4359,7 +4838,7 @@ function abandonPending() {
4359
4838
  });
4360
4839
  if (autoStarted) {
4361
4840
  try {
4362
- import_fs10.default.unlinkSync(DAEMON_PID_FILE);
4841
+ import_fs11.default.unlinkSync(DAEMON_PID_FILE);
4363
4842
  } catch {
4364
4843
  }
4365
4844
  setTimeout(() => {
@@ -4370,7 +4849,7 @@ function abandonPending() {
4370
4849
  }
4371
4850
  function startActivitySocket() {
4372
4851
  try {
4373
- import_fs10.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
4852
+ import_fs11.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
4374
4853
  } catch {
4375
4854
  }
4376
4855
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -4412,29 +4891,29 @@ function startActivitySocket() {
4412
4891
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
4413
4892
  process.on("exit", () => {
4414
4893
  try {
4415
- import_fs10.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
4894
+ import_fs11.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
4416
4895
  } catch {
4417
4896
  }
4418
4897
  });
4419
4898
  }
4420
- var import_net2, import_fs10, import_path12, import_os10, import_child_process3, import_crypto3, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, pending, sseClients, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE;
4899
+ var import_net2, import_fs11, import_path14, import_os11, import_child_process3, import_crypto3, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, pending, sseClients, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE;
4421
4900
  var init_state2 = __esm({
4422
4901
  "src/daemon/state.ts"() {
4423
4902
  "use strict";
4424
4903
  import_net2 = __toESM(require("net"));
4425
- import_fs10 = __toESM(require("fs"));
4426
- import_path12 = __toESM(require("path"));
4427
- import_os10 = __toESM(require("os"));
4904
+ import_fs11 = __toESM(require("fs"));
4905
+ import_path14 = __toESM(require("path"));
4906
+ import_os11 = __toESM(require("os"));
4428
4907
  import_child_process3 = require("child_process");
4429
4908
  import_crypto3 = require("crypto");
4430
4909
  init_daemon();
4431
- homeDir = import_os10.default.homedir();
4432
- DAEMON_PID_FILE = import_path12.default.join(homeDir, ".node9", "daemon.pid");
4433
- DECISIONS_FILE = import_path12.default.join(homeDir, ".node9", "decisions.json");
4434
- AUDIT_LOG_FILE = import_path12.default.join(homeDir, ".node9", "audit.log");
4435
- TRUST_FILE2 = import_path12.default.join(homeDir, ".node9", "trust.json");
4436
- GLOBAL_CONFIG_FILE = import_path12.default.join(homeDir, ".node9", "config.json");
4437
- CREDENTIALS_FILE = import_path12.default.join(homeDir, ".node9", "credentials.json");
4910
+ homeDir = import_os11.default.homedir();
4911
+ DAEMON_PID_FILE = import_path14.default.join(homeDir, ".node9", "daemon.pid");
4912
+ DECISIONS_FILE = import_path14.default.join(homeDir, ".node9", "decisions.json");
4913
+ AUDIT_LOG_FILE = import_path14.default.join(homeDir, ".node9", "audit.log");
4914
+ TRUST_FILE2 = import_path14.default.join(homeDir, ".node9", "trust.json");
4915
+ GLOBAL_CONFIG_FILE = import_path14.default.join(homeDir, ".node9", "config.json");
4916
+ CREDENTIALS_FILE = import_path14.default.join(homeDir, ".node9", "credentials.json");
4438
4917
  pending = /* @__PURE__ */ new Map();
4439
4918
  sseClients = /* @__PURE__ */ new Set();
4440
4919
  _abandonTimer = null;
@@ -4448,7 +4927,7 @@ var init_state2 = __esm({
4448
4927
  "2h": 2 * 60 * 6e4
4449
4928
  };
4450
4929
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
4451
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path12.default.join(import_os10.default.tmpdir(), "node9-activity.sock");
4930
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path14.default.join(import_os11.default.tmpdir(), "node9-activity.sock");
4452
4931
  ACTIVITY_RING_SIZE = 100;
4453
4932
  activityRing = [];
4454
4933
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -4471,7 +4950,7 @@ function startDaemon() {
4471
4950
  idleTimer = setTimeout(() => {
4472
4951
  if (autoStarted) {
4473
4952
  try {
4474
- import_fs11.default.unlinkSync(DAEMON_PID_FILE);
4953
+ import_fs12.default.unlinkSync(DAEMON_PID_FILE);
4475
4954
  } catch {
4476
4955
  }
4477
4956
  }
@@ -4613,7 +5092,7 @@ data: ${JSON.stringify(item.data)}
4613
5092
  status: "pending"
4614
5093
  });
4615
5094
  }
4616
- const projectCwd = typeof cwd === "string" && import_path13.default.isAbsolute(cwd) ? cwd : void 0;
5095
+ const projectCwd = typeof cwd === "string" && import_path15.default.isAbsolute(cwd) ? cwd : void 0;
4617
5096
  const projectConfig = getConfig(projectCwd);
4618
5097
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
4619
5098
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -4917,14 +5396,14 @@ data: ${JSON.stringify(item.data)}
4917
5396
  server.on("error", (e) => {
4918
5397
  if (e.code === "EADDRINUSE") {
4919
5398
  try {
4920
- if (import_fs11.default.existsSync(DAEMON_PID_FILE)) {
4921
- const { pid } = JSON.parse(import_fs11.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
5399
+ if (import_fs12.default.existsSync(DAEMON_PID_FILE)) {
5400
+ const { pid } = JSON.parse(import_fs12.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
4922
5401
  process.kill(pid, 0);
4923
5402
  return process.exit(0);
4924
5403
  }
4925
5404
  } catch {
4926
5405
  try {
4927
- import_fs11.default.unlinkSync(DAEMON_PID_FILE);
5406
+ import_fs12.default.unlinkSync(DAEMON_PID_FILE);
4928
5407
  } catch {
4929
5408
  }
4930
5409
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -4983,13 +5462,13 @@ data: ${JSON.stringify(item.data)}
4983
5462
  }
4984
5463
  startActivitySocket();
4985
5464
  }
4986
- var import_http, import_fs11, import_path13, import_crypto4, import_child_process4, import_chalk2;
5465
+ var import_http, import_fs12, import_path15, import_crypto4, import_child_process4, import_chalk2;
4987
5466
  var init_server = __esm({
4988
5467
  "src/daemon/server.ts"() {
4989
5468
  "use strict";
4990
5469
  import_http = __toESM(require("http"));
4991
- import_fs11 = __toESM(require("fs"));
4992
- import_path13 = __toESM(require("path"));
5470
+ import_fs12 = __toESM(require("fs"));
5471
+ import_path15 = __toESM(require("path"));
4993
5472
  import_crypto4 = require("crypto");
4994
5473
  import_child_process4 = require("child_process");
4995
5474
  import_chalk2 = __toESM(require("chalk"));
@@ -5002,24 +5481,24 @@ var init_server = __esm({
5002
5481
 
5003
5482
  // src/daemon/index.ts
5004
5483
  function stopDaemon() {
5005
- if (!import_fs12.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
5484
+ if (!import_fs13.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
5006
5485
  try {
5007
- const { pid } = JSON.parse(import_fs12.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
5486
+ const { pid } = JSON.parse(import_fs13.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
5008
5487
  process.kill(pid, "SIGTERM");
5009
5488
  console.log(import_chalk3.default.green("\u2705 Stopped."));
5010
5489
  } catch {
5011
5490
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
5012
5491
  } finally {
5013
5492
  try {
5014
- import_fs12.default.unlinkSync(DAEMON_PID_FILE);
5493
+ import_fs13.default.unlinkSync(DAEMON_PID_FILE);
5015
5494
  } catch {
5016
5495
  }
5017
5496
  }
5018
5497
  }
5019
5498
  function daemonStatus() {
5020
- if (import_fs12.default.existsSync(DAEMON_PID_FILE)) {
5499
+ if (import_fs13.default.existsSync(DAEMON_PID_FILE)) {
5021
5500
  try {
5022
- const { pid } = JSON.parse(import_fs12.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
5501
+ const { pid } = JSON.parse(import_fs13.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
5023
5502
  process.kill(pid, 0);
5024
5503
  console.log(import_chalk3.default.green("Node9 daemon: running"));
5025
5504
  return;
@@ -5038,11 +5517,11 @@ function daemonStatus() {
5038
5517
  console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
5039
5518
  }
5040
5519
  }
5041
- var import_fs12, import_chalk3, import_child_process5;
5520
+ var import_fs13, import_chalk3, import_child_process5;
5042
5521
  var init_daemon2 = __esm({
5043
5522
  "src/daemon/index.ts"() {
5044
5523
  "use strict";
5045
- import_fs12 = __toESM(require("fs"));
5524
+ import_fs13 = __toESM(require("fs"));
5046
5525
  import_chalk3 = __toESM(require("chalk"));
5047
5526
  import_child_process5 = require("child_process");
5048
5527
  init_server();
@@ -5093,9 +5572,9 @@ function renderPending(activity) {
5093
5572
  }
5094
5573
  async function ensureDaemon() {
5095
5574
  let pidPort = null;
5096
- if (import_fs19.default.existsSync(PID_FILE)) {
5575
+ if (import_fs20.default.existsSync(PID_FILE)) {
5097
5576
  try {
5098
- const { port } = JSON.parse(import_fs19.default.readFileSync(PID_FILE, "utf-8"));
5577
+ const { port } = JSON.parse(import_fs20.default.readFileSync(PID_FILE, "utf-8"));
5099
5578
  pidPort = port;
5100
5579
  } catch {
5101
5580
  console.error(import_chalk14.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
@@ -5260,8 +5739,8 @@ async function startTail(options = {}) {
5260
5739
  process.stdout.write(SHOW_CURSOR);
5261
5740
  postDecisionHttp(req2.id, decision, csrfToken, port).catch((err) => {
5262
5741
  try {
5263
- import_fs19.default.appendFileSync(
5264
- import_path20.default.join(import_os17.default.homedir(), ".node9", "hook-debug.log"),
5742
+ import_fs20.default.appendFileSync(
5743
+ import_path22.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log"),
5265
5744
  `[tail] POST /decision failed: ${String(err)}
5266
5745
  `
5267
5746
  );
@@ -5446,20 +5925,20 @@ async function startTail(options = {}) {
5446
5925
  process.exit(1);
5447
5926
  });
5448
5927
  }
5449
- var import_http2, import_chalk14, import_fs19, import_os17, import_path20, import_readline3, import_child_process13, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, SAVE_CURSOR, RESTORE_CURSOR;
5928
+ var import_http2, import_chalk14, import_fs20, import_os18, import_path22, import_readline3, import_child_process13, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, SAVE_CURSOR, RESTORE_CURSOR;
5450
5929
  var init_tail = __esm({
5451
5930
  "src/tui/tail.ts"() {
5452
5931
  "use strict";
5453
5932
  import_http2 = __toESM(require("http"));
5454
5933
  import_chalk14 = __toESM(require("chalk"));
5455
- import_fs19 = __toESM(require("fs"));
5456
- import_os17 = __toESM(require("os"));
5457
- import_path20 = __toESM(require("path"));
5934
+ import_fs20 = __toESM(require("fs"));
5935
+ import_os18 = __toESM(require("os"));
5936
+ import_path22 = __toESM(require("path"));
5458
5937
  import_readline3 = __toESM(require("readline"));
5459
5938
  import_child_process13 = require("child_process");
5460
5939
  init_daemon2();
5461
5940
  init_core();
5462
- PID_FILE = import_path20.default.join(import_os17.default.homedir(), ".node9", "daemon.pid");
5941
+ PID_FILE = import_path22.default.join(import_os18.default.homedir(), ".node9", "daemon.pid");
5463
5942
  ICONS = {
5464
5943
  bash: "\u{1F4BB}",
5465
5944
  shell: "\u{1F4BB}",
@@ -5497,9 +5976,9 @@ var import_commander = require("commander");
5497
5976
  init_core();
5498
5977
 
5499
5978
  // src/setup.ts
5500
- var import_fs9 = __toESM(require("fs"));
5501
- var import_path11 = __toESM(require("path"));
5502
- var import_os9 = __toESM(require("os"));
5979
+ var import_fs10 = __toESM(require("fs"));
5980
+ var import_path13 = __toESM(require("path"));
5981
+ var import_os10 = __toESM(require("os"));
5503
5982
  var import_chalk = __toESM(require("chalk"));
5504
5983
  var import_prompts = require("@inquirer/prompts");
5505
5984
  function printDaemonTip() {
@@ -5516,26 +5995,26 @@ function fullPathCommand(subcommand) {
5516
5995
  }
5517
5996
  function readJson(filePath) {
5518
5997
  try {
5519
- if (import_fs9.default.existsSync(filePath)) {
5520
- return JSON.parse(import_fs9.default.readFileSync(filePath, "utf-8"));
5998
+ if (import_fs10.default.existsSync(filePath)) {
5999
+ return JSON.parse(import_fs10.default.readFileSync(filePath, "utf-8"));
5521
6000
  }
5522
6001
  } catch {
5523
6002
  }
5524
6003
  return null;
5525
6004
  }
5526
6005
  function writeJson(filePath, data) {
5527
- const dir = import_path11.default.dirname(filePath);
5528
- if (!import_fs9.default.existsSync(dir)) import_fs9.default.mkdirSync(dir, { recursive: true });
5529
- import_fs9.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
6006
+ const dir = import_path13.default.dirname(filePath);
6007
+ if (!import_fs10.default.existsSync(dir)) import_fs10.default.mkdirSync(dir, { recursive: true });
6008
+ import_fs10.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
5530
6009
  }
5531
6010
  function isNode9Hook(cmd) {
5532
6011
  if (!cmd) return false;
5533
6012
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
5534
6013
  }
5535
6014
  function teardownClaude() {
5536
- const homeDir2 = import_os9.default.homedir();
5537
- const hooksPath = import_path11.default.join(homeDir2, ".claude", "settings.json");
5538
- const mcpPath = import_path11.default.join(homeDir2, ".claude.json");
6015
+ const homeDir2 = import_os10.default.homedir();
6016
+ const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
6017
+ const mcpPath = import_path13.default.join(homeDir2, ".claude.json");
5539
6018
  let changed = false;
5540
6019
  const settings = readJson(hooksPath);
5541
6020
  if (settings?.hooks) {
@@ -5583,8 +6062,8 @@ function teardownClaude() {
5583
6062
  }
5584
6063
  }
5585
6064
  function teardownGemini() {
5586
- const homeDir2 = import_os9.default.homedir();
5587
- const settingsPath = import_path11.default.join(homeDir2, ".gemini", "settings.json");
6065
+ const homeDir2 = import_os10.default.homedir();
6066
+ const settingsPath = import_path13.default.join(homeDir2, ".gemini", "settings.json");
5588
6067
  const settings = readJson(settingsPath);
5589
6068
  if (!settings) {
5590
6069
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -5622,8 +6101,8 @@ function teardownGemini() {
5622
6101
  }
5623
6102
  }
5624
6103
  function teardownCursor() {
5625
- const homeDir2 = import_os9.default.homedir();
5626
- const mcpPath = import_path11.default.join(homeDir2, ".cursor", "mcp.json");
6104
+ const homeDir2 = import_os10.default.homedir();
6105
+ const mcpPath = import_path13.default.join(homeDir2, ".cursor", "mcp.json");
5627
6106
  const mcpConfig = readJson(mcpPath);
5628
6107
  if (!mcpConfig?.mcpServers) {
5629
6108
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -5649,9 +6128,9 @@ function teardownCursor() {
5649
6128
  }
5650
6129
  }
5651
6130
  async function setupClaude() {
5652
- const homeDir2 = import_os9.default.homedir();
5653
- const mcpPath = import_path11.default.join(homeDir2, ".claude.json");
5654
- const hooksPath = import_path11.default.join(homeDir2, ".claude", "settings.json");
6131
+ const homeDir2 = import_os10.default.homedir();
6132
+ const mcpPath = import_path13.default.join(homeDir2, ".claude.json");
6133
+ const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
5655
6134
  const claudeConfig = readJson(mcpPath) ?? {};
5656
6135
  const settings = readJson(hooksPath) ?? {};
5657
6136
  const servers = claudeConfig.mcpServers ?? {};
@@ -5725,8 +6204,8 @@ async function setupClaude() {
5725
6204
  }
5726
6205
  }
5727
6206
  async function setupGemini() {
5728
- const homeDir2 = import_os9.default.homedir();
5729
- const settingsPath = import_path11.default.join(homeDir2, ".gemini", "settings.json");
6207
+ const homeDir2 = import_os10.default.homedir();
6208
+ const settingsPath = import_path13.default.join(homeDir2, ".gemini", "settings.json");
5730
6209
  const settings = readJson(settingsPath) ?? {};
5731
6210
  const servers = settings.mcpServers ?? {};
5732
6211
  let anythingChanged = false;
@@ -5808,8 +6287,8 @@ async function setupGemini() {
5808
6287
  }
5809
6288
  }
5810
6289
  async function setupCursor() {
5811
- const homeDir2 = import_os9.default.homedir();
5812
- const mcpPath = import_path11.default.join(homeDir2, ".cursor", "mcp.json");
6290
+ const homeDir2 = import_os10.default.homedir();
6291
+ const mcpPath = import_path13.default.join(homeDir2, ".cursor", "mcp.json");
5813
6292
  const mcpConfig = readJson(mcpPath) ?? {};
5814
6293
  const servers = mcpConfig.mcpServers ?? {};
5815
6294
  let anythingChanged = false;
@@ -5866,9 +6345,9 @@ async function setupCursor() {
5866
6345
  // src/cli.ts
5867
6346
  init_daemon2();
5868
6347
  var import_chalk15 = __toESM(require("chalk"));
5869
- var import_fs20 = __toESM(require("fs"));
5870
- var import_path21 = __toESM(require("path"));
5871
- var import_os18 = __toESM(require("os"));
6348
+ var import_fs21 = __toESM(require("fs"));
6349
+ var import_path23 = __toESM(require("path"));
6350
+ var import_os19 = __toESM(require("os"));
5872
6351
  var import_prompts3 = require("@inquirer/prompts");
5873
6352
 
5874
6353
  // src/utils/duration.ts
@@ -6089,9 +6568,9 @@ async function autoStartDaemonAndWait() {
6089
6568
 
6090
6569
  // src/cli/commands/check.ts
6091
6570
  var import_chalk5 = __toESM(require("chalk"));
6092
- var import_fs14 = __toESM(require("fs"));
6093
- var import_path15 = __toESM(require("path"));
6094
- var import_os12 = __toESM(require("os"));
6571
+ var import_fs15 = __toESM(require("fs"));
6572
+ var import_path17 = __toESM(require("path"));
6573
+ var import_os13 = __toESM(require("os"));
6095
6574
  init_orchestrator();
6096
6575
  init_daemon();
6097
6576
  init_config();
@@ -6100,25 +6579,25 @@ init_policy();
6100
6579
  // src/undo.ts
6101
6580
  var import_child_process8 = require("child_process");
6102
6581
  var import_crypto5 = __toESM(require("crypto"));
6103
- var import_fs13 = __toESM(require("fs"));
6104
- var import_path14 = __toESM(require("path"));
6105
- var import_os11 = __toESM(require("os"));
6106
- var SNAPSHOT_STACK_PATH = import_path14.default.join(import_os11.default.homedir(), ".node9", "snapshots.json");
6107
- var UNDO_LATEST_PATH = import_path14.default.join(import_os11.default.homedir(), ".node9", "undo_latest.txt");
6582
+ var import_fs14 = __toESM(require("fs"));
6583
+ var import_path16 = __toESM(require("path"));
6584
+ var import_os12 = __toESM(require("os"));
6585
+ var SNAPSHOT_STACK_PATH = import_path16.default.join(import_os12.default.homedir(), ".node9", "snapshots.json");
6586
+ var UNDO_LATEST_PATH = import_path16.default.join(import_os12.default.homedir(), ".node9", "undo_latest.txt");
6108
6587
  var MAX_SNAPSHOTS = 10;
6109
6588
  var GIT_TIMEOUT = 15e3;
6110
6589
  function readStack() {
6111
6590
  try {
6112
- if (import_fs13.default.existsSync(SNAPSHOT_STACK_PATH))
6113
- return JSON.parse(import_fs13.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
6591
+ if (import_fs14.default.existsSync(SNAPSHOT_STACK_PATH))
6592
+ return JSON.parse(import_fs14.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
6114
6593
  } catch {
6115
6594
  }
6116
6595
  return [];
6117
6596
  }
6118
6597
  function writeStack(stack) {
6119
- const dir = import_path14.default.dirname(SNAPSHOT_STACK_PATH);
6120
- if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
6121
- import_fs13.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
6598
+ const dir = import_path16.default.dirname(SNAPSHOT_STACK_PATH);
6599
+ if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
6600
+ import_fs14.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
6122
6601
  }
6123
6602
  function buildArgsSummary(tool, args) {
6124
6603
  if (!args || typeof args !== "object") return "";
@@ -6134,7 +6613,7 @@ function buildArgsSummary(tool, args) {
6134
6613
  function normalizeCwdForHash(cwd) {
6135
6614
  let normalized;
6136
6615
  try {
6137
- normalized = import_fs13.default.realpathSync(cwd);
6616
+ normalized = import_fs14.default.realpathSync(cwd);
6138
6617
  } catch {
6139
6618
  normalized = cwd;
6140
6619
  }
@@ -6144,16 +6623,16 @@ function normalizeCwdForHash(cwd) {
6144
6623
  }
6145
6624
  function getShadowRepoDir(cwd) {
6146
6625
  const hash = import_crypto5.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
6147
- return import_path14.default.join(import_os11.default.homedir(), ".node9", "snapshots", hash);
6626
+ return import_path16.default.join(import_os12.default.homedir(), ".node9", "snapshots", hash);
6148
6627
  }
6149
6628
  function cleanOrphanedIndexFiles(shadowDir) {
6150
6629
  try {
6151
6630
  const cutoff = Date.now() - 6e4;
6152
- for (const f of import_fs13.default.readdirSync(shadowDir)) {
6631
+ for (const f of import_fs14.default.readdirSync(shadowDir)) {
6153
6632
  if (f.startsWith("index_")) {
6154
- const fp = import_path14.default.join(shadowDir, f);
6633
+ const fp = import_path16.default.join(shadowDir, f);
6155
6634
  try {
6156
- if (import_fs13.default.statSync(fp).mtimeMs < cutoff) import_fs13.default.unlinkSync(fp);
6635
+ if (import_fs14.default.statSync(fp).mtimeMs < cutoff) import_fs14.default.unlinkSync(fp);
6157
6636
  } catch {
6158
6637
  }
6159
6638
  }
@@ -6165,7 +6644,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
6165
6644
  const hardcoded = [".git", ".node9"];
6166
6645
  const lines = [...hardcoded, ...ignorePaths].join("\n");
6167
6646
  try {
6168
- import_fs13.default.writeFileSync(import_path14.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
6647
+ import_fs14.default.writeFileSync(import_path16.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
6169
6648
  } catch {
6170
6649
  }
6171
6650
  }
@@ -6178,25 +6657,25 @@ function ensureShadowRepo(shadowDir, cwd) {
6178
6657
  timeout: 3e3
6179
6658
  });
6180
6659
  if (check.status === 0) {
6181
- const ptPath = import_path14.default.join(shadowDir, "project-path.txt");
6660
+ const ptPath = import_path16.default.join(shadowDir, "project-path.txt");
6182
6661
  try {
6183
- const stored = import_fs13.default.readFileSync(ptPath, "utf8").trim();
6662
+ const stored = import_fs14.default.readFileSync(ptPath, "utf8").trim();
6184
6663
  if (stored === normalizedCwd) return true;
6185
6664
  if (process.env.NODE9_DEBUG === "1")
6186
6665
  console.error(
6187
6666
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
6188
6667
  );
6189
- import_fs13.default.rmSync(shadowDir, { recursive: true, force: true });
6668
+ import_fs14.default.rmSync(shadowDir, { recursive: true, force: true });
6190
6669
  } catch {
6191
6670
  try {
6192
- import_fs13.default.writeFileSync(ptPath, normalizedCwd, "utf8");
6671
+ import_fs14.default.writeFileSync(ptPath, normalizedCwd, "utf8");
6193
6672
  } catch {
6194
6673
  }
6195
6674
  return true;
6196
6675
  }
6197
6676
  }
6198
6677
  try {
6199
- import_fs13.default.mkdirSync(shadowDir, { recursive: true });
6678
+ import_fs14.default.mkdirSync(shadowDir, { recursive: true });
6200
6679
  } catch {
6201
6680
  }
6202
6681
  const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -6205,7 +6684,7 @@ function ensureShadowRepo(shadowDir, cwd) {
6205
6684
  console.error("[Node9] git init --bare failed:", init.stderr?.toString());
6206
6685
  return false;
6207
6686
  }
6208
- const configFile = import_path14.default.join(shadowDir, "config");
6687
+ const configFile = import_path16.default.join(shadowDir, "config");
6209
6688
  (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
6210
6689
  timeout: 3e3
6211
6690
  });
@@ -6213,7 +6692,7 @@ function ensureShadowRepo(shadowDir, cwd) {
6213
6692
  timeout: 3e3
6214
6693
  });
6215
6694
  try {
6216
- import_fs13.default.writeFileSync(import_path14.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
6695
+ import_fs14.default.writeFileSync(import_path16.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
6217
6696
  } catch {
6218
6697
  }
6219
6698
  return true;
@@ -6236,7 +6715,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6236
6715
  const shadowDir = getShadowRepoDir(cwd);
6237
6716
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
6238
6717
  writeShadowExcludes(shadowDir, ignorePaths);
6239
- indexFile = import_path14.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
6718
+ indexFile = import_path16.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
6240
6719
  const shadowEnv = {
6241
6720
  ...process.env,
6242
6721
  GIT_DIR: shadowDir,
@@ -6265,7 +6744,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6265
6744
  const shouldGc = stack.length % 5 === 0;
6266
6745
  if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
6267
6746
  writeStack(stack);
6268
- import_fs13.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
6747
+ import_fs14.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
6269
6748
  if (shouldGc) {
6270
6749
  (0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
6271
6750
  }
@@ -6276,7 +6755,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6276
6755
  } finally {
6277
6756
  if (indexFile) {
6278
6757
  try {
6279
- import_fs13.default.unlinkSync(indexFile);
6758
+ import_fs14.default.unlinkSync(indexFile);
6280
6759
  } catch {
6281
6760
  }
6282
6761
  }
@@ -6345,9 +6824,9 @@ function applyUndo(hash, cwd) {
6345
6824
  timeout: GIT_TIMEOUT
6346
6825
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
6347
6826
  for (const file of [...tracked, ...untracked]) {
6348
- const fullPath = import_path14.default.join(dir, file);
6349
- if (!snapshotFiles.has(file) && import_fs13.default.existsSync(fullPath)) {
6350
- import_fs13.default.unlinkSync(fullPath);
6827
+ const fullPath = import_path16.default.join(dir, file);
6828
+ if (!snapshotFiles.has(file) && import_fs14.default.existsSync(fullPath)) {
6829
+ import_fs14.default.unlinkSync(fullPath);
6351
6830
  }
6352
6831
  }
6353
6832
  return true;
@@ -6371,9 +6850,9 @@ function registerCheckCommand(program2) {
6371
6850
  } catch (err) {
6372
6851
  const tempConfig = getConfig();
6373
6852
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
6374
- const logPath = import_path15.default.join(import_os12.default.homedir(), ".node9", "hook-debug.log");
6853
+ const logPath = import_path17.default.join(import_os13.default.homedir(), ".node9", "hook-debug.log");
6375
6854
  const errMsg = err instanceof Error ? err.message : String(err);
6376
- import_fs14.default.appendFileSync(
6855
+ import_fs15.default.appendFileSync(
6377
6856
  logPath,
6378
6857
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
6379
6858
  RAW: ${raw}
@@ -6384,10 +6863,10 @@ RAW: ${raw}
6384
6863
  }
6385
6864
  const config = getConfig(payload.cwd || void 0);
6386
6865
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
6387
- const logPath = import_path15.default.join(import_os12.default.homedir(), ".node9", "hook-debug.log");
6388
- if (!import_fs14.default.existsSync(import_path15.default.dirname(logPath)))
6389
- import_fs14.default.mkdirSync(import_path15.default.dirname(logPath), { recursive: true });
6390
- import_fs14.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
6866
+ const logPath = import_path17.default.join(import_os13.default.homedir(), ".node9", "hook-debug.log");
6867
+ if (!import_fs15.default.existsSync(import_path17.default.dirname(logPath)))
6868
+ import_fs15.default.mkdirSync(import_path17.default.dirname(logPath), { recursive: true });
6869
+ import_fs15.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
6391
6870
  `);
6392
6871
  }
6393
6872
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -6400,8 +6879,8 @@ RAW: ${raw}
6400
6879
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
6401
6880
  let ttyFd = null;
6402
6881
  try {
6403
- ttyFd = import_fs14.default.openSync("/dev/tty", "w");
6404
- const writeTty = (line) => import_fs14.default.writeSync(ttyFd, line + "\n");
6882
+ ttyFd = import_fs15.default.openSync("/dev/tty", "w");
6883
+ const writeTty = (line) => import_fs15.default.writeSync(ttyFd, line + "\n");
6405
6884
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
6406
6885
  writeTty(import_chalk5.default.bgRed.white.bold(`
6407
6886
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -6417,7 +6896,7 @@ RAW: ${raw}
6417
6896
  } finally {
6418
6897
  if (ttyFd !== null)
6419
6898
  try {
6420
- import_fs14.default.closeSync(ttyFd);
6899
+ import_fs15.default.closeSync(ttyFd);
6421
6900
  } catch {
6422
6901
  }
6423
6902
  }
@@ -6448,7 +6927,7 @@ RAW: ${raw}
6448
6927
  if (shouldSnapshot(toolName, toolInput, config)) {
6449
6928
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
6450
6929
  }
6451
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path15.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6930
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path17.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6452
6931
  const result = await authorizeHeadless(toolName, toolInput, meta, {
6453
6932
  cwd: safeCwdForAuth
6454
6933
  });
@@ -6460,12 +6939,12 @@ RAW: ${raw}
6460
6939
  }
6461
6940
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
6462
6941
  try {
6463
- const tty = import_fs14.default.openSync("/dev/tty", "w");
6464
- import_fs14.default.writeSync(
6942
+ const tty = import_fs15.default.openSync("/dev/tty", "w");
6943
+ import_fs15.default.writeSync(
6465
6944
  tty,
6466
6945
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
6467
6946
  );
6468
- import_fs14.default.closeSync(tty);
6947
+ import_fs15.default.closeSync(tty);
6469
6948
  } catch {
6470
6949
  }
6471
6950
  const daemonReady = await autoStartDaemonAndWait();
@@ -6492,9 +6971,9 @@ RAW: ${raw}
6492
6971
  });
6493
6972
  } catch (err) {
6494
6973
  if (process.env.NODE9_DEBUG === "1") {
6495
- const logPath = import_path15.default.join(import_os12.default.homedir(), ".node9", "hook-debug.log");
6974
+ const logPath = import_path17.default.join(import_os13.default.homedir(), ".node9", "hook-debug.log");
6496
6975
  const errMsg = err instanceof Error ? err.message : String(err);
6497
- import_fs14.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
6976
+ import_fs15.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
6498
6977
  `);
6499
6978
  }
6500
6979
  process.exit(0);
@@ -6528,9 +7007,9 @@ RAW: ${raw}
6528
7007
  }
6529
7008
 
6530
7009
  // src/cli/commands/log.ts
6531
- var import_fs15 = __toESM(require("fs"));
6532
- var import_path16 = __toESM(require("path"));
6533
- var import_os13 = __toESM(require("os"));
7010
+ var import_fs16 = __toESM(require("fs"));
7011
+ var import_path18 = __toESM(require("path"));
7012
+ var import_os14 = __toESM(require("os"));
6534
7013
  init_audit();
6535
7014
  init_config();
6536
7015
  init_policy();
@@ -6552,11 +7031,11 @@ function registerLogCommand(program2) {
6552
7031
  decision: "allowed",
6553
7032
  source: "post-hook"
6554
7033
  };
6555
- const logPath = import_path16.default.join(import_os13.default.homedir(), ".node9", "audit.log");
6556
- if (!import_fs15.default.existsSync(import_path16.default.dirname(logPath)))
6557
- import_fs15.default.mkdirSync(import_path16.default.dirname(logPath), { recursive: true });
6558
- import_fs15.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
6559
- const safeCwd = typeof payload.cwd === "string" && import_path16.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
7034
+ const logPath = import_path18.default.join(import_os14.default.homedir(), ".node9", "audit.log");
7035
+ if (!import_fs16.default.existsSync(import_path18.default.dirname(logPath)))
7036
+ import_fs16.default.mkdirSync(import_path18.default.dirname(logPath), { recursive: true });
7037
+ import_fs16.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
7038
+ const safeCwd = typeof payload.cwd === "string" && import_path18.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6560
7039
  const config = getConfig(safeCwd);
6561
7040
  if (shouldSnapshot(tool, {}, config)) {
6562
7041
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -6565,9 +7044,9 @@ function registerLogCommand(program2) {
6565
7044
  const msg = err instanceof Error ? err.message : String(err);
6566
7045
  process.stderr.write(`[Node9] audit log error: ${msg}
6567
7046
  `);
6568
- const debugPath = import_path16.default.join(import_os13.default.homedir(), ".node9", "hook-debug.log");
7047
+ const debugPath = import_path18.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
6569
7048
  try {
6570
- import_fs15.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
7049
+ import_fs16.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
6571
7050
  `);
6572
7051
  } catch {
6573
7052
  }
@@ -6871,14 +7350,14 @@ function registerConfigShowCommand(program2) {
6871
7350
 
6872
7351
  // src/cli/commands/doctor.ts
6873
7352
  var import_chalk7 = __toESM(require("chalk"));
6874
- var import_fs16 = __toESM(require("fs"));
6875
- var import_path17 = __toESM(require("path"));
6876
- var import_os14 = __toESM(require("os"));
7353
+ var import_fs17 = __toESM(require("fs"));
7354
+ var import_path19 = __toESM(require("path"));
7355
+ var import_os15 = __toESM(require("os"));
6877
7356
  var import_child_process9 = require("child_process");
6878
7357
  init_daemon();
6879
7358
  function registerDoctorCommand(program2, version2) {
6880
7359
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
6881
- const homeDir2 = import_os14.default.homedir();
7360
+ const homeDir2 = import_os15.default.homedir();
6882
7361
  let failures = 0;
6883
7362
  function pass(msg) {
6884
7363
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -6927,10 +7406,10 @@ function registerDoctorCommand(program2, version2) {
6927
7406
  );
6928
7407
  }
6929
7408
  section("Configuration");
6930
- const globalConfigPath = import_path17.default.join(homeDir2, ".node9", "config.json");
6931
- if (import_fs16.default.existsSync(globalConfigPath)) {
7409
+ const globalConfigPath = import_path19.default.join(homeDir2, ".node9", "config.json");
7410
+ if (import_fs17.default.existsSync(globalConfigPath)) {
6932
7411
  try {
6933
- JSON.parse(import_fs16.default.readFileSync(globalConfigPath, "utf-8"));
7412
+ JSON.parse(import_fs17.default.readFileSync(globalConfigPath, "utf-8"));
6934
7413
  pass("~/.node9/config.json found and valid");
6935
7414
  } catch {
6936
7415
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -6938,10 +7417,10 @@ function registerDoctorCommand(program2, version2) {
6938
7417
  } else {
6939
7418
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
6940
7419
  }
6941
- const projectConfigPath = import_path17.default.join(process.cwd(), "node9.config.json");
6942
- if (import_fs16.default.existsSync(projectConfigPath)) {
7420
+ const projectConfigPath = import_path19.default.join(process.cwd(), "node9.config.json");
7421
+ if (import_fs17.default.existsSync(projectConfigPath)) {
6943
7422
  try {
6944
- JSON.parse(import_fs16.default.readFileSync(projectConfigPath, "utf-8"));
7423
+ JSON.parse(import_fs17.default.readFileSync(projectConfigPath, "utf-8"));
6945
7424
  pass("node9.config.json found and valid (project)");
6946
7425
  } catch {
6947
7426
  fail(
@@ -6950,8 +7429,8 @@ function registerDoctorCommand(program2, version2) {
6950
7429
  );
6951
7430
  }
6952
7431
  }
6953
- const credsPath = import_path17.default.join(homeDir2, ".node9", "credentials.json");
6954
- if (import_fs16.default.existsSync(credsPath)) {
7432
+ const credsPath = import_path19.default.join(homeDir2, ".node9", "credentials.json");
7433
+ if (import_fs17.default.existsSync(credsPath)) {
6955
7434
  pass("Cloud credentials found (~/.node9/credentials.json)");
6956
7435
  } else {
6957
7436
  warn(
@@ -6960,10 +7439,10 @@ function registerDoctorCommand(program2, version2) {
6960
7439
  );
6961
7440
  }
6962
7441
  section("Agent Hooks");
6963
- const claudeSettingsPath = import_path17.default.join(homeDir2, ".claude", "settings.json");
6964
- if (import_fs16.default.existsSync(claudeSettingsPath)) {
7442
+ const claudeSettingsPath = import_path19.default.join(homeDir2, ".claude", "settings.json");
7443
+ if (import_fs17.default.existsSync(claudeSettingsPath)) {
6965
7444
  try {
6966
- const cs = JSON.parse(import_fs16.default.readFileSync(claudeSettingsPath, "utf-8"));
7445
+ const cs = JSON.parse(import_fs17.default.readFileSync(claudeSettingsPath, "utf-8"));
6967
7446
  const hasHook = cs.hooks?.PreToolUse?.some(
6968
7447
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
6969
7448
  );
@@ -6979,10 +7458,10 @@ function registerDoctorCommand(program2, version2) {
6979
7458
  } else {
6980
7459
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
6981
7460
  }
6982
- const geminiSettingsPath = import_path17.default.join(homeDir2, ".gemini", "settings.json");
6983
- if (import_fs16.default.existsSync(geminiSettingsPath)) {
7461
+ const geminiSettingsPath = import_path19.default.join(homeDir2, ".gemini", "settings.json");
7462
+ if (import_fs17.default.existsSync(geminiSettingsPath)) {
6984
7463
  try {
6985
- const gs = JSON.parse(import_fs16.default.readFileSync(geminiSettingsPath, "utf-8"));
7464
+ const gs = JSON.parse(import_fs17.default.readFileSync(geminiSettingsPath, "utf-8"));
6986
7465
  const hasHook = gs.hooks?.BeforeTool?.some(
6987
7466
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
6988
7467
  );
@@ -6998,10 +7477,10 @@ function registerDoctorCommand(program2, version2) {
6998
7477
  } else {
6999
7478
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
7000
7479
  }
7001
- const cursorHooksPath = import_path17.default.join(homeDir2, ".cursor", "hooks.json");
7002
- if (import_fs16.default.existsSync(cursorHooksPath)) {
7480
+ const cursorHooksPath = import_path19.default.join(homeDir2, ".cursor", "hooks.json");
7481
+ if (import_fs17.default.existsSync(cursorHooksPath)) {
7003
7482
  try {
7004
- const cur = JSON.parse(import_fs16.default.readFileSync(cursorHooksPath, "utf-8"));
7483
+ const cur = JSON.parse(import_fs17.default.readFileSync(cursorHooksPath, "utf-8"));
7005
7484
  const hasHook = cur.hooks?.preToolUse?.some(
7006
7485
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
7007
7486
  );
@@ -7039,9 +7518,9 @@ function registerDoctorCommand(program2, version2) {
7039
7518
 
7040
7519
  // src/cli/commands/audit.ts
7041
7520
  var import_chalk8 = __toESM(require("chalk"));
7042
- var import_fs17 = __toESM(require("fs"));
7043
- var import_path18 = __toESM(require("path"));
7044
- var import_os15 = __toESM(require("os"));
7521
+ var import_fs18 = __toESM(require("fs"));
7522
+ var import_path20 = __toESM(require("path"));
7523
+ var import_os16 = __toESM(require("os"));
7045
7524
  function formatRelativeTime(timestamp) {
7046
7525
  const diff = Date.now() - new Date(timestamp).getTime();
7047
7526
  const sec = Math.floor(diff / 1e3);
@@ -7054,14 +7533,14 @@ function formatRelativeTime(timestamp) {
7054
7533
  }
7055
7534
  function registerAuditCommand(program2) {
7056
7535
  program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
7057
- const logPath = import_path18.default.join(import_os15.default.homedir(), ".node9", "audit.log");
7058
- if (!import_fs17.default.existsSync(logPath)) {
7536
+ const logPath = import_path20.default.join(import_os16.default.homedir(), ".node9", "audit.log");
7537
+ if (!import_fs18.default.existsSync(logPath)) {
7059
7538
  console.log(
7060
7539
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
7061
7540
  );
7062
7541
  return;
7063
7542
  }
7064
- const raw = import_fs17.default.readFileSync(logPath, "utf-8");
7543
+ const raw = import_fs18.default.readFileSync(logPath, "utf-8");
7065
7544
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
7066
7545
  let entries = lines.flatMap((line) => {
7067
7546
  try {
@@ -7179,9 +7658,9 @@ function registerDaemonCommand(program2) {
7179
7658
 
7180
7659
  // src/cli/commands/status.ts
7181
7660
  var import_chalk10 = __toESM(require("chalk"));
7182
- var import_fs18 = __toESM(require("fs"));
7183
- var import_path19 = __toESM(require("path"));
7184
- var import_os16 = __toESM(require("os"));
7661
+ var import_fs19 = __toESM(require("fs"));
7662
+ var import_path21 = __toESM(require("path"));
7663
+ var import_os17 = __toESM(require("os"));
7185
7664
  init_core();
7186
7665
  init_daemon();
7187
7666
  function registerStatusCommand(program2) {
@@ -7218,13 +7697,13 @@ function registerStatusCommand(program2) {
7218
7697
  console.log("");
7219
7698
  const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
7220
7699
  console.log(` Mode: ${modeLabel}`);
7221
- const projectConfig = import_path19.default.join(process.cwd(), "node9.config.json");
7222
- const globalConfig = import_path19.default.join(import_os16.default.homedir(), ".node9", "config.json");
7700
+ const projectConfig = import_path21.default.join(process.cwd(), "node9.config.json");
7701
+ const globalConfig = import_path21.default.join(import_os17.default.homedir(), ".node9", "config.json");
7223
7702
  console.log(
7224
- ` Local: ${import_fs18.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
7703
+ ` Local: ${import_fs19.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
7225
7704
  );
7226
7705
  console.log(
7227
- ` Global: ${import_fs18.default.existsSync(globalConfig) ? import_chalk10.default.green("Active (~/.node9/config.json)") : import_chalk10.default.gray("Not present")}`
7706
+ ` Global: ${import_fs19.default.existsSync(globalConfig) ? import_chalk10.default.green("Active (~/.node9/config.json)") : import_chalk10.default.gray("Not present")}`
7228
7707
  );
7229
7708
  if (mergedConfig.policy.sandboxPaths.length > 0) {
7230
7709
  console.log(
@@ -7403,6 +7882,7 @@ var import_chalk13 = __toESM(require("chalk"));
7403
7882
  var import_child_process12 = require("child_process");
7404
7883
  var import_execa3 = require("execa");
7405
7884
  init_orchestrator();
7885
+ init_provenance();
7406
7886
  function sanitize4(value) {
7407
7887
  return value.replace(/[\x00-\x1F\x7F]/g, "");
7408
7888
  }
@@ -7415,7 +7895,7 @@ function extractMcpServer(toolName) {
7415
7895
  const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
7416
7896
  return match?.[1];
7417
7897
  }
7418
- function tokenize2(cmd) {
7898
+ function tokenize3(cmd) {
7419
7899
  const tokens = [];
7420
7900
  let current = "";
7421
7901
  let inDouble = false;
@@ -7450,7 +7930,7 @@ function tokenize2(cmd) {
7450
7930
  return tokens;
7451
7931
  }
7452
7932
  async function runMcpGateway(upstreamCommand) {
7453
- const commandParts = tokenize2(upstreamCommand);
7933
+ const commandParts = tokenize3(upstreamCommand);
7454
7934
  const cmd = commandParts[0];
7455
7935
  const cmdArgs = commandParts.slice(1);
7456
7936
  let executable = cmd;
@@ -7459,6 +7939,15 @@ async function runMcpGateway(upstreamCommand) {
7459
7939
  if (stdout) executable = stdout.trim();
7460
7940
  } catch {
7461
7941
  }
7942
+ const prov = checkProvenance(executable);
7943
+ if (prov.trustLevel === "suspect") {
7944
+ console.error(
7945
+ import_chalk13.default.red(
7946
+ `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
7947
+ )
7948
+ );
7949
+ console.error(import_chalk13.default.red(" Verify this binary is trusted before proceeding."));
7950
+ }
7462
7951
  console.error(import_chalk13.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
7463
7952
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
7464
7953
  "NODE_OPTIONS",
@@ -7600,20 +8089,20 @@ function registerMcpGatewayCommand(program2) {
7600
8089
 
7601
8090
  // src/cli.ts
7602
8091
  var { version } = JSON.parse(
7603
- import_fs20.default.readFileSync(import_path21.default.join(__dirname, "../package.json"), "utf-8")
8092
+ import_fs21.default.readFileSync(import_path23.default.join(__dirname, "../package.json"), "utf-8")
7604
8093
  );
7605
8094
  var program = new import_commander.Command();
7606
8095
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
7607
8096
  program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
7608
8097
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
7609
- const credPath = import_path21.default.join(import_os18.default.homedir(), ".node9", "credentials.json");
7610
- if (!import_fs20.default.existsSync(import_path21.default.dirname(credPath)))
7611
- import_fs20.default.mkdirSync(import_path21.default.dirname(credPath), { recursive: true });
8098
+ const credPath = import_path23.default.join(import_os19.default.homedir(), ".node9", "credentials.json");
8099
+ if (!import_fs21.default.existsSync(import_path23.default.dirname(credPath)))
8100
+ import_fs21.default.mkdirSync(import_path23.default.dirname(credPath), { recursive: true });
7612
8101
  const profileName = options.profile || "default";
7613
8102
  let existingCreds = {};
7614
8103
  try {
7615
- if (import_fs20.default.existsSync(credPath)) {
7616
- const raw = JSON.parse(import_fs20.default.readFileSync(credPath, "utf-8"));
8104
+ if (import_fs21.default.existsSync(credPath)) {
8105
+ const raw = JSON.parse(import_fs21.default.readFileSync(credPath, "utf-8"));
7617
8106
  if (raw.apiKey) {
7618
8107
  existingCreds = {
7619
8108
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -7625,13 +8114,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
7625
8114
  } catch {
7626
8115
  }
7627
8116
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
7628
- import_fs20.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
8117
+ import_fs21.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
7629
8118
  if (profileName === "default") {
7630
- const configPath = import_path21.default.join(import_os18.default.homedir(), ".node9", "config.json");
8119
+ const configPath = import_path23.default.join(import_os19.default.homedir(), ".node9", "config.json");
7631
8120
  let config = {};
7632
8121
  try {
7633
- if (import_fs20.default.existsSync(configPath))
7634
- config = JSON.parse(import_fs20.default.readFileSync(configPath, "utf-8"));
8122
+ if (import_fs21.default.existsSync(configPath))
8123
+ config = JSON.parse(import_fs21.default.readFileSync(configPath, "utf-8"));
7635
8124
  } catch {
7636
8125
  }
7637
8126
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -7646,9 +8135,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
7646
8135
  approvers.cloud = false;
7647
8136
  }
7648
8137
  s.approvers = approvers;
7649
- if (!import_fs20.default.existsSync(import_path21.default.dirname(configPath)))
7650
- import_fs20.default.mkdirSync(import_path21.default.dirname(configPath), { recursive: true });
7651
- import_fs20.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
8138
+ if (!import_fs21.default.existsSync(import_path23.default.dirname(configPath)))
8139
+ import_fs21.default.mkdirSync(import_path23.default.dirname(configPath), { recursive: true });
8140
+ import_fs21.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
7652
8141
  }
7653
8142
  if (options.profile && profileName !== "default") {
7654
8143
  console.log(import_chalk15.default.green(`\u2705 Profile "${profileName}" saved`));
@@ -7734,15 +8223,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
7734
8223
  }
7735
8224
  }
7736
8225
  if (options.purge) {
7737
- const node9Dir = import_path21.default.join(import_os18.default.homedir(), ".node9");
7738
- if (import_fs20.default.existsSync(node9Dir)) {
8226
+ const node9Dir = import_path23.default.join(import_os19.default.homedir(), ".node9");
8227
+ if (import_fs21.default.existsSync(node9Dir)) {
7739
8228
  const confirmed = await (0, import_prompts3.confirm)({
7740
8229
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
7741
8230
  default: false
7742
8231
  });
7743
8232
  if (confirmed) {
7744
- import_fs20.default.rmSync(node9Dir, { recursive: true });
7745
- if (import_fs20.default.existsSync(node9Dir)) {
8233
+ import_fs21.default.rmSync(node9Dir, { recursive: true });
8234
+ if (import_fs21.default.existsSync(node9Dir)) {
7746
8235
  console.error(
7747
8236
  import_chalk15.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
7748
8237
  );
@@ -7842,8 +8331,8 @@ program.command("explain").description(
7842
8331
  console.log("");
7843
8332
  });
7844
8333
  program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
7845
- const configPath = import_path21.default.join(import_os18.default.homedir(), ".node9", "config.json");
7846
- if (import_fs20.default.existsSync(configPath) && !options.force) {
8334
+ const configPath = import_path23.default.join(import_os19.default.homedir(), ".node9", "config.json");
8335
+ if (import_fs21.default.existsSync(configPath) && !options.force) {
7847
8336
  console.log(import_chalk15.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
7848
8337
  console.log(import_chalk15.default.gray(` Run with --force to overwrite.`));
7849
8338
  return;
@@ -7857,9 +8346,9 @@ program.command("init").description("Create ~/.node9/config.json with default po
7857
8346
  mode: safeMode
7858
8347
  }
7859
8348
  };
7860
- const dir = import_path21.default.dirname(configPath);
7861
- if (!import_fs20.default.existsSync(dir)) import_fs20.default.mkdirSync(dir, { recursive: true });
7862
- import_fs20.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
8349
+ const dir = import_path23.default.dirname(configPath);
8350
+ if (!import_fs21.default.existsSync(dir)) import_fs21.default.mkdirSync(dir, { recursive: true });
8351
+ import_fs21.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
7863
8352
  console.log(import_chalk15.default.green(`\u2705 Global config created: ${configPath}`));
7864
8353
  console.log(import_chalk15.default.cyan(` Mode set to: ${safeMode}`));
7865
8354
  console.log(
@@ -7977,9 +8466,9 @@ if (process.argv[2] !== "daemon") {
7977
8466
  const isCheckHook = process.argv[2] === "check";
7978
8467
  if (isCheckHook) {
7979
8468
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
7980
- const logPath = import_path21.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
8469
+ const logPath = import_path23.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
7981
8470
  const msg = reason instanceof Error ? reason.message : String(reason);
7982
- import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
8471
+ import_fs21.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
7983
8472
  `);
7984
8473
  }
7985
8474
  process.exit(0);