@node9/proxy 1.29.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -52,11 +52,17 @@ __export(audit_exports, {
52
52
  appendHookDebug: () => appendHookDebug,
53
53
  appendLocalAudit: () => appendLocalAudit,
54
54
  appendToLog: () => appendToLog,
55
+ buildArgsPreview: () => buildArgsPreview,
56
+ generateEventId: () => generateEventId,
55
57
  redactSecrets: () => redactSecrets
56
58
  });
57
59
  import fs from "fs";
58
60
  import path from "path";
59
61
  import os from "os";
62
+ import crypto from "crypto";
63
+ function generateEventId() {
64
+ return `${Date.now().toString(36)}-${crypto.randomBytes(6).toString("hex")}`;
65
+ }
60
66
  function isTestCall(toolName, args) {
61
67
  if (toolName !== "Bash" && toolName !== "bash") return false;
62
68
  const cmd = args?.command;
@@ -75,6 +81,17 @@ function redactSecrets(text) {
75
81
  );
76
82
  return redacted;
77
83
  }
84
+ function buildArgsPreview(args) {
85
+ try {
86
+ const o = args && typeof args === "object" ? args : null;
87
+ const primary = o && (o.command ?? o.file_path ?? o.path ?? o.url ?? o.query);
88
+ const text = typeof primary === "string" ? primary : args ? JSON.stringify(args) : "";
89
+ if (!text) return void 0;
90
+ return redactSecrets(text).slice(0, 120);
91
+ } catch {
92
+ return void 0;
93
+ }
94
+ }
78
95
  function appendToLog(logPath, entry) {
79
96
  try {
80
97
  const dir = path.dirname(logPath);
@@ -97,11 +114,18 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
97
114
  });
98
115
  }
99
116
  function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
100
- const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
117
+ const isDlpRow = checkedBy.toLowerCase().includes("dlp") || Boolean(meta?.dlpPattern);
118
+ const preview2 = auditHashArgsEnabled && !isDlpRow ? buildArgsPreview(args) : void 0;
119
+ const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args), ...preview2 ? { argsPreview: preview2 } : {} } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
101
120
  const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
102
121
  const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
103
122
  const agentToolNameField = meta?.agentToolName ? { agentToolName: meta.agentToolName } : {};
