@node9/proxy 1.11.13 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +676 -596
- package/dist/cli.mjs +662 -582
- package/dist/index.js +9 -1
- package/dist/index.mjs +9 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -147,8 +147,8 @@ function sanitizeConfig(raw) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
const lines = result.error.issues.map((issue) => {
|
|
150
|
-
const
|
|
151
|
-
return ` \u2022 ${
|
|
150
|
+
const path45 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
151
|
+
return ` \u2022 ${path45}: ${issue.message}`;
|
|
152
152
|
});
|
|
153
153
|
return {
|
|
154
154
|
sanitized,
|
|
@@ -2066,9 +2066,9 @@ function matchesPattern(text, patterns) {
|
|
|
2066
2066
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
2067
2067
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
2068
2068
|
}
|
|
2069
|
-
function getNestedValue(obj,
|
|
2069
|
+
function getNestedValue(obj, path45) {
|
|
2070
2070
|
if (!obj || typeof obj !== "object") return null;
|
|
2071
|
-
return
|
|
2071
|
+
return path45.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
2072
2072
|
}
|
|
2073
2073
|
function normalizeCommandForPolicy(command) {
|
|
2074
2074
|
try {
|
|
@@ -2327,7 +2327,12 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
2327
2327
|
pathTokens = analyzed.paths;
|
|
2328
2328
|
const INLINE_EXEC_PATTERN = /^(python3?|bash|sh|zsh|perl|ruby|node|php|lua)\s+(-c|-e|-eval)\s/i;
|
|
2329
2329
|
if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
|
|
2330
|
-
return {
|
|
2330
|
+
return {
|
|
2331
|
+
decision: "review",
|
|
2332
|
+
blockedByLabel: "Node9 Standard (Inline Execution)",
|
|
2333
|
+
ruleDescription: "The AI is running code directly from the command line. Review the full script below before allowing it to execute.",
|
|
2334
|
+
tier: 3
|
|
2335
|
+
};
|
|
2331
2336
|
}
|
|
2332
2337
|
const evalVerdict = detectDangerousShellExec(shellCommand);
|
|
2333
2338
|
if (evalVerdict === "block") {
|
|
@@ -2335,6 +2340,7 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
2335
2340
|
decision: "block",
|
|
2336
2341
|
blockedByLabel: "Node9: Eval Remote Execution",
|
|
2337
2342
|
reason: "eval of remote download (curl/wget) is a near-certain supply-chain attack",
|
|
2343
|
+
ruleDescription: "The AI is downloading a script from the internet and running it immediately without inspection. This is a common way malware gets installed.",
|
|
2338
2344
|
tier: 3
|
|
2339
2345
|
};
|
|
2340
2346
|
}
|
|
@@ -2343,6 +2349,7 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
2343
2349
|
decision: "review",
|
|
2344
2350
|
blockedByLabel: "Node9: Eval Dynamic Content",
|
|
2345
2351
|
reason: "eval of dynamic content (variable or subshell expansion) requires approval",
|
|
2352
|
+
ruleDescription: "The AI is running a command that includes a variable or subshell expansion. The actual command executed at runtime may differ from what is shown here.",
|
|
2346
2353
|
tier: 3
|
|
2347
2354
|
};
|
|
2348
2355
|
}
|
|
@@ -2472,6 +2479,7 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
2472
2479
|
blockedByLabel: `Project/Global Config \u2014 dangerous word: "${matchedDangerousWord}"`,
|
|
2473
2480
|
matchedWord: matchedDangerousWord,
|
|
2474
2481
|
matchedField,
|
|
2482
|
+
ruleDescription: `This command contains a flagged keyword ("${matchedDangerousWord}") from your node9 config. Review it before allowing.`,
|
|
2475
2483
|
tier: 6
|
|
2476
2484
|
};
|
|
2477
2485
|
}
|
|
@@ -2624,7 +2632,8 @@ async function explainPolicy(toolName, args) {
|
|
|
2624
2632
|
waterfall,
|
|
2625
2633
|
steps,
|
|
2626
2634
|
decision: "review",
|
|
2627
|
-
blockedByLabel: "Node9 Standard (Inline Execution)"
|
|
2635
|
+
blockedByLabel: "Node9 Standard (Inline Execution)",
|
|
2636
|
+
ruleDescription: "The AI is running code directly from the command line. Review the full script below before allowing it to execute."
|
|
2628
2637
|
};
|
|
2629
2638
|
}
|
|
2630
2639
|
steps.push({
|
|
@@ -2730,7 +2739,8 @@ async function explainPolicy(toolName, args) {
|
|
|
2730
2739
|
steps,
|
|
2731
2740
|
decision: "review",
|
|
2732
2741
|
blockedByLabel: `Project/Global Config \u2014 dangerous word: "${matchedDangerousWord}"`,
|
|
2733
|
-
matchedToken: matchedDangerousWord
|
|
2742
|
+
matchedToken: matchedDangerousWord,
|
|
2743
|
+
ruleDescription: `This command contains a flagged keyword ("${matchedDangerousWord}") from your node9 config. Review it before allowing.`
|
|
2734
2744
|
};
|
|
2735
2745
|
}
|
|
2736
2746
|
steps.push({
|
|
@@ -5243,6 +5253,10 @@ var init_ui = __esm({
|
|
|
5243
5253
|
border-color: rgba(83, 155, 245, 0.35);
|
|
5244
5254
|
background: rgba(83, 155, 245, 0.04);
|
|
5245
5255
|
}
|
|
5256
|
+
.pending-card.discovery-card {
|
|
5257
|
+
border-color: rgba(120, 100, 255, 0.45);
|
|
5258
|
+
background: rgba(100, 80, 255, 0.06);
|
|
5259
|
+
}
|
|
5246
5260
|
.pending-card-header {
|
|
5247
5261
|
display: flex;
|
|
5248
5262
|
align-items: center;
|
|
@@ -7429,7 +7443,7 @@ var init_ui = __esm({
|
|
|
7429
7443
|
});
|
|
7430
7444
|
|
|
7431
7445
|
try {
|
|
7432
|
-
await fetch('/mcp/approve', {
|
|
7446
|
+
const res = await fetch('/mcp/approve', {
|
|
7433
7447
|
method: 'POST',
|
|
7434
7448
|
headers: {
|
|
7435
7449
|
'Content-Type': 'application/json',
|
|
@@ -7437,9 +7451,19 @@ var init_ui = __esm({
|
|
|
7437
7451
|
},
|
|
7438
7452
|
body: JSON.stringify({ id, serverKey, disabledTools }),
|
|
7439
7453
|
});
|
|
7454
|
+
if (!res.ok) {
|
|
7455
|
+
showToast(
|
|
7456
|
+
'\u26A0\uFE0F',
|
|
7457
|
+
'Approval failed',
|
|
7458
|
+
\`Server responded with \${res.status}\`,
|
|
7459
|
+
'toast-block'
|
|
7460
|
+
);
|
|
7461
|
+
return;
|
|
7462
|
+
}
|
|
7440
7463
|
closeMcpReview();
|
|
7441
7464
|
refreshMcpTools();
|
|
7442
7465
|
} catch (err) {
|
|
7466
|
+
showToast('\u26A0\uFE0F', 'Approval failed', 'Network error \u2014 check the daemon', 'toast-block');
|
|
7443
7467
|
console.error('Failed to approve MCP server:', err);
|
|
7444
7468
|
}
|
|
7445
7469
|
}
|
|
@@ -8254,6 +8278,11 @@ var init_ui2 = __esm({
|
|
|
8254
8278
|
|
|
8255
8279
|
// src/cli/daemon-starter.ts
|
|
8256
8280
|
import { spawn as spawn2, execSync } from "child_process";
|
|
8281
|
+
import path16 from "path";
|
|
8282
|
+
import fs13 from "fs";
|
|
8283
|
+
function isTestingMode() {
|
|
8284
|
+
return /^(1|true|yes)$/i.test(process.env.NODE9_TESTING ?? "");
|
|
8285
|
+
}
|
|
8257
8286
|
function openBrowserLocal() {
|
|
8258
8287
|
const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
|
|
8259
8288
|
try {
|
|
@@ -8264,26 +8293,41 @@ function openBrowserLocal() {
|
|
|
8264
8293
|
} catch {
|
|
8265
8294
|
}
|
|
8266
8295
|
}
|
|
8267
|
-
async function autoStartDaemonAndWait() {
|
|
8268
|
-
if (
|
|
8296
|
+
async function autoStartDaemonAndWait(openBrowser2 = true) {
|
|
8297
|
+
if (isTestingMode()) return false;
|
|
8298
|
+
if (!path16.isAbsolute(process.argv[1])) return false;
|
|
8299
|
+
let resolvedArgv1;
|
|
8269
8300
|
try {
|
|
8270
|
-
|
|
8301
|
+
resolvedArgv1 = fs13.realpathSync(process.argv[1]);
|
|
8302
|
+
} catch {
|
|
8303
|
+
return false;
|
|
8304
|
+
}
|
|
8305
|
+
if (!resolvedArgv1.endsWith(".js")) return false;
|
|
8306
|
+
try {
|
|
8307
|
+
const child = spawn2(process.execPath, [resolvedArgv1, "daemon"], {
|
|
8271
8308
|
detached: true,
|
|
8272
8309
|
stdio: "ignore",
|
|
8273
8310
|
// NODE9_BROWSER_OPENED=1 tells the daemon we will open the browser ourselves
|
|
8274
8311
|
// (openBrowserLocal below), so it must not open a duplicate tab on first approval.
|
|
8275
|
-
|
|
8312
|
+
// Only set NODE9_BROWSER_OPENED when we actually intend to open it here.
|
|
8313
|
+
env: {
|
|
8314
|
+
...process.env,
|
|
8315
|
+
NODE9_AUTO_STARTED: "1",
|
|
8316
|
+
...openBrowser2 && { NODE9_BROWSER_OPENED: "1" }
|
|
8317
|
+
}
|
|
8276
8318
|
});
|
|
8277
8319
|
child.unref();
|
|
8278
8320
|
for (let i = 0; i < 20; i++) {
|
|
8279
8321
|
await new Promise((r) => setTimeout(r, 250));
|
|
8280
8322
|
if (!isDaemonRunning()) continue;
|
|
8281
8323
|
try {
|
|
8282
|
-
const res = await fetch(
|
|
8324
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/settings`, {
|
|
8283
8325
|
signal: AbortSignal.timeout(500)
|
|
8284
8326
|
});
|
|
8285
8327
|
if (res.ok) {
|
|
8286
|
-
|
|
8328
|
+
if (openBrowser2) {
|
|
8329
|
+
openBrowserLocal();
|
|
8330
|
+
}
|
|
8287
8331
|
return true;
|
|
8288
8332
|
}
|
|
8289
8333
|
} catch {
|
|
@@ -8491,8 +8535,8 @@ var init_scan_summary = __esm({
|
|
|
8491
8535
|
|
|
8492
8536
|
// src/cli/commands/scan.ts
|
|
8493
8537
|
import chalk2 from "chalk";
|
|
8494
|
-
import
|
|
8495
|
-
import
|
|
8538
|
+
import fs14 from "fs";
|
|
8539
|
+
import path17 from "path";
|
|
8496
8540
|
import os12 from "os";
|
|
8497
8541
|
function claudeModelPrice(model) {
|
|
8498
8542
|
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
@@ -8592,14 +8636,14 @@ function buildRuleSources() {
|
|
|
8592
8636
|
}
|
|
8593
8637
|
function countScanFiles() {
|
|
8594
8638
|
let total = 0;
|
|
8595
|
-
const claudeDir =
|
|
8596
|
-
if (
|
|
8639
|
+
const claudeDir = path17.join(os12.homedir(), ".claude", "projects");
|
|
8640
|
+
if (fs14.existsSync(claudeDir)) {
|
|
8597
8641
|
try {
|
|
8598
|
-
for (const proj of
|
|
8599
|
-
const p =
|
|
8642
|
+
for (const proj of fs14.readdirSync(claudeDir)) {
|
|
8643
|
+
const p = path17.join(claudeDir, proj);
|
|
8600
8644
|
try {
|
|
8601
|
-
if (!
|
|
8602
|
-
total +=
|
|
8645
|
+
if (!fs14.statSync(p).isDirectory()) continue;
|
|
8646
|
+
total += fs14.readdirSync(p).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).length;
|
|
8603
8647
|
} catch {
|
|
8604
8648
|
continue;
|
|
8605
8649
|
}
|
|
@@ -8607,17 +8651,17 @@ function countScanFiles() {
|
|
|
8607
8651
|
} catch {
|
|
8608
8652
|
}
|
|
8609
8653
|
}
|
|
8610
|
-
const geminiDir =
|
|
8611
|
-
if (
|
|
8654
|
+
const geminiDir = path17.join(os12.homedir(), ".gemini", "tmp");
|
|
8655
|
+
if (fs14.existsSync(geminiDir)) {
|
|
8612
8656
|
try {
|
|
8613
|
-
for (const slug of
|
|
8614
|
-
const p =
|
|
8657
|
+
for (const slug of fs14.readdirSync(geminiDir)) {
|
|
8658
|
+
const p = path17.join(geminiDir, slug);
|
|
8615
8659
|
try {
|
|
8616
|
-
if (!
|
|
8617
|
-
const chatsDir =
|
|
8618
|
-
if (
|
|
8660
|
+
if (!fs14.statSync(p).isDirectory()) continue;
|
|
8661
|
+
const chatsDir = path17.join(p, "chats");
|
|
8662
|
+
if (fs14.existsSync(chatsDir)) {
|
|
8619
8663
|
try {
|
|
8620
|
-
total +=
|
|
8664
|
+
total += fs14.readdirSync(chatsDir).filter((f) => f.endsWith(".json")).length;
|
|
8621
8665
|
} catch {
|
|
8622
8666
|
}
|
|
8623
8667
|
}
|
|
@@ -8628,22 +8672,22 @@ function countScanFiles() {
|
|
|
8628
8672
|
} catch {
|
|
8629
8673
|
}
|
|
8630
8674
|
}
|
|
8631
|
-
const codexDir =
|
|
8632
|
-
if (
|
|
8675
|
+
const codexDir = path17.join(os12.homedir(), ".codex", "sessions");
|
|
8676
|
+
if (fs14.existsSync(codexDir)) {
|
|
8633
8677
|
try {
|
|
8634
|
-
for (const year of
|
|
8635
|
-
const yp =
|
|
8678
|
+
for (const year of fs14.readdirSync(codexDir)) {
|
|
8679
|
+
const yp = path17.join(codexDir, year);
|
|
8636
8680
|
try {
|
|
8637
|
-
if (!
|
|
8638
|
-
for (const month of
|
|
8639
|
-
const mp =
|
|
8681
|
+
if (!fs14.statSync(yp).isDirectory()) continue;
|
|
8682
|
+
for (const month of fs14.readdirSync(yp)) {
|
|
8683
|
+
const mp = path17.join(yp, month);
|
|
8640
8684
|
try {
|
|
8641
|
-
if (!
|
|
8642
|
-
for (const day of
|
|
8643
|
-
const dp =
|
|
8685
|
+
if (!fs14.statSync(mp).isDirectory()) continue;
|
|
8686
|
+
for (const day of fs14.readdirSync(mp)) {
|
|
8687
|
+
const dp = path17.join(mp, day);
|
|
8644
8688
|
try {
|
|
8645
|
-
if (!
|
|
8646
|
-
total +=
|
|
8689
|
+
if (!fs14.statSync(dp).isDirectory()) continue;
|
|
8690
|
+
total += fs14.readdirSync(dp).filter((f) => f.endsWith(".jsonl")).length;
|
|
8647
8691
|
} catch {
|
|
8648
8692
|
continue;
|
|
8649
8693
|
}
|
|
@@ -8661,18 +8705,19 @@ function countScanFiles() {
|
|
|
8661
8705
|
}
|
|
8662
8706
|
return total;
|
|
8663
8707
|
}
|
|
8664
|
-
function renderProgressBar(done, total) {
|
|
8708
|
+
function renderProgressBar(done, total, lines) {
|
|
8665
8709
|
const width = 28;
|
|
8666
8710
|
const pct = total > 0 ? done / total : 0;
|
|
8667
8711
|
const filled = Math.min(width, Math.round(pct * width));
|
|
8668
8712
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
8669
|
-
const
|
|
8713
|
+
const fileLabel = total > 0 ? `${done}/${total} files` : `${done} files`;
|
|
8714
|
+
const lineLabel = lines > 0 ? chalk2.dim(` ${lines.toLocaleString()} lines`) : "";
|
|
8670
8715
|
process.stdout.write(
|
|
8671
|
-
`\r ${chalk2.cyan("Scanning")} [${chalk2.cyan(bar)}] ${chalk2.dim(
|
|
8716
|
+
`\r ${chalk2.cyan("Scanning")} [${chalk2.cyan(bar)}] ${chalk2.dim(fileLabel)}${lineLabel} `
|
|
8672
8717
|
);
|
|
8673
8718
|
}
|
|
8674
|
-
function scanClaudeHistory(startDate, onProgress) {
|
|
8675
|
-
const projectsDir =
|
|
8719
|
+
function scanClaudeHistory(startDate, onProgress, onLine) {
|
|
8720
|
+
const projectsDir = path17.join(os12.homedir(), ".claude", "projects");
|
|
8676
8721
|
const result = {
|
|
8677
8722
|
filesScanned: 0,
|
|
8678
8723
|
sessions: 0,
|
|
@@ -8685,25 +8730,25 @@ function scanClaudeHistory(startDate, onProgress) {
|
|
|
8685
8730
|
firstDate: null,
|
|
8686
8731
|
lastDate: null
|
|
8687
8732
|
};
|
|
8688
|
-
if (!
|
|
8733
|
+
if (!fs14.existsSync(projectsDir)) return result;
|
|
8689
8734
|
let projDirs;
|
|
8690
8735
|
try {
|
|
8691
|
-
projDirs =
|
|
8736
|
+
projDirs = fs14.readdirSync(projectsDir);
|
|
8692
8737
|
} catch {
|
|
8693
8738
|
return result;
|
|
8694
8739
|
}
|
|
8695
8740
|
const ruleSources = buildRuleSources();
|
|
8696
8741
|
for (const proj of projDirs) {
|
|
8697
|
-
const projPath =
|
|
8742
|
+
const projPath = path17.join(projectsDir, proj);
|
|
8698
8743
|
try {
|
|
8699
|
-
if (!
|
|
8744
|
+
if (!fs14.statSync(projPath).isDirectory()) continue;
|
|
8700
8745
|
} catch {
|
|
8701
8746
|
continue;
|
|
8702
8747
|
}
|
|
8703
8748
|
const projLabel = decodeURIComponent(proj).replace(os12.homedir(), "~").slice(0, 40);
|
|
8704
8749
|
let files;
|
|
8705
8750
|
try {
|
|
8706
|
-
files =
|
|
8751
|
+
files = fs14.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
8707
8752
|
} catch {
|
|
8708
8753
|
continue;
|
|
8709
8754
|
}
|
|
@@ -8714,13 +8759,14 @@ function scanClaudeHistory(startDate, onProgress) {
|
|
|
8714
8759
|
const sessionId = file.replace(/\.jsonl$/, "");
|
|
8715
8760
|
let raw;
|
|
8716
8761
|
try {
|
|
8717
|
-
raw =
|
|
8762
|
+
raw = fs14.readFileSync(path17.join(projPath, file), "utf-8");
|
|
8718
8763
|
} catch {
|
|
8719
8764
|
continue;
|
|
8720
8765
|
}
|
|
8721
8766
|
const sessionCalls = [];
|
|
8722
8767
|
for (const line of raw.split("\n")) {
|
|
8723
8768
|
if (!line.trim()) continue;
|
|
8769
|
+
onLine?.();
|
|
8724
8770
|
let entry;
|
|
8725
8771
|
try {
|
|
8726
8772
|
entry = JSON.parse(line);
|
|
@@ -8866,8 +8912,8 @@ function scanClaudeHistory(startDate, onProgress) {
|
|
|
8866
8912
|
}
|
|
8867
8913
|
return result;
|
|
8868
8914
|
}
|
|
8869
|
-
function scanGeminiHistory(startDate, onProgress) {
|
|
8870
|
-
const tmpDir =
|
|
8915
|
+
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
8916
|
+
const tmpDir = path17.join(os12.homedir(), ".gemini", "tmp");
|
|
8871
8917
|
const result = {
|
|
8872
8918
|
filesScanned: 0,
|
|
8873
8919
|
sessions: 0,
|
|
@@ -8880,31 +8926,31 @@ function scanGeminiHistory(startDate, onProgress) {
|
|
|
8880
8926
|
firstDate: null,
|
|
8881
8927
|
lastDate: null
|
|
8882
8928
|
};
|
|
8883
|
-
if (!
|
|
8929
|
+
if (!fs14.existsSync(tmpDir)) return result;
|
|
8884
8930
|
let slugDirs;
|
|
8885
8931
|
try {
|
|
8886
|
-
slugDirs =
|
|
8932
|
+
slugDirs = fs14.readdirSync(tmpDir);
|
|
8887
8933
|
} catch {
|
|
8888
8934
|
return result;
|
|
8889
8935
|
}
|
|
8890
8936
|
const ruleSources = buildRuleSources();
|
|
8891
8937
|
for (const slug of slugDirs) {
|
|
8892
|
-
const slugPath =
|
|
8938
|
+
const slugPath = path17.join(tmpDir, slug);
|
|
8893
8939
|
try {
|
|
8894
|
-
if (!
|
|
8940
|
+
if (!fs14.statSync(slugPath).isDirectory()) continue;
|
|
8895
8941
|
} catch {
|
|
8896
8942
|
continue;
|
|
8897
8943
|
}
|
|
8898
8944
|
let projLabel = slug;
|
|
8899
8945
|
try {
|
|
8900
|
-
projLabel =
|
|
8946
|
+
projLabel = fs14.readFileSync(path17.join(slugPath, ".project_root"), "utf-8").trim().replace(os12.homedir(), "~").slice(0, 40);
|
|
8901
8947
|
} catch {
|
|
8902
8948
|
}
|
|
8903
|
-
const chatsDir =
|
|
8904
|
-
if (!
|
|
8949
|
+
const chatsDir = path17.join(slugPath, "chats");
|
|
8950
|
+
if (!fs14.existsSync(chatsDir)) continue;
|
|
8905
8951
|
let chatFiles;
|
|
8906
8952
|
try {
|
|
8907
|
-
chatFiles =
|
|
8953
|
+
chatFiles = fs14.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
8908
8954
|
} catch {
|
|
8909
8955
|
continue;
|
|
8910
8956
|
}
|
|
@@ -8914,7 +8960,7 @@ function scanGeminiHistory(startDate, onProgress) {
|
|
|
8914
8960
|
const sessionId = chatFile.replace(/\.json$/, "");
|
|
8915
8961
|
let raw;
|
|
8916
8962
|
try {
|
|
8917
|
-
raw =
|
|
8963
|
+
raw = fs14.readFileSync(path17.join(chatsDir, chatFile), "utf-8");
|
|
8918
8964
|
} catch {
|
|
8919
8965
|
continue;
|
|
8920
8966
|
}
|
|
@@ -8927,6 +8973,7 @@ function scanGeminiHistory(startDate, onProgress) {
|
|
|
8927
8973
|
}
|
|
8928
8974
|
result.sessions++;
|
|
8929
8975
|
for (const msg of session.messages ?? []) {
|
|
8976
|
+
onLine?.();
|
|
8930
8977
|
if (msg.type === "user") {
|
|
8931
8978
|
const content = msg.content;
|
|
8932
8979
|
const text = Array.isArray(content) ? content.map((c) => c.text ?? "").join("\n") : typeof content === "string" ? content : "";
|
|
@@ -9062,8 +9109,8 @@ function scanGeminiHistory(startDate, onProgress) {
|
|
|
9062
9109
|
}
|
|
9063
9110
|
return result;
|
|
9064
9111
|
}
|
|
9065
|
-
function scanCodexHistory(startDate, onProgress) {
|
|
9066
|
-
const sessionsBase =
|
|
9112
|
+
function scanCodexHistory(startDate, onProgress, onLine) {
|
|
9113
|
+
const sessionsBase = path17.join(os12.homedir(), ".codex", "sessions");
|
|
9067
9114
|
const result = {
|
|
9068
9115
|
filesScanned: 0,
|
|
9069
9116
|
sessions: 0,
|
|
@@ -9076,32 +9123,32 @@ function scanCodexHistory(startDate, onProgress) {
|
|
|
9076
9123
|
firstDate: null,
|
|
9077
9124
|
lastDate: null
|
|
9078
9125
|
};
|
|
9079
|
-
if (!
|
|
9126
|
+
if (!fs14.existsSync(sessionsBase)) return result;
|
|
9080
9127
|
const jsonlFiles = [];
|
|
9081
9128
|
try {
|
|
9082
|
-
for (const year of
|
|
9083
|
-
const yearPath =
|
|
9129
|
+
for (const year of fs14.readdirSync(sessionsBase)) {
|
|
9130
|
+
const yearPath = path17.join(sessionsBase, year);
|
|
9084
9131
|
try {
|
|
9085
|
-
if (!
|
|
9132
|
+
if (!fs14.statSync(yearPath).isDirectory()) continue;
|
|
9086
9133
|
} catch {
|
|
9087
9134
|
continue;
|
|
9088
9135
|
}
|
|
9089
|
-
for (const month of
|
|
9090
|
-
const monthPath =
|
|
9136
|
+
for (const month of fs14.readdirSync(yearPath)) {
|
|
9137
|
+
const monthPath = path17.join(yearPath, month);
|
|
9091
9138
|
try {
|
|
9092
|
-
if (!
|
|
9139
|
+
if (!fs14.statSync(monthPath).isDirectory()) continue;
|
|
9093
9140
|
} catch {
|
|
9094
9141
|
continue;
|
|
9095
9142
|
}
|
|
9096
|
-
for (const day of
|
|
9097
|
-
const dayPath =
|
|
9143
|
+
for (const day of fs14.readdirSync(monthPath)) {
|
|
9144
|
+
const dayPath = path17.join(monthPath, day);
|
|
9098
9145
|
try {
|
|
9099
|
-
if (!
|
|
9146
|
+
if (!fs14.statSync(dayPath).isDirectory()) continue;
|
|
9100
9147
|
} catch {
|
|
9101
9148
|
continue;
|
|
9102
9149
|
}
|
|
9103
|
-
for (const file of
|
|
9104
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
9150
|
+
for (const file of fs14.readdirSync(dayPath)) {
|
|
9151
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path17.join(dayPath, file));
|
|
9105
9152
|
}
|
|
9106
9153
|
}
|
|
9107
9154
|
}
|
|
@@ -9115,7 +9162,7 @@ function scanCodexHistory(startDate, onProgress) {
|
|
|
9115
9162
|
onProgress?.(result.filesScanned);
|
|
9116
9163
|
let lines;
|
|
9117
9164
|
try {
|
|
9118
|
-
lines =
|
|
9165
|
+
lines = fs14.readFileSync(filePath, "utf-8").split("\n");
|
|
9119
9166
|
} catch {
|
|
9120
9167
|
continue;
|
|
9121
9168
|
}
|
|
@@ -9129,6 +9176,7 @@ function scanCodexHistory(startDate, onProgress) {
|
|
|
9129
9176
|
let lastTotalOutput = 0;
|
|
9130
9177
|
for (const line of lines) {
|
|
9131
9178
|
if (!line.trim()) continue;
|
|
9179
|
+
onLine?.();
|
|
9132
9180
|
let entry;
|
|
9133
9181
|
try {
|
|
9134
9182
|
entry = JSON.parse(line);
|
|
@@ -9333,17 +9381,17 @@ function printRuleGroup(rule, topN, drillDown, previewWidth) {
|
|
|
9333
9381
|
}
|
|
9334
9382
|
}
|
|
9335
9383
|
function registerScanCommand(program2) {
|
|
9336
|
-
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last
|
|
9384
|
+
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 30 days)").option("--days <n>", "Scan last N days of history", "30").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").action(async (options) => {
|
|
9337
9385
|
const drillDown = options.drillDown ?? false;
|
|
9338
9386
|
const topN = drillDown ? Infinity : Math.max(1, parseInt(options.top, 10) || 5);
|
|
9339
9387
|
const previewWidth = 70;
|
|
9340
9388
|
const startDate = options.all ? null : (() => {
|
|
9341
9389
|
const d = /* @__PURE__ */ new Date();
|
|
9342
|
-
d.setDate(d.getDate() - (parseInt(options.days, 10) ||
|
|
9390
|
+
d.setDate(d.getDate() - (parseInt(options.days, 10) || 30));
|
|
9343
9391
|
d.setHours(0, 0, 0, 0);
|
|
9344
9392
|
return d;
|
|
9345
9393
|
})();
|
|
9346
|
-
const isInstalled =
|
|
9394
|
+
const isInstalled = fs14.existsSync(path17.join(os12.homedir(), ".node9", "audit.log"));
|
|
9347
9395
|
console.log("");
|
|
9348
9396
|
if (!isInstalled) {
|
|
9349
9397
|
console.log(
|
|
@@ -9360,19 +9408,32 @@ function registerScanCommand(program2) {
|
|
|
9360
9408
|
console.log("");
|
|
9361
9409
|
const totalFiles = countScanFiles();
|
|
9362
9410
|
let filesScanned = 0;
|
|
9411
|
+
let linesScanned = 0;
|
|
9412
|
+
let lastRender = 0;
|
|
9363
9413
|
const onProgress = (done) => {
|
|
9364
9414
|
filesScanned = done;
|
|
9365
|
-
renderProgressBar(filesScanned, totalFiles);
|
|
9415
|
+
renderProgressBar(filesScanned, totalFiles, linesScanned);
|
|
9416
|
+
lastRender = Date.now();
|
|
9366
9417
|
};
|
|
9367
|
-
|
|
9368
|
-
|
|
9418
|
+
const onLine = () => {
|
|
9419
|
+
linesScanned++;
|
|
9420
|
+
const now = Date.now();
|
|
9421
|
+
if (now - lastRender >= 80) {
|
|
9422
|
+
lastRender = now;
|
|
9423
|
+
renderProgressBar(filesScanned, totalFiles, linesScanned);
|
|
9424
|
+
}
|
|
9425
|
+
};
|
|
9426
|
+
renderProgressBar(0, totalFiles, 0);
|
|
9427
|
+
const claudeScan = scanClaudeHistory(startDate, onProgress, onLine);
|
|
9369
9428
|
const geminiScan = scanGeminiHistory(
|
|
9370
9429
|
startDate,
|
|
9371
|
-
(done) => onProgress(claudeScan.filesScanned + done)
|
|
9430
|
+
(done) => onProgress(claudeScan.filesScanned + done),
|
|
9431
|
+
onLine
|
|
9372
9432
|
);
|
|
9373
9433
|
const codexScan = scanCodexHistory(
|
|
9374
9434
|
startDate,
|
|
9375
|
-
(done) => onProgress(claudeScan.filesScanned + geminiScan.filesScanned + done)
|
|
9435
|
+
(done) => onProgress(claudeScan.filesScanned + geminiScan.filesScanned + done),
|
|
9436
|
+
onLine
|
|
9376
9437
|
);
|
|
9377
9438
|
const scan = mergeScans(mergeScans(claudeScan, geminiScan), codexScan);
|
|
9378
9439
|
const summary = buildScanSummary([
|
|
@@ -9390,7 +9451,7 @@ function registerScanCommand(program2) {
|
|
|
9390
9451
|
);
|
|
9391
9452
|
return;
|
|
9392
9453
|
}
|
|
9393
|
-
const rangeLabel = options.all ? chalk2.dim("all time") : chalk2.dim(`last ${options.days ??
|
|
9454
|
+
const rangeLabel = options.all ? chalk2.dim("all time") : chalk2.dim(`last ${options.days ?? 30} days`);
|
|
9394
9455
|
const dateRange = scan.firstDate && scan.lastDate ? chalk2.dim(` ${fmtTs(scan.firstDate)} \u2013 ${fmtTs(scan.lastDate)}`) : "";
|
|
9395
9456
|
const breakdownParts = [];
|
|
9396
9457
|
if (claudeScan.sessions > 0)
|
|
@@ -9568,29 +9629,33 @@ function registerScanCommand(program2) {
|
|
|
9568
9629
|
console.log(" " + chalk2.dim("\u2192 ") + chalk2.underline("https://node9.ai"));
|
|
9569
9630
|
}
|
|
9570
9631
|
console.log("");
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
if (
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
} catch {
|
|
9632
|
+
const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
|
|
9633
|
+
if (!isTestingMode()) {
|
|
9634
|
+
if (isDaemonRunning()) {
|
|
9635
|
+
const internalToken = getInternalToken();
|
|
9636
|
+
if (internalToken) {
|
|
9637
|
+
try {
|
|
9638
|
+
const summary2 = buildScanSummary([
|
|
9639
|
+
{ id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
|
|
9640
|
+
{ id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
|
|
9641
|
+
{ id: "codex", label: "Codex", icon: "\u{1F52E}", scan: codexScan }
|
|
9642
|
+
]);
|
|
9643
|
+
await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/scan/push`, {
|
|
9644
|
+
method: "POST",
|
|
9645
|
+
headers: { "Content-Type": "application/json", "x-node9-internal": internalToken },
|
|
9646
|
+
body: JSON.stringify({ status: "complete", summary: summary2 }),
|
|
9647
|
+
signal: AbortSignal.timeout(3e3)
|
|
9648
|
+
});
|
|
9649
|
+
openBrowserLocal();
|
|
9650
|
+
} catch {
|
|
9651
|
+
}
|
|
9592
9652
|
}
|
|
9593
9653
|
}
|
|
9654
|
+
console.log(" " + chalk2.cyan("\u{1F310} View in browser:") + " " + chalk2.underline(url));
|
|
9655
|
+
if (!isDaemonRunning()) {
|
|
9656
|
+
console.log(" " + chalk2.dim(" run node9 daemon --background to activate"));
|
|
9657
|
+
}
|
|
9658
|
+
console.log("");
|
|
9594
9659
|
}
|
|
9595
9660
|
});
|
|
9596
9661
|
}
|
|
@@ -9733,8 +9798,8 @@ var init_suggestion_tracker = __esm({
|
|
|
9733
9798
|
});
|
|
9734
9799
|
|
|
9735
9800
|
// src/daemon/taint-store.ts
|
|
9736
|
-
import
|
|
9737
|
-
import
|
|
9801
|
+
import fs15 from "fs";
|
|
9802
|
+
import path18 from "path";
|
|
9738
9803
|
var DEFAULT_TTL_MS, TaintStore;
|
|
9739
9804
|
var init_taint_store = __esm({
|
|
9740
9805
|
"src/daemon/taint-store.ts"() {
|
|
@@ -9803,9 +9868,9 @@ var init_taint_store = __esm({
|
|
|
9803
9868
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
9804
9869
|
_resolve(filePath) {
|
|
9805
9870
|
try {
|
|
9806
|
-
return
|
|
9871
|
+
return fs15.realpathSync.native(path18.resolve(filePath));
|
|
9807
9872
|
} catch {
|
|
9808
|
-
return
|
|
9873
|
+
return path18.resolve(filePath);
|
|
9809
9874
|
}
|
|
9810
9875
|
}
|
|
9811
9876
|
};
|
|
@@ -9922,15 +9987,15 @@ var init_session_history = __esm({
|
|
|
9922
9987
|
|
|
9923
9988
|
// src/daemon/state.ts
|
|
9924
9989
|
import net2 from "net";
|
|
9925
|
-
import
|
|
9926
|
-
import
|
|
9990
|
+
import fs16 from "fs";
|
|
9991
|
+
import path19 from "path";
|
|
9927
9992
|
import os13 from "os";
|
|
9928
9993
|
import { spawn as spawn3 } from "child_process";
|
|
9929
9994
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9930
9995
|
function loadInsightCounts() {
|
|
9931
9996
|
try {
|
|
9932
|
-
if (!
|
|
9933
|
-
const data = JSON.parse(
|
|
9997
|
+
if (!fs16.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
9998
|
+
const data = JSON.parse(fs16.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
|
|
9934
9999
|
for (const [tool, count] of Object.entries(data)) {
|
|
9935
10000
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
9936
10001
|
}
|
|
@@ -9973,23 +10038,23 @@ function setCachedScanResult(result) {
|
|
|
9973
10038
|
cachedScanTs = Date.now();
|
|
9974
10039
|
}
|
|
9975
10040
|
function atomicWriteSync2(filePath, data, options) {
|
|
9976
|
-
const dir =
|
|
9977
|
-
if (!
|
|
10041
|
+
const dir = path19.dirname(filePath);
|
|
10042
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
9978
10043
|
const tmpPath = `${filePath}.${randomUUID3()}.tmp`;
|
|
9979
10044
|
try {
|
|
9980
|
-
|
|
10045
|
+
fs16.writeFileSync(tmpPath, data, options);
|
|
9981
10046
|
} catch (err2) {
|
|
9982
10047
|
try {
|
|
9983
|
-
|
|
10048
|
+
fs16.unlinkSync(tmpPath);
|
|
9984
10049
|
} catch {
|
|
9985
10050
|
}
|
|
9986
10051
|
throw err2;
|
|
9987
10052
|
}
|
|
9988
10053
|
try {
|
|
9989
|
-
|
|
10054
|
+
fs16.renameSync(tmpPath, filePath);
|
|
9990
10055
|
} catch (err2) {
|
|
9991
10056
|
try {
|
|
9992
|
-
|
|
10057
|
+
fs16.unlinkSync(tmpPath);
|
|
9993
10058
|
} catch {
|
|
9994
10059
|
}
|
|
9995
10060
|
throw err2;
|
|
@@ -10013,16 +10078,16 @@ function appendAuditLog(data) {
|
|
|
10013
10078
|
decision: data.decision,
|
|
10014
10079
|
source: "daemon"
|
|
10015
10080
|
};
|
|
10016
|
-
const dir =
|
|
10017
|
-
if (!
|
|
10018
|
-
|
|
10081
|
+
const dir = path19.dirname(AUDIT_LOG_FILE);
|
|
10082
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
10083
|
+
fs16.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
10019
10084
|
} catch {
|
|
10020
10085
|
}
|
|
10021
10086
|
}
|
|
10022
10087
|
function getAuditHistory(limit = 20) {
|
|
10023
10088
|
try {
|
|
10024
|
-
if (!
|
|
10025
|
-
const lines =
|
|
10089
|
+
if (!fs16.existsSync(AUDIT_LOG_FILE)) return [];
|
|
10090
|
+
const lines = fs16.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
10026
10091
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
10027
10092
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
10028
10093
|
} catch {
|
|
@@ -10031,19 +10096,19 @@ function getAuditHistory(limit = 20) {
|
|
|
10031
10096
|
}
|
|
10032
10097
|
function getOrgName() {
|
|
10033
10098
|
try {
|
|
10034
|
-
if (
|
|
10099
|
+
if (fs16.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
10035
10100
|
} catch {
|
|
10036
10101
|
}
|
|
10037
10102
|
return null;
|
|
10038
10103
|
}
|
|
10039
10104
|
function hasStoredSlackKey() {
|
|
10040
|
-
return
|
|
10105
|
+
return fs16.existsSync(CREDENTIALS_FILE);
|
|
10041
10106
|
}
|
|
10042
10107
|
function writeGlobalSetting(key, value) {
|
|
10043
10108
|
let config = {};
|
|
10044
10109
|
try {
|
|
10045
|
-
if (
|
|
10046
|
-
config = JSON.parse(
|
|
10110
|
+
if (fs16.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
10111
|
+
config = JSON.parse(fs16.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
10047
10112
|
}
|
|
10048
10113
|
} catch {
|
|
10049
10114
|
}
|
|
@@ -10055,8 +10120,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
10055
10120
|
try {
|
|
10056
10121
|
let trust = { entries: [] };
|
|
10057
10122
|
try {
|
|
10058
|
-
if (
|
|
10059
|
-
trust = JSON.parse(
|
|
10123
|
+
if (fs16.existsSync(TRUST_FILE2))
|
|
10124
|
+
trust = JSON.parse(fs16.readFileSync(TRUST_FILE2, "utf-8"));
|
|
10060
10125
|
} catch {
|
|
10061
10126
|
}
|
|
10062
10127
|
trust.entries = trust.entries.filter(
|
|
@@ -10073,8 +10138,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
|
10073
10138
|
}
|
|
10074
10139
|
function readPersistentDecisions() {
|
|
10075
10140
|
try {
|
|
10076
|
-
if (
|
|
10077
|
-
return JSON.parse(
|
|
10141
|
+
if (fs16.existsSync(DECISIONS_FILE)) {
|
|
10142
|
+
return JSON.parse(fs16.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
10078
10143
|
}
|
|
10079
10144
|
} catch {
|
|
10080
10145
|
}
|
|
@@ -10111,7 +10176,7 @@ function estimateToolCost(tool, args) {
|
|
|
10111
10176
|
const filePath = a.file_path ?? a.path;
|
|
10112
10177
|
if (filePath) {
|
|
10113
10178
|
try {
|
|
10114
|
-
const bytes =
|
|
10179
|
+
const bytes = fs16.statSync(filePath).size;
|
|
10115
10180
|
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
10116
10181
|
} catch {
|
|
10117
10182
|
}
|
|
@@ -10169,7 +10234,7 @@ function abandonPending() {
|
|
|
10169
10234
|
});
|
|
10170
10235
|
if (autoStarted) {
|
|
10171
10236
|
try {
|
|
10172
|
-
|
|
10237
|
+
fs16.unlinkSync(DAEMON_PID_FILE);
|
|
10173
10238
|
} catch {
|
|
10174
10239
|
}
|
|
10175
10240
|
setTimeout(() => {
|
|
@@ -10180,7 +10245,7 @@ function abandonPending() {
|
|
|
10180
10245
|
}
|
|
10181
10246
|
function startActivitySocket() {
|
|
10182
10247
|
try {
|
|
10183
|
-
|
|
10248
|
+
fs16.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
10184
10249
|
} catch {
|
|
10185
10250
|
}
|
|
10186
10251
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -10262,7 +10327,7 @@ function startActivitySocket() {
|
|
|
10262
10327
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
10263
10328
|
process.on("exit", () => {
|
|
10264
10329
|
try {
|
|
10265
|
-
|
|
10330
|
+
fs16.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
10266
10331
|
} catch {
|
|
10267
10332
|
}
|
|
10268
10333
|
});
|
|
@@ -10277,13 +10342,13 @@ var init_state2 = __esm({
|
|
|
10277
10342
|
init_session_counters();
|
|
10278
10343
|
init_session_history();
|
|
10279
10344
|
homeDir = os13.homedir();
|
|
10280
|
-
DAEMON_PID_FILE =
|
|
10281
|
-
DECISIONS_FILE =
|
|
10282
|
-
AUDIT_LOG_FILE =
|
|
10283
|
-
TRUST_FILE2 =
|
|
10284
|
-
GLOBAL_CONFIG_FILE =
|
|
10285
|
-
CREDENTIALS_FILE =
|
|
10286
|
-
INSIGHT_COUNTS_FILE =
|
|
10345
|
+
DAEMON_PID_FILE = path19.join(homeDir, ".node9", "daemon.pid");
|
|
10346
|
+
DECISIONS_FILE = path19.join(homeDir, ".node9", "decisions.json");
|
|
10347
|
+
AUDIT_LOG_FILE = path19.join(homeDir, ".node9", "audit.log");
|
|
10348
|
+
TRUST_FILE2 = path19.join(homeDir, ".node9", "trust.json");
|
|
10349
|
+
GLOBAL_CONFIG_FILE = path19.join(homeDir, ".node9", "config.json");
|
|
10350
|
+
CREDENTIALS_FILE = path19.join(homeDir, ".node9", "credentials.json");
|
|
10351
|
+
INSIGHT_COUNTS_FILE = path19.join(homeDir, ".node9", "insight-counts.json");
|
|
10287
10352
|
pending = /* @__PURE__ */ new Map();
|
|
10288
10353
|
sseClients = /* @__PURE__ */ new Set();
|
|
10289
10354
|
suggestionTracker = new SuggestionTracker(3);
|
|
@@ -10301,7 +10366,7 @@ var init_state2 = __esm({
|
|
|
10301
10366
|
"2h": 2 * 60 * 6e4
|
|
10302
10367
|
};
|
|
10303
10368
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
10304
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
10369
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path19.join(os13.tmpdir(), "node9-activity.sock");
|
|
10305
10370
|
ACTIVITY_RING_SIZE = 100;
|
|
10306
10371
|
activityRing = [];
|
|
10307
10372
|
LARGE_RESPONSE_RING_SIZE = 20;
|
|
@@ -10328,14 +10393,14 @@ var init_state2 = __esm({
|
|
|
10328
10393
|
});
|
|
10329
10394
|
|
|
10330
10395
|
// src/config/patch.ts
|
|
10331
|
-
import
|
|
10332
|
-
import
|
|
10396
|
+
import fs17 from "fs";
|
|
10397
|
+
import path20 from "path";
|
|
10333
10398
|
import os14 from "os";
|
|
10334
10399
|
function patchConfig(configPath, patch) {
|
|
10335
10400
|
let config = {};
|
|
10336
10401
|
try {
|
|
10337
|
-
if (
|
|
10338
|
-
config = JSON.parse(
|
|
10402
|
+
if (fs17.existsSync(configPath)) {
|
|
10403
|
+
config = JSON.parse(fs17.readFileSync(configPath, "utf8"));
|
|
10339
10404
|
}
|
|
10340
10405
|
} catch {
|
|
10341
10406
|
throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
|
|
@@ -10354,23 +10419,23 @@ function patchConfig(configPath, patch) {
|
|
|
10354
10419
|
ignored.push(patch.toolName);
|
|
10355
10420
|
}
|
|
10356
10421
|
}
|
|
10357
|
-
const dir =
|
|
10358
|
-
|
|
10422
|
+
const dir = path20.dirname(configPath);
|
|
10423
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
10359
10424
|
const tmp = configPath + ".node9-tmp";
|
|
10360
10425
|
try {
|
|
10361
|
-
|
|
10426
|
+
fs17.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
10362
10427
|
} catch (err2) {
|
|
10363
10428
|
try {
|
|
10364
|
-
|
|
10429
|
+
fs17.unlinkSync(tmp);
|
|
10365
10430
|
} catch {
|
|
10366
10431
|
}
|
|
10367
10432
|
throw err2;
|
|
10368
10433
|
}
|
|
10369
10434
|
try {
|
|
10370
|
-
|
|
10435
|
+
fs17.renameSync(tmp, configPath);
|
|
10371
10436
|
} catch (err2) {
|
|
10372
10437
|
try {
|
|
10373
|
-
|
|
10438
|
+
fs17.unlinkSync(tmp);
|
|
10374
10439
|
} catch {
|
|
10375
10440
|
}
|
|
10376
10441
|
throw err2;
|
|
@@ -10380,13 +10445,13 @@ var GLOBAL_CONFIG_PATH;
|
|
|
10380
10445
|
var init_patch = __esm({
|
|
10381
10446
|
"src/config/patch.ts"() {
|
|
10382
10447
|
"use strict";
|
|
10383
|
-
GLOBAL_CONFIG_PATH =
|
|
10448
|
+
GLOBAL_CONFIG_PATH = path20.join(os14.homedir(), ".node9", "config.json");
|
|
10384
10449
|
}
|
|
10385
10450
|
});
|
|
10386
10451
|
|
|
10387
10452
|
// src/costSync.ts
|
|
10388
|
-
import
|
|
10389
|
-
import
|
|
10453
|
+
import fs18 from "fs";
|
|
10454
|
+
import path21 from "path";
|
|
10390
10455
|
import os15 from "os";
|
|
10391
10456
|
function normalizeModel(raw) {
|
|
10392
10457
|
return raw.replace(/-\d{8}$/, "");
|
|
@@ -10403,7 +10468,7 @@ function pricingFor(model) {
|
|
|
10403
10468
|
function parseJSONLFile(filePath) {
|
|
10404
10469
|
let content;
|
|
10405
10470
|
try {
|
|
10406
|
-
content =
|
|
10471
|
+
content = fs18.readFileSync(filePath, "utf8");
|
|
10407
10472
|
} catch {
|
|
10408
10473
|
return /* @__PURE__ */ new Map();
|
|
10409
10474
|
}
|
|
@@ -10455,30 +10520,30 @@ function parseJSONLFile(filePath) {
|
|
|
10455
10520
|
return daily;
|
|
10456
10521
|
}
|
|
10457
10522
|
function collectEntries() {
|
|
10458
|
-
const projectsDir =
|
|
10459
|
-
if (!
|
|
10523
|
+
const projectsDir = path21.join(os15.homedir(), ".claude", "projects");
|
|
10524
|
+
if (!fs18.existsSync(projectsDir)) return [];
|
|
10460
10525
|
const combined = /* @__PURE__ */ new Map();
|
|
10461
10526
|
let dirs;
|
|
10462
10527
|
try {
|
|
10463
|
-
dirs =
|
|
10528
|
+
dirs = fs18.readdirSync(projectsDir);
|
|
10464
10529
|
} catch {
|
|
10465
10530
|
return [];
|
|
10466
10531
|
}
|
|
10467
10532
|
for (const dir of dirs) {
|
|
10468
|
-
const dirPath =
|
|
10533
|
+
const dirPath = path21.join(projectsDir, dir);
|
|
10469
10534
|
try {
|
|
10470
|
-
if (!
|
|
10535
|
+
if (!fs18.statSync(dirPath).isDirectory()) continue;
|
|
10471
10536
|
} catch {
|
|
10472
10537
|
continue;
|
|
10473
10538
|
}
|
|
10474
10539
|
let files;
|
|
10475
10540
|
try {
|
|
10476
|
-
files =
|
|
10541
|
+
files = fs18.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
10477
10542
|
} catch {
|
|
10478
10543
|
continue;
|
|
10479
10544
|
}
|
|
10480
10545
|
for (const file of files) {
|
|
10481
|
-
const entries = parseJSONLFile(
|
|
10546
|
+
const entries = parseJSONLFile(path21.join(dirPath, file));
|
|
10482
10547
|
for (const [key, e] of entries) {
|
|
10483
10548
|
const prev = combined.get(key);
|
|
10484
10549
|
if (prev) {
|
|
@@ -10514,11 +10579,11 @@ async function syncCost() {
|
|
|
10514
10579
|
signal: AbortSignal.timeout(15e3)
|
|
10515
10580
|
});
|
|
10516
10581
|
if (!res.ok) {
|
|
10517
|
-
|
|
10582
|
+
fs18.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
|
|
10518
10583
|
`);
|
|
10519
10584
|
}
|
|
10520
10585
|
} catch (err2) {
|
|
10521
|
-
|
|
10586
|
+
fs18.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
|
|
10522
10587
|
`);
|
|
10523
10588
|
}
|
|
10524
10589
|
}
|
|
@@ -10551,10 +10616,10 @@ var init_costSync = __esm({
|
|
|
10551
10616
|
});
|
|
10552
10617
|
|
|
10553
10618
|
// src/daemon/sync.ts
|
|
10554
|
-
import
|
|
10619
|
+
import fs19 from "fs";
|
|
10555
10620
|
import https from "https";
|
|
10556
10621
|
import os16 from "os";
|
|
10557
|
-
import
|
|
10622
|
+
import path22 from "path";
|
|
10558
10623
|
function readCredentials() {
|
|
10559
10624
|
if (process.env.NODE9_API_KEY) {
|
|
10560
10625
|
return {
|
|
@@ -10563,8 +10628,8 @@ function readCredentials() {
|
|
|
10563
10628
|
};
|
|
10564
10629
|
}
|
|
10565
10630
|
try {
|
|
10566
|
-
const credPath =
|
|
10567
|
-
const creds = JSON.parse(
|
|
10631
|
+
const credPath = path22.join(os16.homedir(), ".node9", "credentials.json");
|
|
10632
|
+
const creds = JSON.parse(fs19.readFileSync(credPath, "utf-8"));
|
|
10568
10633
|
const profileName = process.env.NODE9_PROFILE ?? "default";
|
|
10569
10634
|
const profile = creds[profileName];
|
|
10570
10635
|
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
@@ -10626,9 +10691,9 @@ async function syncOnce() {
|
|
|
10626
10691
|
try {
|
|
10627
10692
|
const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
|
|
10628
10693
|
const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
|
|
10629
|
-
const dir =
|
|
10630
|
-
if (!
|
|
10631
|
-
|
|
10694
|
+
const dir = path22.dirname(rulesCacheFile());
|
|
10695
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
10696
|
+
fs19.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
10632
10697
|
} catch {
|
|
10633
10698
|
}
|
|
10634
10699
|
}
|
|
@@ -10640,9 +10705,9 @@ async function runCloudSync() {
|
|
|
10640
10705
|
try {
|
|
10641
10706
|
const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
|
|
10642
10707
|
const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
|
|
10643
|
-
const dir =
|
|
10644
|
-
if (!
|
|
10645
|
-
|
|
10708
|
+
const dir = path22.dirname(rulesCacheFile());
|
|
10709
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
10710
|
+
fs19.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
10646
10711
|
return { ok: true, rules: rules.length, fetchedAt: cache.fetchedAt };
|
|
10647
10712
|
} catch (err2) {
|
|
10648
10713
|
return { ok: false, reason: err2 instanceof Error ? err2.message : String(err2) };
|
|
@@ -10650,7 +10715,7 @@ async function runCloudSync() {
|
|
|
10650
10715
|
}
|
|
10651
10716
|
function getCloudSyncStatus() {
|
|
10652
10717
|
try {
|
|
10653
|
-
const raw = JSON.parse(
|
|
10718
|
+
const raw = JSON.parse(fs19.readFileSync(rulesCacheFile(), "utf-8"));
|
|
10654
10719
|
if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
|
|
10655
10720
|
return { cached: true, rules: raw.rules.length, fetchedAt: raw.fetchedAt };
|
|
10656
10721
|
} catch {
|
|
@@ -10659,7 +10724,7 @@ function getCloudSyncStatus() {
|
|
|
10659
10724
|
}
|
|
10660
10725
|
function getCloudRules() {
|
|
10661
10726
|
try {
|
|
10662
|
-
const raw = JSON.parse(
|
|
10727
|
+
const raw = JSON.parse(fs19.readFileSync(rulesCacheFile(), "utf-8"));
|
|
10663
10728
|
return Array.isArray(raw.rules) ? raw.rules : null;
|
|
10664
10729
|
} catch {
|
|
10665
10730
|
return null;
|
|
@@ -10679,7 +10744,7 @@ var init_sync = __esm({
|
|
|
10679
10744
|
"src/daemon/sync.ts"() {
|
|
10680
10745
|
"use strict";
|
|
10681
10746
|
init_config();
|
|
10682
|
-
rulesCacheFile = () =>
|
|
10747
|
+
rulesCacheFile = () => path22.join(os16.homedir(), ".node9", "rules-cache.json");
|
|
10683
10748
|
DEFAULT_API_URL = "https://api.node9.ai/api/v1/policy";
|
|
10684
10749
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
10685
10750
|
MIN_INTERVAL_HOURS = 1;
|
|
@@ -10687,73 +10752,73 @@ var init_sync = __esm({
|
|
|
10687
10752
|
});
|
|
10688
10753
|
|
|
10689
10754
|
// src/daemon/dlp-scanner.ts
|
|
10690
|
-
import
|
|
10691
|
-
import
|
|
10755
|
+
import fs20 from "fs";
|
|
10756
|
+
import path23 from "path";
|
|
10692
10757
|
import os17 from "os";
|
|
10693
10758
|
function loadIndex() {
|
|
10694
10759
|
try {
|
|
10695
|
-
return JSON.parse(
|
|
10760
|
+
return JSON.parse(fs20.readFileSync(INDEX_FILE, "utf-8"));
|
|
10696
10761
|
} catch {
|
|
10697
10762
|
return {};
|
|
10698
10763
|
}
|
|
10699
10764
|
}
|
|
10700
10765
|
function saveIndex(index) {
|
|
10701
10766
|
try {
|
|
10702
|
-
|
|
10767
|
+
fs20.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
|
|
10703
10768
|
} catch {
|
|
10704
10769
|
}
|
|
10705
10770
|
}
|
|
10706
10771
|
function appendAuditEntry(entry) {
|
|
10707
10772
|
try {
|
|
10708
|
-
|
|
10773
|
+
fs20.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
10709
10774
|
} catch {
|
|
10710
10775
|
}
|
|
10711
10776
|
}
|
|
10712
10777
|
function runDlpScan() {
|
|
10713
|
-
if (!
|
|
10778
|
+
if (!fs20.existsSync(PROJECTS_DIR)) return;
|
|
10714
10779
|
const index = loadIndex();
|
|
10715
10780
|
let updated = false;
|
|
10716
10781
|
let projDirs;
|
|
10717
10782
|
try {
|
|
10718
|
-
projDirs =
|
|
10783
|
+
projDirs = fs20.readdirSync(PROJECTS_DIR);
|
|
10719
10784
|
} catch {
|
|
10720
10785
|
return;
|
|
10721
10786
|
}
|
|
10722
10787
|
for (const proj of projDirs) {
|
|
10723
|
-
const projPath =
|
|
10788
|
+
const projPath = path23.join(PROJECTS_DIR, proj);
|
|
10724
10789
|
try {
|
|
10725
|
-
if (!
|
|
10726
|
-
const real =
|
|
10727
|
-
if (!real.startsWith(PROJECTS_DIR +
|
|
10790
|
+
if (!fs20.lstatSync(projPath).isDirectory()) continue;
|
|
10791
|
+
const real = fs20.realpathSync(projPath);
|
|
10792
|
+
if (!real.startsWith(PROJECTS_DIR + path23.sep) && real !== PROJECTS_DIR) continue;
|
|
10728
10793
|
} catch {
|
|
10729
10794
|
continue;
|
|
10730
10795
|
}
|
|
10731
10796
|
let files;
|
|
10732
10797
|
try {
|
|
10733
|
-
files =
|
|
10798
|
+
files = fs20.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
10734
10799
|
} catch {
|
|
10735
10800
|
continue;
|
|
10736
10801
|
}
|
|
10737
10802
|
for (const file of files) {
|
|
10738
|
-
const filePath =
|
|
10803
|
+
const filePath = path23.join(projPath, file);
|
|
10739
10804
|
const lastOffset = index[filePath] ?? 0;
|
|
10740
10805
|
let size;
|
|
10741
10806
|
try {
|
|
10742
|
-
size =
|
|
10807
|
+
size = fs20.statSync(filePath).size;
|
|
10743
10808
|
} catch {
|
|
10744
10809
|
continue;
|
|
10745
10810
|
}
|
|
10746
10811
|
if (size <= lastOffset) continue;
|
|
10747
10812
|
let fd;
|
|
10748
10813
|
try {
|
|
10749
|
-
fd =
|
|
10814
|
+
fd = fs20.openSync(filePath, "r");
|
|
10750
10815
|
} catch {
|
|
10751
10816
|
continue;
|
|
10752
10817
|
}
|
|
10753
10818
|
try {
|
|
10754
10819
|
const chunkSize = size - lastOffset;
|
|
10755
10820
|
const buf = Buffer.alloc(chunkSize);
|
|
10756
|
-
|
|
10821
|
+
fs20.readSync(fd, buf, 0, chunkSize, lastOffset);
|
|
10757
10822
|
const chunk = buf.toString("utf-8");
|
|
10758
10823
|
for (const line of chunk.split("\n")) {
|
|
10759
10824
|
if (!line.trim()) continue;
|
|
@@ -10798,7 +10863,7 @@ Run: node9 report --period 30d`
|
|
|
10798
10863
|
updated = true;
|
|
10799
10864
|
} finally {
|
|
10800
10865
|
try {
|
|
10801
|
-
|
|
10866
|
+
fs20.closeSync(fd);
|
|
10802
10867
|
} catch {
|
|
10803
10868
|
}
|
|
10804
10869
|
}
|
|
@@ -10831,23 +10896,23 @@ var init_dlp_scanner = __esm({
|
|
|
10831
10896
|
init_dlp();
|
|
10832
10897
|
init_native();
|
|
10833
10898
|
init_state2();
|
|
10834
|
-
INDEX_FILE =
|
|
10835
|
-
PROJECTS_DIR =
|
|
10899
|
+
INDEX_FILE = path23.join(os17.homedir(), ".node9", "dlp-index.json");
|
|
10900
|
+
PROJECTS_DIR = path23.join(os17.homedir(), ".claude", "projects");
|
|
10836
10901
|
}
|
|
10837
10902
|
});
|
|
10838
10903
|
|
|
10839
10904
|
// src/daemon/mcp-tools.ts
|
|
10840
|
-
import
|
|
10841
|
-
import
|
|
10905
|
+
import fs21 from "fs";
|
|
10906
|
+
import path24 from "path";
|
|
10842
10907
|
import os18 from "os";
|
|
10843
10908
|
function getMcpToolsFile() {
|
|
10844
|
-
return
|
|
10909
|
+
return path24.join(os18.homedir(), ".node9", "mcp-tools.json");
|
|
10845
10910
|
}
|
|
10846
10911
|
function readMcpToolsConfig() {
|
|
10847
10912
|
try {
|
|
10848
10913
|
const file = getMcpToolsFile();
|
|
10849
|
-
if (!
|
|
10850
|
-
const raw =
|
|
10914
|
+
if (!fs21.existsSync(file)) return {};
|
|
10915
|
+
const raw = fs21.readFileSync(file, "utf-8");
|
|
10851
10916
|
return JSON.parse(raw);
|
|
10852
10917
|
} catch {
|
|
10853
10918
|
return {};
|
|
@@ -10856,11 +10921,11 @@ function readMcpToolsConfig() {
|
|
|
10856
10921
|
function writeMcpToolsConfig(config) {
|
|
10857
10922
|
try {
|
|
10858
10923
|
const file = getMcpToolsFile();
|
|
10859
|
-
const dir =
|
|
10860
|
-
if (!
|
|
10924
|
+
const dir = path24.dirname(file);
|
|
10925
|
+
if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
|
|
10861
10926
|
const tmpPath = `${file}.${os18.hostname()}.${process.pid}.tmp`;
|
|
10862
|
-
|
|
10863
|
-
|
|
10927
|
+
fs21.writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
10928
|
+
fs21.renameSync(tmpPath, file);
|
|
10864
10929
|
} catch (e) {
|
|
10865
10930
|
console.error("Failed to write mcp-tools.json", e);
|
|
10866
10931
|
}
|
|
@@ -10907,8 +10972,8 @@ var init_mcp_tools = __esm({
|
|
|
10907
10972
|
|
|
10908
10973
|
// src/daemon/server.ts
|
|
10909
10974
|
import http from "http";
|
|
10910
|
-
import
|
|
10911
|
-
import
|
|
10975
|
+
import fs22 from "fs";
|
|
10976
|
+
import path25 from "path";
|
|
10912
10977
|
import os19 from "os";
|
|
10913
10978
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
10914
10979
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -10932,7 +10997,7 @@ function startDaemon() {
|
|
|
10932
10997
|
idleTimer = setTimeout(() => {
|
|
10933
10998
|
if (autoStarted) {
|
|
10934
10999
|
try {
|
|
10935
|
-
|
|
11000
|
+
fs22.unlinkSync(DAEMON_PID_FILE);
|
|
10936
11001
|
} catch {
|
|
10937
11002
|
}
|
|
10938
11003
|
}
|
|
@@ -11103,7 +11168,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
11103
11168
|
status: "pending"
|
|
11104
11169
|
});
|
|
11105
11170
|
}
|
|
11106
|
-
const projectCwd = typeof cwd === "string" &&
|
|
11171
|
+
const projectCwd = typeof cwd === "string" && path25.isAbsolute(cwd) ? cwd : void 0;
|
|
11107
11172
|
const projectConfig = getConfig(projectCwd);
|
|
11108
11173
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
11109
11174
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -11467,8 +11532,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
11467
11532
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
11468
11533
|
const periodParam = reqUrl.searchParams.get("period") || "7d";
|
|
11469
11534
|
const period = ["today", "7d", "30d", "month"].includes(periodParam) ? periodParam : "7d";
|
|
11470
|
-
const logPath =
|
|
11471
|
-
if (!
|
|
11535
|
+
const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
|
|
11536
|
+
if (!fs22.existsSync(logPath)) {
|
|
11472
11537
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
11473
11538
|
return res.end(
|
|
11474
11539
|
JSON.stringify({
|
|
@@ -11481,7 +11546,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
11481
11546
|
);
|
|
11482
11547
|
}
|
|
11483
11548
|
try {
|
|
11484
|
-
const raw =
|
|
11549
|
+
const raw = fs22.readFileSync(logPath, "utf-8");
|
|
11485
11550
|
const allEntries = raw.split("\n").flatMap((line) => {
|
|
11486
11551
|
if (!line.trim()) return [];
|
|
11487
11552
|
try {
|
|
@@ -11585,7 +11650,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
11585
11650
|
}
|
|
11586
11651
|
try {
|
|
11587
11652
|
const d = /* @__PURE__ */ new Date();
|
|
11588
|
-
d.setDate(d.getDate() -
|
|
11653
|
+
d.setDate(d.getDate() - 30);
|
|
11589
11654
|
d.setHours(0, 0, 0, 0);
|
|
11590
11655
|
const EMPTY_SCAN = {
|
|
11591
11656
|
filesScanned: 0,
|
|
@@ -11661,8 +11726,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
11661
11726
|
const body = await readBody(req);
|
|
11662
11727
|
const data = body ? JSON.parse(body) : {};
|
|
11663
11728
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
11664
|
-
const node9Dir =
|
|
11665
|
-
if (!
|
|
11729
|
+
const node9Dir = path25.dirname(GLOBAL_CONFIG_PATH);
|
|
11730
|
+
if (!path25.resolve(configPath).startsWith(node9Dir + path25.sep)) {
|
|
11666
11731
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
11667
11732
|
return res.end(
|
|
11668
11733
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -11776,11 +11841,16 @@ data: ${JSON.stringify(item.data)}
|
|
|
11776
11841
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
11777
11842
|
try {
|
|
11778
11843
|
const { serverKey, disabledTools } = JSON.parse(await readBody(req));
|
|
11779
|
-
if (typeof serverKey !== "string" || !Array.isArray(disabledTools)) {
|
|
11844
|
+
if (typeof serverKey !== "string" || serverKey.length < 1 || serverKey.length > 256 || !/^[\w.-]+$/.test(serverKey) || !Array.isArray(disabledTools)) {
|
|
11780
11845
|
res.writeHead(400).end();
|
|
11781
11846
|
return;
|
|
11782
11847
|
}
|
|
11783
11848
|
approveServer(serverKey, disabledTools);
|
|
11849
|
+
appendAuditLog({
|
|
11850
|
+
toolName: `mcp-server:${serverKey}`,
|
|
11851
|
+
args: { disabledTools },
|
|
11852
|
+
decision: "allow"
|
|
11853
|
+
});
|
|
11784
11854
|
broadcast("mcp-tools-updated", { serverKey, disabledTools });
|
|
11785
11855
|
res.writeHead(200).end();
|
|
11786
11856
|
return;
|
|
@@ -11793,12 +11863,17 @@ data: ${JSON.stringify(item.data)}
|
|
|
11793
11863
|
if (req.headers["x-node9-internal"] !== internalToken) return res.writeHead(403).end();
|
|
11794
11864
|
try {
|
|
11795
11865
|
const { serverKey, tools } = JSON.parse(await readBody(req));
|
|
11796
|
-
if (typeof serverKey !== "string" || !Array.isArray(tools)) {
|
|
11866
|
+
if (typeof serverKey !== "string" || serverKey.length < 1 || serverKey.length > 256 || !/^[\w.-]+$/.test(serverKey) || !Array.isArray(tools)) {
|
|
11797
11867
|
res.writeHead(400).end();
|
|
11798
11868
|
return;
|
|
11799
11869
|
}
|
|
11800
11870
|
const status = updateServerDiscovery(serverKey, tools);
|
|
11801
11871
|
if (status === "new" || status === "drift") {
|
|
11872
|
+
appendAuditLog({
|
|
11873
|
+
toolName: `mcp-server:${serverKey}`,
|
|
11874
|
+
args: { toolCount: tools.length, status },
|
|
11875
|
+
decision: "mcp-discovered"
|
|
11876
|
+
});
|
|
11802
11877
|
const id = randomUUID4();
|
|
11803
11878
|
const entry = {
|
|
11804
11879
|
id,
|
|
@@ -11867,6 +11942,11 @@ data: ${JSON.stringify(item.data)}
|
|
|
11867
11942
|
}
|
|
11868
11943
|
clearTimeout(entry.timer);
|
|
11869
11944
|
approveServer(serverKey, disabledTools);
|
|
11945
|
+
appendAuditLog({
|
|
11946
|
+
toolName: `mcp-server:${serverKey}`,
|
|
11947
|
+
args: { disabledTools },
|
|
11948
|
+
decision: "allow"
|
|
11949
|
+
});
|
|
11870
11950
|
pending.delete(id);
|
|
11871
11951
|
broadcast("remove", { id, decision: "allow" });
|
|
11872
11952
|
res.writeHead(200).end();
|
|
@@ -11889,14 +11969,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
11889
11969
|
server.on("error", (e) => {
|
|
11890
11970
|
if (e.code === "EADDRINUSE") {
|
|
11891
11971
|
try {
|
|
11892
|
-
if (
|
|
11893
|
-
const { pid } = JSON.parse(
|
|
11972
|
+
if (fs22.existsSync(DAEMON_PID_FILE)) {
|
|
11973
|
+
const { pid } = JSON.parse(fs22.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
11894
11974
|
process.kill(pid, 0);
|
|
11895
11975
|
return process.exit(0);
|
|
11896
11976
|
}
|
|
11897
11977
|
} catch {
|
|
11898
11978
|
try {
|
|
11899
|
-
|
|
11979
|
+
fs22.unlinkSync(DAEMON_PID_FILE);
|
|
11900
11980
|
} catch {
|
|
11901
11981
|
}
|
|
11902
11982
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -11975,15 +12055,15 @@ var init_server = __esm({
|
|
|
11975
12055
|
});
|
|
11976
12056
|
|
|
11977
12057
|
// src/daemon/service.ts
|
|
11978
|
-
import
|
|
11979
|
-
import
|
|
12058
|
+
import fs23 from "fs";
|
|
12059
|
+
import path26 from "path";
|
|
11980
12060
|
import os20 from "os";
|
|
11981
12061
|
import { spawnSync as spawnSync3, execFileSync } from "child_process";
|
|
11982
12062
|
function resolveNode9Binary() {
|
|
11983
12063
|
try {
|
|
11984
12064
|
const script = process.argv[1];
|
|
11985
|
-
if (typeof script === "string" &&
|
|
11986
|
-
return
|
|
12065
|
+
if (typeof script === "string" && path26.isAbsolute(script) && fs23.existsSync(script)) {
|
|
12066
|
+
return fs23.realpathSync(script);
|
|
11987
12067
|
}
|
|
11988
12068
|
} catch {
|
|
11989
12069
|
}
|
|
@@ -12001,11 +12081,11 @@ function xmlEscape(s) {
|
|
|
12001
12081
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
12002
12082
|
}
|
|
12003
12083
|
function launchdPlist(binaryPath) {
|
|
12004
|
-
const logDir =
|
|
12084
|
+
const logDir = path26.join(os20.homedir(), ".node9");
|
|
12005
12085
|
const nodePath = xmlEscape(process.execPath);
|
|
12006
12086
|
const scriptPath = xmlEscape(binaryPath);
|
|
12007
|
-
const outLog = xmlEscape(
|
|
12008
|
-
const errLog = xmlEscape(
|
|
12087
|
+
const outLog = xmlEscape(path26.join(logDir, "daemon.log"));
|
|
12088
|
+
const errLog = xmlEscape(path26.join(logDir, "daemon-error.log"));
|
|
12009
12089
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
12010
12090
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
12011
12091
|
<plist version="1.0">
|
|
@@ -12040,9 +12120,9 @@ function launchdPlist(binaryPath) {
|
|
|
12040
12120
|
`;
|
|
12041
12121
|
}
|
|
12042
12122
|
function installLaunchd(binaryPath) {
|
|
12043
|
-
const dir =
|
|
12044
|
-
if (!
|
|
12045
|
-
|
|
12123
|
+
const dir = path26.dirname(LAUNCHD_PLIST);
|
|
12124
|
+
if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
|
|
12125
|
+
fs23.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
|
|
12046
12126
|
spawnSync3("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
12047
12127
|
const r = spawnSync3("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
12048
12128
|
encoding: "utf8",
|
|
@@ -12053,13 +12133,13 @@ function installLaunchd(binaryPath) {
|
|
|
12053
12133
|
}
|
|
12054
12134
|
}
|
|
12055
12135
|
function uninstallLaunchd() {
|
|
12056
|
-
if (
|
|
12136
|
+
if (fs23.existsSync(LAUNCHD_PLIST)) {
|
|
12057
12137
|
spawnSync3("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
12058
|
-
|
|
12138
|
+
fs23.unlinkSync(LAUNCHD_PLIST);
|
|
12059
12139
|
}
|
|
12060
12140
|
}
|
|
12061
12141
|
function isLaunchdInstalled() {
|
|
12062
|
-
return
|
|
12142
|
+
return fs23.existsSync(LAUNCHD_PLIST);
|
|
12063
12143
|
}
|
|
12064
12144
|
function systemdUnit(binaryPath) {
|
|
12065
12145
|
return `[Unit]
|
|
@@ -12079,10 +12159,10 @@ WantedBy=default.target
|
|
|
12079
12159
|
`;
|
|
12080
12160
|
}
|
|
12081
12161
|
function installSystemd(binaryPath) {
|
|
12082
|
-
if (!
|
|
12083
|
-
|
|
12162
|
+
if (!fs23.existsSync(SYSTEMD_UNIT_DIR)) {
|
|
12163
|
+
fs23.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
12084
12164
|
}
|
|
12085
|
-
|
|
12165
|
+
fs23.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
|
|
12086
12166
|
try {
|
|
12087
12167
|
execFileSync("loginctl", ["enable-linger", os20.userInfo().username], { timeout: 3e3 });
|
|
12088
12168
|
} catch {
|
|
@@ -12104,23 +12184,23 @@ function installSystemd(binaryPath) {
|
|
|
12104
12184
|
}
|
|
12105
12185
|
}
|
|
12106
12186
|
function uninstallSystemd() {
|
|
12107
|
-
if (
|
|
12187
|
+
if (fs23.existsSync(SYSTEMD_UNIT)) {
|
|
12108
12188
|
spawnSync3("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
12109
12189
|
encoding: "utf8",
|
|
12110
12190
|
timeout: 5e3
|
|
12111
12191
|
});
|
|
12112
12192
|
spawnSync3("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
12113
|
-
|
|
12193
|
+
fs23.unlinkSync(SYSTEMD_UNIT);
|
|
12114
12194
|
}
|
|
12115
12195
|
}
|
|
12116
12196
|
function isSystemdInstalled() {
|
|
12117
|
-
return
|
|
12197
|
+
return fs23.existsSync(SYSTEMD_UNIT);
|
|
12118
12198
|
}
|
|
12119
12199
|
function stopRunningDaemon() {
|
|
12120
|
-
const pidFile =
|
|
12121
|
-
if (!
|
|
12200
|
+
const pidFile = path26.join(os20.homedir(), ".node9", "daemon.pid");
|
|
12201
|
+
if (!fs23.existsSync(pidFile)) return;
|
|
12122
12202
|
try {
|
|
12123
|
-
const data = JSON.parse(
|
|
12203
|
+
const data = JSON.parse(fs23.readFileSync(pidFile, "utf-8"));
|
|
12124
12204
|
const pid = data.pid;
|
|
12125
12205
|
const MAX_PID2 = 4194304;
|
|
12126
12206
|
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
|
|
@@ -12140,7 +12220,7 @@ function stopRunningDaemon() {
|
|
|
12140
12220
|
}
|
|
12141
12221
|
}
|
|
12142
12222
|
try {
|
|
12143
|
-
|
|
12223
|
+
fs23.unlinkSync(pidFile);
|
|
12144
12224
|
} catch {
|
|
12145
12225
|
}
|
|
12146
12226
|
} catch {
|
|
@@ -12215,20 +12295,20 @@ var init_service = __esm({
|
|
|
12215
12295
|
"src/daemon/service.ts"() {
|
|
12216
12296
|
"use strict";
|
|
12217
12297
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
12218
|
-
LAUNCHD_PLIST =
|
|
12219
|
-
SYSTEMD_UNIT_DIR =
|
|
12220
|
-
SYSTEMD_UNIT =
|
|
12298
|
+
LAUNCHD_PLIST = path26.join(os20.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
12299
|
+
SYSTEMD_UNIT_DIR = path26.join(os20.homedir(), ".config", "systemd", "user");
|
|
12300
|
+
SYSTEMD_UNIT = path26.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
|
|
12221
12301
|
}
|
|
12222
12302
|
});
|
|
12223
12303
|
|
|
12224
12304
|
// src/daemon/index.ts
|
|
12225
|
-
import
|
|
12305
|
+
import fs24 from "fs";
|
|
12226
12306
|
import chalk4 from "chalk";
|
|
12227
12307
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
12228
12308
|
function stopDaemon() {
|
|
12229
|
-
if (!
|
|
12309
|
+
if (!fs24.existsSync(DAEMON_PID_FILE)) return console.log(chalk4.yellow("Not running."));
|
|
12230
12310
|
try {
|
|
12231
|
-
const data = JSON.parse(
|
|
12311
|
+
const data = JSON.parse(fs24.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
12232
12312
|
const pid = data.pid;
|
|
12233
12313
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
12234
12314
|
console.log(chalk4.gray("Cleaned up invalid PID file."));
|
|
@@ -12240,7 +12320,7 @@ function stopDaemon() {
|
|
|
12240
12320
|
console.log(chalk4.gray("Cleaned up stale PID file."));
|
|
12241
12321
|
} finally {
|
|
12242
12322
|
try {
|
|
12243
|
-
|
|
12323
|
+
fs24.unlinkSync(DAEMON_PID_FILE);
|
|
12244
12324
|
} catch {
|
|
12245
12325
|
}
|
|
12246
12326
|
}
|
|
@@ -12249,9 +12329,9 @@ function daemonStatus() {
|
|
|
12249
12329
|
const serviceInstalled = isDaemonServiceInstalled();
|
|
12250
12330
|
const serviceLabel = serviceInstalled ? chalk4.green("installed (starts on login)") : chalk4.yellow("not installed \u2014 run: node9 daemon install");
|
|
12251
12331
|
let processStatus;
|
|
12252
|
-
if (
|
|
12332
|
+
if (fs24.existsSync(DAEMON_PID_FILE)) {
|
|
12253
12333
|
try {
|
|
12254
|
-
const data = JSON.parse(
|
|
12334
|
+
const data = JSON.parse(fs24.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
12255
12335
|
const pid = data.pid;
|
|
12256
12336
|
const port = data.port;
|
|
12257
12337
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
@@ -12301,9 +12381,9 @@ __export(tail_exports, {
|
|
|
12301
12381
|
});
|
|
12302
12382
|
import http2 from "http";
|
|
12303
12383
|
import chalk25 from "chalk";
|
|
12304
|
-
import
|
|
12384
|
+
import fs39 from "fs";
|
|
12305
12385
|
import os35 from "os";
|
|
12306
|
-
import
|
|
12386
|
+
import path42 from "path";
|
|
12307
12387
|
import readline5 from "readline";
|
|
12308
12388
|
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
12309
12389
|
function getIcon(tool) {
|
|
@@ -12321,20 +12401,20 @@ function getModelContextLimit(model) {
|
|
|
12321
12401
|
return 2e5;
|
|
12322
12402
|
}
|
|
12323
12403
|
function readSessionUsage() {
|
|
12324
|
-
const projectsDir =
|
|
12325
|
-
if (!
|
|
12404
|
+
const projectsDir = path42.join(os35.homedir(), ".claude", "projects");
|
|
12405
|
+
if (!fs39.existsSync(projectsDir)) return null;
|
|
12326
12406
|
let latestFile = null;
|
|
12327
12407
|
let latestMtime = 0;
|
|
12328
12408
|
try {
|
|
12329
|
-
for (const dir of
|
|
12330
|
-
const dirPath =
|
|
12409
|
+
for (const dir of fs39.readdirSync(projectsDir)) {
|
|
12410
|
+
const dirPath = path42.join(projectsDir, dir);
|
|
12331
12411
|
try {
|
|
12332
|
-
if (!
|
|
12333
|
-
for (const file of
|
|
12412
|
+
if (!fs39.statSync(dirPath).isDirectory()) continue;
|
|
12413
|
+
for (const file of fs39.readdirSync(dirPath)) {
|
|
12334
12414
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
12335
|
-
const filePath =
|
|
12415
|
+
const filePath = path42.join(dirPath, file);
|
|
12336
12416
|
try {
|
|
12337
|
-
const mtime =
|
|
12417
|
+
const mtime = fs39.statSync(filePath).mtimeMs;
|
|
12338
12418
|
if (mtime > latestMtime) {
|
|
12339
12419
|
latestMtime = mtime;
|
|
12340
12420
|
latestFile = filePath;
|
|
@@ -12349,7 +12429,7 @@ function readSessionUsage() {
|
|
|
12349
12429
|
}
|
|
12350
12430
|
if (!latestFile) return null;
|
|
12351
12431
|
try {
|
|
12352
|
-
const lines =
|
|
12432
|
+
const lines = fs39.readFileSync(latestFile, "utf-8").split("\n");
|
|
12353
12433
|
let lastModel = "";
|
|
12354
12434
|
let lastInput = 0;
|
|
12355
12435
|
let lastOutput = 0;
|
|
@@ -12438,9 +12518,9 @@ function renderPending(activity) {
|
|
|
12438
12518
|
}
|
|
12439
12519
|
async function ensureDaemon() {
|
|
12440
12520
|
let pidPort = null;
|
|
12441
|
-
if (
|
|
12521
|
+
if (fs39.existsSync(PID_FILE)) {
|
|
12442
12522
|
try {
|
|
12443
|
-
const { port } = JSON.parse(
|
|
12523
|
+
const { port } = JSON.parse(fs39.readFileSync(PID_FILE, "utf-8"));
|
|
12444
12524
|
pidPort = port;
|
|
12445
12525
|
} catch {
|
|
12446
12526
|
console.error(chalk25.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -12593,9 +12673,9 @@ function buildRecoveryCardLines(req) {
|
|
|
12593
12673
|
];
|
|
12594
12674
|
}
|
|
12595
12675
|
function readApproversFromDisk() {
|
|
12596
|
-
const configPath =
|
|
12676
|
+
const configPath = path42.join(os35.homedir(), ".node9", "config.json");
|
|
12597
12677
|
try {
|
|
12598
|
-
const raw = JSON.parse(
|
|
12678
|
+
const raw = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
|
|
12599
12679
|
const settings = raw.settings ?? {};
|
|
12600
12680
|
return settings.approvers ?? {};
|
|
12601
12681
|
} catch {
|
|
@@ -12611,15 +12691,15 @@ function approverStatusLine() {
|
|
|
12611
12691
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
12612
12692
|
}
|
|
12613
12693
|
function toggleApprover(channel) {
|
|
12614
|
-
const configPath =
|
|
12694
|
+
const configPath = path42.join(os35.homedir(), ".node9", "config.json");
|
|
12615
12695
|
try {
|
|
12616
|
-
const raw = JSON.parse(
|
|
12696
|
+
const raw = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
|
|
12617
12697
|
const settings = raw.settings ?? {};
|
|
12618
12698
|
const approvers = settings.approvers ?? {};
|
|
12619
12699
|
approvers[channel] = approvers[channel] === false;
|
|
12620
12700
|
settings.approvers = approvers;
|
|
12621
12701
|
raw.settings = settings;
|
|
12622
|
-
|
|
12702
|
+
fs39.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
12623
12703
|
} catch (err2) {
|
|
12624
12704
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
12625
12705
|
`);
|
|
@@ -12789,8 +12869,8 @@ async function startTail(options = {}) {
|
|
|
12789
12869
|
}
|
|
12790
12870
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
12791
12871
|
try {
|
|
12792
|
-
|
|
12793
|
-
|
|
12872
|
+
fs39.appendFileSync(
|
|
12873
|
+
path42.join(os35.homedir(), ".node9", "hook-debug.log"),
|
|
12794
12874
|
`[tail] POST /decision failed: ${String(err2)}
|
|
12795
12875
|
`
|
|
12796
12876
|
);
|
|
@@ -12871,9 +12951,9 @@ async function startTail(options = {}) {
|
|
|
12871
12951
|
}
|
|
12872
12952
|
} catch {
|
|
12873
12953
|
}
|
|
12874
|
-
const auditLog =
|
|
12954
|
+
const auditLog = path42.join(os35.homedir(), ".node9", "audit.log");
|
|
12875
12955
|
try {
|
|
12876
|
-
const unackedDlp =
|
|
12956
|
+
const unackedDlp = fs39.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
12877
12957
|
if (unackedDlp > 0) {
|
|
12878
12958
|
console.log("");
|
|
12879
12959
|
console.log(
|
|
@@ -13065,7 +13145,7 @@ var init_tail = __esm({
|
|
|
13065
13145
|
init_daemon2();
|
|
13066
13146
|
init_daemon();
|
|
13067
13147
|
init_core();
|
|
13068
|
-
PID_FILE =
|
|
13148
|
+
PID_FILE = path42.join(os35.homedir(), ".node9", "daemon.pid");
|
|
13069
13149
|
ICONS = {
|
|
13070
13150
|
bash: "\u{1F4BB}",
|
|
13071
13151
|
shell: "\u{1F4BB}",
|
|
@@ -13113,8 +13193,8 @@ __export(hud_exports, {
|
|
|
13113
13193
|
main: () => main,
|
|
13114
13194
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
13115
13195
|
});
|
|
13116
|
-
import
|
|
13117
|
-
import
|
|
13196
|
+
import fs40 from "fs";
|
|
13197
|
+
import path43 from "path";
|
|
13118
13198
|
import os36 from "os";
|
|
13119
13199
|
import http3 from "http";
|
|
13120
13200
|
async function readStdin() {
|
|
@@ -13191,9 +13271,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
13191
13271
|
return ` (${m}m left)`;
|
|
13192
13272
|
}
|
|
13193
13273
|
function safeReadJson(filePath) {
|
|
13194
|
-
if (!
|
|
13274
|
+
if (!fs40.existsSync(filePath)) return null;
|
|
13195
13275
|
try {
|
|
13196
|
-
return JSON.parse(
|
|
13276
|
+
return JSON.parse(fs40.readFileSync(filePath, "utf-8"));
|
|
13197
13277
|
} catch {
|
|
13198
13278
|
return null;
|
|
13199
13279
|
}
|
|
@@ -13214,12 +13294,12 @@ function countHooksInFile(filePath) {
|
|
|
13214
13294
|
return Object.keys(cfg.hooks).length;
|
|
13215
13295
|
}
|
|
13216
13296
|
function countRulesInDir(rulesDir) {
|
|
13217
|
-
if (!
|
|
13297
|
+
if (!fs40.existsSync(rulesDir)) return 0;
|
|
13218
13298
|
let count = 0;
|
|
13219
13299
|
try {
|
|
13220
|
-
for (const entry of
|
|
13300
|
+
for (const entry of fs40.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
13221
13301
|
if (entry.isDirectory()) {
|
|
13222
|
-
count += countRulesInDir(
|
|
13302
|
+
count += countRulesInDir(path43.join(rulesDir, entry.name));
|
|
13223
13303
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
13224
13304
|
count++;
|
|
13225
13305
|
}
|
|
@@ -13230,46 +13310,46 @@ function countRulesInDir(rulesDir) {
|
|
|
13230
13310
|
}
|
|
13231
13311
|
function isSamePath(a, b) {
|
|
13232
13312
|
try {
|
|
13233
|
-
return
|
|
13313
|
+
return path43.resolve(a) === path43.resolve(b);
|
|
13234
13314
|
} catch {
|
|
13235
13315
|
return false;
|
|
13236
13316
|
}
|
|
13237
13317
|
}
|
|
13238
13318
|
function countConfigs(cwd) {
|
|
13239
13319
|
const homeDir2 = os36.homedir();
|
|
13240
|
-
const claudeDir =
|
|
13320
|
+
const claudeDir = path43.join(homeDir2, ".claude");
|
|
13241
13321
|
let claudeMdCount = 0;
|
|
13242
13322
|
let rulesCount = 0;
|
|
13243
13323
|
let hooksCount = 0;
|
|
13244
13324
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
13245
13325
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
13246
|
-
if (
|
|
13247
|
-
rulesCount += countRulesInDir(
|
|
13248
|
-
const userSettings =
|
|
13326
|
+
if (fs40.existsSync(path43.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
13327
|
+
rulesCount += countRulesInDir(path43.join(claudeDir, "rules"));
|
|
13328
|
+
const userSettings = path43.join(claudeDir, "settings.json");
|
|
13249
13329
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
13250
13330
|
hooksCount += countHooksInFile(userSettings);
|
|
13251
|
-
const userClaudeJson =
|
|
13331
|
+
const userClaudeJson = path43.join(homeDir2, ".claude.json");
|
|
13252
13332
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
13253
13333
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
13254
13334
|
userMcpServers.delete(name);
|
|
13255
13335
|
}
|
|
13256
13336
|
if (cwd) {
|
|
13257
|
-
if (
|
|
13258
|
-
if (
|
|
13259
|
-
const projectClaudeDir =
|
|
13337
|
+
if (fs40.existsSync(path43.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
13338
|
+
if (fs40.existsSync(path43.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
13339
|
+
const projectClaudeDir = path43.join(cwd, ".claude");
|
|
13260
13340
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
13261
13341
|
if (!overlapsUserScope) {
|
|
13262
|
-
if (
|
|
13263
|
-
rulesCount += countRulesInDir(
|
|
13264
|
-
const projSettings =
|
|
13342
|
+
if (fs40.existsSync(path43.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
13343
|
+
rulesCount += countRulesInDir(path43.join(projectClaudeDir, "rules"));
|
|
13344
|
+
const projSettings = path43.join(projectClaudeDir, "settings.json");
|
|
13265
13345
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
13266
13346
|
hooksCount += countHooksInFile(projSettings);
|
|
13267
13347
|
}
|
|
13268
|
-
if (
|
|
13269
|
-
const localSettings =
|
|
13348
|
+
if (fs40.existsSync(path43.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
13349
|
+
const localSettings = path43.join(projectClaudeDir, "settings.local.json");
|
|
13270
13350
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
13271
13351
|
hooksCount += countHooksInFile(localSettings);
|
|
13272
|
-
const mcpJsonServers = getMcpServerNames(
|
|
13352
|
+
const mcpJsonServers = getMcpServerNames(path43.join(cwd, ".mcp.json"));
|
|
13273
13353
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
13274
13354
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
13275
13355
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -13302,12 +13382,12 @@ function readActiveShieldsHud() {
|
|
|
13302
13382
|
return shieldsCache.value;
|
|
13303
13383
|
}
|
|
13304
13384
|
try {
|
|
13305
|
-
const shieldsPath =
|
|
13306
|
-
if (!
|
|
13385
|
+
const shieldsPath = path43.join(os36.homedir(), ".node9", "shields.json");
|
|
13386
|
+
if (!fs40.existsSync(shieldsPath)) {
|
|
13307
13387
|
shieldsCache = { value: [], ts: now };
|
|
13308
13388
|
return [];
|
|
13309
13389
|
}
|
|
13310
|
-
const parsed = JSON.parse(
|
|
13390
|
+
const parsed = JSON.parse(fs40.readFileSync(shieldsPath, "utf-8"));
|
|
13311
13391
|
if (!Array.isArray(parsed.active)) {
|
|
13312
13392
|
shieldsCache = { value: [], ts: now };
|
|
13313
13393
|
return [];
|
|
@@ -13409,17 +13489,17 @@ function renderContextLine(stdin) {
|
|
|
13409
13489
|
async function main() {
|
|
13410
13490
|
try {
|
|
13411
13491
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
13412
|
-
if (
|
|
13492
|
+
if (fs40.existsSync(path43.join(os36.homedir(), ".node9", "hud-debug"))) {
|
|
13413
13493
|
try {
|
|
13414
|
-
const logPath =
|
|
13494
|
+
const logPath = path43.join(os36.homedir(), ".node9", "hud-debug.log");
|
|
13415
13495
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
13416
13496
|
let size = 0;
|
|
13417
13497
|
try {
|
|
13418
|
-
size =
|
|
13498
|
+
size = fs40.statSync(logPath).size;
|
|
13419
13499
|
} catch {
|
|
13420
13500
|
}
|
|
13421
13501
|
if (size < MAX_LOG_SIZE) {
|
|
13422
|
-
|
|
13502
|
+
fs40.appendFileSync(
|
|
13423
13503
|
logPath,
|
|
13424
13504
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
13425
13505
|
);
|
|
@@ -13440,11 +13520,11 @@ async function main() {
|
|
|
13440
13520
|
try {
|
|
13441
13521
|
const cwd = stdin.cwd ?? process.cwd();
|
|
13442
13522
|
for (const configPath of [
|
|
13443
|
-
|
|
13444
|
-
|
|
13523
|
+
path43.join(cwd, "node9.config.json"),
|
|
13524
|
+
path43.join(os36.homedir(), ".node9", "config.json")
|
|
13445
13525
|
]) {
|
|
13446
|
-
if (!
|
|
13447
|
-
const cfg = JSON.parse(
|
|
13526
|
+
if (!fs40.existsSync(configPath)) continue;
|
|
13527
|
+
const cfg = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
|
|
13448
13528
|
const hud = cfg.settings?.hud;
|
|
13449
13529
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
13450
13530
|
}
|
|
@@ -14512,8 +14592,8 @@ function getAgentsStatus(homeDir2 = os11.homedir()) {
|
|
|
14512
14592
|
// src/cli.ts
|
|
14513
14593
|
init_daemon2();
|
|
14514
14594
|
import chalk26 from "chalk";
|
|
14515
|
-
import
|
|
14516
|
-
import
|
|
14595
|
+
import fs41 from "fs";
|
|
14596
|
+
import path44 from "path";
|
|
14517
14597
|
import os37 from "os";
|
|
14518
14598
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
14519
14599
|
|
|
@@ -14703,19 +14783,19 @@ init_daemon();
|
|
|
14703
14783
|
init_config();
|
|
14704
14784
|
init_policy();
|
|
14705
14785
|
import chalk6 from "chalk";
|
|
14706
|
-
import
|
|
14786
|
+
import fs27 from "fs";
|
|
14707
14787
|
import { spawn as spawn6 } from "child_process";
|
|
14708
|
-
import
|
|
14788
|
+
import path29 from "path";
|
|
14709
14789
|
import os23 from "os";
|
|
14710
14790
|
|
|
14711
14791
|
// src/undo.ts
|
|
14712
14792
|
import { spawnSync as spawnSync5, spawn as spawn5 } from "child_process";
|
|
14713
14793
|
import crypto3 from "crypto";
|
|
14714
|
-
import
|
|
14794
|
+
import fs25 from "fs";
|
|
14715
14795
|
import net3 from "net";
|
|
14716
|
-
import
|
|
14796
|
+
import path27 from "path";
|
|
14717
14797
|
import os21 from "os";
|
|
14718
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
14798
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path27.join(os21.tmpdir(), "node9-activity.sock");
|
|
14719
14799
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
14720
14800
|
try {
|
|
14721
14801
|
const payload = JSON.stringify({
|
|
@@ -14735,22 +14815,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
14735
14815
|
} catch {
|
|
14736
14816
|
}
|
|
14737
14817
|
}
|
|
14738
|
-
var SNAPSHOT_STACK_PATH =
|
|
14739
|
-
var UNDO_LATEST_PATH =
|
|
14818
|
+
var SNAPSHOT_STACK_PATH = path27.join(os21.homedir(), ".node9", "snapshots.json");
|
|
14819
|
+
var UNDO_LATEST_PATH = path27.join(os21.homedir(), ".node9", "undo_latest.txt");
|
|
14740
14820
|
var MAX_SNAPSHOTS = 10;
|
|
14741
14821
|
var GIT_TIMEOUT = 15e3;
|
|
14742
14822
|
function readStack() {
|
|
14743
14823
|
try {
|
|
14744
|
-
if (
|
|
14745
|
-
return JSON.parse(
|
|
14824
|
+
if (fs25.existsSync(SNAPSHOT_STACK_PATH))
|
|
14825
|
+
return JSON.parse(fs25.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
14746
14826
|
} catch {
|
|
14747
14827
|
}
|
|
14748
14828
|
return [];
|
|
14749
14829
|
}
|
|
14750
14830
|
function writeStack(stack) {
|
|
14751
|
-
const dir =
|
|
14752
|
-
if (!
|
|
14753
|
-
|
|
14831
|
+
const dir = path27.dirname(SNAPSHOT_STACK_PATH);
|
|
14832
|
+
if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
|
|
14833
|
+
fs25.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
14754
14834
|
}
|
|
14755
14835
|
function extractFilePath(args) {
|
|
14756
14836
|
if (!args || typeof args !== "object") return null;
|
|
@@ -14770,12 +14850,12 @@ function buildArgsSummary(tool, args) {
|
|
|
14770
14850
|
return "";
|
|
14771
14851
|
}
|
|
14772
14852
|
function findProjectRoot(filePath) {
|
|
14773
|
-
let dir =
|
|
14853
|
+
let dir = path27.dirname(filePath);
|
|
14774
14854
|
while (true) {
|
|
14775
|
-
if (
|
|
14855
|
+
if (fs25.existsSync(path27.join(dir, ".git")) || fs25.existsSync(path27.join(dir, "package.json"))) {
|
|
14776
14856
|
return dir;
|
|
14777
14857
|
}
|
|
14778
|
-
const parent =
|
|
14858
|
+
const parent = path27.dirname(dir);
|
|
14779
14859
|
if (parent === dir) return process.cwd();
|
|
14780
14860
|
dir = parent;
|
|
14781
14861
|
}
|
|
@@ -14783,7 +14863,7 @@ function findProjectRoot(filePath) {
|
|
|
14783
14863
|
function normalizeCwdForHash(cwd) {
|
|
14784
14864
|
let normalized;
|
|
14785
14865
|
try {
|
|
14786
|
-
normalized =
|
|
14866
|
+
normalized = fs25.realpathSync(cwd);
|
|
14787
14867
|
} catch {
|
|
14788
14868
|
normalized = cwd;
|
|
14789
14869
|
}
|
|
@@ -14793,16 +14873,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
14793
14873
|
}
|
|
14794
14874
|
function getShadowRepoDir(cwd) {
|
|
14795
14875
|
const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
14796
|
-
return
|
|
14876
|
+
return path27.join(os21.homedir(), ".node9", "snapshots", hash);
|
|
14797
14877
|
}
|
|
14798
14878
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
14799
14879
|
try {
|
|
14800
14880
|
const cutoff = Date.now() - 6e4;
|
|
14801
|
-
for (const f of
|
|
14881
|
+
for (const f of fs25.readdirSync(shadowDir)) {
|
|
14802
14882
|
if (f.startsWith("index_")) {
|
|
14803
|
-
const fp =
|
|
14883
|
+
const fp = path27.join(shadowDir, f);
|
|
14804
14884
|
try {
|
|
14805
|
-
if (
|
|
14885
|
+
if (fs25.statSync(fp).mtimeMs < cutoff) fs25.unlinkSync(fp);
|
|
14806
14886
|
} catch {
|
|
14807
14887
|
}
|
|
14808
14888
|
}
|
|
@@ -14814,7 +14894,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
14814
14894
|
const hardcoded = [".git", ".node9"];
|
|
14815
14895
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
14816
14896
|
try {
|
|
14817
|
-
|
|
14897
|
+
fs25.writeFileSync(path27.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
14818
14898
|
} catch {
|
|
14819
14899
|
}
|
|
14820
14900
|
}
|
|
@@ -14827,25 +14907,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
14827
14907
|
timeout: 3e3
|
|
14828
14908
|
});
|
|
14829
14909
|
if (check.status === 0) {
|
|
14830
|
-
const ptPath =
|
|
14910
|
+
const ptPath = path27.join(shadowDir, "project-path.txt");
|
|
14831
14911
|
try {
|
|
14832
|
-
const stored =
|
|
14912
|
+
const stored = fs25.readFileSync(ptPath, "utf8").trim();
|
|
14833
14913
|
if (stored === normalizedCwd) return true;
|
|
14834
14914
|
if (process.env.NODE9_DEBUG === "1")
|
|
14835
14915
|
console.error(
|
|
14836
14916
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
14837
14917
|
);
|
|
14838
|
-
|
|
14918
|
+
fs25.rmSync(shadowDir, { recursive: true, force: true });
|
|
14839
14919
|
} catch {
|
|
14840
14920
|
try {
|
|
14841
|
-
|
|
14921
|
+
fs25.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
14842
14922
|
} catch {
|
|
14843
14923
|
}
|
|
14844
14924
|
return true;
|
|
14845
14925
|
}
|
|
14846
14926
|
}
|
|
14847
14927
|
try {
|
|
14848
|
-
|
|
14928
|
+
fs25.mkdirSync(shadowDir, { recursive: true });
|
|
14849
14929
|
} catch {
|
|
14850
14930
|
}
|
|
14851
14931
|
const init = spawnSync5("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -14854,7 +14934,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
14854
14934
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
14855
14935
|
return false;
|
|
14856
14936
|
}
|
|
14857
|
-
const configFile =
|
|
14937
|
+
const configFile = path27.join(shadowDir, "config");
|
|
14858
14938
|
spawnSync5("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
14859
14939
|
timeout: 3e3
|
|
14860
14940
|
});
|
|
@@ -14862,7 +14942,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
14862
14942
|
timeout: 3e3
|
|
14863
14943
|
});
|
|
14864
14944
|
try {
|
|
14865
|
-
|
|
14945
|
+
fs25.writeFileSync(path27.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
14866
14946
|
} catch {
|
|
14867
14947
|
}
|
|
14868
14948
|
return true;
|
|
@@ -14882,12 +14962,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
14882
14962
|
let indexFile = null;
|
|
14883
14963
|
try {
|
|
14884
14964
|
const rawFilePath = extractFilePath(args);
|
|
14885
|
-
const absFilePath = rawFilePath &&
|
|
14965
|
+
const absFilePath = rawFilePath && path27.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
14886
14966
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
14887
14967
|
const shadowDir = getShadowRepoDir(cwd);
|
|
14888
14968
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
14889
14969
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
14890
|
-
indexFile =
|
|
14970
|
+
indexFile = path27.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
14891
14971
|
const shadowEnv = {
|
|
14892
14972
|
...process.env,
|
|
14893
14973
|
GIT_DIR: shadowDir,
|
|
@@ -14959,7 +15039,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
14959
15039
|
writeStack(stack);
|
|
14960
15040
|
const entry = stack[stack.length - 1];
|
|
14961
15041
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
14962
|
-
|
|
15042
|
+
fs25.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
14963
15043
|
if (shouldGc) {
|
|
14964
15044
|
spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
14965
15045
|
}
|
|
@@ -14970,7 +15050,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
14970
15050
|
} finally {
|
|
14971
15051
|
if (indexFile) {
|
|
14972
15052
|
try {
|
|
14973
|
-
|
|
15053
|
+
fs25.unlinkSync(indexFile);
|
|
14974
15054
|
} catch {
|
|
14975
15055
|
}
|
|
14976
15056
|
}
|
|
@@ -15046,9 +15126,9 @@ function applyUndo(hash, cwd) {
|
|
|
15046
15126
|
timeout: GIT_TIMEOUT
|
|
15047
15127
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
15048
15128
|
for (const file of [...tracked, ...untracked]) {
|
|
15049
|
-
const fullPath =
|
|
15050
|
-
if (!snapshotFiles.has(file) &&
|
|
15051
|
-
|
|
15129
|
+
const fullPath = path27.join(dir, file);
|
|
15130
|
+
if (!snapshotFiles.has(file) && fs25.existsSync(fullPath)) {
|
|
15131
|
+
fs25.unlinkSync(fullPath);
|
|
15052
15132
|
}
|
|
15053
15133
|
}
|
|
15054
15134
|
return true;
|
|
@@ -15061,12 +15141,12 @@ function applyUndo(hash, cwd) {
|
|
|
15061
15141
|
init_daemon_starter();
|
|
15062
15142
|
|
|
15063
15143
|
// src/skill-pin.ts
|
|
15064
|
-
import
|
|
15065
|
-
import
|
|
15144
|
+
import fs26 from "fs";
|
|
15145
|
+
import path28 from "path";
|
|
15066
15146
|
import os22 from "os";
|
|
15067
15147
|
import crypto4 from "crypto";
|
|
15068
15148
|
function getPinsFilePath() {
|
|
15069
|
-
return
|
|
15149
|
+
return path28.join(os22.homedir(), ".node9", "skill-pins.json");
|
|
15070
15150
|
}
|
|
15071
15151
|
var MAX_FILES = 5e3;
|
|
15072
15152
|
var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
@@ -15080,18 +15160,18 @@ function walkDir(root) {
|
|
|
15080
15160
|
if (out.length >= MAX_FILES) return;
|
|
15081
15161
|
let entries;
|
|
15082
15162
|
try {
|
|
15083
|
-
entries =
|
|
15163
|
+
entries = fs26.readdirSync(dir, { withFileTypes: true });
|
|
15084
15164
|
} catch {
|
|
15085
15165
|
return;
|
|
15086
15166
|
}
|
|
15087
15167
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
15088
15168
|
for (const entry of entries) {
|
|
15089
15169
|
if (out.length >= MAX_FILES) return;
|
|
15090
|
-
const full =
|
|
15091
|
-
const rel = relDir ?
|
|
15170
|
+
const full = path28.join(dir, entry.name);
|
|
15171
|
+
const rel = relDir ? path28.posix.join(relDir, entry.name) : entry.name;
|
|
15092
15172
|
let lst;
|
|
15093
15173
|
try {
|
|
15094
|
-
lst =
|
|
15174
|
+
lst = fs26.lstatSync(full);
|
|
15095
15175
|
} catch {
|
|
15096
15176
|
continue;
|
|
15097
15177
|
}
|
|
@@ -15103,7 +15183,7 @@ function walkDir(root) {
|
|
|
15103
15183
|
if (!lst.isFile()) continue;
|
|
15104
15184
|
if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
|
|
15105
15185
|
try {
|
|
15106
|
-
const buf =
|
|
15186
|
+
const buf = fs26.readFileSync(full);
|
|
15107
15187
|
totalBytes += buf.length;
|
|
15108
15188
|
out.push({ rel, hash: sha256Bytes(buf) });
|
|
15109
15189
|
} catch {
|
|
@@ -15117,14 +15197,14 @@ function walkDir(root) {
|
|
|
15117
15197
|
function hashSkillRoot(absPath) {
|
|
15118
15198
|
let lst;
|
|
15119
15199
|
try {
|
|
15120
|
-
lst =
|
|
15200
|
+
lst = fs26.lstatSync(absPath);
|
|
15121
15201
|
} catch {
|
|
15122
15202
|
return { exists: false, contentHash: "", fileCount: 0 };
|
|
15123
15203
|
}
|
|
15124
15204
|
if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
|
|
15125
15205
|
if (lst.isFile()) {
|
|
15126
15206
|
try {
|
|
15127
|
-
return { exists: true, contentHash: sha256Bytes(
|
|
15207
|
+
return { exists: true, contentHash: sha256Bytes(fs26.readFileSync(absPath)), fileCount: 1 };
|
|
15128
15208
|
} catch {
|
|
15129
15209
|
return { exists: false, contentHash: "", fileCount: 0 };
|
|
15130
15210
|
}
|
|
@@ -15142,7 +15222,7 @@ function getRootKey(absPath) {
|
|
|
15142
15222
|
function readSkillPinsSafe() {
|
|
15143
15223
|
const filePath = getPinsFilePath();
|
|
15144
15224
|
try {
|
|
15145
|
-
const raw =
|
|
15225
|
+
const raw = fs26.readFileSync(filePath, "utf-8");
|
|
15146
15226
|
if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
15147
15227
|
const parsed = JSON.parse(raw);
|
|
15148
15228
|
if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
|
|
@@ -15162,10 +15242,10 @@ function readSkillPins() {
|
|
|
15162
15242
|
}
|
|
15163
15243
|
function writeSkillPins(data) {
|
|
15164
15244
|
const filePath = getPinsFilePath();
|
|
15165
|
-
|
|
15245
|
+
fs26.mkdirSync(path28.dirname(filePath), { recursive: true });
|
|
15166
15246
|
const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
|
|
15167
|
-
|
|
15168
|
-
|
|
15247
|
+
fs26.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
15248
|
+
fs26.renameSync(tmp, filePath);
|
|
15169
15249
|
}
|
|
15170
15250
|
function removePin(rootKey) {
|
|
15171
15251
|
const pins = readSkillPins();
|
|
@@ -15209,36 +15289,36 @@ function verifyAndPinRoots(roots) {
|
|
|
15209
15289
|
return { kind: "verified" };
|
|
15210
15290
|
}
|
|
15211
15291
|
function defaultSkillRoots(_cwd) {
|
|
15212
|
-
const marketplaces =
|
|
15292
|
+
const marketplaces = path28.join(os22.homedir(), ".claude", "plugins", "marketplaces");
|
|
15213
15293
|
const roots = [];
|
|
15214
15294
|
let registries;
|
|
15215
15295
|
try {
|
|
15216
|
-
registries =
|
|
15296
|
+
registries = fs26.readdirSync(marketplaces, { withFileTypes: true });
|
|
15217
15297
|
} catch {
|
|
15218
15298
|
return [];
|
|
15219
15299
|
}
|
|
15220
15300
|
for (const registry of registries) {
|
|
15221
15301
|
if (!registry.isDirectory()) continue;
|
|
15222
|
-
const pluginsDir =
|
|
15302
|
+
const pluginsDir = path28.join(marketplaces, registry.name, "plugins");
|
|
15223
15303
|
let plugins;
|
|
15224
15304
|
try {
|
|
15225
|
-
plugins =
|
|
15305
|
+
plugins = fs26.readdirSync(pluginsDir, { withFileTypes: true });
|
|
15226
15306
|
} catch {
|
|
15227
15307
|
continue;
|
|
15228
15308
|
}
|
|
15229
15309
|
for (const plugin of plugins) {
|
|
15230
15310
|
if (!plugin.isDirectory()) continue;
|
|
15231
|
-
roots.push(
|
|
15311
|
+
roots.push(path28.join(pluginsDir, plugin.name));
|
|
15232
15312
|
}
|
|
15233
15313
|
}
|
|
15234
15314
|
return roots;
|
|
15235
15315
|
}
|
|
15236
15316
|
function resolveUserSkillRoot(entry, cwd) {
|
|
15237
15317
|
if (!entry) return null;
|
|
15238
|
-
if (entry.startsWith("~/") || entry === "~") return
|
|
15239
|
-
if (
|
|
15240
|
-
if (!cwd || !
|
|
15241
|
-
return
|
|
15318
|
+
if (entry.startsWith("~/") || entry === "~") return path28.join(os22.homedir(), entry.slice(1));
|
|
15319
|
+
if (path28.isAbsolute(entry)) return entry;
|
|
15320
|
+
if (!cwd || !path28.isAbsolute(cwd)) return null;
|
|
15321
|
+
return path28.join(cwd, entry);
|
|
15242
15322
|
}
|
|
15243
15323
|
|
|
15244
15324
|
// src/cli/commands/check.ts
|
|
@@ -15256,9 +15336,9 @@ function registerCheckCommand(program2) {
|
|
|
15256
15336
|
} catch (err2) {
|
|
15257
15337
|
const tempConfig = getConfig();
|
|
15258
15338
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
15259
|
-
const logPath =
|
|
15339
|
+
const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
|
|
15260
15340
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
15261
|
-
|
|
15341
|
+
fs27.appendFileSync(
|
|
15262
15342
|
logPath,
|
|
15263
15343
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
15264
15344
|
RAW: ${raw}
|
|
@@ -15271,11 +15351,11 @@ RAW: ${raw}
|
|
|
15271
15351
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
15272
15352
|
try {
|
|
15273
15353
|
const scriptPath = process.argv[1];
|
|
15274
|
-
if (typeof scriptPath !== "string" || !
|
|
15354
|
+
if (typeof scriptPath !== "string" || !path29.isAbsolute(scriptPath))
|
|
15275
15355
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
15276
|
-
const resolvedScript =
|
|
15277
|
-
const packageDist =
|
|
15278
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
15356
|
+
const resolvedScript = fs27.realpathSync(scriptPath);
|
|
15357
|
+
const packageDist = fs27.realpathSync(path29.resolve(__dirname, "../.."));
|
|
15358
|
+
if (!resolvedScript.startsWith(packageDist + path29.sep) && resolvedScript !== packageDist)
|
|
15279
15359
|
throw new Error(
|
|
15280
15360
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
15281
15361
|
);
|
|
@@ -15297,10 +15377,10 @@ RAW: ${raw}
|
|
|
15297
15377
|
});
|
|
15298
15378
|
d.unref();
|
|
15299
15379
|
} catch (spawnErr) {
|
|
15300
|
-
const logPath =
|
|
15380
|
+
const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
|
|
15301
15381
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
15302
15382
|
try {
|
|
15303
|
-
|
|
15383
|
+
fs27.appendFileSync(
|
|
15304
15384
|
logPath,
|
|
15305
15385
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
15306
15386
|
`
|
|
@@ -15310,10 +15390,10 @@ RAW: ${raw}
|
|
|
15310
15390
|
}
|
|
15311
15391
|
}
|
|
15312
15392
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
15313
|
-
const logPath =
|
|
15314
|
-
if (!
|
|
15315
|
-
|
|
15316
|
-
|
|
15393
|
+
const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
|
|
15394
|
+
if (!fs27.existsSync(path29.dirname(logPath)))
|
|
15395
|
+
fs27.mkdirSync(path29.dirname(logPath), { recursive: true });
|
|
15396
|
+
fs27.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
15317
15397
|
`);
|
|
15318
15398
|
}
|
|
15319
15399
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -15326,8 +15406,8 @@ RAW: ${raw}
|
|
|
15326
15406
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
15327
15407
|
let ttyFd = null;
|
|
15328
15408
|
try {
|
|
15329
|
-
ttyFd =
|
|
15330
|
-
const writeTty = (line) =>
|
|
15409
|
+
ttyFd = fs27.openSync("/dev/tty", "w");
|
|
15410
|
+
const writeTty = (line) => fs27.writeSync(ttyFd, line + "\n");
|
|
15331
15411
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
15332
15412
|
writeTty(chalk6.bgRed.white.bold(`
|
|
15333
15413
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -15346,7 +15426,7 @@ RAW: ${raw}
|
|
|
15346
15426
|
} finally {
|
|
15347
15427
|
if (ttyFd !== null)
|
|
15348
15428
|
try {
|
|
15349
|
-
|
|
15429
|
+
fs27.closeSync(ttyFd);
|
|
15350
15430
|
} catch {
|
|
15351
15431
|
}
|
|
15352
15432
|
}
|
|
@@ -15380,17 +15460,17 @@ RAW: ${raw}
|
|
|
15380
15460
|
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
15381
15461
|
if (skillPinCfg.enabled && safeSessionId) {
|
|
15382
15462
|
try {
|
|
15383
|
-
const sessionsDir =
|
|
15384
|
-
const flagPath =
|
|
15463
|
+
const sessionsDir = path29.join(os23.homedir(), ".node9", "skill-sessions");
|
|
15464
|
+
const flagPath = path29.join(sessionsDir, `${safeSessionId}.json`);
|
|
15385
15465
|
let flag = null;
|
|
15386
15466
|
try {
|
|
15387
|
-
flag = JSON.parse(
|
|
15467
|
+
flag = JSON.parse(fs27.readFileSync(flagPath, "utf-8"));
|
|
15388
15468
|
} catch {
|
|
15389
15469
|
}
|
|
15390
15470
|
const writeFlag = (data2) => {
|
|
15391
15471
|
try {
|
|
15392
|
-
|
|
15393
|
-
|
|
15472
|
+
fs27.mkdirSync(sessionsDir, { recursive: true });
|
|
15473
|
+
fs27.writeFileSync(
|
|
15394
15474
|
flagPath,
|
|
15395
15475
|
JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
15396
15476
|
{ mode: 384 }
|
|
@@ -15401,8 +15481,8 @@ RAW: ${raw}
|
|
|
15401
15481
|
const sendSkillWarn = (detail, recoveryCmd) => {
|
|
15402
15482
|
let ttyFd = null;
|
|
15403
15483
|
try {
|
|
15404
|
-
ttyFd =
|
|
15405
|
-
const w = (line) =>
|
|
15484
|
+
ttyFd = fs27.openSync("/dev/tty", "w");
|
|
15485
|
+
const w = (line) => fs27.writeSync(ttyFd, line + "\n");
|
|
15406
15486
|
w(chalk6.yellow(`
|
|
15407
15487
|
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
15408
15488
|
w(chalk6.gray(` ${detail}`));
|
|
@@ -15417,7 +15497,7 @@ RAW: ${raw}
|
|
|
15417
15497
|
} finally {
|
|
15418
15498
|
if (ttyFd !== null)
|
|
15419
15499
|
try {
|
|
15420
|
-
|
|
15500
|
+
fs27.closeSync(ttyFd);
|
|
15421
15501
|
} catch {
|
|
15422
15502
|
}
|
|
15423
15503
|
}
|
|
@@ -15433,7 +15513,7 @@ RAW: ${raw}
|
|
|
15433
15513
|
return;
|
|
15434
15514
|
}
|
|
15435
15515
|
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
15436
|
-
const absoluteCwd = typeof payload.cwd === "string" &&
|
|
15516
|
+
const absoluteCwd = typeof payload.cwd === "string" && path29.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
15437
15517
|
const extraRoots = skillPinCfg.roots;
|
|
15438
15518
|
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
15439
15519
|
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
@@ -15474,10 +15554,10 @@ RAW: ${raw}
|
|
|
15474
15554
|
}
|
|
15475
15555
|
try {
|
|
15476
15556
|
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
15477
|
-
for (const name of
|
|
15478
|
-
const p =
|
|
15557
|
+
for (const name of fs27.readdirSync(sessionsDir)) {
|
|
15558
|
+
const p = path29.join(sessionsDir, name);
|
|
15479
15559
|
try {
|
|
15480
|
-
if (
|
|
15560
|
+
if (fs27.statSync(p).mtimeMs < cutoff) fs27.unlinkSync(p);
|
|
15481
15561
|
} catch {
|
|
15482
15562
|
}
|
|
15483
15563
|
}
|
|
@@ -15487,9 +15567,9 @@ RAW: ${raw}
|
|
|
15487
15567
|
} catch (err2) {
|
|
15488
15568
|
if (process.env.NODE9_DEBUG === "1") {
|
|
15489
15569
|
try {
|
|
15490
|
-
const dbg =
|
|
15570
|
+
const dbg = path29.join(os23.homedir(), ".node9", "hook-debug.log");
|
|
15491
15571
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
15492
|
-
|
|
15572
|
+
fs27.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
|
|
15493
15573
|
`);
|
|
15494
15574
|
} catch {
|
|
15495
15575
|
}
|
|
@@ -15499,7 +15579,7 @@ RAW: ${raw}
|
|
|
15499
15579
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
15500
15580
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
15501
15581
|
}
|
|
15502
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
15582
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && path29.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
15503
15583
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
15504
15584
|
cwd: safeCwdForAuth
|
|
15505
15585
|
});
|
|
@@ -15511,12 +15591,12 @@ RAW: ${raw}
|
|
|
15511
15591
|
}
|
|
15512
15592
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
15513
15593
|
try {
|
|
15514
|
-
const tty =
|
|
15515
|
-
|
|
15594
|
+
const tty = fs27.openSync("/dev/tty", "w");
|
|
15595
|
+
fs27.writeSync(
|
|
15516
15596
|
tty,
|
|
15517
15597
|
chalk6.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
15518
15598
|
);
|
|
15519
|
-
|
|
15599
|
+
fs27.closeSync(tty);
|
|
15520
15600
|
} catch {
|
|
15521
15601
|
}
|
|
15522
15602
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -15543,9 +15623,9 @@ RAW: ${raw}
|
|
|
15543
15623
|
});
|
|
15544
15624
|
} catch (err2) {
|
|
15545
15625
|
if (process.env.NODE9_DEBUG === "1") {
|
|
15546
|
-
const logPath =
|
|
15626
|
+
const logPath = path29.join(os23.homedir(), ".node9", "hook-debug.log");
|
|
15547
15627
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
15548
|
-
|
|
15628
|
+
fs27.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
15549
15629
|
`);
|
|
15550
15630
|
}
|
|
15551
15631
|
process.exit(0);
|
|
@@ -15581,8 +15661,8 @@ RAW: ${raw}
|
|
|
15581
15661
|
// src/cli/commands/log.ts
|
|
15582
15662
|
init_audit();
|
|
15583
15663
|
init_config();
|
|
15584
|
-
import
|
|
15585
|
-
import
|
|
15664
|
+
import fs28 from "fs";
|
|
15665
|
+
import path30 from "path";
|
|
15586
15666
|
import os24 from "os";
|
|
15587
15667
|
init_daemon();
|
|
15588
15668
|
|
|
@@ -15656,10 +15736,10 @@ function registerLogCommand(program2) {
|
|
|
15656
15736
|
decision: "allowed",
|
|
15657
15737
|
source: "post-hook"
|
|
15658
15738
|
};
|
|
15659
|
-
const logPath =
|
|
15660
|
-
if (!
|
|
15661
|
-
|
|
15662
|
-
|
|
15739
|
+
const logPath = path30.join(os24.homedir(), ".node9", "audit.log");
|
|
15740
|
+
if (!fs28.existsSync(path30.dirname(logPath)))
|
|
15741
|
+
fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
|
|
15742
|
+
fs28.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
15663
15743
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
15664
15744
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
15665
15745
|
if (command) {
|
|
@@ -15692,7 +15772,7 @@ function registerLogCommand(program2) {
|
|
|
15692
15772
|
}
|
|
15693
15773
|
}
|
|
15694
15774
|
}
|
|
15695
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
15775
|
+
const safeCwd = typeof payload.cwd === "string" && path30.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
15696
15776
|
const config = getConfig(safeCwd);
|
|
15697
15777
|
if ((tool === "Bash" || tool === "bash") && config.settings.enableUndo !== false) {
|
|
15698
15778
|
const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
@@ -15713,9 +15793,9 @@ function registerLogCommand(program2) {
|
|
|
15713
15793
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
15714
15794
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
15715
15795
|
`);
|
|
15716
|
-
const debugPath =
|
|
15796
|
+
const debugPath = path30.join(os24.homedir(), ".node9", "hook-debug.log");
|
|
15717
15797
|
try {
|
|
15718
|
-
|
|
15798
|
+
fs28.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
15719
15799
|
`);
|
|
15720
15800
|
} catch {
|
|
15721
15801
|
}
|
|
@@ -16116,8 +16196,8 @@ function registerConfigShowCommand(program2) {
|
|
|
16116
16196
|
// src/cli/commands/doctor.ts
|
|
16117
16197
|
init_daemon();
|
|
16118
16198
|
import chalk8 from "chalk";
|
|
16119
|
-
import
|
|
16120
|
-
import
|
|
16199
|
+
import fs29 from "fs";
|
|
16200
|
+
import path31 from "path";
|
|
16121
16201
|
import os25 from "os";
|
|
16122
16202
|
import { execSync as execSync2 } from "child_process";
|
|
16123
16203
|
function registerDoctorCommand(program2, version2) {
|
|
@@ -16171,10 +16251,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16171
16251
|
);
|
|
16172
16252
|
}
|
|
16173
16253
|
section("Configuration");
|
|
16174
|
-
const globalConfigPath =
|
|
16175
|
-
if (
|
|
16254
|
+
const globalConfigPath = path31.join(homeDir2, ".node9", "config.json");
|
|
16255
|
+
if (fs29.existsSync(globalConfigPath)) {
|
|
16176
16256
|
try {
|
|
16177
|
-
JSON.parse(
|
|
16257
|
+
JSON.parse(fs29.readFileSync(globalConfigPath, "utf-8"));
|
|
16178
16258
|
pass("~/.node9/config.json found and valid");
|
|
16179
16259
|
} catch {
|
|
16180
16260
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -16182,10 +16262,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16182
16262
|
} else {
|
|
16183
16263
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
16184
16264
|
}
|
|
16185
|
-
const projectConfigPath =
|
|
16186
|
-
if (
|
|
16265
|
+
const projectConfigPath = path31.join(process.cwd(), "node9.config.json");
|
|
16266
|
+
if (fs29.existsSync(projectConfigPath)) {
|
|
16187
16267
|
try {
|
|
16188
|
-
JSON.parse(
|
|
16268
|
+
JSON.parse(fs29.readFileSync(projectConfigPath, "utf-8"));
|
|
16189
16269
|
pass("node9.config.json found and valid (project)");
|
|
16190
16270
|
} catch {
|
|
16191
16271
|
fail(
|
|
@@ -16194,8 +16274,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16194
16274
|
);
|
|
16195
16275
|
}
|
|
16196
16276
|
}
|
|
16197
|
-
const credsPath =
|
|
16198
|
-
if (
|
|
16277
|
+
const credsPath = path31.join(homeDir2, ".node9", "credentials.json");
|
|
16278
|
+
if (fs29.existsSync(credsPath)) {
|
|
16199
16279
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
16200
16280
|
} else {
|
|
16201
16281
|
warn(
|
|
@@ -16204,10 +16284,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16204
16284
|
);
|
|
16205
16285
|
}
|
|
16206
16286
|
section("Agent Hooks");
|
|
16207
|
-
const claudeSettingsPath =
|
|
16208
|
-
if (
|
|
16287
|
+
const claudeSettingsPath = path31.join(homeDir2, ".claude", "settings.json");
|
|
16288
|
+
if (fs29.existsSync(claudeSettingsPath)) {
|
|
16209
16289
|
try {
|
|
16210
|
-
const cs = JSON.parse(
|
|
16290
|
+
const cs = JSON.parse(fs29.readFileSync(claudeSettingsPath, "utf-8"));
|
|
16211
16291
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
16212
16292
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
16213
16293
|
);
|
|
@@ -16223,10 +16303,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16223
16303
|
} else {
|
|
16224
16304
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
16225
16305
|
}
|
|
16226
|
-
const geminiSettingsPath =
|
|
16227
|
-
if (
|
|
16306
|
+
const geminiSettingsPath = path31.join(homeDir2, ".gemini", "settings.json");
|
|
16307
|
+
if (fs29.existsSync(geminiSettingsPath)) {
|
|
16228
16308
|
try {
|
|
16229
|
-
const gs = JSON.parse(
|
|
16309
|
+
const gs = JSON.parse(fs29.readFileSync(geminiSettingsPath, "utf-8"));
|
|
16230
16310
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
16231
16311
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
16232
16312
|
);
|
|
@@ -16242,10 +16322,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16242
16322
|
} else {
|
|
16243
16323
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
16244
16324
|
}
|
|
16245
|
-
const cursorHooksPath =
|
|
16246
|
-
if (
|
|
16325
|
+
const cursorHooksPath = path31.join(homeDir2, ".cursor", "hooks.json");
|
|
16326
|
+
if (fs29.existsSync(cursorHooksPath)) {
|
|
16247
16327
|
try {
|
|
16248
|
-
const cur = JSON.parse(
|
|
16328
|
+
const cur = JSON.parse(fs29.readFileSync(cursorHooksPath, "utf-8"));
|
|
16249
16329
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
16250
16330
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
16251
16331
|
);
|
|
@@ -16283,8 +16363,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16283
16363
|
|
|
16284
16364
|
// src/cli/commands/audit.ts
|
|
16285
16365
|
import chalk9 from "chalk";
|
|
16286
|
-
import
|
|
16287
|
-
import
|
|
16366
|
+
import fs30 from "fs";
|
|
16367
|
+
import path32 from "path";
|
|
16288
16368
|
import os26 from "os";
|
|
16289
16369
|
function formatRelativeTime(timestamp) {
|
|
16290
16370
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
@@ -16298,14 +16378,14 @@ function formatRelativeTime(timestamp) {
|
|
|
16298
16378
|
}
|
|
16299
16379
|
function registerAuditCommand(program2) {
|
|
16300
16380
|
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) => {
|
|
16301
|
-
const logPath =
|
|
16302
|
-
if (!
|
|
16381
|
+
const logPath = path32.join(os26.homedir(), ".node9", "audit.log");
|
|
16382
|
+
if (!fs30.existsSync(logPath)) {
|
|
16303
16383
|
console.log(
|
|
16304
16384
|
chalk9.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
16305
16385
|
);
|
|
16306
16386
|
return;
|
|
16307
16387
|
}
|
|
16308
|
-
const raw =
|
|
16388
|
+
const raw = fs30.readFileSync(logPath, "utf-8");
|
|
16309
16389
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
16310
16390
|
let entries = lines.flatMap((line) => {
|
|
16311
16391
|
try {
|
|
@@ -16359,8 +16439,8 @@ function registerAuditCommand(program2) {
|
|
|
16359
16439
|
|
|
16360
16440
|
// src/cli/commands/report.ts
|
|
16361
16441
|
import chalk10 from "chalk";
|
|
16362
|
-
import
|
|
16363
|
-
import
|
|
16442
|
+
import fs31 from "fs";
|
|
16443
|
+
import path33 from "path";
|
|
16364
16444
|
import os27 from "os";
|
|
16365
16445
|
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;
|
|
16366
16446
|
function buildTestTimestamps(allEntries) {
|
|
@@ -16408,8 +16488,8 @@ function getDateRange(period) {
|
|
|
16408
16488
|
}
|
|
16409
16489
|
}
|
|
16410
16490
|
function parseAuditLog(logPath) {
|
|
16411
|
-
if (!
|
|
16412
|
-
const raw =
|
|
16491
|
+
if (!fs31.existsSync(logPath)) return [];
|
|
16492
|
+
const raw = fs31.readFileSync(logPath, "utf-8");
|
|
16413
16493
|
return raw.split("\n").flatMap((line) => {
|
|
16414
16494
|
if (!line.trim()) return [];
|
|
16415
16495
|
try {
|
|
@@ -16488,11 +16568,11 @@ function loadClaudeCost(start, end) {
|
|
|
16488
16568
|
cacheWriteTokens: 0,
|
|
16489
16569
|
cacheReadTokens: 0
|
|
16490
16570
|
};
|
|
16491
|
-
const projectsDir =
|
|
16492
|
-
if (!
|
|
16571
|
+
const projectsDir = path33.join(os27.homedir(), ".claude", "projects");
|
|
16572
|
+
if (!fs31.existsSync(projectsDir)) return empty;
|
|
16493
16573
|
let dirs;
|
|
16494
16574
|
try {
|
|
16495
|
-
dirs =
|
|
16575
|
+
dirs = fs31.readdirSync(projectsDir);
|
|
16496
16576
|
} catch {
|
|
16497
16577
|
return empty;
|
|
16498
16578
|
}
|
|
@@ -16504,18 +16584,18 @@ function loadClaudeCost(start, end) {
|
|
|
16504
16584
|
const byDay = /* @__PURE__ */ new Map();
|
|
16505
16585
|
const byModel = /* @__PURE__ */ new Map();
|
|
16506
16586
|
for (const proj of dirs) {
|
|
16507
|
-
const projPath =
|
|
16587
|
+
const projPath = path33.join(projectsDir, proj);
|
|
16508
16588
|
let files;
|
|
16509
16589
|
try {
|
|
16510
|
-
const stat =
|
|
16590
|
+
const stat = fs31.statSync(projPath);
|
|
16511
16591
|
if (!stat.isDirectory()) continue;
|
|
16512
|
-
files =
|
|
16592
|
+
files = fs31.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
16513
16593
|
} catch {
|
|
16514
16594
|
continue;
|
|
16515
16595
|
}
|
|
16516
16596
|
for (const file of files) {
|
|
16517
16597
|
try {
|
|
16518
|
-
const raw =
|
|
16598
|
+
const raw = fs31.readFileSync(path33.join(projPath, file), "utf-8");
|
|
16519
16599
|
for (const line of raw.split("\n")) {
|
|
16520
16600
|
if (!line.trim()) continue;
|
|
16521
16601
|
let entry;
|
|
@@ -16556,36 +16636,36 @@ function loadClaudeCost(start, end) {
|
|
|
16556
16636
|
return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
|
|
16557
16637
|
}
|
|
16558
16638
|
function loadCodexCost(start, end) {
|
|
16559
|
-
const sessionsBase =
|
|
16639
|
+
const sessionsBase = path33.join(os27.homedir(), ".codex", "sessions");
|
|
16560
16640
|
const byDay = /* @__PURE__ */ new Map();
|
|
16561
16641
|
let total = 0;
|
|
16562
16642
|
let toolCalls = 0;
|
|
16563
|
-
if (!
|
|
16643
|
+
if (!fs31.existsSync(sessionsBase)) return { total, byDay, toolCalls };
|
|
16564
16644
|
const jsonlFiles = [];
|
|
16565
16645
|
try {
|
|
16566
|
-
for (const year of
|
|
16567
|
-
const yearPath =
|
|
16646
|
+
for (const year of fs31.readdirSync(sessionsBase)) {
|
|
16647
|
+
const yearPath = path33.join(sessionsBase, year);
|
|
16568
16648
|
try {
|
|
16569
|
-
if (!
|
|
16649
|
+
if (!fs31.statSync(yearPath).isDirectory()) continue;
|
|
16570
16650
|
} catch {
|
|
16571
16651
|
continue;
|
|
16572
16652
|
}
|
|
16573
|
-
for (const month of
|
|
16574
|
-
const monthPath =
|
|
16653
|
+
for (const month of fs31.readdirSync(yearPath)) {
|
|
16654
|
+
const monthPath = path33.join(yearPath, month);
|
|
16575
16655
|
try {
|
|
16576
|
-
if (!
|
|
16656
|
+
if (!fs31.statSync(monthPath).isDirectory()) continue;
|
|
16577
16657
|
} catch {
|
|
16578
16658
|
continue;
|
|
16579
16659
|
}
|
|
16580
|
-
for (const day of
|
|
16581
|
-
const dayPath =
|
|
16660
|
+
for (const day of fs31.readdirSync(monthPath)) {
|
|
16661
|
+
const dayPath = path33.join(monthPath, day);
|
|
16582
16662
|
try {
|
|
16583
|
-
if (!
|
|
16663
|
+
if (!fs31.statSync(dayPath).isDirectory()) continue;
|
|
16584
16664
|
} catch {
|
|
16585
16665
|
continue;
|
|
16586
16666
|
}
|
|
16587
|
-
for (const file of
|
|
16588
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
16667
|
+
for (const file of fs31.readdirSync(dayPath)) {
|
|
16668
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path33.join(dayPath, file));
|
|
16589
16669
|
}
|
|
16590
16670
|
}
|
|
16591
16671
|
}
|
|
@@ -16596,7 +16676,7 @@ function loadCodexCost(start, end) {
|
|
|
16596
16676
|
for (const filePath of jsonlFiles) {
|
|
16597
16677
|
let lines;
|
|
16598
16678
|
try {
|
|
16599
|
-
lines =
|
|
16679
|
+
lines = fs31.readFileSync(filePath, "utf-8").split("\n");
|
|
16600
16680
|
} catch {
|
|
16601
16681
|
continue;
|
|
16602
16682
|
}
|
|
@@ -16646,7 +16726,7 @@ function registerReportCommand(program2) {
|
|
|
16646
16726
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
16647
16727
|
options.period
|
|
16648
16728
|
) ? options.period : "7d";
|
|
16649
|
-
const logPath =
|
|
16729
|
+
const logPath = path33.join(os27.homedir(), ".node9", "audit.log");
|
|
16650
16730
|
const allEntries = parseAuditLog(logPath);
|
|
16651
16731
|
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
16652
16732
|
if (unackedDlp.length > 0) {
|
|
@@ -17153,12 +17233,12 @@ function registerDaemonCommand(program2) {
|
|
|
17153
17233
|
init_core();
|
|
17154
17234
|
init_daemon();
|
|
17155
17235
|
import chalk12 from "chalk";
|
|
17156
|
-
import
|
|
17157
|
-
import
|
|
17236
|
+
import fs32 from "fs";
|
|
17237
|
+
import path34 from "path";
|
|
17158
17238
|
import os28 from "os";
|
|
17159
17239
|
function readJson2(filePath) {
|
|
17160
17240
|
try {
|
|
17161
|
-
if (
|
|
17241
|
+
if (fs32.existsSync(filePath)) return JSON.parse(fs32.readFileSync(filePath, "utf-8"));
|
|
17162
17242
|
} catch {
|
|
17163
17243
|
}
|
|
17164
17244
|
return null;
|
|
@@ -17223,13 +17303,13 @@ function registerStatusCommand(program2) {
|
|
|
17223
17303
|
console.log("");
|
|
17224
17304
|
const modeLabel = settings.mode === "audit" ? chalk12.blue("audit") : settings.mode === "strict" ? chalk12.red("strict") : chalk12.white("standard");
|
|
17225
17305
|
console.log(` Mode: ${modeLabel}`);
|
|
17226
|
-
const projectConfig =
|
|
17227
|
-
const globalConfig =
|
|
17306
|
+
const projectConfig = path34.join(process.cwd(), "node9.config.json");
|
|
17307
|
+
const globalConfig = path34.join(os28.homedir(), ".node9", "config.json");
|
|
17228
17308
|
console.log(
|
|
17229
|
-
` Local: ${
|
|
17309
|
+
` Local: ${fs32.existsSync(projectConfig) ? chalk12.green("Active (node9.config.json)") : chalk12.gray("Not present")}`
|
|
17230
17310
|
);
|
|
17231
17311
|
console.log(
|
|
17232
|
-
` Global: ${
|
|
17312
|
+
` Global: ${fs32.existsSync(globalConfig) ? chalk12.green("Active (~/.node9/config.json)") : chalk12.gray("Not present")}`
|
|
17233
17313
|
);
|
|
17234
17314
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
17235
17315
|
console.log(
|
|
@@ -17238,13 +17318,13 @@ function registerStatusCommand(program2) {
|
|
|
17238
17318
|
}
|
|
17239
17319
|
const homeDir2 = os28.homedir();
|
|
17240
17320
|
const claudeSettings = readJson2(
|
|
17241
|
-
|
|
17321
|
+
path34.join(homeDir2, ".claude", "settings.json")
|
|
17242
17322
|
);
|
|
17243
|
-
const claudeConfig = readJson2(
|
|
17323
|
+
const claudeConfig = readJson2(path34.join(homeDir2, ".claude.json"));
|
|
17244
17324
|
const geminiSettings = readJson2(
|
|
17245
|
-
|
|
17325
|
+
path34.join(homeDir2, ".gemini", "settings.json")
|
|
17246
17326
|
);
|
|
17247
|
-
const cursorConfig = readJson2(
|
|
17327
|
+
const cursorConfig = readJson2(path34.join(homeDir2, ".cursor", "mcp.json"));
|
|
17248
17328
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
17249
17329
|
if (agentFound) {
|
|
17250
17330
|
console.log("");
|
|
@@ -17304,8 +17384,8 @@ function registerStatusCommand(program2) {
|
|
|
17304
17384
|
// src/cli/commands/init.ts
|
|
17305
17385
|
init_core();
|
|
17306
17386
|
import chalk13 from "chalk";
|
|
17307
|
-
import
|
|
17308
|
-
import
|
|
17387
|
+
import fs33 from "fs";
|
|
17388
|
+
import path35 from "path";
|
|
17309
17389
|
import os29 from "os";
|
|
17310
17390
|
import https3 from "https";
|
|
17311
17391
|
init_shields();
|
|
@@ -17366,15 +17446,15 @@ function registerInitCommand(program2) {
|
|
|
17366
17446
|
}
|
|
17367
17447
|
console.log("");
|
|
17368
17448
|
}
|
|
17369
|
-
const configPath =
|
|
17370
|
-
if (
|
|
17449
|
+
const configPath = path35.join(os29.homedir(), ".node9", "config.json");
|
|
17450
|
+
if (fs33.existsSync(configPath) && !options.force) {
|
|
17371
17451
|
try {
|
|
17372
|
-
const existing = JSON.parse(
|
|
17452
|
+
const existing = JSON.parse(fs33.readFileSync(configPath, "utf-8"));
|
|
17373
17453
|
const settings = existing.settings ?? {};
|
|
17374
17454
|
if (settings.mode !== chosenMode) {
|
|
17375
17455
|
settings.mode = chosenMode;
|
|
17376
17456
|
existing.settings = settings;
|
|
17377
|
-
|
|
17457
|
+
fs33.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
17378
17458
|
console.log(chalk13.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
17379
17459
|
} else {
|
|
17380
17460
|
console.log(chalk13.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -17387,9 +17467,9 @@ function registerInitCommand(program2) {
|
|
|
17387
17467
|
...DEFAULT_CONFIG,
|
|
17388
17468
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
17389
17469
|
};
|
|
17390
|
-
const dir =
|
|
17391
|
-
if (!
|
|
17392
|
-
|
|
17470
|
+
const dir = path35.dirname(configPath);
|
|
17471
|
+
if (!fs33.existsSync(dir)) fs33.mkdirSync(dir, { recursive: true });
|
|
17472
|
+
fs33.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
17393
17473
|
console.log(chalk13.green(`\u2705 Config created: ${configPath}`));
|
|
17394
17474
|
console.log(chalk13.gray(` Mode: ${chosenMode}`));
|
|
17395
17475
|
}
|
|
@@ -17474,7 +17554,7 @@ function registerInitCommand(program2) {
|
|
|
17474
17554
|
}
|
|
17475
17555
|
|
|
17476
17556
|
// src/cli/commands/undo.ts
|
|
17477
|
-
import
|
|
17557
|
+
import path36 from "path";
|
|
17478
17558
|
import chalk15 from "chalk";
|
|
17479
17559
|
|
|
17480
17560
|
// src/tui/undo-navigator.ts
|
|
@@ -17633,7 +17713,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
17633
17713
|
let dir = startDir;
|
|
17634
17714
|
while (true) {
|
|
17635
17715
|
if (cwds.has(dir)) return dir;
|
|
17636
|
-
const parent =
|
|
17716
|
+
const parent = path36.dirname(dir);
|
|
17637
17717
|
if (parent === dir) return null;
|
|
17638
17718
|
dir = parent;
|
|
17639
17719
|
}
|
|
@@ -17829,12 +17909,12 @@ import { execa as execa2 } from "execa";
|
|
|
17829
17909
|
init_provenance();
|
|
17830
17910
|
|
|
17831
17911
|
// src/mcp-pin.ts
|
|
17832
|
-
import
|
|
17833
|
-
import
|
|
17912
|
+
import fs34 from "fs";
|
|
17913
|
+
import path37 from "path";
|
|
17834
17914
|
import os30 from "os";
|
|
17835
17915
|
import crypto5 from "crypto";
|
|
17836
17916
|
function getPinsFilePath2() {
|
|
17837
|
-
return
|
|
17917
|
+
return path37.join(os30.homedir(), ".node9", "mcp-pins.json");
|
|
17838
17918
|
}
|
|
17839
17919
|
function hashToolDefinitions(tools) {
|
|
17840
17920
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -17851,7 +17931,7 @@ function getServerKey(upstreamCommand) {
|
|
|
17851
17931
|
function readMcpPinsSafe() {
|
|
17852
17932
|
const filePath = getPinsFilePath2();
|
|
17853
17933
|
try {
|
|
17854
|
-
const raw =
|
|
17934
|
+
const raw = fs34.readFileSync(filePath, "utf-8");
|
|
17855
17935
|
if (!raw.trim()) {
|
|
17856
17936
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
17857
17937
|
}
|
|
@@ -17875,10 +17955,10 @@ function readMcpPins() {
|
|
|
17875
17955
|
}
|
|
17876
17956
|
function writeMcpPins(data) {
|
|
17877
17957
|
const filePath = getPinsFilePath2();
|
|
17878
|
-
|
|
17958
|
+
fs34.mkdirSync(path37.dirname(filePath), { recursive: true });
|
|
17879
17959
|
const tmp = `${filePath}.${crypto5.randomBytes(6).toString("hex")}.tmp`;
|
|
17880
|
-
|
|
17881
|
-
|
|
17960
|
+
fs34.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
17961
|
+
fs34.renameSync(tmp, filePath);
|
|
17882
17962
|
}
|
|
17883
17963
|
function checkPin(serverKey, currentHash) {
|
|
17884
17964
|
const result = readMcpPinsSafe();
|
|
@@ -18326,9 +18406,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
18326
18406
|
|
|
18327
18407
|
// src/mcp-server/index.ts
|
|
18328
18408
|
import readline4 from "readline";
|
|
18329
|
-
import
|
|
18409
|
+
import fs35 from "fs";
|
|
18330
18410
|
import os31 from "os";
|
|
18331
|
-
import
|
|
18411
|
+
import path38 from "path";
|
|
18332
18412
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
18333
18413
|
init_core();
|
|
18334
18414
|
init_daemon();
|
|
@@ -18579,13 +18659,13 @@ function handleStatus() {
|
|
|
18579
18659
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
18580
18660
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
18581
18661
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
18582
|
-
const projectConfig =
|
|
18583
|
-
const globalConfig =
|
|
18662
|
+
const projectConfig = path38.join(process.cwd(), "node9.config.json");
|
|
18663
|
+
const globalConfig = path38.join(os31.homedir(), ".node9", "config.json");
|
|
18584
18664
|
lines.push(
|
|
18585
|
-
`Project config (node9.config.json): ${
|
|
18665
|
+
`Project config (node9.config.json): ${fs35.existsSync(projectConfig) ? "present" : "not found"}`
|
|
18586
18666
|
);
|
|
18587
18667
|
lines.push(
|
|
18588
|
-
`Global config (~/.node9/config.json): ${
|
|
18668
|
+
`Global config (~/.node9/config.json): ${fs35.existsSync(globalConfig) ? "present" : "not found"}`
|
|
18589
18669
|
);
|
|
18590
18670
|
return lines.join("\n");
|
|
18591
18671
|
}
|
|
@@ -18659,21 +18739,21 @@ function handleShieldDisable(args) {
|
|
|
18659
18739
|
writeActiveShields(active.filter((s) => s !== name));
|
|
18660
18740
|
return `Shield "${name}" disabled.`;
|
|
18661
18741
|
}
|
|
18662
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
18742
|
+
var GLOBAL_CONFIG_PATH2 = path38.join(os31.homedir(), ".node9", "config.json");
|
|
18663
18743
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
18664
18744
|
function readGlobalConfigRaw() {
|
|
18665
18745
|
try {
|
|
18666
|
-
if (
|
|
18667
|
-
return JSON.parse(
|
|
18746
|
+
if (fs35.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
18747
|
+
return JSON.parse(fs35.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
18668
18748
|
}
|
|
18669
18749
|
} catch {
|
|
18670
18750
|
}
|
|
18671
18751
|
return {};
|
|
18672
18752
|
}
|
|
18673
18753
|
function writeGlobalConfigRaw(data) {
|
|
18674
|
-
const dir =
|
|
18675
|
-
if (!
|
|
18676
|
-
|
|
18754
|
+
const dir = path38.dirname(GLOBAL_CONFIG_PATH2);
|
|
18755
|
+
if (!fs35.existsSync(dir)) fs35.mkdirSync(dir, { recursive: true });
|
|
18756
|
+
fs35.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
18677
18757
|
}
|
|
18678
18758
|
function handleApproverList() {
|
|
18679
18759
|
const config = getConfig();
|
|
@@ -18717,9 +18797,9 @@ function handleApproverSet(args) {
|
|
|
18717
18797
|
function handleAuditGet(args) {
|
|
18718
18798
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
18719
18799
|
const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
|
|
18720
|
-
const auditPath =
|
|
18721
|
-
if (!
|
|
18722
|
-
const rawLines =
|
|
18800
|
+
const auditPath = path38.join(os31.homedir(), ".node9", "audit.log");
|
|
18801
|
+
if (!fs35.existsSync(auditPath)) return "No audit log found.";
|
|
18802
|
+
const rawLines = fs35.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
18723
18803
|
const parsed = [];
|
|
18724
18804
|
for (const line of rawLines) {
|
|
18725
18805
|
try {
|
|
@@ -19273,8 +19353,8 @@ init_scan();
|
|
|
19273
19353
|
|
|
19274
19354
|
// src/cli/commands/sessions.ts
|
|
19275
19355
|
import chalk22 from "chalk";
|
|
19276
|
-
import
|
|
19277
|
-
import
|
|
19356
|
+
import fs36 from "fs";
|
|
19357
|
+
import path39 from "path";
|
|
19278
19358
|
import os32 from "os";
|
|
19279
19359
|
var CLAUDE_PRICING3 = {
|
|
19280
19360
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -19316,7 +19396,7 @@ function encodeProjectPath(projectPath) {
|
|
|
19316
19396
|
}
|
|
19317
19397
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
19318
19398
|
const encoded = encodeProjectPath(projectPath);
|
|
19319
|
-
return
|
|
19399
|
+
return path39.join(os32.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
19320
19400
|
}
|
|
19321
19401
|
function projectLabel(projectPath) {
|
|
19322
19402
|
return projectPath.replace(os32.homedir(), "~");
|
|
@@ -19388,10 +19468,10 @@ function parseSessionLines(lines) {
|
|
|
19388
19468
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
19389
19469
|
}
|
|
19390
19470
|
function loadAuditEntries(auditPath) {
|
|
19391
|
-
const aPath = auditPath ??
|
|
19471
|
+
const aPath = auditPath ?? path39.join(os32.homedir(), ".node9", "audit.log");
|
|
19392
19472
|
let raw;
|
|
19393
19473
|
try {
|
|
19394
|
-
raw =
|
|
19474
|
+
raw = fs36.readFileSync(aPath, "utf-8");
|
|
19395
19475
|
} catch {
|
|
19396
19476
|
return [];
|
|
19397
19477
|
}
|
|
@@ -19427,8 +19507,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
19427
19507
|
return result;
|
|
19428
19508
|
}
|
|
19429
19509
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
19430
|
-
const tmpDir =
|
|
19431
|
-
if (!
|
|
19510
|
+
const tmpDir = path39.join(os32.homedir(), ".gemini", "tmp");
|
|
19511
|
+
if (!fs36.existsSync(tmpDir)) return [];
|
|
19432
19512
|
const cutoff = days !== null ? (() => {
|
|
19433
19513
|
const d = /* @__PURE__ */ new Date();
|
|
19434
19514
|
d.setDate(d.getDate() - days);
|
|
@@ -19437,35 +19517,35 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
19437
19517
|
})() : null;
|
|
19438
19518
|
let slugDirs;
|
|
19439
19519
|
try {
|
|
19440
|
-
slugDirs =
|
|
19520
|
+
slugDirs = fs36.readdirSync(tmpDir);
|
|
19441
19521
|
} catch {
|
|
19442
19522
|
return [];
|
|
19443
19523
|
}
|
|
19444
19524
|
const summaries = [];
|
|
19445
19525
|
for (const slug of slugDirs) {
|
|
19446
|
-
const slugPath =
|
|
19526
|
+
const slugPath = path39.join(tmpDir, slug);
|
|
19447
19527
|
try {
|
|
19448
|
-
if (!
|
|
19528
|
+
if (!fs36.statSync(slugPath).isDirectory()) continue;
|
|
19449
19529
|
} catch {
|
|
19450
19530
|
continue;
|
|
19451
19531
|
}
|
|
19452
|
-
let projectRoot =
|
|
19532
|
+
let projectRoot = path39.join(os32.homedir(), slug);
|
|
19453
19533
|
try {
|
|
19454
|
-
projectRoot =
|
|
19534
|
+
projectRoot = fs36.readFileSync(path39.join(slugPath, ".project_root"), "utf-8").trim();
|
|
19455
19535
|
} catch {
|
|
19456
19536
|
}
|
|
19457
|
-
const chatsDir =
|
|
19458
|
-
if (!
|
|
19537
|
+
const chatsDir = path39.join(slugPath, "chats");
|
|
19538
|
+
if (!fs36.existsSync(chatsDir)) continue;
|
|
19459
19539
|
let chatFiles;
|
|
19460
19540
|
try {
|
|
19461
|
-
chatFiles =
|
|
19541
|
+
chatFiles = fs36.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
19462
19542
|
} catch {
|
|
19463
19543
|
continue;
|
|
19464
19544
|
}
|
|
19465
19545
|
for (const chatFile of chatFiles) {
|
|
19466
19546
|
let raw;
|
|
19467
19547
|
try {
|
|
19468
|
-
raw =
|
|
19548
|
+
raw = fs36.readFileSync(path39.join(chatsDir, chatFile), "utf-8");
|
|
19469
19549
|
} catch {
|
|
19470
19550
|
continue;
|
|
19471
19551
|
}
|
|
@@ -19545,8 +19625,8 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
19545
19625
|
return summaries;
|
|
19546
19626
|
}
|
|
19547
19627
|
function buildCodexSessions(days, allAuditEntries) {
|
|
19548
|
-
const sessionsBase =
|
|
19549
|
-
if (!
|
|
19628
|
+
const sessionsBase = path39.join(os32.homedir(), ".codex", "sessions");
|
|
19629
|
+
if (!fs36.existsSync(sessionsBase)) return [];
|
|
19550
19630
|
const cutoff = days !== null ? (() => {
|
|
19551
19631
|
const d = /* @__PURE__ */ new Date();
|
|
19552
19632
|
d.setDate(d.getDate() - days);
|
|
@@ -19555,29 +19635,29 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
19555
19635
|
})() : null;
|
|
19556
19636
|
const jsonlFiles = [];
|
|
19557
19637
|
try {
|
|
19558
|
-
for (const year of
|
|
19559
|
-
const yearPath =
|
|
19638
|
+
for (const year of fs36.readdirSync(sessionsBase)) {
|
|
19639
|
+
const yearPath = path39.join(sessionsBase, year);
|
|
19560
19640
|
try {
|
|
19561
|
-
if (!
|
|
19641
|
+
if (!fs36.statSync(yearPath).isDirectory()) continue;
|
|
19562
19642
|
} catch {
|
|
19563
19643
|
continue;
|
|
19564
19644
|
}
|
|
19565
|
-
for (const month of
|
|
19566
|
-
const monthPath =
|
|
19645
|
+
for (const month of fs36.readdirSync(yearPath)) {
|
|
19646
|
+
const monthPath = path39.join(yearPath, month);
|
|
19567
19647
|
try {
|
|
19568
|
-
if (!
|
|
19648
|
+
if (!fs36.statSync(monthPath).isDirectory()) continue;
|
|
19569
19649
|
} catch {
|
|
19570
19650
|
continue;
|
|
19571
19651
|
}
|
|
19572
|
-
for (const day of
|
|
19573
|
-
const dayPath =
|
|
19652
|
+
for (const day of fs36.readdirSync(monthPath)) {
|
|
19653
|
+
const dayPath = path39.join(monthPath, day);
|
|
19574
19654
|
try {
|
|
19575
|
-
if (!
|
|
19655
|
+
if (!fs36.statSync(dayPath).isDirectory()) continue;
|
|
19576
19656
|
} catch {
|
|
19577
19657
|
continue;
|
|
19578
19658
|
}
|
|
19579
|
-
for (const file of
|
|
19580
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
19659
|
+
for (const file of fs36.readdirSync(dayPath)) {
|
|
19660
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path39.join(dayPath, file));
|
|
19581
19661
|
}
|
|
19582
19662
|
}
|
|
19583
19663
|
}
|
|
@@ -19589,7 +19669,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
19589
19669
|
for (const filePath of jsonlFiles) {
|
|
19590
19670
|
let lines;
|
|
19591
19671
|
try {
|
|
19592
|
-
lines =
|
|
19672
|
+
lines = fs36.readFileSync(filePath, "utf-8").split("\n");
|
|
19593
19673
|
} catch {
|
|
19594
19674
|
continue;
|
|
19595
19675
|
}
|
|
@@ -19667,10 +19747,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
19667
19747
|
return summaries;
|
|
19668
19748
|
}
|
|
19669
19749
|
function buildSessions(days, historyPath) {
|
|
19670
|
-
const hPath = historyPath ??
|
|
19750
|
+
const hPath = historyPath ?? path39.join(os32.homedir(), ".claude", "history.jsonl");
|
|
19671
19751
|
let historyRaw;
|
|
19672
19752
|
try {
|
|
19673
|
-
historyRaw =
|
|
19753
|
+
historyRaw = fs36.readFileSync(hPath, "utf-8");
|
|
19674
19754
|
} catch {
|
|
19675
19755
|
return [];
|
|
19676
19756
|
}
|
|
@@ -19695,7 +19775,7 @@ function buildSessions(days, historyPath) {
|
|
|
19695
19775
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
19696
19776
|
let sessionLines = [];
|
|
19697
19777
|
try {
|
|
19698
|
-
sessionLines =
|
|
19778
|
+
sessionLines = fs36.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
19699
19779
|
} catch {
|
|
19700
19780
|
}
|
|
19701
19781
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -19961,8 +20041,8 @@ function registerSessionsCommand(program2) {
|
|
|
19961
20041
|
console.log("");
|
|
19962
20042
|
console.log(chalk22.cyan.bold("\u{1F4CB} node9 sessions") + chalk22.dim(" \u2014 what your AI agent did"));
|
|
19963
20043
|
console.log("");
|
|
19964
|
-
const historyPath =
|
|
19965
|
-
if (!
|
|
20044
|
+
const historyPath = path39.join(os32.homedir(), ".claude", "history.jsonl");
|
|
20045
|
+
if (!fs36.existsSync(historyPath)) {
|
|
19966
20046
|
console.log(chalk22.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
19967
20047
|
console.log(chalk22.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
19968
20048
|
return;
|
|
@@ -19999,12 +20079,12 @@ function registerSessionsCommand(program2) {
|
|
|
19999
20079
|
|
|
20000
20080
|
// src/cli/commands/skill-pin.ts
|
|
20001
20081
|
import chalk23 from "chalk";
|
|
20002
|
-
import
|
|
20082
|
+
import fs37 from "fs";
|
|
20003
20083
|
import os33 from "os";
|
|
20004
|
-
import
|
|
20084
|
+
import path40 from "path";
|
|
20005
20085
|
function wipeSkillSessions() {
|
|
20006
20086
|
try {
|
|
20007
|
-
|
|
20087
|
+
fs37.rmSync(path40.join(os33.homedir(), ".node9", "skill-sessions"), {
|
|
20008
20088
|
recursive: true,
|
|
20009
20089
|
force: true
|
|
20010
20090
|
});
|
|
@@ -20087,18 +20167,18 @@ function registerSkillPinCommand(program2) {
|
|
|
20087
20167
|
|
|
20088
20168
|
// src/cli/commands/dlp.ts
|
|
20089
20169
|
import chalk24 from "chalk";
|
|
20090
|
-
import
|
|
20091
|
-
import
|
|
20170
|
+
import fs38 from "fs";
|
|
20171
|
+
import path41 from "path";
|
|
20092
20172
|
import os34 from "os";
|
|
20093
|
-
var AUDIT_LOG =
|
|
20094
|
-
var RESOLVED_FILE =
|
|
20173
|
+
var AUDIT_LOG = path41.join(os34.homedir(), ".node9", "audit.log");
|
|
20174
|
+
var RESOLVED_FILE = path41.join(os34.homedir(), ".node9", "dlp-resolved.json");
|
|
20095
20175
|
var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
|
|
20096
20176
|
function stripAnsi(s) {
|
|
20097
20177
|
return s.replace(ANSI_RE, "");
|
|
20098
20178
|
}
|
|
20099
20179
|
function loadResolved() {
|
|
20100
20180
|
try {
|
|
20101
|
-
const raw = JSON.parse(
|
|
20181
|
+
const raw = JSON.parse(fs38.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
20102
20182
|
return new Set(raw);
|
|
20103
20183
|
} catch {
|
|
20104
20184
|
return /* @__PURE__ */ new Set();
|
|
@@ -20106,13 +20186,13 @@ function loadResolved() {
|
|
|
20106
20186
|
}
|
|
20107
20187
|
function saveResolved(resolved) {
|
|
20108
20188
|
try {
|
|
20109
|
-
|
|
20189
|
+
fs38.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
20110
20190
|
} catch {
|
|
20111
20191
|
}
|
|
20112
20192
|
}
|
|
20113
20193
|
function loadDlpFindings() {
|
|
20114
|
-
if (!
|
|
20115
|
-
return
|
|
20194
|
+
if (!fs38.existsSync(AUDIT_LOG)) return [];
|
|
20195
|
+
return fs38.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
20116
20196
|
if (!line.trim()) return [];
|
|
20117
20197
|
try {
|
|
20118
20198
|
const e = JSON.parse(line);
|
|
@@ -20210,20 +20290,20 @@ function registerDlpCommand(program2) {
|
|
|
20210
20290
|
|
|
20211
20291
|
// src/cli.ts
|
|
20212
20292
|
var { version } = JSON.parse(
|
|
20213
|
-
|
|
20293
|
+
fs41.readFileSync(path44.join(__dirname, "../package.json"), "utf-8")
|
|
20214
20294
|
);
|
|
20215
20295
|
var program = new Command();
|
|
20216
20296
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
20217
20297
|
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) => {
|
|
20218
20298
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
20219
|
-
const credPath =
|
|
20220
|
-
if (!
|
|
20221
|
-
|
|
20299
|
+
const credPath = path44.join(os37.homedir(), ".node9", "credentials.json");
|
|
20300
|
+
if (!fs41.existsSync(path44.dirname(credPath)))
|
|
20301
|
+
fs41.mkdirSync(path44.dirname(credPath), { recursive: true });
|
|
20222
20302
|
const profileName = options.profile || "default";
|
|
20223
20303
|
let existingCreds = {};
|
|
20224
20304
|
try {
|
|
20225
|
-
if (
|
|
20226
|
-
const raw = JSON.parse(
|
|
20305
|
+
if (fs41.existsSync(credPath)) {
|
|
20306
|
+
const raw = JSON.parse(fs41.readFileSync(credPath, "utf-8"));
|
|
20227
20307
|
if (raw.apiKey) {
|
|
20228
20308
|
existingCreds = {
|
|
20229
20309
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -20235,13 +20315,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
20235
20315
|
} catch {
|
|
20236
20316
|
}
|
|
20237
20317
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
20238
|
-
|
|
20318
|
+
fs41.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
20239
20319
|
if (profileName === "default") {
|
|
20240
|
-
const configPath =
|
|
20320
|
+
const configPath = path44.join(os37.homedir(), ".node9", "config.json");
|
|
20241
20321
|
let config = {};
|
|
20242
20322
|
try {
|
|
20243
|
-
if (
|
|
20244
|
-
config = JSON.parse(
|
|
20323
|
+
if (fs41.existsSync(configPath))
|
|
20324
|
+
config = JSON.parse(fs41.readFileSync(configPath, "utf-8"));
|
|
20245
20325
|
} catch {
|
|
20246
20326
|
}
|
|
20247
20327
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -20256,9 +20336,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
20256
20336
|
approvers.cloud = false;
|
|
20257
20337
|
}
|
|
20258
20338
|
s.approvers = approvers;
|
|
20259
|
-
if (!
|
|
20260
|
-
|
|
20261
|
-
|
|
20339
|
+
if (!fs41.existsSync(path44.dirname(configPath)))
|
|
20340
|
+
fs41.mkdirSync(path44.dirname(configPath), { recursive: true });
|
|
20341
|
+
fs41.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
20262
20342
|
}
|
|
20263
20343
|
if (options.profile && profileName !== "default") {
|
|
20264
20344
|
console.log(chalk26.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -20395,15 +20475,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
20395
20475
|
}
|
|
20396
20476
|
}
|
|
20397
20477
|
if (options.purge) {
|
|
20398
|
-
const node9Dir =
|
|
20399
|
-
if (
|
|
20478
|
+
const node9Dir = path44.join(os37.homedir(), ".node9");
|
|
20479
|
+
if (fs41.existsSync(node9Dir)) {
|
|
20400
20480
|
const confirmed = await confirm2({
|
|
20401
20481
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
20402
20482
|
default: false
|
|
20403
20483
|
});
|
|
20404
20484
|
if (confirmed) {
|
|
20405
|
-
|
|
20406
|
-
if (
|
|
20485
|
+
fs41.rmSync(node9Dir, { recursive: true });
|
|
20486
|
+
if (fs41.existsSync(node9Dir)) {
|
|
20407
20487
|
console.error(
|
|
20408
20488
|
chalk26.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
20409
20489
|
);
|
|
@@ -20545,14 +20625,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
20545
20625
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
20546
20626
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
20547
20627
|
if (subcommand === "debug") {
|
|
20548
|
-
const flagFile =
|
|
20628
|
+
const flagFile = path44.join(os37.homedir(), ".node9", "hud-debug");
|
|
20549
20629
|
if (state === "on") {
|
|
20550
|
-
|
|
20551
|
-
|
|
20630
|
+
fs41.mkdirSync(path44.dirname(flagFile), { recursive: true });
|
|
20631
|
+
fs41.writeFileSync(flagFile, "");
|
|
20552
20632
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
20553
20633
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
20554
20634
|
} else if (state === "off") {
|
|
20555
|
-
if (
|
|
20635
|
+
if (fs41.existsSync(flagFile)) fs41.unlinkSync(flagFile);
|
|
20556
20636
|
console.log("HUD debug logging disabled.");
|
|
20557
20637
|
} else {
|
|
20558
20638
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -20664,9 +20744,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
20664
20744
|
const isCheckHook = process.argv[2] === "check";
|
|
20665
20745
|
if (isCheckHook) {
|
|
20666
20746
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
20667
|
-
const logPath =
|
|
20747
|
+
const logPath = path44.join(os37.homedir(), ".node9", "hook-debug.log");
|
|
20668
20748
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
20669
|
-
|
|
20749
|
+
fs41.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
20670
20750
|
`);
|
|
20671
20751
|
}
|
|
20672
20752
|
process.exit(0);
|