123
+ const dlpFields = meta?.dlpPattern ? { dlpPattern: meta.dlpPattern, dlpSample: meta.dlpSample } : {};
124
+ const cloudLinkField = meta?.cloudRequestId ? { cloudRequestId: meta.cloudRequestId } : {};
104
125
  appendToLog(LOCAL_AUDIT_LOG, {
126
+ // eid first: the outbox shipper dedups on it, and a fixed leading field
127
+ // makes the JSONL easy to eyeball.
128
+ eid: generateEventId(),
105
129
  ts: (/* @__PURE__ */ new Date()).toISOString(),
106
130
  tool: toolName,
107
131
  ...agentToolNameField,
@@ -109,6 +133,8 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
109
133
  decision,
110
134
  checkedBy,
111
135
  ...ruleNameField,
136
+ ...dlpFields,
137
+ ...cloudLinkField,
112
138
  ...testRun,
113
139
  agent: meta?.agent,
114
140
  mcpServer: meta?.mcpServer,
@@ -153,8 +179,8 @@ function sanitizeConfig(raw) {
153
179
  }
154
180
  }
155
181
  const lines = result.error.issues.map((issue) => {
156
- const path50 = issue.path.length > 0 ? issue.path.join(".") : "root";
157
- return ` \u2022 ${path50}: ${issue.message}`;
182
+ const path51 = issue.path.length > 0 ? issue.path.join(".") : "root";
183
+ return ` \u2022 ${path51}: ${issue.message}`;
158
184
  });
159
185
  return {
160
186
  sanitized,
@@ -239,7 +265,13 @@ var init_config_schema = __esm({
239
265
  allowGlobalPause: z.boolean().optional(),
240
266
  auditHashArgs: z.boolean().optional(),
241
267
  agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional(),
242
- cloudSyncIntervalHours: z.number().positive().optional()
268
+ cloudSyncIntervalHours: z.number().positive().optional(),
269
+ // Outbox shipper (audit.log → SaaS batch ingest). enabled defaults
270
+ // to true; set false to fall back to local-only auditing.
271
+ shipper: z.object({
272
+ enabled: z.boolean().optional(),
273
+ intervalSeconds: z.number().min(5).optional()
274
+ }).optional()
243
275
  }).optional(),
244
276
  policy: z.object({
245
277
  sandboxPaths: z.array(z.string()).optional(),
@@ -278,7 +310,7 @@ import mvdanSh from "mvdan-sh";
278
310
  import pm from "picomatch";
279
311
  import safeRegex2 from "safe-regex2";
280
312
  import safeRegex3 from "safe-regex2";
281
- import crypto from "crypto";
313
+ import crypto2 from "crypto";
282
314
  function isAssignmentContext(text) {
283
315
  return ASSIGNMENT_CONTEXT_RE.test(text);
284
316
  }
@@ -991,9 +1023,9 @@ function matchesPattern(text, patterns) {
991
1023
  const withoutDotSlash = text.replace(/^\.\//, "");
992
1024
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
993
1025
  }
994
- function getNestedValue(obj, path50) {
1026
+ function getNestedValue(obj, path51) {
995
1027
  if (!obj || typeof obj !== "object") return null;
996
- const segments = path50.split(".");
1028
+ const segments = path51.split(".");
997
1029
  for (const seg of segments) {
998
1030
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
999
1031
  }
@@ -1355,7 +1387,7 @@ function assertBuiltinShieldRegexesAreSafe() {
1355
1387
  }
1356
1388
  function computeArgsHash(args) {
1357
1389
  const str = JSON.stringify(args ?? "");
1358
- return crypto.createHash("sha256").update(str).digest("hex").slice(0, 16);
1390
+ return crypto2.createHash("sha256").update(str).digest("hex").slice(0, 16);
1359
1391
  }
1360
1392
  function evaluateLoopWindow(records, tool, args, threshold, windowMs, now) {
1361
1393
  const hash = computeArgsHash(args);
@@ -3394,7 +3426,7 @@ var init_dist = __esm({
3394
3426
  import fs2 from "fs";
3395
3427
  import path2 from "path";
3396
3428
  import os2 from "os";
3397
- import crypto2 from "crypto";
3429
+ import crypto3 from "crypto";
3398
3430
  function loadUserShields() {
3399
3431
  const result = {};
3400
3432
  let entries;
@@ -3469,7 +3501,7 @@ function readShieldsFile() {
3469
3501
  }
3470
3502
  function writeShieldsFile(data) {
3471
3503
  fs2.mkdirSync(path2.dirname(SHIELDS_STATE_FILE), { recursive: true });
3472
- const tmp = `${SHIELDS_STATE_FILE}.${crypto2.randomBytes(6).toString("hex")}.tmp`;
3504
+ const tmp = `${SHIELDS_STATE_FILE}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
3473
3505
  const toWrite = { active: data.active };
3474
3506
  if (data.overrides && Object.keys(data.overrides).length > 0) toWrite.overrides = data.overrides;
3475
3507
  fs2.writeFileSync(tmp, JSON.stringify(toWrite, null, 2), { mode: 384 });
@@ -3559,7 +3591,7 @@ function installShield(name, shieldJson) {
3559
3591
  }
3560
3592
  fs2.mkdirSync(USER_SHIELDS_DIR, { recursive: true });
3561
3593
  const filePath = path2.join(USER_SHIELDS_DIR, `${name}.json`);
3562
- const tmp = `${filePath}.${crypto2.randomBytes(6).toString("hex")}.tmp`;
3594
+ const tmp = `${filePath}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
3563
3595
  fs2.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
3564
3596
  fs2.renameSync(tmp, filePath);
3565
3597
  }
@@ -3657,7 +3689,8 @@ function getConfig(cwd) {
3657
3689
  const projectConfig = tryLoadConfig(projectPath);
3658
3690
  const mergedSettings = {
3659
3691
  ...DEFAULT_CONFIG.settings,
3660
- approvers: { ...DEFAULT_CONFIG.settings.approvers }
3692
+ approvers: { ...DEFAULT_CONFIG.settings.approvers },
3693
+ shipper: { ...DEFAULT_CONFIG.settings.shipper }
3661
3694
  };
3662
3695
  const mergedPolicy = {
3663
3696
  sandboxPaths: [...DEFAULT_CONFIG.policy.sandboxPaths],
@@ -3688,6 +3721,7 @@ function getConfig(cwd) {
3688
3721
  if (s.enableHookLogDebug !== void 0)
3689
3722
  mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
3690
3723
  if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
3724
+ if (s.shipper) mergedSettings.shipper = { ...mergedSettings.shipper, ...s.shipper };
3691
3725
  if (s.approvalTimeoutMs !== void 0) mergedSettings.approvalTimeoutMs = s.approvalTimeoutMs;
3692
3726
  if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
3693
3727
  mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
@@ -3885,7 +3919,8 @@ var init_config = __esm({
3885
3919
  flightRecorder: true,
3886
3920
  auditHashArgs: true,
3887
3921
  approvers: { native: true, browser: false, cloud: false, terminal: true },
3888
- cloudSyncIntervalHours: 5
3922
+ cloudSyncIntervalHours: 5,
3923
+ shipper: { enabled: true, intervalSeconds: 20 }
3889
3924
  },
3890
3925
  policy: {
3891
3926
  sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
@@ -5430,55 +5465,6 @@ function validateApiUrl(raw) {
5430
5465
  }
5431
5466
  return null;
5432
5467
  }
5433
- function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false, riskMetadata) {
5434
- const validated = validateApiUrl(creds.apiUrl);
5435
- if (!validated) {
5436
- try {
5437
- fs10.appendFileSync(
5438
- HOOK_DEBUG_LOG,
5439
- `[audit] refused to send: invalid apiUrl scheme/host (got "${String(creds.apiUrl).slice(0, 200)}")
5440
- `
5441
- );
5442
- } catch {
5443
- }
5444
- return Promise.resolve();
5445
- }
5446
- const safeArgs = containsSensitiveArgs ? { tool: toolName, redacted: true } : args;
5447
- const dlpSample = dlpInfo && typeof dlpInfo.redactedSample === "string" ? dlpInfo.redactedSample.slice(0, DLP_SAMPLE_MAX_LEN) : void 0;
5448
- const dlpPattern = dlpInfo && typeof dlpInfo.pattern === "string" ? dlpInfo.pattern.slice(0, DLP_PATTERN_MAX_LEN) : void 0;
5449
- const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
5450
- const cleanedRiskMetadata = riskMetadata ? Object.fromEntries(
5451
- Object.entries(riskMetadata).filter(([, v]) => typeof v === "string" && v.length > 0)
5452
- ) : void 0;
5453
- const hasRiskMetadata = cleanedRiskMetadata && Object.keys(cleanedRiskMetadata).length > 0;
5454
- return fetch(`${validated.toString().replace(/\/$/, "")}/audit`, {
5455
- method: "POST",
5456
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
5457
- body: JSON.stringify({
5458
- toolName,
5459
- args: safeArgs,
5460
- checkedBy: safeCheckedBy,
5461
- ...dlpInfo && { dlpPattern, dlpSample },
5462
- ...hasRiskMetadata && { riskMetadata: cleanedRiskMetadata },
5463
- // session_id (Claude Code + Gemini CLI) groups all audit rows from one
5464
- // agent run; transcript_path is the authoritative pointer to the
5465
- // session log (survives Gemini resume drift). Both optional —
5466
- // unsupported agents (MCP-mediated) leave them undefined.
5467
- ...meta?.sessionId && { runId: meta.sessionId },
5468
- ...meta?.transcriptPath && { transcriptPath: meta.transcriptPath },
5469
- context: {
5470
- agent: meta?.agent,
5471
- mcpServer: meta?.mcpServer,
5472
- hostname: os9.hostname(),
5473
- cwd: process.cwd(),
5474
- platform: os9.platform()
5475
- }
5476
- }),
5477
- signal: AbortSignal.timeout(5e3)
5478
- }).then(() => {
5479
- }).catch(() => {
5480
- });
5481
- }
5482
5468
  async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
5483
5469
  const controller = new AbortController();
5484
5470
  const timeout = setTimeout(() => controller.abort(), 1e4);
@@ -5602,32 +5588,10 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
5602
5588
  );
5603
5589
  }
5604
5590
  }
5605
- var DLP_SAMPLE_MAX_LEN, DLP_PATTERN_MAX_LEN, KNOWN_CHECKED_BY;
5606
5591
  var init_cloud = __esm({
5607
5592
  "src/auth/cloud.ts"() {
5608
5593
  "use strict";
5609
5594
  init_audit();
5610
- DLP_SAMPLE_MAX_LEN = 200;
5611
- DLP_PATTERN_MAX_LEN = 100;
5612
- KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
5613
- "dlp-block",
5614
- "observe-mode-dlp-would-block",
5615
- "dlp-review-flagged",
5616
- "loop-detected",
5617
- "audit-mode",
5618
- "local-policy",
5619
- "smart-rule-block",
5620
- // Smart-rule block was downgraded to review because the daemon was
5621
- // running and we're not in CI. The block attempt is still recorded;
5622
- // the user got a popup. Distinct from 'smart-rule-block' so the
5623
- // dashboard can show "block rule overridden" separately from a hard
5624
- // block that fired with no human in the loop.
5625
- "smart-rule-block-override",
5626
- "persistent",
5627
- "trust",
5628
- "observe-mode",
5629
- "observe-mode-would-block"
5630
- ]);
5631
5595
  }
5632
5596
  });
5633
5597
 
@@ -5809,17 +5773,11 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5809
5773
  args,
5810
5774
  "deny",
5811
5775
  isObserveMode ? "observe-mode-dlp-would-block" : "dlp-block",
5812
- meta,
5813
- true
5814
- );
5815
- if (approvers.cloud && creds?.apiKey)
5816
- auditLocalAllow(
5817
- toolName,
5818
- args,
5819
- isObserveMode ? "observe-mode-dlp-would-block" : "dlp-block",
5820
- creds,
5821
- meta,
5822
- { pattern: dlpMatch.patternName, redactedSample: dlpMatch.redactedSample },
5776
+ {
5777
+ ...meta,
5778
+ dlpPattern: dlpMatch.patternName,
5779
+ dlpSample: dlpMatch.redactedSample
5780
+ },
5823
5781
  true
5824
5782
  );
5825
5783
  if (isWriteTool(toolName) && filePath) {
@@ -5875,9 +5833,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5875
5833
  const policyResult = await evaluatePolicy2(toolName, args, meta?.agent, options?.cwd);
5876
5834
  if (policyResult.decision === "review") {
5877
5835
  appendLocalAudit(toolName, args, "allow", "audit-mode", meta, hashAuditArgs);
5878
- if (approvers.cloud && creds?.apiKey) {
5879
- await auditLocalAllow(toolName, args, "audit-mode", creds, meta);
5880
- }
5881
5836
  }
5882
5837
  }
5883
5838
  return { approved: true, checkedBy: "audit" };
@@ -5890,8 +5845,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5890
5845
  const reason = `It looks like you've called "${toolName}" ${loopResult.count} times with identical arguments in the last ${ld.windowSeconds}s. Are you stuck? Step back and reconsider your approach \u2014 what are you actually trying to accomplish, and is there a different way to get there?`;
5891
5846
  if (!isManual)
5892
5847
  appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
5893
- if (approvers.cloud && creds?.apiKey)
5894
- auditLocalAllow(toolName, args, "loop-detected", creds, meta, void 0, true);
5895
5848
  return {
5896
5849
  approved: false,
5897
5850
  reason,
@@ -5910,15 +5863,15 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5910
5863
  };
5911
5864
  }
5912
5865
  if (policyResult.decision === "allow") {
5913
- if (approvers.cloud && creds?.apiKey)
5914
- await auditLocalAllow(toolName, args, "local-policy", creds, meta, void 0, false, {
5915
- ruleName: policyResult.ruleName,
5916
- ruleDescription: policyResult.ruleDescription,
5917
- blockedByLabel: policyResult.blockedByLabel,
5918
- matchedField: policyResult.matchedField,
5919
- matchedWord: policyResult.matchedWord
5920
- });
5921
- if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
5866
+ if (!isManual)
5867
+ appendLocalAudit(
5868
+ toolName,
5869
+ args,
5870
+ "allow",
5871
+ "local-policy",
5872
+ { ...meta, ruleName: policyResult.ruleName },
5873
+ hashAuditArgs
5874
+ );
5922
5875
  return { approved: true, checkedBy: "local-policy" };
5923
5876
  }
5924
5877
  if (policyResult.decision === "block") {
@@ -5951,23 +5904,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5951
5904
  { ...meta, ruleName: policyResult.ruleName },
5952
5905
  hashAuditArgs
5953
5906
  );
5954
- if (approvers.cloud && creds?.apiKey)
5955
- auditLocalAllow(
5956
- toolName,
5957
- args,
5958
- "smart-rule-block-override",
5959
- creds,
5960
- meta,
5961
- void 0,
5962
- false,
5963
- {
5964
- ruleName: policyResult.ruleName,
5965
- ruleDescription: policyResult.ruleDescription,
5966
- blockedByLabel: policyResult.blockedByLabel,
5967
- matchedField: policyResult.matchedField,
5968
- matchedWord: policyResult.matchedWord
5969
- }
5970
- );
5971
5907
  const baseLabel = policyResult.blockedByLabel || "Smart Rule";
5972
5908
  const OVERRIDE_PREFIX = "\u26A0\uFE0F Override block rule: ";
5973
5909
  if (!baseLabel.startsWith(OVERRIDE_PREFIX)) {
@@ -5992,14 +5928,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
5992
5928
  { ...meta, ruleName: policyResult.ruleName },
5993
5929
  hashAuditArgs
5994
5930
  );
5995
- if (approvers.cloud && creds?.apiKey)
5996
- auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
5997
- ruleName: policyResult.ruleName,
5998
- ruleDescription: policyResult.ruleDescription,
5999
- blockedByLabel: policyResult.blockedByLabel,
6000
- matchedField: policyResult.matchedField,
6001
- matchedWord: policyResult.matchedWord
6002
- });
6003
5931
  return {
6004
5932
  approved: false,
6005
5933
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
@@ -6028,8 +5956,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
6028
5956
  if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
6029
5957
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
6030
5958
  if (persistent === "allow") {
6031
- if (approvers.cloud && creds?.apiKey)
6032
- await auditLocalAllow(toolName, args, "persistent", creds, meta);
6033
5959
  if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
6034
5960
  return { approved: true, checkedBy: "persistent" };
6035
5961
  }
@@ -6062,8 +5988,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
6062
5988
  }
6063
5989
  }
6064
5990
  if (!taintWarning && getActiveTrustSession(toolName, args)) {
6065
- if (approvers.cloud && creds?.apiKey)
6066
- await auditLocalAllow(toolName, args, "trust", creds, meta);
6067
5991
  if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
6068
5992
  return { approved: true, checkedBy: "trust" };
6069
5993
  }
@@ -6308,7 +6232,12 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
6308
6232
  args,
6309
6233
  finalResult.approved ? "allow" : "deny",
6310
6234
  finalResult.checkedBy || finalResult.blockedBy || "unknown",
6311
- meta,
6235
+ // cloudRequestId links this row to the BE-origin AuditLog row the
6236
+ // /intercept handshake created — the shipper hands it to the SaaS so
6237
+ // the BE enriches that row instead of inserting a duplicate. Matters
6238
+ // for EVERY racer outcome, not just cloud wins: a native-popup
6239
+ // decision on a cloud-pending request would otherwise count twice.
6240
+ cloudRequestId ? { ...meta, cloudRequestId } : meta,
6312
6241
  hashAuditArgs
6313
6242
  );
6314
6243
  }
@@ -6362,7 +6291,7 @@ var init_core = __esm({
6362
6291
  import fs12 from "fs";
6363
6292
  import path14 from "path";
6364
6293
  import os11 from "os";
6365
- import crypto3 from "crypto";
6294
+ import crypto4 from "crypto";
6366
6295
  function getHomePinsFilePath() {
6367
6296
  return path14.join(os11.homedir(), ".node9", "mcp-pins.json");
6368
6297
  }
@@ -6392,10 +6321,10 @@ function hashToolDefinitions(tools) {
6392
6321
  return nameA.localeCompare(nameB);
6393
6322
  });
6394
6323
  const canonical = JSON.stringify(sorted);
6395
- return crypto3.createHash("sha256").update(canonical).digest("hex");
6324
+ return crypto4.createHash("sha256").update(canonical).digest("hex");
6396
6325
  }
6397
6326
  function getServerKey(upstreamCommand) {
6398
- return crypto3.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
6327
+ return crypto4.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
6399
6328
  }
6400
6329
  function readPinsFile(filePath) {
6401
6330
  try {
@@ -6426,7 +6355,7 @@ function readMcpPins() {
6426
6355
  }
6427
6356
  function writePinsFile(filePath, data) {
6428
6357
  fs12.mkdirSync(path14.dirname(filePath), { recursive: true });
6429
- const tmp = `${filePath}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
6358
+ const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
6430
6359
  const isHome = filePath === getHomePinsFilePath();
6431
6360
  fs12.writeFileSync(tmp, JSON.stringify(data, null, 2), isHome ? { mode: 384 } : {});
6432
6361
  fs12.renameSync(tmp, filePath);
@@ -14256,74 +14185,285 @@ var init_sync = __esm({
14256
14185
  }
14257
14186
  });
14258
14187
 
14259
- // src/daemon/dlp-scanner.ts
14188
+ // src/daemon/audit-shipper.ts
14189
+ var audit_shipper_exports = {};
14190
+ __export(audit_shipper_exports, {
14191
+ AUDIT_SHIP_WATERMARK: () => AUDIT_SHIP_WATERMARK,
14192
+ buildWireRows: () => buildWireRows,
14193
+ fileSignature: () => fileSignature,
14194
+ readWatermark: () => readWatermark,
14195
+ shipLagBytes: () => shipLagBytes,
14196
+ shipOnce: () => shipOnce,
14197
+ startAuditShipper: () => startAuditShipper,
14198
+ writeWatermark: () => writeWatermark
14199
+ });
14260
14200
  import fs24 from "fs";
14261
14201
  import path26 from "path";
14262
14202
  import os22 from "os";
14203
+ import crypto5 from "crypto";
14204
+ function fileSignature(filePath) {
14205
+ const fd = fs24.openSync(filePath, "r");
14206
+ try {
14207
+ const buf = Buffer.alloc(512);
14208
+ const read = fs24.readSync(fd, buf, 0, 512, 0);
14209
+ const slice = buf.subarray(0, read);
14210
+ const nl = slice.indexOf(10);
14211
+ const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
14212
+ return crypto5.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
14213
+ } finally {
14214
+ fs24.closeSync(fd);
14215
+ }
14216
+ }
14217
+ function readWatermark(watermarkPath) {
14218
+ try {
14219
+ const raw = JSON.parse(fs24.readFileSync(watermarkPath, "utf-8"));
14220
+ if (typeof raw.fileSig === "string" && typeof raw.offset === "number" && raw.offset >= 0)
14221
+ return raw;
14222
+ } catch {
14223
+ }
14224
+ return null;
14225
+ }
14226
+ function writeWatermark(watermarkPath, wm) {
14227
+ const tmp = `${watermarkPath}.tmp`;
14228
+ fs24.writeFileSync(tmp, JSON.stringify(wm));
14229
+ fs24.renameSync(tmp, watermarkPath);
14230
+ }
14231
+ function buildWireRows(chunk) {
14232
+ const lastNl = chunk.lastIndexOf(10);
14233
+ if (lastNl === -1) return { rows: [], consumed: 0 };
14234
+ const complete = chunk.subarray(0, lastNl + 1);
14235
+ const rows = [];
14236
+ for (const line of complete.toString("utf-8").split("\n")) {
14237
+ if (!line.trim()) continue;
14238
+ let parsed;
14239
+ try {
14240
+ parsed = JSON.parse(line);
14241
+ } catch {
14242
+ continue;
14243
+ }
14244
+ if (typeof parsed.eid !== "string" || parsed.eid.length < 8) continue;
14245
+ if (typeof parsed.tool !== "string" || !parsed.tool) continue;
14246
+ if (parsed.decision !== "allow" && parsed.decision !== "deny") continue;
14247
+ if (typeof parsed.ts !== "string") continue;
14248
+ if (parsed.testRun === true) continue;
14249
+ const checkedBy = typeof parsed.checkedBy === "string" ? parsed.checkedBy : void 0;
14250
+ if (checkedBy && SKIP_CHECKED_BY.has(checkedBy)) continue;
14251
+ const cloudRequestId = typeof parsed.cloudRequestId === "string" ? parsed.cloudRequestId : void 0;
14252
+ if (checkedBy === "cloud" && !cloudRequestId) continue;
14253
+ rows.push({
14254
+ eid: parsed.eid,
14255
+ ts: parsed.ts,
14256
+ tool: parsed.tool,
14257
+ ...parsed.args && typeof parsed.args === "object" ? { args: parsed.args } : {},
14258
+ ...typeof parsed.argsHash === "string" ? { argsHash: parsed.argsHash } : {},
14259
+ ...typeof parsed.argsPreview === "string" ? { argsPreview: parsed.argsPreview } : {},
14260
+ decision: parsed.decision,
14261
+ ...checkedBy ? { checkedBy } : {},
14262
+ ...typeof parsed.ruleName === "string" ? { ruleName: parsed.ruleName } : {},
14263
+ ...typeof parsed.agent === "string" ? { agent: parsed.agent } : {},
14264
+ ...typeof parsed.mcpServer === "string" ? { mcpServer: parsed.mcpServer } : {},
14265
+ ...typeof parsed.sessionId === "string" ? { sessionId: parsed.sessionId } : {},
14266
+ ...typeof parsed.dlpPattern === "string" ? { dlpPattern: parsed.dlpPattern } : {},
14267
+ ...typeof parsed.dlpSample === "string" ? { dlpSample: parsed.dlpSample } : {},
14268
+ ...cloudRequestId ? { cloudRequestId } : {}
14269
+ });
14270
+ }
14271
+ return { rows, consumed: lastNl + 1 };
14272
+ }
14273
+ async function shipOnce(deps = {}) {
14274
+ const auditLogPath = deps.auditLogPath ?? LOCAL_AUDIT_LOG;
14275
+ const watermarkPath = deps.watermarkPath ?? AUDIT_SHIP_WATERMARK;
14276
+ const fetchImpl = deps.fetchImpl ?? fetch;
14277
+ let cloudEnabled = deps.cloudEnabled;
14278
+ if (cloudEnabled === void 0) {
14279
+ try {
14280
+ const settings = getConfig().settings;
14281
+ cloudEnabled = settings.shipper.enabled !== false && settings.approvers.cloud;
14282
+ } catch {
14283
+ cloudEnabled = false;
14284
+ }
14285
+ }
14286
+ if (!cloudEnabled) return { status: "disabled", shipped: 0 };
14287
+ const creds = deps.creds !== void 0 ? deps.creds : readCredentials();
14288
+ if (!creds?.apiKey) return { status: "no-creds", shipped: 0 };
14289
+ const validated = validateApiUrl(creds.apiUrl);
14290
+ if (!validated) return { status: "no-creds", shipped: 0 };
14291
+ const endpoint = `${validated.toString().replace(/\/$/, "")}/audit/batch`;
14292
+ if (!fs24.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
14293
+ let shipped = 0;
14294
+ try {
14295
+ for (let chunkN = 0; chunkN < MAX_CHUNKS_PER_TICK; chunkN++) {
14296
+ const size = fs24.statSync(auditLogPath).size;
14297
+ if (size === 0) break;
14298
+ const sig = fileSignature(auditLogPath);
14299
+ const wm = readWatermark(watermarkPath);
14300
+ const offset = wm && wm.fileSig === sig && wm.offset <= size ? wm.offset : 0;
14301
+ if (offset >= size) break;
14302
+ const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
14303
+ const buf = Buffer.alloc(toRead);
14304
+ const fd = fs24.openSync(auditLogPath, "r");
14305
+ let read;
14306
+ try {
14307
+ read = fs24.readSync(fd, buf, 0, toRead, offset);
14308
+ } finally {
14309
+ fs24.closeSync(fd);
14310
+ }
14311
+ const { rows, consumed } = buildWireRows(buf.subarray(0, read));
14312
+ if (consumed === 0) break;
14313
+ for (let i = 0; i < rows.length; i += MAX_BATCH) {
14314
+ const batch = rows.slice(i, i + MAX_BATCH);
14315
+ const controller = new AbortController();
14316
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
14317
+ try {
14318
+ const res = await fetchImpl(endpoint, {
14319
+ method: "POST",
14320
+ headers: {
14321
+ "Content-Type": "application/json",
14322
+ Authorization: `Bearer ${creds.apiKey}`
14323
+ },
14324
+ body: JSON.stringify({ rows: batch }),
14325
+ signal: controller.signal
14326
+ });
14327
+ if (!res.ok) throw new Error(`audit/batch HTTP ${res.status}`);
14328
+ } finally {
14329
+ clearTimeout(timer);
14330
+ }
14331
+ shipped += batch.length;
14332
+ }
14333
+ writeWatermark(watermarkPath, {
14334
+ fileSig: sig,
14335
+ offset: offset + consumed,
14336
+ lastEid: rows.length > 0 ? rows[rows.length - 1].eid : wm?.lastEid,
14337
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
14338
+ });
14339
+ if (consumed < toRead) break;
14340
+ }
14341
+ } catch (err2) {
14342
+ try {
14343
+ appendToLog(HOOK_DEBUG_LOG, {
14344
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
14345
+ shipper: "error",
14346
+ message: err2.message
14347
+ });
14348
+ } catch {
14349
+ }
14350
+ return { status: "error", shipped };
14351
+ }
14352
+ return { status: shipped > 0 ? "shipped" : "idle", shipped };
14353
+ }
14354
+ function shipLagBytes(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK) {
14355
+ try {
14356
+ if (!fs24.existsSync(auditLogPath)) return 0;
14357
+ const size = fs24.statSync(auditLogPath).size;
14358
+ const wm = readWatermark(watermarkPath);
14359
+ if (!wm) return size;
14360
+ if (wm.fileSig !== fileSignature(auditLogPath)) return size;
14361
+ return Math.max(0, size - wm.offset);
14362
+ } catch {
14363
+ return null;
14364
+ }
14365
+ }
14366
+ function startAuditShipper() {
14367
+ if (shipperStarted) return;
14368
+ shipperStarted = true;
14369
+ const intervalMs = (() => {
14370
+ try {
14371
+ const sec = getConfig().settings.shipper.intervalSeconds;
14372
+ return sec >= 5 ? sec * 1e3 : DEFAULT_INTERVAL_MS;
14373
+ } catch {
14374
+ return DEFAULT_INTERVAL_MS;
14375
+ }
14376
+ })();
14377
+ setTimeout(() => void shipOnce(), 3e3);
14378
+ setInterval(() => void shipOnce(), intervalMs);
14379
+ }
14380
+ var AUDIT_SHIP_WATERMARK, DEFAULT_INTERVAL_MS, MAX_BATCH, MAX_CHUNK_BYTES, MAX_CHUNKS_PER_TICK, FETCH_TIMEOUT_MS, SKIP_CHECKED_BY, shipperStarted;
14381
+ var init_audit_shipper = __esm({
14382
+ "src/daemon/audit-shipper.ts"() {
14383
+ "use strict";
14384
+ init_audit();
14385
+ init_config();
14386
+ init_sync();
14387
+ init_cloud();
14388
+ AUDIT_SHIP_WATERMARK = path26.join(os22.homedir(), ".node9", "audit-ship.json");
14389
+ DEFAULT_INTERVAL_MS = 2e4;
14390
+ MAX_BATCH = 500;
14391
+ MAX_CHUNK_BYTES = 4 * 1024 * 1024;
14392
+ MAX_CHUNKS_PER_TICK = 10;
14393
+ FETCH_TIMEOUT_MS = 1e4;
14394
+ SKIP_CHECKED_BY = /* @__PURE__ */ new Set(["ignored"]);
14395
+ shipperStarted = false;
14396
+ }
14397
+ });
14398
+
14399
+ // src/daemon/dlp-scanner.ts
14400
+ import fs25 from "fs";
14401
+ import path27 from "path";
14402
+ import os23 from "os";
14263
14403
  function loadIndex() {
14264
14404
  try {
14265
- return JSON.parse(fs24.readFileSync(INDEX_FILE, "utf-8"));
14405
+ return JSON.parse(fs25.readFileSync(INDEX_FILE, "utf-8"));
14266
14406
  } catch {
14267
14407
  return {};
14268
14408
  }
14269
14409
  }
14270
14410
  function saveIndex(index) {
14271
14411
  try {
14272
- fs24.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
14412
+ fs25.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
14273
14413
  } catch {
14274
14414
  }
14275
14415
  }
14276
14416
  function appendAuditEntry(entry) {
14277
14417
  try {
14278
- fs24.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
14418
+ fs25.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
14279
14419
  } catch {
14280
14420
  }
14281
14421
  }
14282
14422
  function runDlpScan() {
14283
- if (!fs24.existsSync(PROJECTS_DIR2)) return;
14423
+ if (!fs25.existsSync(PROJECTS_DIR2)) return;
14284
14424
  const index = loadIndex();
14285
14425
  let updated = false;
14286
14426
  let projDirs;
14287
14427
  try {
14288
- projDirs = fs24.readdirSync(PROJECTS_DIR2);
14428
+ projDirs = fs25.readdirSync(PROJECTS_DIR2);
14289
14429
  } catch {
14290
14430
  return;
14291
14431
  }
14292
14432
  for (const proj of projDirs) {
14293
- const projPath = path26.join(PROJECTS_DIR2, proj);
14433
+ const projPath = path27.join(PROJECTS_DIR2, proj);
14294
14434
  try {
14295
- if (!fs24.lstatSync(projPath).isDirectory()) continue;
14296
- const real = fs24.realpathSync(projPath);
14297
- if (!real.startsWith(PROJECTS_DIR2 + path26.sep) && real !== PROJECTS_DIR2) continue;
14435
+ if (!fs25.lstatSync(projPath).isDirectory()) continue;
14436
+ const real = fs25.realpathSync(projPath);
14437
+ if (!real.startsWith(PROJECTS_DIR2 + path27.sep) && real !== PROJECTS_DIR2) continue;
14298
14438
  } catch {
14299
14439
  continue;
14300
14440
  }
14301
14441
  let files;
14302
14442
  try {
14303
- files = fs24.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
14443
+ files = fs25.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
14304
14444
  } catch {
14305
14445
  continue;
14306
14446
  }
14307
14447
  for (const file of files) {
14308
- const filePath = path26.join(projPath, file);
14448
+ const filePath = path27.join(projPath, file);
14309
14449
  const lastOffset = index[filePath] ?? 0;
14310
14450
  let size;
14311
14451
  try {
14312
- size = fs24.statSync(filePath).size;
14452
+ size = fs25.statSync(filePath).size;
14313
14453
  } catch {
14314
14454
  continue;
14315
14455
  }
14316
14456
  if (size <= lastOffset) continue;
14317
14457
  let fd;
14318
14458
  try {
14319
- fd = fs24.openSync(filePath, "r");
14459
+ fd = fs25.openSync(filePath, "r");
14320
14460
  } catch {
14321
14461
  continue;
14322
14462
  }
14323
14463
  try {
14324
14464
  const chunkSize = size - lastOffset;
14325
14465
  const buf = Buffer.alloc(chunkSize);
14326
- fs24.readSync(fd, buf, 0, chunkSize, lastOffset);
14466
+ fs25.readSync(fd, buf, 0, chunkSize, lastOffset);
14327
14467
  const chunk = buf.toString("utf-8");
14328
14468
  for (const line of chunk.split("\n")) {
14329
14469
  if (!line.trim()) continue;
@@ -14343,7 +14483,7 @@ function runDlpScan() {
14343
14483
  if (typeof text !== "string") continue;
14344
14484
  const match = scanText(text);
14345
14485
  if (!match) continue;
14346
- const projLabel = decodeURIComponent(proj).replace(os22.homedir(), "~").slice(0, 40);
14486
+ const projLabel = decodeURIComponent(proj).replace(os23.homedir(), "~").slice(0, 40);
14347
14487
  const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
14348
14488
  appendAuditEntry({
14349
14489
  ts,
@@ -14368,7 +14508,7 @@ Run: node9 report --period 30d`
14368
14508
  updated = true;
14369
14509
  } finally {
14370
14510
  try {
14371
- fs24.closeSync(fd);
14511
+ fs25.closeSync(fd);
14372
14512
  } catch {
14373
14513
  }
14374
14514
  }
@@ -14401,23 +14541,23 @@ var init_dlp_scanner = __esm({
14401
14541
  init_dlp();
14402
14542
  init_native();
14403
14543
  init_state2();
14404
- INDEX_FILE = path26.join(os22.homedir(), ".node9", "dlp-index.json");
14405
- PROJECTS_DIR2 = path26.join(os22.homedir(), ".claude", "projects");
14544
+ INDEX_FILE = path27.join(os23.homedir(), ".node9", "dlp-index.json");
14545
+ PROJECTS_DIR2 = path27.join(os23.homedir(), ".claude", "projects");
14406
14546
  }
14407
14547
  });
14408
14548
 
14409
14549
  // src/daemon/mcp-tools.ts
14410
- import fs25 from "fs";
14411
- import path27 from "path";
14412
- import os23 from "os";
14550
+ import fs26 from "fs";
14551
+ import path28 from "path";
14552
+ import os24 from "os";
14413
14553
  function getMcpToolsFile() {
14414
- return path27.join(os23.homedir(), ".node9", "mcp-tools.json");
14554
+ return path28.join(os24.homedir(), ".node9", "mcp-tools.json");
14415
14555
  }
14416
14556
  function readMcpToolsConfig() {
14417
14557
  try {
14418
14558
  const file = getMcpToolsFile();
14419
- if (!fs25.existsSync(file)) return {};
14420
- const raw = fs25.readFileSync(file, "utf-8");
14559
+ if (!fs26.existsSync(file)) return {};
14560
+ const raw = fs26.readFileSync(file, "utf-8");
14421
14561
  return JSON.parse(raw);
14422
14562
  } catch {
14423
14563
  return {};
@@ -14426,11 +14566,11 @@ function readMcpToolsConfig() {
14426
14566
  function writeMcpToolsConfig(config) {
14427
14567
  try {
14428
14568
  const file = getMcpToolsFile();
14429
- const dir = path27.dirname(file);
14430
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
14431
- const tmpPath = `${file}.${os23.hostname()}.${process.pid}.tmp`;
14432
- fs25.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
14433
- fs25.renameSync(tmpPath, file);
14569
+ const dir = path28.dirname(file);
14570
+ if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
14571
+ const tmpPath = `${file}.${os24.hostname()}.${process.pid}.tmp`;
14572
+ fs26.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
14573
+ fs26.renameSync(tmpPath, file);
14434
14574
  } catch (e) {
14435
14575
  console.error("Failed to write mcp-tools.json", e);
14436
14576
  }
@@ -14477,9 +14617,9 @@ var init_mcp_tools = __esm({
14477
14617
 
14478
14618
  // src/daemon/server.ts
14479
14619
  import http from "http";
14480
- import fs26 from "fs";
14481
- import path28 from "path";
14482
- import os24 from "os";
14620
+ import fs27 from "fs";
14621
+ import path29 from "path";
14622
+ import os25 from "os";
14483
14623
  import { randomUUID as randomUUID4 } from "crypto";
14484
14624
  import { spawnSync } from "child_process";
14485
14625
  import chalk6 from "chalk";
@@ -14487,6 +14627,7 @@ function startDaemon() {
14487
14627
  startCostSync();
14488
14628
  startCloudSync();
14489
14629
  startForensicBroadcast();
14630
+ startAuditShipper();
14490
14631
  startDlpScanner();
14491
14632
  loadInsightCounts();
14492
14633
  const internalToken = randomUUID4();
@@ -14500,7 +14641,7 @@ function startDaemon() {
14500
14641
  idleTimer = setTimeout(() => {
14501
14642
  if (autoStarted) {
14502
14643
  try {
14503
- fs26.unlinkSync(DAEMON_PID_FILE);
14644
+ fs27.unlinkSync(DAEMON_PID_FILE);
14504
14645
  } catch {
14505
14646
  }
14506
14647
  }
@@ -14645,7 +14786,7 @@ data: ${JSON.stringify(item.data)}
14645
14786
  mcpServer: entry.mcpServer
14646
14787
  });
14647
14788
  }
14648
- const projectCwd = typeof cwd === "string" && path28.isAbsolute(cwd) ? cwd : void 0;
14789
+ const projectCwd = typeof cwd === "string" && path29.isAbsolute(cwd) ? cwd : void 0;
14649
14790
  const projectConfig = getConfig(projectCwd);
14650
14791
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
14651
14792
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -14937,8 +15078,8 @@ data: ${JSON.stringify(item.data)}
14937
15078
  if (!validToken(req)) return res.writeHead(403).end();
14938
15079
  const periodParam = reqUrl.searchParams.get("period") || "7d";
14939
15080
  const period = ["today", "7d", "30d", "month"].includes(periodParam) ? periodParam : "7d";
14940
- const logPath = path28.join(os24.homedir(), ".node9", "audit.log");
14941
- if (!fs26.existsSync(logPath)) {
15081
+ const logPath = path29.join(os25.homedir(), ".node9", "audit.log");
15082
+ if (!fs27.existsSync(logPath)) {
14942
15083
  res.writeHead(200, { "Content-Type": "application/json" });
14943
15084
  return res.end(
14944
15085
  JSON.stringify({
@@ -14951,7 +15092,7 @@ data: ${JSON.stringify(item.data)}
14951
15092
  );
14952
15093
  }
14953
15094
  try {
14954
- const raw = fs26.readFileSync(logPath, "utf-8");
15095
+ const raw = fs27.readFileSync(logPath, "utf-8");
14955
15096
  const allEntries = raw.split("\n").flatMap((line) => {
14956
15097
  if (!line.trim()) return [];
14957
15098
  try {
@@ -15274,14 +15415,14 @@ data: ${JSON.stringify(item.data)}
15274
15415
  server.on("error", (e) => {
15275
15416
  if (e.code === "EADDRINUSE") {
15276
15417
  try {
15277
- if (fs26.existsSync(DAEMON_PID_FILE)) {
15278
- const { pid } = JSON.parse(fs26.readFileSync(DAEMON_PID_FILE, "utf-8"));
15418
+ if (fs27.existsSync(DAEMON_PID_FILE)) {
15419
+ const { pid } = JSON.parse(fs27.readFileSync(DAEMON_PID_FILE, "utf-8"));
15279
15420
  process.kill(pid, 0);
15280
15421
  return process.exit(0);
15281
15422
  }
15282
15423
  } catch {
15283
15424
  try {
15284
- fs26.unlinkSync(DAEMON_PID_FILE);
15425
+ fs27.unlinkSync(DAEMON_PID_FILE);
15285
15426
  } catch {
15286
15427
  }
15287
15428
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -15363,21 +15504,22 @@ var init_server = __esm({
15363
15504
  init_state();
15364
15505
  init_costSync();
15365
15506
  init_sync();
15507
+ init_audit_shipper();
15366
15508
  init_dlp_scanner();
15367
15509
  init_mcp_tools();
15368
15510
  }
15369
15511
  });
15370
15512
 
15371
15513
  // src/daemon/service.ts
15372
- import fs27 from "fs";
15373
- import path29 from "path";
15374
- import os25 from "os";
15514
+ import fs28 from "fs";
15515
+ import path30 from "path";
15516
+ import os26 from "os";
15375
15517
  import { spawnSync as spawnSync2, execFileSync } from "child_process";
15376
15518
  function resolveNode9Binary() {
15377
15519
  try {
15378
15520
  const script = process.argv[1];
15379
- if (typeof script === "string" && path29.isAbsolute(script) && fs27.existsSync(script)) {
15380
- return fs27.realpathSync(script);
15521
+ if (typeof script === "string" && path30.isAbsolute(script) && fs28.existsSync(script)) {
15522
+ return fs28.realpathSync(script);
15381
15523
  }
15382
15524
  } catch {
15383
15525
  }
@@ -15395,11 +15537,11 @@ function xmlEscape(s) {
15395
15537
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
15396
15538
  }
15397
15539
  function launchdPlist(binaryPath) {
15398
- const logDir = path29.join(os25.homedir(), ".node9");
15540
+ const logDir = path30.join(os26.homedir(), ".node9");
15399
15541
  const nodePath = xmlEscape(process.execPath);
15400
15542
  const scriptPath = xmlEscape(binaryPath);
15401
- const outLog = xmlEscape(path29.join(logDir, "daemon.log"));
15402
- const errLog = xmlEscape(path29.join(logDir, "daemon-error.log"));
15543
+ const outLog = xmlEscape(path30.join(logDir, "daemon.log"));
15544
+ const errLog = xmlEscape(path30.join(logDir, "daemon-error.log"));
15403
15545
  return `<?xml version="1.0" encoding="UTF-8"?>
15404
15546
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
15405
15547
  <plist version="1.0">
@@ -15432,9 +15574,9 @@ function launchdPlist(binaryPath) {
15432
15574
  `;
15433
15575
  }
15434
15576
  function installLaunchd(binaryPath) {
15435
- const dir = path29.dirname(LAUNCHD_PLIST);
15436
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
15437
- fs27.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
15577
+ const dir = path30.dirname(LAUNCHD_PLIST);
15578
+ if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
15579
+ fs28.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
15438
15580
  spawnSync2("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
15439
15581
  const r = spawnSync2("launchctl", ["load", "-w", LAUNCHD_PLIST], {
15440
15582
  encoding: "utf8",
@@ -15445,13 +15587,13 @@ function installLaunchd(binaryPath) {
15445
15587
  }
15446
15588
  }
15447
15589
  function uninstallLaunchd() {
15448
- if (fs27.existsSync(LAUNCHD_PLIST)) {
15590
+ if (fs28.existsSync(LAUNCHD_PLIST)) {
15449
15591
  spawnSync2("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
15450
- fs27.unlinkSync(LAUNCHD_PLIST);
15592
+ fs28.unlinkSync(LAUNCHD_PLIST);
15451
15593
  }
15452
15594
  }
15453
15595
  function isLaunchdInstalled() {
15454
- return fs27.existsSync(LAUNCHD_PLIST);
15596
+ return fs28.existsSync(LAUNCHD_PLIST);
15455
15597
  }
15456
15598
  function systemdUnit(binaryPath) {
15457
15599
  return `[Unit]
@@ -15470,12 +15612,12 @@ WantedBy=default.target
15470
15612
  `;
15471
15613
  }
15472
15614
  function installSystemd(binaryPath) {
15473
- if (!fs27.existsSync(SYSTEMD_UNIT_DIR)) {
15474
- fs27.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
15615
+ if (!fs28.existsSync(SYSTEMD_UNIT_DIR)) {
15616
+ fs28.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
15475
15617
  }
15476
- fs27.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
15618
+ fs28.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
15477
15619
  try {
15478
- execFileSync("loginctl", ["enable-linger", os25.userInfo().username], { timeout: 3e3 });
15620
+ execFileSync("loginctl", ["enable-linger", os26.userInfo().username], { timeout: 3e3 });
15479
15621
  } catch {
15480
15622
  }
15481
15623
  const reload = spawnSync2("systemctl", ["--user", "daemon-reload"], {
@@ -15495,23 +15637,23 @@ function installSystemd(binaryPath) {
15495
15637
  }
15496
15638
  }
15497
15639
  function uninstallSystemd() {
15498
- if (fs27.existsSync(SYSTEMD_UNIT)) {
15640
+ if (fs28.existsSync(SYSTEMD_UNIT)) {
15499
15641
  spawnSync2("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
15500
15642
  encoding: "utf8",
15501
15643
  timeout: 5e3
15502
15644
  });
15503
15645
  spawnSync2("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
15504
- fs27.unlinkSync(SYSTEMD_UNIT);
15646
+ fs28.unlinkSync(SYSTEMD_UNIT);
15505
15647
  }
15506
15648
  }
15507
15649
  function isSystemdInstalled() {
15508
- return fs27.existsSync(SYSTEMD_UNIT);
15650
+ return fs28.existsSync(SYSTEMD_UNIT);
15509
15651
  }
15510
15652
  function stopRunningDaemon() {
15511
- const pidFile = path29.join(os25.homedir(), ".node9", "daemon.pid");
15512
- if (!fs27.existsSync(pidFile)) return;
15653
+ const pidFile = path30.join(os26.homedir(), ".node9", "daemon.pid");
15654
+ if (!fs28.existsSync(pidFile)) return;
15513
15655
  try {
15514
- const data = JSON.parse(fs27.readFileSync(pidFile, "utf-8"));
15656
+ const data = JSON.parse(fs28.readFileSync(pidFile, "utf-8"));
15515
15657
  const pid = data.pid;
15516
15658
  const MAX_PID2 = 4194304;
15517
15659
  if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
@@ -15531,7 +15673,7 @@ function stopRunningDaemon() {
15531
15673
  }
15532
15674
  }
15533
15675
  try {
15534
- fs27.unlinkSync(pidFile);
15676
+ fs28.unlinkSync(pidFile);
15535
15677
  } catch {
15536
15678
  }
15537
15679
  } catch {
@@ -15606,19 +15748,19 @@ var init_service = __esm({
15606
15748
  "src/daemon/service.ts"() {
15607
15749
  "use strict";
15608
15750
  LAUNCHD_LABEL = "ai.node9.daemon";
15609
- LAUNCHD_PLIST = path29.join(os25.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
15610
- SYSTEMD_UNIT_DIR = path29.join(os25.homedir(), ".config", "systemd", "user");
15611
- SYSTEMD_UNIT = path29.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
15751
+ LAUNCHD_PLIST = path30.join(os26.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
15752
+ SYSTEMD_UNIT_DIR = path30.join(os26.homedir(), ".config", "systemd", "user");
15753
+ SYSTEMD_UNIT = path30.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
15612
15754
  }
15613
15755
  });
15614
15756
 
15615
15757
  // src/daemon/index.ts
15616
- import fs28 from "fs";
15758
+ import fs29 from "fs";
15617
15759
  import chalk7 from "chalk";
15618
15760
  function stopDaemon() {
15619
- if (!fs28.existsSync(DAEMON_PID_FILE)) return console.log(chalk7.yellow("Not running."));
15761
+ if (!fs29.existsSync(DAEMON_PID_FILE)) return console.log(chalk7.yellow("Not running."));
15620
15762
  try {
15621
- const data = JSON.parse(fs28.readFileSync(DAEMON_PID_FILE, "utf-8"));
15763
+ const data = JSON.parse(fs29.readFileSync(DAEMON_PID_FILE, "utf-8"));
15622
15764
  const pid = data.pid;
15623
15765
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
15624
15766
  console.log(chalk7.gray("Cleaned up invalid PID file."));
@@ -15630,7 +15772,7 @@ function stopDaemon() {
15630
15772
  console.log(chalk7.gray("Cleaned up stale PID file."));
15631
15773
  } finally {
15632
15774
  try {
15633
- fs28.unlinkSync(DAEMON_PID_FILE);
15775
+ fs29.unlinkSync(DAEMON_PID_FILE);
15634
15776
  } catch {
15635
15777
  }
15636
15778
  }
@@ -15639,9 +15781,9 @@ function daemonStatus() {
15639
15781
  const serviceInstalled = isDaemonServiceInstalled();
15640
15782
  const serviceLabel = serviceInstalled ? chalk7.green("installed (starts on login)") : chalk7.yellow("not installed \u2014 run: node9 daemon install");
15641
15783
  let processStatus;
15642
- if (fs28.existsSync(DAEMON_PID_FILE)) {
15784
+ if (fs29.existsSync(DAEMON_PID_FILE)) {
15643
15785
  try {
15644
- const data = JSON.parse(fs28.readFileSync(DAEMON_PID_FILE, "utf-8"));
15786
+ const data = JSON.parse(fs29.readFileSync(DAEMON_PID_FILE, "utf-8"));
15645
15787
  const pid = data.pid;
15646
15788
  const port = data.port;
15647
15789
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
@@ -15686,9 +15828,9 @@ __export(tail_exports, {
15686
15828
  });
15687
15829
  import http2 from "http";
15688
15830
  import chalk29 from "chalk";
15689
- import fs46 from "fs";
15690
- import os41 from "os";
15691
- import path47 from "path";
15831
+ import fs47 from "fs";
15832
+ import os42 from "os";
15833
+ import path48 from "path";
15692
15834
  import readline6 from "readline";
15693
15835
  import { spawn as spawn8 } from "child_process";
15694
15836
  function shortenPathSummary(s) {
@@ -15712,20 +15854,20 @@ function getModelContextLimit(model) {
15712
15854
  return 2e5;
15713
15855
  }
15714
15856
  function readSessionUsage() {
15715
- const projectsDir = path47.join(os41.homedir(), ".claude", "projects");
15716
- if (!fs46.existsSync(projectsDir)) return null;
15857
+ const projectsDir = path48.join(os42.homedir(), ".claude", "projects");
15858
+ if (!fs47.existsSync(projectsDir)) return null;
15717
15859
  let latestFile = null;
15718
15860
  let latestMtime = 0;
15719
15861
  try {
15720
- for (const dir of fs46.readdirSync(projectsDir)) {
15721
- const dirPath = path47.join(projectsDir, dir);
15862
+ for (const dir of fs47.readdirSync(projectsDir)) {
15863
+ const dirPath = path48.join(projectsDir, dir);
15722
15864
  try {
15723
- if (!fs46.statSync(dirPath).isDirectory()) continue;
15724
- for (const file of fs46.readdirSync(dirPath)) {
15865
+ if (!fs47.statSync(dirPath).isDirectory()) continue;
15866
+ for (const file of fs47.readdirSync(dirPath)) {
15725
15867
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
15726
- const filePath = path47.join(dirPath, file);
15868
+ const filePath = path48.join(dirPath, file);
15727
15869
  try {
15728
- const mtime = fs46.statSync(filePath).mtimeMs;
15870
+ const mtime = fs47.statSync(filePath).mtimeMs;
15729
15871
  if (mtime > latestMtime) {
15730
15872
  latestMtime = mtime;
15731
15873
  latestFile = filePath;
@@ -15740,7 +15882,7 @@ function readSessionUsage() {
15740
15882
  }
15741
15883
  if (!latestFile) return null;
15742
15884
  try {
15743
- const lines = fs46.readFileSync(latestFile, "utf-8").split("\n");
15885
+ const lines = fs47.readFileSync(latestFile, "utf-8").split("\n");
15744
15886
  let lastModel = "";
15745
15887
  let lastInput = 0;
15746
15888
  let lastOutput = 0;
@@ -15801,7 +15943,7 @@ function formatBase(activity) {
15801
15943
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
15802
15944
  const icon = getIcon(activity.tool);
15803
15945
  const toolName = activity.tool.slice(0, 16).padEnd(16);
15804
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os41.homedir(), "~");
15946
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os42.homedir(), "~");
15805
15947
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
15806
15948
  return `${chalk29.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk29.white.bold(toolName)} ${chalk29.dim(argsPreview)}`;
15807
15949
  }
@@ -15840,9 +15982,9 @@ function renderPending(activity) {
15840
15982
  }
15841
15983
  async function ensureDaemon() {
15842
15984
  let pidPort = null;
15843
- if (fs46.existsSync(PID_FILE)) {
15985
+ if (fs47.existsSync(PID_FILE)) {
15844
15986
  try {
15845
- const { port } = JSON.parse(fs46.readFileSync(PID_FILE, "utf-8"));
15987
+ const { port } = JSON.parse(fs47.readFileSync(PID_FILE, "utf-8"));
15846
15988
  pidPort = port;
15847
15989
  } catch {
15848
15990
  console.error(chalk29.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
@@ -15998,9 +16140,9 @@ function buildRecoveryCardLines(req) {
15998
16140
  ];
15999
16141
  }
16000
16142
  function readApproversFromDisk() {
16001
- const configPath = path47.join(os41.homedir(), ".node9", "config.json");
16143
+ const configPath = path48.join(os42.homedir(), ".node9", "config.json");
16002
16144
  try {
16003
- const raw = JSON.parse(fs46.readFileSync(configPath, "utf-8"));
16145
+ const raw = JSON.parse(fs47.readFileSync(configPath, "utf-8"));
16004
16146
  const settings = raw.settings ?? {};
16005
16147
  return settings.approvers ?? {};
16006
16148
  } catch {
@@ -16016,15 +16158,15 @@ function approverStatusLine() {
16016
16158
  return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
16017
16159
  }
16018
16160
  function toggleApprover(channel) {
16019
- const configPath = path47.join(os41.homedir(), ".node9", "config.json");
16161
+ const configPath = path48.join(os42.homedir(), ".node9", "config.json");
16020
16162
  try {
16021
- const raw = JSON.parse(fs46.readFileSync(configPath, "utf-8"));
16163
+ const raw = JSON.parse(fs47.readFileSync(configPath, "utf-8"));
16022
16164
  const settings = raw.settings ?? {};
16023
16165
  const approvers = settings.approvers ?? {};
16024
16166
  approvers[channel] = approvers[channel] === false;
16025
16167
  settings.approvers = approvers;
16026
16168
  raw.settings = settings;
16027
- fs46.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
16169
+ fs47.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
16028
16170
  } catch (err2) {
16029
16171
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
16030
16172
  `);
@@ -16196,8 +16338,8 @@ async function startTail(options = {}) {
16196
16338
  }
16197
16339
  postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
16198
16340
  try {
16199
- fs46.appendFileSync(
16200
- path47.join(os41.homedir(), ".node9", "hook-debug.log"),
16341
+ fs47.appendFileSync(
16342
+ path48.join(os42.homedir(), ".node9", "hook-debug.log"),
16201
16343
  `[tail] POST /decision failed: ${String(err2)}
16202
16344
  `
16203
16345
  );
@@ -16261,9 +16403,9 @@ async function startTail(options = {}) {
16261
16403
  };
16262
16404
  process.stdin.on("keypress", onKeypress);
16263
16405
  }
16264
- const auditLog = path47.join(os41.homedir(), ".node9", "audit.log");
16406
+ const auditLog = path48.join(os42.homedir(), ".node9", "audit.log");
16265
16407
  try {
16266
- const unackedDlp = fs46.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
16408
+ const unackedDlp = fs47.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
16267
16409
  if (unackedDlp > 0) {
16268
16410
  console.log("");
16269
16411
  console.log(
@@ -16303,7 +16445,7 @@ async function startTail(options = {}) {
16303
16445
  if (stallWarned) return;
16304
16446
  if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
16305
16447
  try {
16306
- const auditMtime = fs46.statSync(auditLog).mtimeMs;
16448
+ const auditMtime = fs47.statSync(auditLog).mtimeMs;
16307
16449
  if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
16308
16450
  console.log("");
16309
16451
  console.log(
@@ -16494,7 +16636,7 @@ var init_tail = __esm({
16494
16636
  "use strict";
16495
16637
  init_daemon2();
16496
16638
  init_daemon();
16497
- PID_FILE = path47.join(os41.homedir(), ".node9", "daemon.pid");
16639
+ PID_FILE = path48.join(os42.homedir(), ".node9", "daemon.pid");
16498
16640
  ICONS = {
16499
16641
  bash: "\u{1F4BB}",
16500
16642
  shell: "\u{1F4BB}",
@@ -16542,9 +16684,9 @@ __export(hud_exports, {
16542
16684
  main: () => main,
16543
16685
  renderEnvironmentLine: () => renderEnvironmentLine
16544
16686
  });
16545
- import fs47 from "fs";
16546
- import path48 from "path";
16547
- import os42 from "os";
16687
+ import fs48 from "fs";
16688
+ import path49 from "path";
16689
+ import os43 from "os";
16548
16690
  import http3 from "http";
16549
16691
  async function readStdin() {
16550
16692
  const chunks = [];
@@ -16620,9 +16762,9 @@ function formatTimeLeft(resetsAt) {
16620
16762
  return ` (${m}m left)`;
16621
16763
  }
16622
16764
  function safeReadJson(filePath) {
16623
- if (!fs47.existsSync(filePath)) return null;
16765
+ if (!fs48.existsSync(filePath)) return null;
16624
16766
  try {
16625
- return JSON.parse(fs47.readFileSync(filePath, "utf-8"));
16767
+ return JSON.parse(fs48.readFileSync(filePath, "utf-8"));
16626
16768
  } catch {
16627
16769
  return null;
16628
16770
  }
@@ -16643,12 +16785,12 @@ function countHooksInFile(filePath) {
16643
16785
  return Object.keys(cfg.hooks).length;
16644
16786
  }
16645
16787
  function countRulesInDir(rulesDir) {
16646
- if (!fs47.existsSync(rulesDir)) return 0;
16788
+ if (!fs48.existsSync(rulesDir)) return 0;
16647
16789
  let count = 0;
16648
16790
  try {
16649
- for (const entry of fs47.readdirSync(rulesDir, { withFileTypes: true })) {
16791
+ for (const entry of fs48.readdirSync(rulesDir, { withFileTypes: true })) {
16650
16792
  if (entry.isDirectory()) {
16651
- count += countRulesInDir(path48.join(rulesDir, entry.name));
16793
+ count += countRulesInDir(path49.join(rulesDir, entry.name));
16652
16794
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
16653
16795
  count++;
16654
16796
  }
@@ -16659,46 +16801,46 @@ function countRulesInDir(rulesDir) {
16659
16801
  }
16660
16802
  function isSamePath(a, b) {
16661
16803
  try {
16662
- return path48.resolve(a) === path48.resolve(b);
16804
+ return path49.resolve(a) === path49.resolve(b);
16663
16805
  } catch {
16664
16806
  return false;
16665
16807
  }
16666
16808
  }
16667
16809
  function countConfigs(cwd) {
16668
- const homeDir2 = os42.homedir();
16669
- const claudeDir = path48.join(homeDir2, ".claude");
16810
+ const homeDir2 = os43.homedir();
16811
+ const claudeDir = path49.join(homeDir2, ".claude");
16670
16812
  let claudeMdCount = 0;
16671
16813
  let rulesCount = 0;
16672
16814
  let hooksCount = 0;
16673
16815
  const userMcpServers = /* @__PURE__ */ new Set();
16674
16816
  const projectMcpServers = /* @__PURE__ */ new Set();
16675
- if (fs47.existsSync(path48.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
16676
- rulesCount += countRulesInDir(path48.join(claudeDir, "rules"));
16677
- const userSettings = path48.join(claudeDir, "settings.json");
16817
+ if (fs48.existsSync(path49.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
16818
+ rulesCount += countRulesInDir(path49.join(claudeDir, "rules"));
16819
+ const userSettings = path49.join(claudeDir, "settings.json");
16678
16820
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
16679
16821
  hooksCount += countHooksInFile(userSettings);
16680
- const userClaudeJson = path48.join(homeDir2, ".claude.json");
16822
+ const userClaudeJson = path49.join(homeDir2, ".claude.json");
16681
16823
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
16682
16824
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
16683
16825
  userMcpServers.delete(name);
16684
16826
  }
16685
16827
  if (cwd) {
16686
- if (fs47.existsSync(path48.join(cwd, "CLAUDE.md"))) claudeMdCount++;
16687
- if (fs47.existsSync(path48.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
16688
- const projectClaudeDir = path48.join(cwd, ".claude");
16828
+ if (fs48.existsSync(path49.join(cwd, "CLAUDE.md"))) claudeMdCount++;
16829
+ if (fs48.existsSync(path49.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
16830
+ const projectClaudeDir = path49.join(cwd, ".claude");
16689
16831
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
16690
16832
  if (!overlapsUserScope) {
16691
- if (fs47.existsSync(path48.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
16692
- rulesCount += countRulesInDir(path48.join(projectClaudeDir, "rules"));
16693
- const projSettings = path48.join(projectClaudeDir, "settings.json");
16833
+ if (fs48.existsSync(path49.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
16834
+ rulesCount += countRulesInDir(path49.join(projectClaudeDir, "rules"));
16835
+ const projSettings = path49.join(projectClaudeDir, "settings.json");
16694
16836
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
16695
16837
  hooksCount += countHooksInFile(projSettings);
16696
16838
  }
16697
- if (fs47.existsSync(path48.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
16698
- const localSettings = path48.join(projectClaudeDir, "settings.local.json");
16839
+ if (fs48.existsSync(path49.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
16840
+ const localSettings = path49.join(projectClaudeDir, "settings.local.json");
16699
16841
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
16700
16842
  hooksCount += countHooksInFile(localSettings);
16701
- const mcpJsonServers = getMcpServerNames(path48.join(cwd, ".mcp.json"));
16843
+ const mcpJsonServers = getMcpServerNames(path49.join(cwd, ".mcp.json"));
16702
16844
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
16703
16845
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
16704
16846
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -16731,12 +16873,12 @@ function readActiveShieldsHud() {
16731
16873
  return shieldsCache.value;
16732
16874
  }
16733
16875
  try {
16734
- const shieldsPath = path48.join(os42.homedir(), ".node9", "shields.json");
16735
- if (!fs47.existsSync(shieldsPath)) {
16876
+ const shieldsPath = path49.join(os43.homedir(), ".node9", "shields.json");
16877
+ if (!fs48.existsSync(shieldsPath)) {
16736
16878
  shieldsCache = { value: [], ts: now };
16737
16879
  return [];
16738
16880
  }
16739
- const parsed = JSON.parse(fs47.readFileSync(shieldsPath, "utf-8"));
16881
+ const parsed = JSON.parse(fs48.readFileSync(shieldsPath, "utf-8"));
16740
16882
  if (!Array.isArray(parsed.active)) {
16741
16883
  shieldsCache = { value: [], ts: now };
16742
16884
  return [];
@@ -16838,17 +16980,17 @@ function renderContextLine(stdin) {
16838
16980
  async function main() {
16839
16981
  try {
16840
16982
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
16841
- if (fs47.existsSync(path48.join(os42.homedir(), ".node9", "hud-debug"))) {
16983
+ if (fs48.existsSync(path49.join(os43.homedir(), ".node9", "hud-debug"))) {
16842
16984
  try {
16843
- const logPath = path48.join(os42.homedir(), ".node9", "hud-debug.log");
16985
+ const logPath = path49.join(os43.homedir(), ".node9", "hud-debug.log");
16844
16986
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
16845
16987
  let size = 0;
16846
16988
  try {
16847
- size = fs47.statSync(logPath).size;
16989
+ size = fs48.statSync(logPath).size;
16848
16990
  } catch {
16849
16991
  }
16850
16992
  if (size < MAX_LOG_SIZE) {
16851
- fs47.appendFileSync(
16993
+ fs48.appendFileSync(
16852
16994
  logPath,
16853
16995
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
16854
16996
  );
@@ -16869,11 +17011,11 @@ async function main() {
16869
17011
  try {
16870
17012
  const cwd = stdin.cwd ?? process.cwd();
16871
17013
  for (const configPath of [
16872
- path48.join(cwd, "node9.config.json"),
16873
- path48.join(os42.homedir(), ".node9", "config.json")
17014
+ path49.join(cwd, "node9.config.json"),
17015
+ path49.join(os43.homedir(), ".node9", "config.json")
16874
17016
  ]) {
16875
- if (!fs47.existsSync(configPath)) continue;
16876
- const cfg = JSON.parse(fs47.readFileSync(configPath, "utf-8"));
17017
+ if (!fs48.existsSync(configPath)) continue;
17018
+ const cfg = JSON.parse(fs48.readFileSync(configPath, "utf-8"));
16877
17019
  const hud = cfg.settings?.hud;
16878
17020
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
16879
17021
  }
@@ -16920,9 +17062,9 @@ init_setup();
16920
17062
  init_daemon2();
16921
17063
  import { Command } from "commander";
16922
17064
  import chalk30 from "chalk";
16923
- import fs48 from "fs";
16924
- import path49 from "path";
16925
- import os43 from "os";
17065
+ import fs49 from "fs";
17066
+ import path50 from "path";
17067
+ import os44 from "os";
16926
17068
  import { confirm as confirm2 } from "@inquirer/prompts";
16927
17069
 
16928
17070
  // src/utils/duration.ts
@@ -17105,17 +17247,17 @@ async function runProxy(targetCommand) {
17105
17247
  // src/cli/daemon-starter.ts
17106
17248
  init_daemon();
17107
17249
  import { spawn as spawn3 } from "child_process";
17108
- import path30 from "path";
17109
- import fs29 from "fs";
17250
+ import path31 from "path";
17251
+ import fs30 from "fs";
17110
17252
  function isTestingMode() {
17111
17253
  return /^(1|true|yes)$/i.test(process.env.NODE9_TESTING ?? "");
17112
17254
  }
17113
17255
  async function autoStartDaemonAndWait() {
17114
17256
  if (isTestingMode()) return false;
17115
- if (!path30.isAbsolute(process.argv[1])) return false;
17257
+ if (!path31.isAbsolute(process.argv[1])) return false;
17116
17258
  let resolvedArgv1;
17117
17259
  try {
17118
- resolvedArgv1 = fs29.realpathSync(process.argv[1]);
17260
+ resolvedArgv1 = fs30.realpathSync(process.argv[1]);
17119
17261
  } catch {
17120
17262
  return false;
17121
17263
  }
@@ -17146,19 +17288,19 @@ init_daemon();
17146
17288
  init_config();
17147
17289
  init_policy();
17148
17290
  import chalk9 from "chalk";
17149
- import fs32 from "fs";
17291
+ import fs33 from "fs";
17150
17292
  import { spawn as spawn5 } from "child_process";
17151
- import path33 from "path";
17152
- import os28 from "os";
17293
+ import path34 from "path";
17294
+ import os29 from "os";
17153
17295
 
17154
17296
  // src/undo.ts
17155
17297
  import { spawnSync as spawnSync3, spawn as spawn4 } from "child_process";
17156
- import crypto4 from "crypto";
17157
- import fs30 from "fs";
17298
+ import crypto6 from "crypto";
17299
+ import fs31 from "fs";
17158
17300
  import net3 from "net";
17159
- import path31 from "path";
17160
- import os26 from "os";
17161
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path31.join(os26.tmpdir(), "node9-activity.sock");
17301
+ import path32 from "path";
17302
+ import os27 from "os";
17303
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path32.join(os27.tmpdir(), "node9-activity.sock");
17162
17304
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
17163
17305
  try {
17164
17306
  const payload = JSON.stringify({
@@ -17178,22 +17320,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
17178
17320
  } catch {
17179
17321
  }
17180
17322
  }
17181
- var SNAPSHOT_STACK_PATH = path31.join(os26.homedir(), ".node9", "snapshots.json");
17182
- var UNDO_LATEST_PATH = path31.join(os26.homedir(), ".node9", "undo_latest.txt");
17323
+ var SNAPSHOT_STACK_PATH = path32.join(os27.homedir(), ".node9", "snapshots.json");
17324
+ var UNDO_LATEST_PATH = path32.join(os27.homedir(), ".node9", "undo_latest.txt");
17183
17325
  var MAX_SNAPSHOTS = 10;
17184
17326
  var GIT_TIMEOUT = 15e3;
17185
17327
  function readStack() {
17186
17328
  try {
17187
- if (fs30.existsSync(SNAPSHOT_STACK_PATH))
17188
- return JSON.parse(fs30.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
17329
+ if (fs31.existsSync(SNAPSHOT_STACK_PATH))
17330
+ return JSON.parse(fs31.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
17189
17331
  } catch {
17190
17332
  }
17191
17333
  return [];
17192
17334
  }
17193
17335
  function writeStack(stack) {
17194
- const dir = path31.dirname(SNAPSHOT_STACK_PATH);
17195
- if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
17196
- fs30.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
17336
+ const dir = path32.dirname(SNAPSHOT_STACK_PATH);
17337
+ if (!fs31.existsSync(dir)) fs31.mkdirSync(dir, { recursive: true });
17338
+ fs31.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
17197
17339
  }
17198
17340
  function extractFilePath(args) {
17199
17341
  if (!args || typeof args !== "object") return null;
@@ -17213,12 +17355,12 @@ function buildArgsSummary(tool, args) {
17213
17355
  return "";
17214
17356
  }
17215
17357
  function findProjectRoot(filePath) {
17216
- let dir = path31.dirname(filePath);
17358
+ let dir = path32.dirname(filePath);
17217
17359
  while (true) {
17218
- if (fs30.existsSync(path31.join(dir, ".git")) || fs30.existsSync(path31.join(dir, "package.json"))) {
17360
+ if (fs31.existsSync(path32.join(dir, ".git")) || fs31.existsSync(path32.join(dir, "package.json"))) {
17219
17361
  return dir;
17220
17362
  }
17221
- const parent = path31.dirname(dir);
17363
+ const parent = path32.dirname(dir);
17222
17364
  if (parent === dir) return process.cwd();
17223
17365
  dir = parent;
17224
17366
  }
@@ -17226,7 +17368,7 @@ function findProjectRoot(filePath) {
17226
17368
  function normalizeCwdForHash(cwd) {
17227
17369
  let normalized;
17228
17370
  try {
17229
- normalized = fs30.realpathSync(cwd);
17371
+ normalized = fs31.realpathSync(cwd);
17230
17372
  } catch {
17231
17373
  normalized = cwd;
17232
17374
  }
@@ -17235,17 +17377,17 @@ function normalizeCwdForHash(cwd) {
17235
17377
  return normalized;
17236
17378
  }
17237
17379
  function getShadowRepoDir(cwd) {
17238
- const hash = crypto4.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
17239
- return path31.join(os26.homedir(), ".node9", "snapshots", hash);
17380
+ const hash = crypto6.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
17381
+ return path32.join(os27.homedir(), ".node9", "snapshots", hash);
17240
17382
  }
17241
17383
  function cleanOrphanedIndexFiles(shadowDir) {
17242
17384
  try {
17243
17385
  const cutoff = Date.now() - 6e4;
17244
- for (const f of fs30.readdirSync(shadowDir)) {
17386
+ for (const f of fs31.readdirSync(shadowDir)) {
17245
17387
  if (f.startsWith("index_")) {
17246
- const fp = path31.join(shadowDir, f);
17388
+ const fp = path32.join(shadowDir, f);
17247
17389
  try {
17248
- if (fs30.statSync(fp).mtimeMs < cutoff) fs30.unlinkSync(fp);
17390
+ if (fs31.statSync(fp).mtimeMs < cutoff) fs31.unlinkSync(fp);
17249
17391
  } catch {
17250
17392
  }
17251
17393
  }
@@ -17257,7 +17399,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
17257
17399
  const hardcoded = [".git", ".node9"];
17258
17400
  const lines = [...hardcoded, ...ignorePaths].join("\n");
17259
17401
  try {
17260
- fs30.writeFileSync(path31.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
17402
+ fs31.writeFileSync(path32.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
17261
17403
  } catch {
17262
17404
  }
17263
17405
  }
@@ -17270,25 +17412,25 @@ function ensureShadowRepo(shadowDir, cwd) {
17270
17412
  timeout: 3e3
17271
17413
  });
17272
17414
  if (check.status === 0) {
17273
- const ptPath = path31.join(shadowDir, "project-path.txt");
17415
+ const ptPath = path32.join(shadowDir, "project-path.txt");
17274
17416
  try {
17275
- const stored = fs30.readFileSync(ptPath, "utf8").trim();
17417
+ const stored = fs31.readFileSync(ptPath, "utf8").trim();
17276
17418
  if (stored === normalizedCwd) return true;
17277
17419
  if (process.env.NODE9_DEBUG === "1")
17278
17420
  console.error(
17279
17421
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
17280
17422
  );
17281
- fs30.rmSync(shadowDir, { recursive: true, force: true });
17423
+ fs31.rmSync(shadowDir, { recursive: true, force: true });
17282
17424
  } catch {
17283
17425
  try {
17284
- fs30.writeFileSync(ptPath, normalizedCwd, "utf8");
17426
+ fs31.writeFileSync(ptPath, normalizedCwd, "utf8");
17285
17427
  } catch {
17286
17428
  }
17287
17429
  return true;
17288
17430
  }
17289
17431
  }
17290
17432
  try {
17291
- fs30.mkdirSync(shadowDir, { recursive: true });
17433
+ fs31.mkdirSync(shadowDir, { recursive: true });
17292
17434
  } catch {
17293
17435
  }
17294
17436
  const init = spawnSync3("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -17297,7 +17439,7 @@ function ensureShadowRepo(shadowDir, cwd) {
17297
17439
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
17298
17440
  return false;
17299
17441
  }
17300
- const configFile = path31.join(shadowDir, "config");
17442
+ const configFile = path32.join(shadowDir, "config");
17301
17443
  spawnSync3("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
17302
17444
  timeout: 3e3
17303
17445
  });
@@ -17305,7 +17447,7 @@ function ensureShadowRepo(shadowDir, cwd) {
17305
17447
  timeout: 3e3
17306
17448
  });
17307
17449
  try {
17308
- fs30.writeFileSync(path31.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
17450
+ fs31.writeFileSync(path32.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
17309
17451
  } catch {
17310
17452
  }
17311
17453
  return true;
@@ -17328,12 +17470,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
17328
17470
  let indexFile = null;
17329
17471
  try {
17330
17472
  const rawFilePath = extractFilePath(args);
17331
- const absFilePath = rawFilePath && path31.isAbsolute(rawFilePath) ? rawFilePath : null;
17473
+ const absFilePath = rawFilePath && path32.isAbsolute(rawFilePath) ? rawFilePath : null;
17332
17474
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
17333
17475
  const shadowDir = getShadowRepoDir(cwd);
17334
17476
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
17335
17477
  writeShadowExcludes(shadowDir, ignorePaths);
17336
- indexFile = path31.join(shadowDir, `index_${process.pid}_${Date.now()}`);
17478
+ indexFile = path32.join(shadowDir, `index_${process.pid}_${Date.now()}`);
17337
17479
  const shadowEnv = {
17338
17480
  ...process.env,
17339
17481
  GIT_DIR: shadowDir,
@@ -17405,7 +17547,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
17405
17547
  writeStack(stack);
17406
17548
  const entry = stack[stack.length - 1];
17407
17549
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
17408
- fs30.writeFileSync(UNDO_LATEST_PATH, commitHash);
17550
+ fs31.writeFileSync(UNDO_LATEST_PATH, commitHash);
17409
17551
  if (shouldGc) {
17410
17552
  spawn4("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
17411
17553
  }
@@ -17416,7 +17558,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
17416
17558
  } finally {
17417
17559
  if (indexFile) {
17418
17560
  try {
17419
- fs30.unlinkSync(indexFile);
17561
+ fs31.unlinkSync(indexFile);
17420
17562
  } catch {
17421
17563
  }
17422
17564
  }
@@ -17492,9 +17634,9 @@ function applyUndo(hash, cwd) {
17492
17634
  timeout: GIT_TIMEOUT
17493
17635
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
17494
17636
  for (const file of [...tracked, ...untracked]) {
17495
- const fullPath = path31.join(dir, file);
17496
- if (!snapshotFiles.has(file) && fs30.existsSync(fullPath)) {
17497
- fs30.unlinkSync(fullPath);
17637
+ const fullPath = path32.join(dir, file);
17638
+ if (!snapshotFiles.has(file) && fs31.existsSync(fullPath)) {
17639
+ fs31.unlinkSync(fullPath);
17498
17640
  }
17499
17641
  }
17500
17642
  return true;
@@ -17504,17 +17646,17 @@ function applyUndo(hash, cwd) {
17504
17646
  }
17505
17647
 
17506
17648
  // src/skill-pin.ts
17507
- import fs31 from "fs";
17508
- import path32 from "path";
17509
- import os27 from "os";
17510
- import crypto5 from "crypto";
17649
+ import fs32 from "fs";
17650
+ import path33 from "path";
17651
+ import os28 from "os";
17652
+ import crypto7 from "crypto";
17511
17653
  function getPinsFilePath2() {
17512
- return path32.join(os27.homedir(), ".node9", "skill-pins.json");
17654
+ return path33.join(os28.homedir(), ".node9", "skill-pins.json");
17513
17655
  }
17514
17656
  var MAX_FILES = 5e3;
17515
17657
  var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
17516
17658
  function sha256Bytes(buf) {
17517
- return crypto5.createHash("sha256").update(buf).digest("hex");
17659
+ return crypto7.createHash("sha256").update(buf).digest("hex");
17518
17660
  }
17519
17661
  function walkDir(root) {
17520
17662
  const out = [];
@@ -17523,18 +17665,18 @@ function walkDir(root) {
17523
17665
  if (out.length >= MAX_FILES) return;
17524
17666
  let entries;
17525
17667
  try {
17526
- entries = fs31.readdirSync(dir, { withFileTypes: true });
17668
+ entries = fs32.readdirSync(dir, { withFileTypes: true });
17527
17669
  } catch {
17528
17670
  return;
17529
17671
  }
17530
17672
  entries.sort((a, b) => a.name.localeCompare(b.name));
17531
17673
  for (const entry of entries) {
17532
17674
  if (out.length >= MAX_FILES) return;
17533
- const full = path32.join(dir, entry.name);
17534
- const rel = relDir ? path32.posix.join(relDir, entry.name) : entry.name;
17675
+ const full = path33.join(dir, entry.name);
17676
+ const rel = relDir ? path33.posix.join(relDir, entry.name) : entry.name;
17535
17677
  let lst;
17536
17678
  try {
17537
- lst = fs31.lstatSync(full);
17679
+ lst = fs32.lstatSync(full);
17538
17680
  } catch {
17539
17681
  continue;
17540
17682
  }
@@ -17546,7 +17688,7 @@ function walkDir(root) {
17546
17688
  if (!lst.isFile()) continue;
17547
17689
  if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
17548
17690
  try {
17549
- const buf = fs31.readFileSync(full);
17691
+ const buf = fs32.readFileSync(full);
17550
17692
  totalBytes += buf.length;
17551
17693
  out.push({ rel, hash: sha256Bytes(buf) });
17552
17694
  } catch {
@@ -17560,32 +17702,32 @@ function walkDir(root) {
17560
17702
  function hashSkillRoot(absPath) {
17561
17703
  let lst;
17562
17704
  try {
17563
- lst = fs31.lstatSync(absPath);
17705
+ lst = fs32.lstatSync(absPath);
17564
17706
  } catch {
17565
17707
  return { exists: false, contentHash: "", fileCount: 0 };
17566
17708
  }
17567
17709
  if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
17568
17710
  if (lst.isFile()) {
17569
17711
  try {
17570
- return { exists: true, contentHash: sha256Bytes(fs31.readFileSync(absPath)), fileCount: 1 };
17712
+ return { exists: true, contentHash: sha256Bytes(fs32.readFileSync(absPath)), fileCount: 1 };
17571
17713
  } catch {
17572
17714
  return { exists: false, contentHash: "", fileCount: 0 };
17573
17715
  }
17574
17716
  }
17575
17717
  if (lst.isDirectory()) {
17576
17718
  const entries = walkDir(absPath);
17577
- const contentHash = crypto5.createHash("sha256").update(entries.join("\n")).digest("hex");
17719
+ const contentHash = crypto7.createHash("sha256").update(entries.join("\n")).digest("hex");
17578
17720
  return { exists: true, contentHash, fileCount: entries.length };
17579
17721
  }
17580
17722
  return { exists: false, contentHash: "", fileCount: 0 };
17581
17723
  }
17582
17724
  function getRootKey(absPath) {
17583
- return crypto5.createHash("sha256").update(absPath).digest("hex").slice(0, 16);
17725
+ return crypto7.createHash("sha256").update(absPath).digest("hex").slice(0, 16);
17584
17726
  }
17585
17727
  function readSkillPinsSafe() {
17586
17728
  const filePath = getPinsFilePath2();
17587
17729
  try {
17588
- const raw = fs31.readFileSync(filePath, "utf-8");
17730
+ const raw = fs32.readFileSync(filePath, "utf-8");
17589
17731
  if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
17590
17732
  const parsed = JSON.parse(raw);
17591
17733
  if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
@@ -17605,10 +17747,10 @@ function readSkillPins() {
17605
17747
  }
17606
17748
  function writeSkillPins(data) {
17607
17749
  const filePath = getPinsFilePath2();
17608
- fs31.mkdirSync(path32.dirname(filePath), { recursive: true });
17609
- const tmp = `${filePath}.${crypto5.randomBytes(6).toString("hex")}.tmp`;
17610
- fs31.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
17611
- fs31.renameSync(tmp, filePath);
17750
+ fs32.mkdirSync(path33.dirname(filePath), { recursive: true });
17751
+ const tmp = `${filePath}.${crypto7.randomBytes(6).toString("hex")}.tmp`;
17752
+ fs32.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
17753
+ fs32.renameSync(tmp, filePath);
17612
17754
  }
17613
17755
  function removePin2(rootKey) {
17614
17756
  const pins = readSkillPins();
@@ -17652,36 +17794,36 @@ function verifyAndPinRoots(roots) {
17652
17794
  return { kind: "verified" };
17653
17795
  }
17654
17796
  function defaultSkillRoots(_cwd) {
17655
- const marketplaces = path32.join(os27.homedir(), ".claude", "plugins", "marketplaces");
17797
+ const marketplaces = path33.join(os28.homedir(), ".claude", "plugins", "marketplaces");
17656
17798
  const roots = [];
17657
17799
  let registries;
17658
17800
  try {
17659
- registries = fs31.readdirSync(marketplaces, { withFileTypes: true });
17801
+ registries = fs32.readdirSync(marketplaces, { withFileTypes: true });
17660
17802
  } catch {
17661
17803
  return [];
17662
17804
  }
17663
17805
  for (const registry of registries) {
17664
17806
  if (!registry.isDirectory()) continue;
17665
- const pluginsDir = path32.join(marketplaces, registry.name, "plugins");
17807
+ const pluginsDir = path33.join(marketplaces, registry.name, "plugins");
17666
17808
  let plugins;
17667
17809
  try {
17668
- plugins = fs31.readdirSync(pluginsDir, { withFileTypes: true });
17810
+ plugins = fs32.readdirSync(pluginsDir, { withFileTypes: true });
17669
17811
  } catch {
17670
17812
  continue;
17671
17813
  }
17672
17814
  for (const plugin of plugins) {
17673
17815
  if (!plugin.isDirectory()) continue;
17674
- roots.push(path32.join(pluginsDir, plugin.name));
17816
+ roots.push(path33.join(pluginsDir, plugin.name));
17675
17817
  }
17676
17818
  }
17677
17819
  return roots;
17678
17820
  }
17679
17821
  function resolveUserSkillRoot(entry, cwd) {
17680
17822
  if (!entry) return null;
17681
- if (entry.startsWith("~/") || entry === "~") return path32.join(os27.homedir(), entry.slice(1));
17682
- if (path32.isAbsolute(entry)) return entry;
17683
- if (!cwd || !path32.isAbsolute(cwd)) return null;
17684
- return path32.join(cwd, entry);
17823
+ if (entry.startsWith("~/") || entry === "~") return path33.join(os28.homedir(), entry.slice(1));
17824
+ if (path33.isAbsolute(entry)) return entry;
17825
+ if (!cwd || !path33.isAbsolute(cwd)) return null;
17826
+ return path33.join(cwd, entry);
17685
17827
  }
17686
17828
 
17687
17829
  // src/cli/commands/check.ts
@@ -17750,9 +17892,9 @@ function registerCheckCommand(program2) {
17750
17892
  } catch (err2) {
17751
17893
  const tempConfig = getConfig();
17752
17894
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
17753
- const logPath = path33.join(os28.homedir(), ".node9", "hook-debug.log");
17895
+ const logPath = path34.join(os29.homedir(), ".node9", "hook-debug.log");
17754
17896
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
17755
- fs32.appendFileSync(
17897
+ fs33.appendFileSync(
17756
17898
  logPath,
17757
17899
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
17758
17900
  RAW: ${raw}
@@ -17765,14 +17907,14 @@ RAW: ${raw}
17765
17907
  const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
17766
17908
  if (process.env.NODE9_DEBUG === "1") {
17767
17909
  try {
17768
- const logPath = path33.join(os28.homedir(), ".node9", "hook-debug.log");
17769
- if (!fs32.existsSync(path33.dirname(logPath)))
17770
- fs32.mkdirSync(path33.dirname(logPath), { recursive: true });
17910
+ const logPath = path34.join(os29.homedir(), ".node9", "hook-debug.log");
17911
+ if (!fs33.existsSync(path34.dirname(logPath)))
17912
+ fs33.mkdirSync(path34.dirname(logPath), { recursive: true });
17771
17913
  const sanitized = JSON.stringify({
17772
17914
  ...payload,
17773
17915
  prompt: `<redacted, ${prompt.length} bytes>`
17774
17916
  });
17775
- fs32.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
17917
+ fs33.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${sanitized}
17776
17918
  `);
17777
17919
  } catch {
17778
17920
  }
@@ -17792,8 +17934,8 @@ RAW: ${raw}
17792
17934
  );
17793
17935
  const reason = `\u{1F6A8} Node9 DLP: ${dlpMatch.patternName} detected in prompt (${dlpMatch.redactedSample}). Prompt was not submitted \u2014 remove the credential and try again.`;
17794
17936
  try {
17795
- const ttyFd = fs32.openSync("/dev/tty", "w");
17796
- fs32.writeSync(
17937
+ const ttyFd = fs33.openSync("/dev/tty", "w");
17938
+ fs33.writeSync(
17797
17939
  ttyFd,
17798
17940
  chalk9.bgRed.white.bold(`
17799
17941
  \u{1F6A8} NODE9 DLP \u2014 PROMPT BLOCKED
@@ -17803,7 +17945,7 @@ RAW: ${raw}
17803
17945
 
17804
17946
  `)
17805
17947
  );
17806
- fs32.closeSync(ttyFd);
17948
+ fs33.closeSync(ttyFd);
17807
17949
  } catch {
17808
17950
  }
17809
17951
  const isCodex = agent2 === "Codex";
@@ -17822,16 +17964,16 @@ RAW: ${raw}
17822
17964
  process.exit(2);
17823
17965
  }
17824
17966
  const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
17825
- const safeCwdForConfig = typeof payloadCwd === "string" && path33.isAbsolute(payloadCwd) ? payloadCwd : void 0;
17967
+ const safeCwdForConfig = typeof payloadCwd === "string" && path34.isAbsolute(payloadCwd) ? payloadCwd : void 0;
17826
17968
  const config = getConfig(safeCwdForConfig);
17827
17969
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
17828
17970
  try {
17829
17971
  const scriptPath = process.argv[1];
17830
- if (typeof scriptPath !== "string" || !path33.isAbsolute(scriptPath))
17972
+ if (typeof scriptPath !== "string" || !path34.isAbsolute(scriptPath))
17831
17973
  throw new Error("node9: argv[1] is not an absolute path");
17832
- const resolvedScript = fs32.realpathSync(scriptPath);
17833
- const packageDist = fs32.realpathSync(path33.resolve(__dirname, "../.."));
17834
- if (!resolvedScript.startsWith(packageDist + path33.sep) && resolvedScript !== packageDist)
17974
+ const resolvedScript = fs33.realpathSync(scriptPath);
17975
+ const packageDist = fs33.realpathSync(path34.resolve(__dirname, "../.."));
17976
+ if (!resolvedScript.startsWith(packageDist + path34.sep) && resolvedScript !== packageDist)
17835
17977
  throw new Error(
17836
17978
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
17837
17979
  );
@@ -17853,10 +17995,10 @@ RAW: ${raw}
17853
17995
  });
17854
17996
  d.unref();
17855
17997
  } catch (spawnErr) {
17856
- const logPath = path33.join(os28.homedir(), ".node9", "hook-debug.log");
17998
+ const logPath = path34.join(os29.homedir(), ".node9", "hook-debug.log");
17857
17999
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
17858
18000
  try {
17859
- fs32.appendFileSync(
18001
+ fs33.appendFileSync(
17860
18002
  logPath,
17861
18003
  `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
17862
18004
  `
@@ -17866,10 +18008,10 @@ RAW: ${raw}
17866
18008
  }
17867
18009
  }
17868
18010
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
17869
- const logPath = path33.join(os28.homedir(), ".node9", "hook-debug.log");
17870
- if (!fs32.existsSync(path33.dirname(logPath)))
17871
- fs32.mkdirSync(path33.dirname(logPath), { recursive: true });
17872
- fs32.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
18011
+ const logPath = path34.join(os29.homedir(), ".node9", "hook-debug.log");
18012
+ if (!fs33.existsSync(path34.dirname(logPath)))
18013
+ fs33.mkdirSync(path34.dirname(logPath), { recursive: true });
18014
+ fs33.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
17873
18015
  `);
17874
18016
  }
17875
18017
  const rawToolName = sanitize2(extractToolName(payload));
@@ -17883,8 +18025,8 @@ RAW: ${raw}
17883
18025
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
17884
18026
  let ttyFd = null;
17885
18027
  try {
17886
- ttyFd = fs32.openSync("/dev/tty", "w");
17887
- const writeTty = (line) => fs32.writeSync(ttyFd, line + "\n");
18028
+ ttyFd = fs33.openSync("/dev/tty", "w");
18029
+ const writeTty = (line) => fs33.writeSync(ttyFd, line + "\n");
17888
18030
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
17889
18031
  writeTty(chalk9.bgRed.white.bold(`
17890
18032
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -17903,7 +18045,7 @@ RAW: ${raw}
17903
18045
  } finally {
17904
18046
  if (ttyFd !== null)
17905
18047
  try {
17906
- fs32.closeSync(ttyFd);
18048
+ fs33.closeSync(ttyFd);
17907
18049
  } catch {
17908
18050
  }
17909
18051
  }
@@ -17954,17 +18096,17 @@ RAW: ${raw}
17954
18096
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
17955
18097
  if (skillPinCfg.enabled && safeSessionId) {
17956
18098
  try {
17957
- const sessionsDir = path33.join(os28.homedir(), ".node9", "skill-sessions");
17958
- const flagPath = path33.join(sessionsDir, `${safeSessionId}.json`);
18099
+ const sessionsDir = path34.join(os29.homedir(), ".node9", "skill-sessions");
18100
+ const flagPath = path34.join(sessionsDir, `${safeSessionId}.json`);
17959
18101
  let flag = null;
17960
18102
  try {
17961
- flag = JSON.parse(fs32.readFileSync(flagPath, "utf-8"));
18103
+ flag = JSON.parse(fs33.readFileSync(flagPath, "utf-8"));
17962
18104
  } catch {
17963
18105
  }
17964
18106
  const writeFlag = (data2) => {
17965
18107
  try {
17966
- fs32.mkdirSync(sessionsDir, { recursive: true });
17967
- fs32.writeFileSync(
18108
+ fs33.mkdirSync(sessionsDir, { recursive: true });
18109
+ fs33.writeFileSync(
17968
18110
  flagPath,
17969
18111
  JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
17970
18112
  { mode: 384 }
@@ -17975,8 +18117,8 @@ RAW: ${raw}
17975
18117
  const sendSkillWarn = (detail, recoveryCmd) => {
17976
18118
  let ttyFd = null;
17977
18119
  try {
17978
- ttyFd = fs32.openSync("/dev/tty", "w");
17979
- const w = (line) => fs32.writeSync(ttyFd, line + "\n");
18120
+ ttyFd = fs33.openSync("/dev/tty", "w");
18121
+ const w = (line) => fs33.writeSync(ttyFd, line + "\n");
17980
18122
  w(chalk9.yellow(`
17981
18123
  \u26A0\uFE0F Node9: installed skill drift detected`));
17982
18124
  w(chalk9.gray(` ${detail}`));
@@ -17991,7 +18133,7 @@ RAW: ${raw}
17991
18133
  } finally {
17992
18134
  if (ttyFd !== null)
17993
18135
  try {
17994
- fs32.closeSync(ttyFd);
18136
+ fs33.closeSync(ttyFd);
17995
18137
  } catch {
17996
18138
  }
17997
18139
  }
@@ -18007,7 +18149,7 @@ RAW: ${raw}
18007
18149
  return;
18008
18150
  }
18009
18151
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
18010
- const absoluteCwd = typeof payloadCwd === "string" && path33.isAbsolute(payloadCwd) ? payloadCwd : void 0;
18152
+ const absoluteCwd = typeof payloadCwd === "string" && path34.isAbsolute(payloadCwd) ? payloadCwd : void 0;
18011
18153
  const extraRoots = skillPinCfg.roots;
18012
18154
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
18013
18155
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -18048,10 +18190,10 @@ RAW: ${raw}
18048
18190
  }
18049
18191
  try {
18050
18192
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
18051
- for (const name of fs32.readdirSync(sessionsDir)) {
18052
- const p = path33.join(sessionsDir, name);
18193
+ for (const name of fs33.readdirSync(sessionsDir)) {
18194
+ const p = path34.join(sessionsDir, name);
18053
18195
  try {
18054
- if (fs32.statSync(p).mtimeMs < cutoff) fs32.unlinkSync(p);
18196
+ if (fs33.statSync(p).mtimeMs < cutoff) fs33.unlinkSync(p);
18055
18197
  } catch {
18056
18198
  }
18057
18199
  }
@@ -18061,9 +18203,9 @@ RAW: ${raw}
18061
18203
  } catch (err2) {
18062
18204
  if (process.env.NODE9_DEBUG === "1") {
18063
18205
  try {
18064
- const dbg = path33.join(os28.homedir(), ".node9", "hook-debug.log");
18206
+ const dbg = path34.join(os29.homedir(), ".node9", "hook-debug.log");
18065
18207
  const msg = err2 instanceof Error ? err2.message : String(err2);
18066
- fs32.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
18208
+ fs33.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
18067
18209
  `);
18068
18210
  } catch {
18069
18211
  }
@@ -18073,7 +18215,7 @@ RAW: ${raw}
18073
18215
  if (shouldSnapshot(toolName, toolInput, config)) {
18074
18216
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
18075
18217
  }
18076
- const safeCwdForAuth = typeof payloadCwd === "string" && path33.isAbsolute(payloadCwd) ? payloadCwd : void 0;
18218
+ const safeCwdForAuth = typeof payloadCwd === "string" && path34.isAbsolute(payloadCwd) ? payloadCwd : void 0;
18077
18219
  const result = await authorizeHeadless(toolName, toolInput, meta, {
18078
18220
  cwd: safeCwdForAuth
18079
18221
  });
@@ -18085,12 +18227,12 @@ RAW: ${raw}
18085
18227
  }
18086
18228
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
18087
18229
  try {
18088
- const tty = fs32.openSync("/dev/tty", "w");
18089
- fs32.writeSync(
18230
+ const tty = fs33.openSync("/dev/tty", "w");
18231
+ fs33.writeSync(
18090
18232
  tty,
18091
18233
  chalk9.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
18092
18234
  );
18093
- fs32.closeSync(tty);
18235
+ fs33.closeSync(tty);
18094
18236
  } catch {
18095
18237
  }
18096
18238
  const daemonReady = await autoStartDaemonAndWait();
@@ -18117,9 +18259,9 @@ RAW: ${raw}
18117
18259
  });
18118
18260
  } catch (err2) {
18119
18261
  if (process.env.NODE9_DEBUG === "1") {
18120
- const logPath = path33.join(os28.homedir(), ".node9", "hook-debug.log");
18262
+ const logPath = path34.join(os29.homedir(), ".node9", "hook-debug.log");
18121
18263
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
18122
- fs32.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
18264
+ fs33.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
18123
18265
  `);
18124
18266
  }
18125
18267
  process.exit(0);
@@ -18155,9 +18297,9 @@ RAW: ${raw}
18155
18297
  // src/cli/commands/log.ts
18156
18298
  init_audit();
18157
18299
  init_config();
18158
- import fs33 from "fs";
18159
- import path34 from "path";
18160
- import os29 from "os";
18300
+ import fs34 from "fs";
18301
+ import path35 from "path";
18302
+ import os30 from "os";
18161
18303
  init_daemon();
18162
18304
 
18163
18305
  // src/utils/cp-mv-parser.ts
@@ -18250,10 +18392,10 @@ function registerLogCommand(program2) {
18250
18392
  if (rawToolName !== tool) entry.agentToolName = rawToolName;
18251
18393
  const payloadSessionId = payload.session_id ?? payload.conversationId;
18252
18394
  if (payloadSessionId) entry.sessionId = payloadSessionId;
18253
- const logPath = path34.join(os29.homedir(), ".node9", "audit.log");
18254
- if (!fs33.existsSync(path34.dirname(logPath)))
18255
- fs33.mkdirSync(path34.dirname(logPath), { recursive: true });
18256
- fs33.appendFileSync(logPath, JSON.stringify(entry) + "\n");
18395
+ const logPath = path35.join(os30.homedir(), ".node9", "audit.log");
18396
+ if (!fs34.existsSync(path35.dirname(logPath)))
18397
+ fs34.mkdirSync(path35.dirname(logPath), { recursive: true });
18398
+ fs34.appendFileSync(logPath, JSON.stringify(entry) + "\n");
18257
18399
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
18258
18400
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
18259
18401
  if (command) {
@@ -18287,7 +18429,7 @@ function registerLogCommand(program2) {
18287
18429
  }
18288
18430
  }
18289
18431
  const payloadCwd = typeof payload.cwd === "string" ? payload.cwd : Array.isArray(payload.workspacePaths) && typeof payload.workspacePaths[0] === "string" ? payload.workspacePaths[0] : void 0;
18290
- const safeCwd = typeof payloadCwd === "string" && path34.isAbsolute(payloadCwd) ? payloadCwd : void 0;
18432
+ const safeCwd = typeof payloadCwd === "string" && path35.isAbsolute(payloadCwd) ? payloadCwd : void 0;
18291
18433
  const config = getConfig(safeCwd);
18292
18434
  if ((tool === "Bash" || tool === "bash") && config.settings.enableUndo !== false) {
18293
18435
  const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
@@ -18308,9 +18450,9 @@ function registerLogCommand(program2) {
18308
18450
  const msg = err2 instanceof Error ? err2.message : String(err2);
18309
18451
  process.stderr.write(`[Node9] audit log error: ${msg}
18310
18452
  `);
18311
- const debugPath = path34.join(os29.homedir(), ".node9", "hook-debug.log");
18453
+ const debugPath = path35.join(os30.homedir(), ".node9", "hook-debug.log");
18312
18454
  try {
18313
- fs33.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
18455
+ fs34.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
18314
18456
  `);
18315
18457
  } catch {
18316
18458
  }
@@ -18710,14 +18852,15 @@ function registerConfigShowCommand(program2) {
18710
18852
 
18711
18853
  // src/cli/commands/doctor.ts
18712
18854
  init_daemon();
18855
+ init_config();
18713
18856
  import chalk11 from "chalk";
18714
- import fs34 from "fs";
18715
- import path35 from "path";
18716
- import os30 from "os";
18857
+ import fs35 from "fs";
18858
+ import path36 from "path";
18859
+ import os31 from "os";
18717
18860
  import { execSync } from "child_process";
18718
18861
  function registerDoctorCommand(program2, version2) {
18719
- program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
18720
- const homeDir2 = os30.homedir();
18862
+ program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
18863
+ const homeDir2 = os31.homedir();
18721
18864
  let failures = 0;
18722
18865
  function pass(msg) {
18723
18866
  console.log(chalk11.green(" \u2705 ") + msg);
@@ -18766,10 +18909,10 @@ function registerDoctorCommand(program2, version2) {
18766
18909
  );
18767
18910
  }
18768
18911
  section("Configuration");
18769
- const globalConfigPath = path35.join(homeDir2, ".node9", "config.json");
18770
- if (fs34.existsSync(globalConfigPath)) {
18912
+ const globalConfigPath = path36.join(homeDir2, ".node9", "config.json");
18913
+ if (fs35.existsSync(globalConfigPath)) {
18771
18914
  try {
18772
- JSON.parse(fs34.readFileSync(globalConfigPath, "utf-8"));
18915
+ JSON.parse(fs35.readFileSync(globalConfigPath, "utf-8"));
18773
18916
  pass("~/.node9/config.json found and valid");
18774
18917
  } catch {
18775
18918
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -18777,10 +18920,10 @@ function registerDoctorCommand(program2, version2) {
18777
18920
  } else {
18778
18921
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
18779
18922
  }
18780
- const projectConfigPath = path35.join(process.cwd(), "node9.config.json");
18781
- if (fs34.existsSync(projectConfigPath)) {
18923
+ const projectConfigPath = path36.join(process.cwd(), "node9.config.json");
18924
+ if (fs35.existsSync(projectConfigPath)) {
18782
18925
  try {
18783
- JSON.parse(fs34.readFileSync(projectConfigPath, "utf-8"));
18926
+ JSON.parse(fs35.readFileSync(projectConfigPath, "utf-8"));
18784
18927
  pass("node9.config.json found and valid (project)");
18785
18928
  } catch {
18786
18929
  fail(
@@ -18789,8 +18932,8 @@ function registerDoctorCommand(program2, version2) {
18789
18932
  );
18790
18933
  }
18791
18934
  }
18792
- const credsPath = path35.join(homeDir2, ".node9", "credentials.json");
18793
- if (fs34.existsSync(credsPath)) {
18935
+ const credsPath = path36.join(homeDir2, ".node9", "credentials.json");
18936
+ if (fs35.existsSync(credsPath)) {
18794
18937
  pass("Cloud credentials found (~/.node9/credentials.json)");
18795
18938
  } else {
18796
18939
  warn(
@@ -18799,10 +18942,10 @@ function registerDoctorCommand(program2, version2) {
18799
18942
  );
18800
18943
  }
18801
18944
  section("Agent Hooks");
18802
- const claudeSettingsPath = path35.join(homeDir2, ".claude", "settings.json");
18803
- if (fs34.existsSync(claudeSettingsPath)) {
18945
+ const claudeSettingsPath = path36.join(homeDir2, ".claude", "settings.json");
18946
+ if (fs35.existsSync(claudeSettingsPath)) {
18804
18947
  try {
18805
- const cs = JSON.parse(fs34.readFileSync(claudeSettingsPath, "utf-8"));
18948
+ const cs = JSON.parse(fs35.readFileSync(claudeSettingsPath, "utf-8"));
18806
18949
  const hasHook = cs.hooks?.PreToolUse?.some(
18807
18950
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
18808
18951
  );
@@ -18818,10 +18961,10 @@ function registerDoctorCommand(program2, version2) {
18818
18961
  } else {
18819
18962
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
18820
18963
  }
18821
- const geminiSettingsPath = path35.join(homeDir2, ".gemini", "settings.json");
18822
- if (fs34.existsSync(geminiSettingsPath)) {
18964
+ const geminiSettingsPath = path36.join(homeDir2, ".gemini", "settings.json");
18965
+ if (fs35.existsSync(geminiSettingsPath)) {
18823
18966
  try {
18824
- const gs = JSON.parse(fs34.readFileSync(geminiSettingsPath, "utf-8"));
18967
+ const gs = JSON.parse(fs35.readFileSync(geminiSettingsPath, "utf-8"));
18825
18968
  const hasHook = gs.hooks?.BeforeTool?.some(
18826
18969
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
18827
18970
  );
@@ -18837,10 +18980,10 @@ function registerDoctorCommand(program2, version2) {
18837
18980
  } else {
18838
18981
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
18839
18982
  }
18840
- const cursorHooksPath = path35.join(homeDir2, ".cursor", "hooks.json");
18841
- if (fs34.existsSync(cursorHooksPath)) {
18983
+ const cursorHooksPath = path36.join(homeDir2, ".cursor", "hooks.json");
18984
+ if (fs35.existsSync(cursorHooksPath)) {
18842
18985
  try {
18843
- const cur = JSON.parse(fs34.readFileSync(cursorHooksPath, "utf-8"));
18986
+ const cur = JSON.parse(fs35.readFileSync(cursorHooksPath, "utf-8"));
18844
18987
  const hasHook = cur.hooks?.preToolUse?.some(
18845
18988
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
18846
18989
  );
@@ -18867,6 +19010,47 @@ function registerDoctorCommand(program2, version2) {
18867
19010
  "Run: node9 daemon --background"
18868
19011
  );
18869
19012
  }
19013
+ section("Cloud audit shipping");
19014
+ try {
19015
+ const { shipLagBytes: shipLagBytes2, readWatermark: readWatermark2, AUDIT_SHIP_WATERMARK: AUDIT_SHIP_WATERMARK2 } = await Promise.resolve().then(() => (init_audit_shipper(), audit_shipper_exports));
19016
+ const cfg = getConfig();
19017
+ const creds = fs35.existsSync(path36.join(os31.homedir(), ".node9", "credentials.json"));
19018
+ if (!creds) {
19019
+ warn("Not logged in \u2014 audit rows stay local", "Run: node9 login <api-key>");
19020
+ } else if (!cfg.settings.approvers.cloud) {
19021
+ warn(
19022
+ "Cloud approvals OFF (settings.approvers.cloud=false) \u2014 nothing syncs to the dashboard",
19023
+ "Privacy mode is a valid choice; set approvers.cloud=true to sync."
19024
+ );
19025
+ } else if (cfg.settings.shipper.enabled === false) {
19026
+ warn("Shipper disabled (settings.shipper.enabled=false) \u2014 audit rows stay local");
19027
+ } else {
19028
+ const lag = shipLagBytes2();
19029
+ const wm = readWatermark2(AUDIT_SHIP_WATERMARK2);
19030
+ if (lag === 0) {
19031
+ pass("Audit shipping caught up \u2014 dashboard matches the local log");
19032
+ } else if (wm && lag !== null) {
19033
+ const ageMin = Math.round((Date.now() - new Date(wm.updatedAt).getTime()) / 6e4);
19034
+ if (ageMin > 5 && !isDaemonRunning()) {
19035
+ warn(
19036
+ `${Math.round(lag / 1024)} KB of audit rows not shipped (last ship ${ageMin}m ago)`,
19037
+ "The daemon ships every ~20s \u2014 start it: node9 daemon --background"
19038
+ );
19039
+ } else {
19040
+ pass(
19041
+ `Shipping in progress \u2014 ${Math.round(lag / 1024)} KB queued, last ship ${ageMin}m ago`
19042
+ );
19043
+ }
19044
+ } else {
19045
+ warn(
19046
+ "Shipper has never run on this machine \u2014 dashboard may lag the local log",
19047
+ "Start the daemon: node9 daemon --background"
19048
+ );
19049
+ }
19050
+ }
19051
+ } catch (err2) {
19052
+ warn(`Shipping status unavailable: ${err2.message}`);
19053
+ }
18870
19054
  console.log("");
18871
19055
  if (failures === 0) {
18872
19056
  console.log(chalk11.green.bold(" All checks passed. Node9 is ready.\n"));
@@ -18880,9 +19064,9 @@ function registerDoctorCommand(program2, version2) {
18880
19064
 
18881
19065
  // src/cli/commands/audit.ts
18882
19066
  import chalk12 from "chalk";
18883
- import fs35 from "fs";
18884
- import path36 from "path";
18885
- import os31 from "os";
19067
+ import fs36 from "fs";
19068
+ import path37 from "path";
19069
+ import os32 from "os";
18886
19070
  function formatRelativeTime(timestamp) {
18887
19071
  const diff = Date.now() - new Date(timestamp).getTime();
18888
19072
  const sec = Math.floor(diff / 1e3);
@@ -18895,14 +19079,14 @@ function formatRelativeTime(timestamp) {
18895
19079
  }
18896
19080
  function registerAuditCommand(program2) {
18897
19081
  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) => {
18898
- const logPath = path36.join(os31.homedir(), ".node9", "audit.log");
18899
- if (!fs35.existsSync(logPath)) {
19082
+ const logPath = path37.join(os32.homedir(), ".node9", "audit.log");
19083
+ if (!fs36.existsSync(logPath)) {
18900
19084
  console.log(
18901
19085
  chalk12.yellow("No audit logs found. Run node9 with an agent to generate entries.")
18902
19086
  );
18903
19087
  return;
18904
19088
  }
18905
- const raw = fs35.readFileSync(logPath, "utf-8");
19089
+ const raw = fs36.readFileSync(logPath, "utf-8");
18906
19090
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
18907
19091
  let entries = lines.flatMap((line) => {
18908
19092
  try {
@@ -18960,9 +19144,9 @@ import chalk13 from "chalk";
18960
19144
  // src/cli/aggregate/report-audit.ts
18961
19145
  init_costSync();
18962
19146
  init_litellm();
18963
- import fs36 from "fs";
18964
- import os32 from "os";
18965
- import path37 from "path";
19147
+ import fs37 from "fs";
19148
+ import os33 from "os";
19149
+ import path38 from "path";
18966
19150
  var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
18967
19151
  function buildTestTimestamps(allEntries) {
18968
19152
  const testTs = /* @__PURE__ */ new Set();
@@ -19042,8 +19226,8 @@ function getDateRange(period, now) {
19042
19226
  }
19043
19227
  }
19044
19228
  function parseAuditLog(logPath) {
19045
- if (!fs36.existsSync(logPath)) return [];
19046
- const raw = fs36.readFileSync(logPath, "utf-8");
19229
+ if (!fs37.existsSync(logPath)) return [];
19230
+ const raw = fs37.readFileSync(logPath, "utf-8");
19047
19231
  return raw.split("\n").flatMap((line) => {
19048
19232
  if (!line.trim()) return [];
19049
19233
  try {
@@ -19103,25 +19287,25 @@ function freezeClaudeCost(acc) {
19103
19287
  };
19104
19288
  }
19105
19289
  function processClaudeCostProject(proj, projectsDir, start, end, acc) {
19106
- const projPath = path37.join(projectsDir, proj);
19290
+ const projPath = path38.join(projectsDir, proj);
19107
19291
  let files;
19108
19292
  try {
19109
- const stat = fs36.statSync(projPath);
19293
+ const stat = fs37.statSync(projPath);
19110
19294
  if (!stat.isDirectory()) return;
19111
- files = fs36.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
19295
+ files = fs37.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
19112
19296
  } catch {
19113
19297
  return;
19114
19298
  }
19115
19299
  const startMs = start.getTime();
19116
19300
  for (const file of files) {
19117
- const filePath = path37.join(projPath, file);
19301
+ const filePath = path38.join(projPath, file);
19118
19302
  try {
19119
- if (fs36.statSync(filePath).mtimeMs < startMs) continue;
19303
+ if (fs37.statSync(filePath).mtimeMs < startMs) continue;
19120
19304
  } catch {
19121
19305
  continue;
19122
19306
  }
19123
19307
  try {
19124
- const raw = fs36.readFileSync(filePath, "utf-8");
19308
+ const raw = fs37.readFileSync(filePath, "utf-8");
19125
19309
  for (const line of raw.split("\n")) {
19126
19310
  if (!line.trim()) continue;
19127
19311
  let entry;
@@ -19171,10 +19355,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
19171
19355
  }
19172
19356
  function loadClaudeCost(start, end, projectsDir) {
19173
19357
  const acc = emptyClaudeCostAccumulator();
19174
- if (!fs36.existsSync(projectsDir)) return freezeClaudeCost(acc);
19358
+ if (!fs37.existsSync(projectsDir)) return freezeClaudeCost(acc);
19175
19359
  let dirs;
19176
19360
  try {
19177
- dirs = fs36.readdirSync(projectsDir);
19361
+ dirs = fs37.readdirSync(projectsDir);
19178
19362
  } catch {
19179
19363
  return freezeClaudeCost(acc);
19180
19364
  }
@@ -19186,7 +19370,7 @@ function loadClaudeCost(start, end, projectsDir) {
19186
19370
  function processCodexCostFile(filePath, start, end, acc) {
19187
19371
  let lines;
19188
19372
  try {
19189
- lines = fs36.readFileSync(filePath, "utf-8").split("\n");
19373
+ lines = fs37.readFileSync(filePath, "utf-8").split("\n");
19190
19374
  } catch {
19191
19375
  return;
19192
19376
  }
@@ -19231,31 +19415,31 @@ function processCodexCostFile(filePath, start, end, acc) {
19231
19415
  }
19232
19416
  function listCodexSessionFiles(sessionsBase) {
19233
19417
  const jsonlFiles = [];
19234
- if (!fs36.existsSync(sessionsBase)) return jsonlFiles;
19418
+ if (!fs37.existsSync(sessionsBase)) return jsonlFiles;
19235
19419
  try {
19236
- for (const year of fs36.readdirSync(sessionsBase)) {
19237
- const yearPath = path37.join(sessionsBase, year);
19420
+ for (const year of fs37.readdirSync(sessionsBase)) {
19421
+ const yearPath = path38.join(sessionsBase, year);
19238
19422
  try {
19239
- if (!fs36.statSync(yearPath).isDirectory()) continue;
19423
+ if (!fs37.statSync(yearPath).isDirectory()) continue;
19240
19424
  } catch {
19241
19425
  continue;
19242
19426
  }
19243
- for (const month of fs36.readdirSync(yearPath)) {
19244
- const monthPath = path37.join(yearPath, month);
19427
+ for (const month of fs37.readdirSync(yearPath)) {
19428
+ const monthPath = path38.join(yearPath, month);
19245
19429
  try {
19246
- if (!fs36.statSync(monthPath).isDirectory()) continue;
19430
+ if (!fs37.statSync(monthPath).isDirectory()) continue;
19247
19431
  } catch {
19248
19432
  continue;
19249
19433
  }
19250
- for (const day of fs36.readdirSync(monthPath)) {
19251
- const dayPath = path37.join(monthPath, day);
19434
+ for (const day of fs37.readdirSync(monthPath)) {
19435
+ const dayPath = path38.join(monthPath, day);
19252
19436
  try {
19253
- if (!fs36.statSync(dayPath).isDirectory()) continue;
19437
+ if (!fs37.statSync(dayPath).isDirectory()) continue;
19254
19438
  } catch {
19255
19439
  continue;
19256
19440
  }
19257
- for (const file of fs36.readdirSync(dayPath)) {
19258
- if (file.endsWith(".jsonl")) jsonlFiles.push(path37.join(dayPath, file));
19441
+ for (const file of fs37.readdirSync(dayPath)) {
19442
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path38.join(dayPath, file));
19259
19443
  }
19260
19444
  }
19261
19445
  }
@@ -19308,13 +19492,13 @@ function freezeGeminiCost(acc) {
19308
19492
  function processGeminiCostFile(filePath, projectKey, start, end, acc) {
19309
19493
  const startMs = start.getTime();
19310
19494
  try {
19311
- if (fs36.statSync(filePath).mtimeMs < startMs) return;
19495
+ if (fs37.statSync(filePath).mtimeMs < startMs) return;
19312
19496
  } catch {
19313
19497
  return;
19314
19498
  }
19315
19499
  let raw;
19316
19500
  try {
19317
- raw = fs36.readFileSync(filePath, "utf-8");
19501
+ raw = fs37.readFileSync(filePath, "utf-8");
19318
19502
  } catch {
19319
19503
  return;
19320
19504
  }
@@ -19363,30 +19547,30 @@ function listGeminiSessionFiles(geminiTmpDir) {
19363
19547
  const out = [];
19364
19548
  let dirs;
19365
19549
  try {
19366
- if (!fs36.statSync(geminiTmpDir).isDirectory()) return out;
19367
- dirs = fs36.readdirSync(geminiTmpDir);
19550
+ if (!fs37.statSync(geminiTmpDir).isDirectory()) return out;
19551
+ dirs = fs37.readdirSync(geminiTmpDir);
19368
19552
  } catch {
19369
19553
  return out;
19370
19554
  }
19371
19555
  for (const proj of dirs) {
19372
- const chatsDir = path37.join(geminiTmpDir, proj, "chats");
19556
+ const chatsDir = path38.join(geminiTmpDir, proj, "chats");
19373
19557
  let files;
19374
19558
  try {
19375
- if (!fs36.statSync(chatsDir).isDirectory()) continue;
19376
- files = fs36.readdirSync(chatsDir);
19559
+ if (!fs37.statSync(chatsDir).isDirectory()) continue;
19560
+ files = fs37.readdirSync(chatsDir);
19377
19561
  } catch {
19378
19562
  continue;
19379
19563
  }
19380
19564
  for (const f of files) {
19381
19565
  if (!f.endsWith(".jsonl")) continue;
19382
- out.push({ projectKey: proj, file: path37.join(chatsDir, f) });
19566
+ out.push({ projectKey: proj, file: path38.join(chatsDir, f) });
19383
19567
  }
19384
19568
  }
19385
19569
  return out;
19386
19570
  }
19387
19571
  function loadGeminiCost(start, end, geminiTmpDir) {
19388
19572
  const acc = emptyGeminiAccumulator();
19389
- if (!fs36.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
19573
+ if (!fs37.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
19390
19574
  for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
19391
19575
  processGeminiCostFile(file, projectKey, start, end, acc);
19392
19576
  }
@@ -19394,11 +19578,11 @@ function loadGeminiCost(start, end, geminiTmpDir) {
19394
19578
  }
19395
19579
  function aggregateReportFromAudit(period, opts = {}) {
19396
19580
  const now = opts.now ?? /* @__PURE__ */ new Date();
19397
- const auditLogPath = opts.auditLogPath ?? path37.join(os32.homedir(), ".node9", "audit.log");
19398
- const claudeProjectsDir = opts.claudeProjectsDir ?? path37.join(os32.homedir(), ".claude", "projects");
19399
- const codexSessionsDir = opts.codexSessionsDir ?? path37.join(os32.homedir(), ".codex", "sessions");
19400
- const geminiTmpDir = opts.geminiTmpDir ?? path37.join(os32.homedir(), ".gemini", "tmp");
19401
- const hasAuditFile = fs36.existsSync(auditLogPath);
19581
+ const auditLogPath = opts.auditLogPath ?? path38.join(os33.homedir(), ".node9", "audit.log");
19582
+ const claudeProjectsDir = opts.claudeProjectsDir ?? path38.join(os33.homedir(), ".claude", "projects");
19583
+ const codexSessionsDir = opts.codexSessionsDir ?? path38.join(os33.homedir(), ".codex", "sessions");
19584
+ const geminiTmpDir = opts.geminiTmpDir ?? path38.join(os33.homedir(), ".gemini", "tmp");
19585
+ const hasAuditFile = fs37.existsSync(auditLogPath);
19402
19586
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
19403
19587
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
19404
19588
  const { start, end } = getDateRange(period, now);
@@ -20096,12 +20280,12 @@ function registerDaemonCommand(program2) {
20096
20280
  init_core();
20097
20281
  init_daemon();
20098
20282
  import chalk15 from "chalk";
20099
- import fs37 from "fs";
20100
- import path38 from "path";
20101
- import os33 from "os";
20283
+ import fs38 from "fs";
20284
+ import path39 from "path";
20285
+ import os34 from "os";
20102
20286
  function readJson2(filePath) {
20103
20287
  try {
20104
- if (fs37.existsSync(filePath)) return JSON.parse(fs37.readFileSync(filePath, "utf-8"));
20288
+ if (fs38.existsSync(filePath)) return JSON.parse(fs38.readFileSync(filePath, "utf-8"));
20105
20289
  } catch {
20106
20290
  }
20107
20291
  return null;
@@ -20166,28 +20350,28 @@ function registerStatusCommand(program2) {
20166
20350
  console.log("");
20167
20351
  const modeLabel = settings.mode === "audit" ? chalk15.blue("audit") : settings.mode === "strict" ? chalk15.red("strict") : chalk15.white("standard");
20168
20352
  console.log(` Mode: ${modeLabel}`);
20169
- const projectConfig = path38.join(process.cwd(), "node9.config.json");
20170
- const globalConfig = path38.join(os33.homedir(), ".node9", "config.json");
20353
+ const projectConfig = path39.join(process.cwd(), "node9.config.json");
20354
+ const globalConfig = path39.join(os34.homedir(), ".node9", "config.json");
20171
20355
  console.log(
20172
- ` Local: ${fs37.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
20356
+ ` Local: ${fs38.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
20173
20357
  );
20174
20358
  console.log(
20175
- ` Global: ${fs37.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
20359
+ ` Global: ${fs38.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
20176
20360
  );
20177
20361
  if (mergedConfig.policy.sandboxPaths.length > 0) {
20178
20362
  console.log(
20179
20363
  ` Sandbox: ${chalk15.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
20180
20364
  );
20181
20365
  }
20182
- const homeDir2 = os33.homedir();
20366
+ const homeDir2 = os34.homedir();
20183
20367
  const claudeSettings = readJson2(
20184
- path38.join(homeDir2, ".claude", "settings.json")
20368
+ path39.join(homeDir2, ".claude", "settings.json")
20185
20369
  );
20186
- const claudeConfig = readJson2(path38.join(homeDir2, ".claude.json"));
20370
+ const claudeConfig = readJson2(path39.join(homeDir2, ".claude.json"));
20187
20371
  const geminiSettings = readJson2(
20188
- path38.join(homeDir2, ".gemini", "settings.json")
20372
+ path39.join(homeDir2, ".gemini", "settings.json")
20189
20373
  );
20190
- const cursorConfig = readJson2(path38.join(homeDir2, ".cursor", "mcp.json"));
20374
+ const cursorConfig = readJson2(path39.join(homeDir2, ".cursor", "mcp.json"));
20191
20375
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
20192
20376
  if (agentFound) {
20193
20377
  console.log("");
@@ -20250,9 +20434,9 @@ init_setup();
20250
20434
  init_shields();
20251
20435
  init_service();
20252
20436
  import chalk16 from "chalk";
20253
- import fs38 from "fs";
20254
- import path39 from "path";
20255
- import os34 from "os";
20437
+ import fs39 from "fs";
20438
+ import path40 from "path";
20439
+ import os35 from "os";
20256
20440
  import https4 from "https";
20257
20441
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
20258
20442
  function buildTelemetryPayload(agents, firstInstall) {
@@ -20338,16 +20522,16 @@ function registerInitCommand(program2) {
20338
20522
  }
20339
20523
  console.log("");
20340
20524
  }
20341
- const configPath = path39.join(os34.homedir(), ".node9", "config.json");
20342
- const isFirstInstall = !fs38.existsSync(configPath);
20343
- if (fs38.existsSync(configPath) && !options.force) {
20525
+ const configPath = path40.join(os35.homedir(), ".node9", "config.json");
20526
+ const isFirstInstall = !fs39.existsSync(configPath);
20527
+ if (fs39.existsSync(configPath) && !options.force) {
20344
20528
  try {
20345
- const existing = JSON.parse(fs38.readFileSync(configPath, "utf-8"));
20529
+ const existing = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
20346
20530
  const settings = existing.settings ?? {};
20347
20531
  if (settings.mode !== chosenMode) {
20348
20532
  settings.mode = chosenMode;
20349
20533
  existing.settings = settings;
20350
- fs38.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
20534
+ fs39.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
20351
20535
  console.log(chalk16.green(`\u2705 Mode updated: ${chosenMode}`));
20352
20536
  } else {
20353
20537
  console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -20360,9 +20544,9 @@ function registerInitCommand(program2) {
20360
20544
  ...DEFAULT_CONFIG,
20361
20545
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
20362
20546
  };
20363
- const dir = path39.dirname(configPath);
20364
- if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
20365
- fs38.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
20547
+ const dir = path40.dirname(configPath);
20548
+ if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
20549
+ fs39.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
20366
20550
  console.log(chalk16.green(`\u2705 Config created: ${configPath}`));
20367
20551
  console.log(chalk16.gray(` Mode: ${chosenMode}`));
20368
20552
  }
@@ -20467,7 +20651,7 @@ function registerInitCommand(program2) {
20467
20651
  }
20468
20652
 
20469
20653
  // src/cli/commands/undo.ts
20470
- import path40 from "path";
20654
+ import path41 from "path";
20471
20655
  import chalk18 from "chalk";
20472
20656
 
20473
20657
  // src/tui/undo-navigator.ts
@@ -20626,7 +20810,7 @@ function findMatchingCwd(startDir, history) {
20626
20810
  let dir = startDir;
20627
20811
  while (true) {
20628
20812
  if (cwds.has(dir)) return dir;
20629
- const parent = path40.dirname(dir);
20813
+ const parent = path41.dirname(dir);
20630
20814
  if (parent === dir) return null;
20631
20815
  dir = parent;
20632
20816
  }
@@ -21202,9 +21386,9 @@ function registerMcpGatewayCommand(program2) {
21202
21386
 
21203
21387
  // src/mcp-server/index.ts
21204
21388
  import readline5 from "readline";
21205
- import fs39 from "fs";
21206
- import os35 from "os";
21207
- import path41 from "path";
21389
+ import fs40 from "fs";
21390
+ import os36 from "os";
21391
+ import path42 from "path";
21208
21392
  import { spawnSync as spawnSync4 } from "child_process";
21209
21393
  init_core();
21210
21394
  init_daemon();
@@ -21455,13 +21639,13 @@ function handleStatus() {
21455
21639
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
21456
21640
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
21457
21641
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
21458
- const projectConfig = path41.join(process.cwd(), "node9.config.json");
21459
- const globalConfig = path41.join(os35.homedir(), ".node9", "config.json");
21642
+ const projectConfig = path42.join(process.cwd(), "node9.config.json");
21643
+ const globalConfig = path42.join(os36.homedir(), ".node9", "config.json");
21460
21644
  lines.push(
21461
- `Project config (node9.config.json): ${fs39.existsSync(projectConfig) ? "present" : "not found"}`
21645
+ `Project config (node9.config.json): ${fs40.existsSync(projectConfig) ? "present" : "not found"}`
21462
21646
  );
21463
21647
  lines.push(
21464
- `Global config (~/.node9/config.json): ${fs39.existsSync(globalConfig) ? "present" : "not found"}`
21648
+ `Global config (~/.node9/config.json): ${fs40.existsSync(globalConfig) ? "present" : "not found"}`
21465
21649
  );
21466
21650
  return lines.join("\n");
21467
21651
  }
@@ -21535,21 +21719,21 @@ function handleShieldDisable(args) {
21535
21719
  writeActiveShields(active.filter((s) => s !== name));
21536
21720
  return `Shield "${name}" disabled.`;
21537
21721
  }
21538
- var GLOBAL_CONFIG_PATH = path41.join(os35.homedir(), ".node9", "config.json");
21722
+ var GLOBAL_CONFIG_PATH = path42.join(os36.homedir(), ".node9", "config.json");
21539
21723
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
21540
21724
  function readGlobalConfigRaw() {
21541
21725
  try {
21542
- if (fs39.existsSync(GLOBAL_CONFIG_PATH)) {
21543
- return JSON.parse(fs39.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
21726
+ if (fs40.existsSync(GLOBAL_CONFIG_PATH)) {
21727
+ return JSON.parse(fs40.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
21544
21728
  }
21545
21729
  } catch {
21546
21730
  }
21547
21731
  return {};
21548
21732
  }
21549
21733
  function writeGlobalConfigRaw(data) {
21550
- const dir = path41.dirname(GLOBAL_CONFIG_PATH);
21551
- if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
21552
- fs39.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
21734
+ const dir = path42.dirname(GLOBAL_CONFIG_PATH);
21735
+ if (!fs40.existsSync(dir)) fs40.mkdirSync(dir, { recursive: true });
21736
+ fs40.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
21553
21737
  }
21554
21738
  function handleApproverList() {
21555
21739
  const config = getConfig();
@@ -21593,9 +21777,9 @@ function handleApproverSet(args) {
21593
21777
  function handleAuditGet(args) {
21594
21778
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
21595
21779
  const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
21596
- const auditPath = path41.join(os35.homedir(), ".node9", "audit.log");
21597
- if (!fs39.existsSync(auditPath)) return "No audit log found.";
21598
- const rawLines = fs39.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
21780
+ const auditPath = path42.join(os36.homedir(), ".node9", "audit.log");
21781
+ if (!fs40.existsSync(auditPath)) return "No audit log found.";
21782
+ const rawLines = fs40.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
21599
21783
  const parsed = [];
21600
21784
  for (const line of rawLines) {
21601
21785
  try {
@@ -21930,7 +22114,7 @@ function registerTrustCommand(program2) {
21930
22114
  // src/cli/commands/mcp-pin.ts
21931
22115
  init_mcp_pin();
21932
22116
  import chalk21 from "chalk";
21933
- import fs40 from "fs";
22117
+ import fs41 from "fs";
21934
22118
  function registerMcpPinCommand(program2) {
21935
22119
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
21936
22120
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -21941,7 +22125,7 @@ function registerMcpPinCommand(program2) {
21941
22125
  let repoCorrupt = false;
21942
22126
  if (found.source === "repo") {
21943
22127
  try {
21944
- const raw = fs40.readFileSync(found.path, "utf-8");
22128
+ const raw = fs41.readFileSync(found.path, "utf-8");
21945
22129
  const parsed = JSON.parse(raw);
21946
22130
  repoEntries = parsed.servers ?? {};
21947
22131
  } catch {
@@ -22255,9 +22439,9 @@ init_scan();
22255
22439
  // src/cli/commands/sessions.ts
22256
22440
  init_scan_summary();
22257
22441
  import chalk24 from "chalk";
22258
- import fs41 from "fs";
22259
- import path42 from "path";
22260
- import os36 from "os";
22442
+ import fs42 from "fs";
22443
+ import path43 from "path";
22444
+ import os37 from "os";
22261
22445
  var CLAUDE_PRICING3 = {
22262
22446
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
22263
22447
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -22298,10 +22482,10 @@ function encodeProjectPath(projectPath) {
22298
22482
  }
22299
22483
  function sessionJsonlPath(projectPath, sessionId) {
22300
22484
  const encoded = encodeProjectPath(projectPath);
22301
- return path42.join(os36.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
22485
+ return path43.join(os37.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
22302
22486
  }
22303
22487
  function projectLabel(projectPath) {
22304
- return projectPath.replace(os36.homedir(), "~");
22488
+ return projectPath.replace(os37.homedir(), "~");
22305
22489
  }
22306
22490
  function parseHistoryLines(lines) {
22307
22491
  const entries = [];
@@ -22370,10 +22554,10 @@ function parseSessionLines(lines) {
22370
22554
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
22371
22555
  }
22372
22556
  function loadAuditEntries(auditPath) {
22373
- const aPath = auditPath ?? path42.join(os36.homedir(), ".node9", "audit.log");
22557
+ const aPath = auditPath ?? path43.join(os37.homedir(), ".node9", "audit.log");
22374
22558
  let raw;
22375
22559
  try {
22376
- raw = fs41.readFileSync(aPath, "utf-8");
22560
+ raw = fs42.readFileSync(aPath, "utf-8");
22377
22561
  } catch {
22378
22562
  return [];
22379
22563
  }
@@ -22409,8 +22593,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
22409
22593
  return result;
22410
22594
  }
22411
22595
  function buildGeminiSessions(days, allAuditEntries) {
22412
- const tmpDir = path42.join(os36.homedir(), ".gemini", "tmp");
22413
- if (!fs41.existsSync(tmpDir)) return [];
22596
+ const tmpDir = path43.join(os37.homedir(), ".gemini", "tmp");
22597
+ if (!fs42.existsSync(tmpDir)) return [];
22414
22598
  const cutoff = days !== null ? (() => {
22415
22599
  const d = /* @__PURE__ */ new Date();
22416
22600
  d.setDate(d.getDate() - days);
@@ -22419,35 +22603,35 @@ function buildGeminiSessions(days, allAuditEntries) {
22419
22603
  })() : null;
22420
22604
  let slugDirs;
22421
22605
  try {
22422
- slugDirs = fs41.readdirSync(tmpDir);
22606
+ slugDirs = fs42.readdirSync(tmpDir);
22423
22607
  } catch {
22424
22608
  return [];
22425
22609
  }
22426
22610
  const summaries = [];
22427
22611
  for (const slug of slugDirs) {
22428
- const slugPath = path42.join(tmpDir, slug);
22612
+ const slugPath = path43.join(tmpDir, slug);
22429
22613
  try {
22430
- if (!fs41.statSync(slugPath).isDirectory()) continue;
22614
+ if (!fs42.statSync(slugPath).isDirectory()) continue;
22431
22615
  } catch {
22432
22616
  continue;
22433
22617
  }
22434
- let projectRoot = path42.join(os36.homedir(), slug);
22618
+ let projectRoot = path43.join(os37.homedir(), slug);
22435
22619
  try {
22436
- projectRoot = fs41.readFileSync(path42.join(slugPath, ".project_root"), "utf-8").trim();
22620
+ projectRoot = fs42.readFileSync(path43.join(slugPath, ".project_root"), "utf-8").trim();
22437
22621
  } catch {
22438
22622
  }
22439
- const chatsDir = path42.join(slugPath, "chats");
22440
- if (!fs41.existsSync(chatsDir)) continue;
22623
+ const chatsDir = path43.join(slugPath, "chats");
22624
+ if (!fs42.existsSync(chatsDir)) continue;
22441
22625
  let chatFiles;
22442
22626
  try {
22443
- chatFiles = fs41.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
22627
+ chatFiles = fs42.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
22444
22628
  } catch {
22445
22629
  continue;
22446
22630
  }
22447
22631
  for (const chatFile of chatFiles) {
22448
22632
  let raw;
22449
22633
  try {
22450
- raw = fs41.readFileSync(path42.join(chatsDir, chatFile), "utf-8");
22634
+ raw = fs42.readFileSync(path43.join(chatsDir, chatFile), "utf-8");
22451
22635
  } catch {
22452
22636
  continue;
22453
22637
  }
@@ -22527,8 +22711,8 @@ function buildGeminiSessions(days, allAuditEntries) {
22527
22711
  return summaries;
22528
22712
  }
22529
22713
  function buildCodexSessions(days, allAuditEntries) {
22530
- const sessionsBase = path42.join(os36.homedir(), ".codex", "sessions");
22531
- if (!fs41.existsSync(sessionsBase)) return [];
22714
+ const sessionsBase = path43.join(os37.homedir(), ".codex", "sessions");
22715
+ if (!fs42.existsSync(sessionsBase)) return [];
22532
22716
  const cutoff = days !== null ? (() => {
22533
22717
  const d = /* @__PURE__ */ new Date();
22534
22718
  d.setDate(d.getDate() - days);
@@ -22537,29 +22721,29 @@ function buildCodexSessions(days, allAuditEntries) {
22537
22721
  })() : null;
22538
22722
  const jsonlFiles = [];
22539
22723
  try {
22540
- for (const year of fs41.readdirSync(sessionsBase)) {
22541
- const yearPath = path42.join(sessionsBase, year);
22724
+ for (const year of fs42.readdirSync(sessionsBase)) {
22725
+ const yearPath = path43.join(sessionsBase, year);
22542
22726
  try {
22543
- if (!fs41.statSync(yearPath).isDirectory()) continue;
22727
+ if (!fs42.statSync(yearPath).isDirectory()) continue;
22544
22728
  } catch {
22545
22729
  continue;
22546
22730
  }
22547
- for (const month of fs41.readdirSync(yearPath)) {
22548
- const monthPath = path42.join(yearPath, month);
22731
+ for (const month of fs42.readdirSync(yearPath)) {
22732
+ const monthPath = path43.join(yearPath, month);
22549
22733
  try {
22550
- if (!fs41.statSync(monthPath).isDirectory()) continue;
22734
+ if (!fs42.statSync(monthPath).isDirectory()) continue;
22551
22735
  } catch {
22552
22736
  continue;
22553
22737
  }
22554
- for (const day of fs41.readdirSync(monthPath)) {
22555
- const dayPath = path42.join(monthPath, day);
22738
+ for (const day of fs42.readdirSync(monthPath)) {
22739
+ const dayPath = path43.join(monthPath, day);
22556
22740
  try {
22557
- if (!fs41.statSync(dayPath).isDirectory()) continue;
22741
+ if (!fs42.statSync(dayPath).isDirectory()) continue;
22558
22742
  } catch {
22559
22743
  continue;
22560
22744
  }
22561
- for (const file of fs41.readdirSync(dayPath)) {
22562
- if (file.endsWith(".jsonl")) jsonlFiles.push(path42.join(dayPath, file));
22745
+ for (const file of fs42.readdirSync(dayPath)) {
22746
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path43.join(dayPath, file));
22563
22747
  }
22564
22748
  }
22565
22749
  }
@@ -22571,7 +22755,7 @@ function buildCodexSessions(days, allAuditEntries) {
22571
22755
  for (const filePath of jsonlFiles) {
22572
22756
  let lines;
22573
22757
  try {
22574
- lines = fs41.readFileSync(filePath, "utf-8").split("\n");
22758
+ lines = fs42.readFileSync(filePath, "utf-8").split("\n");
22575
22759
  } catch {
22576
22760
  continue;
22577
22761
  }
@@ -22649,10 +22833,10 @@ function buildCodexSessions(days, allAuditEntries) {
22649
22833
  return summaries;
22650
22834
  }
22651
22835
  function buildSessions(days, historyPath) {
22652
- const hPath = historyPath ?? path42.join(os36.homedir(), ".claude", "history.jsonl");
22836
+ const hPath = historyPath ?? path43.join(os37.homedir(), ".claude", "history.jsonl");
22653
22837
  let historyRaw;
22654
22838
  try {
22655
- historyRaw = fs41.readFileSync(hPath, "utf-8");
22839
+ historyRaw = fs42.readFileSync(hPath, "utf-8");
22656
22840
  } catch {
22657
22841
  return [];
22658
22842
  }
@@ -22677,7 +22861,7 @@ function buildSessions(days, historyPath) {
22677
22861
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
22678
22862
  let sessionLines = [];
22679
22863
  try {
22680
- sessionLines = fs41.readFileSync(jsonlFile, "utf-8").split("\n");
22864
+ sessionLines = fs42.readFileSync(jsonlFile, "utf-8").split("\n");
22681
22865
  } catch {
22682
22866
  }
22683
22867
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -22945,8 +23129,8 @@ function registerSessionsCommand(program2) {
22945
23129
  console.log("");
22946
23130
  console.log(chalk24.cyan.bold("\u{1F4CB} node9 sessions") + chalk24.dim(" \u2014 what your AI agent did"));
22947
23131
  console.log("");
22948
- const historyPath = path42.join(os36.homedir(), ".claude", "history.jsonl");
22949
- if (!fs41.existsSync(historyPath)) {
23132
+ const historyPath = path43.join(os37.homedir(), ".claude", "history.jsonl");
23133
+ if (!fs42.existsSync(historyPath)) {
22950
23134
  console.log(chalk24.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
22951
23135
  console.log(chalk24.gray(" Install Claude Code, run a few sessions, then try again.\n"));
22952
23136
  return;
@@ -22983,12 +23167,12 @@ function registerSessionsCommand(program2) {
22983
23167
 
22984
23168
  // src/cli/commands/skill-pin.ts
22985
23169
  import chalk25 from "chalk";
22986
- import fs42 from "fs";
22987
- import os37 from "os";
22988
- import path43 from "path";
23170
+ import fs43 from "fs";
23171
+ import os38 from "os";
23172
+ import path44 from "path";
22989
23173
  function wipeSkillSessions() {
22990
23174
  try {
22991
- fs42.rmSync(path43.join(os37.homedir(), ".node9", "skill-sessions"), {
23175
+ fs43.rmSync(path44.join(os38.homedir(), ".node9", "skill-sessions"), {
22992
23176
  recursive: true,
22993
23177
  force: true
22994
23178
  });
@@ -23070,15 +23254,15 @@ function registerSkillPinCommand(program2) {
23070
23254
  }
23071
23255
 
23072
23256
  // src/cli/commands/decisions.ts
23073
- import fs43 from "fs";
23074
- import os38 from "os";
23075
- import path44 from "path";
23257
+ import fs44 from "fs";
23258
+ import os39 from "os";
23259
+ import path45 from "path";
23076
23260
  import chalk26 from "chalk";
23077
- var DECISIONS_FILE2 = path44.join(os38.homedir(), ".node9", "decisions.json");
23261
+ var DECISIONS_FILE2 = path45.join(os39.homedir(), ".node9", "decisions.json");
23078
23262
  function readDecisions() {
23079
23263
  try {
23080
- if (!fs43.existsSync(DECISIONS_FILE2)) return {};
23081
- const raw = fs43.readFileSync(DECISIONS_FILE2, "utf-8");
23264
+ if (!fs44.existsSync(DECISIONS_FILE2)) return {};
23265
+ const raw = fs44.readFileSync(DECISIONS_FILE2, "utf-8");
23082
23266
  const parsed = JSON.parse(raw);
23083
23267
  const out = {};
23084
23268
  for (const [k, v] of Object.entries(parsed)) {
@@ -23090,11 +23274,11 @@ function readDecisions() {
23090
23274
  }
23091
23275
  }
23092
23276
  function writeDecisions(d) {
23093
- const dir = path44.dirname(DECISIONS_FILE2);
23094
- if (!fs43.existsSync(dir)) fs43.mkdirSync(dir, { recursive: true });
23277
+ const dir = path45.dirname(DECISIONS_FILE2);
23278
+ if (!fs44.existsSync(dir)) fs44.mkdirSync(dir, { recursive: true });
23095
23279
  const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
23096
- fs43.writeFileSync(tmp, JSON.stringify(d, null, 2));
23097
- fs43.renameSync(tmp, DECISIONS_FILE2);
23280
+ fs44.writeFileSync(tmp, JSON.stringify(d, null, 2));
23281
+ fs44.renameSync(tmp, DECISIONS_FILE2);
23098
23282
  }
23099
23283
  function registerDecisionsCommand(program2) {
23100
23284
  const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
@@ -23151,18 +23335,18 @@ Persistent decisions (${entries.length})
23151
23335
 
23152
23336
  // src/cli/commands/dlp.ts
23153
23337
  import chalk27 from "chalk";
23154
- import fs44 from "fs";
23155
- import path45 from "path";
23156
- import os39 from "os";
23157
- var AUDIT_LOG = path45.join(os39.homedir(), ".node9", "audit.log");
23158
- var RESOLVED_FILE = path45.join(os39.homedir(), ".node9", "dlp-resolved.json");
23338
+ import fs45 from "fs";
23339
+ import path46 from "path";
23340
+ import os40 from "os";
23341
+ var AUDIT_LOG = path46.join(os40.homedir(), ".node9", "audit.log");
23342
+ var RESOLVED_FILE = path46.join(os40.homedir(), ".node9", "dlp-resolved.json");
23159
23343
  var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
23160
23344
  function stripAnsi(s) {
23161
23345
  return s.replace(ANSI_RE, "");
23162
23346
  }
23163
23347
  function loadResolved() {
23164
23348
  try {
23165
- const raw = JSON.parse(fs44.readFileSync(RESOLVED_FILE, "utf-8"));
23349
+ const raw = JSON.parse(fs45.readFileSync(RESOLVED_FILE, "utf-8"));
23166
23350
  return new Set(raw);
23167
23351
  } catch {
23168
23352
  return /* @__PURE__ */ new Set();
@@ -23170,13 +23354,13 @@ function loadResolved() {
23170
23354
  }
23171
23355
  function saveResolved(resolved) {
23172
23356
  try {
23173
- fs44.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
23357
+ fs45.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
23174
23358
  } catch {
23175
23359
  }
23176
23360
  }
23177
23361
  function loadDlpFindings() {
23178
- if (!fs44.existsSync(AUDIT_LOG)) return [];
23179
- return fs44.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
23362
+ if (!fs45.existsSync(AUDIT_LOG)) return [];
23363
+ return fs45.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
23180
23364
  if (!line.trim()) return [];
23181
23365
  try {
23182
23366
  const e = JSON.parse(line);
@@ -23275,14 +23459,14 @@ function registerDlpCommand(program2) {
23275
23459
  // src/cli/commands/mask.ts
23276
23460
  init_dlp();
23277
23461
  import chalk28 from "chalk";
23278
- import fs45 from "fs";
23279
- import path46 from "path";
23280
- import os40 from "os";
23462
+ import fs46 from "fs";
23463
+ import path47 from "path";
23464
+ import os41 from "os";
23281
23465
  function findJsonlFiles(dir) {
23282
23466
  const results = [];
23283
- if (!fs45.existsSync(dir)) return results;
23284
- for (const entry of fs45.readdirSync(dir, { withFileTypes: true })) {
23285
- const full = path46.join(dir, entry.name);
23467
+ if (!fs46.existsSync(dir)) return results;
23468
+ for (const entry of fs46.readdirSync(dir, { withFileTypes: true })) {
23469
+ const full = path47.join(dir, entry.name);
23286
23470
  if (entry.isDirectory()) results.push(...findJsonlFiles(full));
23287
23471
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
23288
23472
  }
@@ -23325,7 +23509,7 @@ function redactJson(obj) {
23325
23509
  function processFile(filePath, dryRun) {
23326
23510
  let raw;
23327
23511
  try {
23328
- raw = fs45.readFileSync(filePath, "utf-8");
23512
+ raw = fs46.readFileSync(filePath, "utf-8");
23329
23513
  } catch {
23330
23514
  return { redactedLines: 0, patterns: [] };
23331
23515
  }
@@ -23357,14 +23541,14 @@ function processFile(filePath, dryRun) {
23357
23541
  }
23358
23542
  }
23359
23543
  if (!dryRun && redactedLines > 0) {
23360
- fs45.writeFileSync(filePath, newLines.join("\n"), "utf-8");
23544
+ fs46.writeFileSync(filePath, newLines.join("\n"), "utf-8");
23361
23545
  }
23362
23546
  return { redactedLines, patterns };
23363
23547
  }
23364
23548
  function processJsonFile(filePath, dryRun) {
23365
23549
  let raw;
23366
23550
  try {
23367
- raw = fs45.readFileSync(filePath, "utf-8");
23551
+ raw = fs46.readFileSync(filePath, "utf-8");
23368
23552
  } catch {
23369
23553
  return { redactedLines: 0, patterns: [] };
23370
23554
  }
@@ -23377,15 +23561,15 @@ function processJsonFile(filePath, dryRun) {
23377
23561
  const { value, modified, found } = redactJson(parsed);
23378
23562
  if (!modified) return { redactedLines: 0, patterns: [] };
23379
23563
  if (!dryRun) {
23380
- fs45.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
23564
+ fs46.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
23381
23565
  }
23382
23566
  return { redactedLines: 1, patterns: found };
23383
23567
  }
23384
23568
  function findJsonFiles(dir) {
23385
23569
  const results = [];
23386
- if (!fs45.existsSync(dir)) return results;
23387
- for (const entry of fs45.readdirSync(dir, { withFileTypes: true })) {
23388
- const full = path46.join(dir, entry.name);
23570
+ if (!fs46.existsSync(dir)) return results;
23571
+ for (const entry of fs46.readdirSync(dir, { withFileTypes: true })) {
23572
+ const full = path47.join(dir, entry.name);
23389
23573
  if (entry.isDirectory()) results.push(...findJsonFiles(full));
23390
23574
  else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
23391
23575
  }
@@ -23394,9 +23578,9 @@ function findJsonFiles(dir) {
23394
23578
  function registerMaskCommand(program2) {
23395
23579
  program2.command("mask").description("Redact plaintext secrets from local AI session history files").option("--dry-run", "show what would be redacted without making changes").option("--all", "scan all history (default: last 30 days)").action(async (options) => {
23396
23580
  const dryRun = !!options.dryRun;
23397
- const home = os40.homedir();
23398
- const claudeDir = path46.join(home, ".claude", "projects");
23399
- const geminiDir = path46.join(home, ".gemini", "tmp");
23581
+ const home = os41.homedir();
23582
+ const claudeDir = path47.join(home, ".claude", "projects");
23583
+ const geminiDir = path47.join(home, ".gemini", "tmp");
23400
23584
  const allFiles = [
23401
23585
  ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
23402
23586
  ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
@@ -23404,7 +23588,7 @@ function registerMaskCommand(program2) {
23404
23588
  const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
23405
23589
  const filtered = cutoff ? allFiles.filter((f) => {
23406
23590
  try {
23407
- return fs45.statSync(f.path).mtime >= cutoff;
23591
+ return fs46.statSync(f.path).mtime >= cutoff;
23408
23592
  } catch {
23409
23593
  return false;
23410
23594
  }
@@ -23460,20 +23644,20 @@ function registerMaskCommand(program2) {
23460
23644
  // src/cli.ts
23461
23645
  init_blast();
23462
23646
  var { version } = JSON.parse(
23463
- fs48.readFileSync(path49.join(__dirname, "../package.json"), "utf-8")
23647
+ fs49.readFileSync(path50.join(__dirname, "../package.json"), "utf-8")
23464
23648
  );
23465
23649
  var program = new Command();
23466
23650
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
23467
23651
  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) => {
23468
23652
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
23469
- const credPath = path49.join(os43.homedir(), ".node9", "credentials.json");
23470
- if (!fs48.existsSync(path49.dirname(credPath)))
23471
- fs48.mkdirSync(path49.dirname(credPath), { recursive: true });
23653
+ const credPath = path50.join(os44.homedir(), ".node9", "credentials.json");
23654
+ if (!fs49.existsSync(path50.dirname(credPath)))
23655
+ fs49.mkdirSync(path50.dirname(credPath), { recursive: true });
23472
23656
  const profileName = options.profile || "default";
23473
23657
  let existingCreds = {};
23474
23658
  try {
23475
- if (fs48.existsSync(credPath)) {
23476
- const raw = JSON.parse(fs48.readFileSync(credPath, "utf-8"));
23659
+ if (fs49.existsSync(credPath)) {
23660
+ const raw = JSON.parse(fs49.readFileSync(credPath, "utf-8"));
23477
23661
  if (raw.apiKey) {
23478
23662
  existingCreds = {
23479
23663
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -23485,13 +23669,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
23485
23669
  } catch {
23486
23670
  }
23487
23671
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
23488
- fs48.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
23672
+ fs49.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
23673
+ let effectiveCloud = null;
23489
23674
  if (profileName === "default") {
23490
- const configPath = path49.join(os43.homedir(), ".node9", "config.json");
23675
+ const configPath = path50.join(os44.homedir(), ".node9", "config.json");
23491
23676
  let config = {};
23492
23677
  try {
23493
- if (fs48.existsSync(configPath))
23494
- config = JSON.parse(fs48.readFileSync(configPath, "utf-8"));
23678
+ if (fs49.existsSync(configPath))
23679
+ config = JSON.parse(fs49.readFileSync(configPath, "utf-8"));
23495
23680
  } catch {
23496
23681
  }
23497
23682
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -23506,16 +23691,25 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
23506
23691
  approvers.cloud = false;
23507
23692
  }
23508
23693
  s.approvers = approvers;
23509
- if (!fs48.existsSync(path49.dirname(configPath)))
23510
- fs48.mkdirSync(path49.dirname(configPath), { recursive: true });
23511
- fs48.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
23694
+ if (!fs49.existsSync(path50.dirname(configPath)))
23695
+ fs49.mkdirSync(path50.dirname(configPath), { recursive: true });
23696
+ fs49.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
23697
+ effectiveCloud = approvers.cloud === true;
23512
23698
  }
23513
23699
  if (options.profile && profileName !== "default") {
23514
23700
  console.log(chalk30.green(`\u2705 Profile "${profileName}" saved`));
23515
23701
  console.log(chalk30.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
23516
- } else if (options.local) {
23517
- console.log(chalk30.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
23518
- console.log(chalk30.gray(` All decisions stay on this machine.`));
23702
+ } else if (options.local || effectiveCloud === false) {
23703
+ console.log(chalk30.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
23704
+ console.log(chalk30.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
23705
+ if (!options.local) {
23706
+ console.log(
23707
+ chalk30.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
23708
+ );
23709
+ console.log(
23710
+ chalk30.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
23711
+ );
23712
+ }
23519
23713
  } else {
23520
23714
  console.log(chalk30.green(`\u2705 Logged in \u2014 agent mode`));
23521
23715
  console.log(chalk30.gray(` Team policy enforced for all calls via Node9 cloud.`));
@@ -23658,15 +23852,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
23658
23852
  }
23659
23853
  }
23660
23854
  if (options.purge) {
23661
- const node9Dir = path49.join(os43.homedir(), ".node9");
23662
- if (fs48.existsSync(node9Dir)) {
23855
+ const node9Dir = path50.join(os44.homedir(), ".node9");
23856
+ if (fs49.existsSync(node9Dir)) {
23663
23857
  const confirmed = await confirm2({
23664
23858
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
23665
23859
  default: false
23666
23860
  });
23667
23861
  if (confirmed) {
23668
- fs48.rmSync(node9Dir, { recursive: true });
23669
- if (fs48.existsSync(node9Dir)) {
23862
+ fs49.rmSync(node9Dir, { recursive: true });
23863
+ if (fs49.existsSync(node9Dir)) {
23670
23864
  console.error(
23671
23865
  chalk30.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
23672
23866
  );
@@ -23781,7 +23975,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
23781
23975
  });
23782
23976
  program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
23783
23977
  try {
23784
- const dashboardPath = path49.join(__dirname, "dashboard.mjs");
23978
+ const dashboardPath = path50.join(__dirname, "dashboard.mjs");
23785
23979
  const dynamicImport = new Function("id", "return import(id)");
23786
23980
  const mod = await dynamicImport(`file://${dashboardPath}`);
23787
23981
  await mod.startMonitor();
@@ -23819,14 +24013,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
23819
24013
  Run "node9 addto claude" to register it as the statusLine.`
23820
24014
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
23821
24015
  if (subcommand === "debug") {
23822
- const flagFile = path49.join(os43.homedir(), ".node9", "hud-debug");
24016
+ const flagFile = path50.join(os44.homedir(), ".node9", "hud-debug");
23823
24017
  if (state === "on") {
23824
- fs48.mkdirSync(path49.dirname(flagFile), { recursive: true });
23825
- fs48.writeFileSync(flagFile, "");
24018
+ fs49.mkdirSync(path50.dirname(flagFile), { recursive: true });
24019
+ fs49.writeFileSync(flagFile, "");
23826
24020
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
23827
24021
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
23828
24022
  } else if (state === "off") {
23829
- if (fs48.existsSync(flagFile)) fs48.unlinkSync(flagFile);
24023
+ if (fs49.existsSync(flagFile)) fs49.unlinkSync(flagFile);
23830
24024
  console.log("HUD debug logging disabled.");
23831
24025
  } else {
23832
24026
  console.error("Usage: node9 hud debug on|off");
@@ -23943,9 +24137,9 @@ if (process.argv[2] !== "daemon") {
23943
24137
  const isCheckHook = process.argv[2] === "check";
23944
24138
  if (isCheckHook) {
23945
24139
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
23946
- const logPath = path49.join(os43.homedir(), ".node9", "hook-debug.log");
24140
+ const logPath = path50.join(os44.homedir(), ".node9", "hook-debug.log");
23947
24141
  const msg = reason instanceof Error ? reason.message : String(reason);
23948
- fs48.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
24142
+ fs49.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
23949
24143
  `);
23950
24144
  }
23951
24145
  process.exit(0);