@node9/proxy 1.11.0 → 1.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/cli.js +1017 -487
- package/dist/cli.mjs +1013 -483
- package/dist/index.js +48 -24
- package/dist/index.mjs +48 -24
- package/dist/shields/builtin/bash-safe.json +2 -2
- 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 path43 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
151
|
+
return ` \u2022 ${path43}: ${issue.message}`;
|
|
152
152
|
});
|
|
153
153
|
return {
|
|
154
154
|
sanitized,
|
|
@@ -820,9 +820,8 @@ var init_config = __esm({
|
|
|
820
820
|
{
|
|
821
821
|
field: "command",
|
|
822
822
|
op: "matches",
|
|
823
|
-
//
|
|
824
|
-
|
|
825
|
-
value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
823
|
+
// Anchor rm as a shell command (not inside a string arg like a git commit message).
|
|
824
|
+
value: "(^|&&|\\|\\||;)\\s*rm\\b[^;&|]*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
826
825
|
},
|
|
827
826
|
{
|
|
828
827
|
field: "command",
|
|
@@ -851,6 +850,13 @@ var init_config = __esm({
|
|
|
851
850
|
name: "review-drop-truncate-shell",
|
|
852
851
|
tool: "bash",
|
|
853
852
|
conditions: [
|
|
853
|
+
{
|
|
854
|
+
field: "command",
|
|
855
|
+
op: "matches",
|
|
856
|
+
// Require a DB CLI in the command so grep/cat/echo of SQL strings don't trigger.
|
|
857
|
+
value: "(^|&&|\\|\\||;|\\|)\\s*(psql|mysql|sqlite3|sqlplus|cockroach|clickhouse-client|mongo)\\b",
|
|
858
|
+
flags: "i"
|
|
859
|
+
},
|
|
854
860
|
{
|
|
855
861
|
field: "command",
|
|
856
862
|
op: "matches",
|
|
@@ -871,7 +877,9 @@ var init_config = __esm({
|
|
|
871
877
|
{
|
|
872
878
|
field: "command",
|
|
873
879
|
op: "matches",
|
|
874
|
-
|
|
880
|
+
// Anchor git as a shell command so node -e / python -c scripts containing
|
|
881
|
+
// "git push --force" as a string don't false-positive.
|
|
882
|
+
value: "(^|&&|\\|\\||;)\\s*git\\s+push[^;&|]*(--force|--force-with-lease|-f\\b)",
|
|
875
883
|
flags: "i"
|
|
876
884
|
}
|
|
877
885
|
],
|
|
@@ -881,29 +889,20 @@ var init_config = __esm({
|
|
|
881
889
|
description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
|
|
882
890
|
},
|
|
883
891
|
{
|
|
884
|
-
name: "review-git-
|
|
892
|
+
name: "review-git-destructive",
|
|
885
893
|
tool: "bash",
|
|
886
894
|
conditions: [
|
|
887
895
|
{
|
|
888
896
|
field: "command",
|
|
889
897
|
op: "matches",
|
|
890
|
-
value: "\\bgit\\
|
|
898
|
+
value: "\\bgit\\s+(reset\\s+--hard|clean\\s+-[fdxX]|rebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
891
899
|
flags: "i"
|
|
892
|
-
}
|
|
893
|
-
],
|
|
894
|
-
conditionMode: "all",
|
|
895
|
-
verdict: "review",
|
|
896
|
-
reason: "git push sends changes to a shared remote",
|
|
897
|
-
description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
name: "review-git-destructive",
|
|
901
|
-
tool: "bash",
|
|
902
|
-
conditions: [
|
|
900
|
+
},
|
|
903
901
|
{
|
|
904
902
|
field: "command",
|
|
905
|
-
op: "
|
|
906
|
-
|
|
903
|
+
op: "notMatches",
|
|
904
|
+
// Exclude recovery ops — these resolve a conflict, not start a destructive action.
|
|
905
|
+
value: "\\bgit\\s+rebase\\s+--(abort|continue|skip)\\b",
|
|
907
906
|
flags: "i"
|
|
908
907
|
}
|
|
909
908
|
],
|
|
@@ -929,7 +928,9 @@ var init_config = __esm({
|
|
|
929
928
|
{
|
|
930
929
|
field: "command",
|
|
931
930
|
op: "matches",
|
|
932
|
-
|
|
931
|
+
// Anchor curl/wget as a shell command so node -e scripts testing this
|
|
932
|
+
// regex pattern don't self-match as a false positive.
|
|
933
|
+
value: "(^|&&|\\|\\||;)\\s*(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
|
|
933
934
|
flags: "i"
|
|
934
935
|
}
|
|
935
936
|
],
|
|
@@ -1153,6 +1154,20 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
|
1153
1154
|
}
|
|
1154
1155
|
return null;
|
|
1155
1156
|
}
|
|
1157
|
+
function scanText(text) {
|
|
1158
|
+
const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
|
|
1159
|
+
for (const pattern of DLP_PATTERNS) {
|
|
1160
|
+
if (pattern.regex.test(t)) {
|
|
1161
|
+
return {
|
|
1162
|
+
patternName: pattern.name,
|
|
1163
|
+
fieldPath: "response-text",
|
|
1164
|
+
redactedSample: maskSecret(t, pattern.regex),
|
|
1165
|
+
severity: pattern.severity
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1156
1171
|
var DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
1157
1172
|
var init_dlp = __esm({
|
|
1158
1173
|
"src/dlp.ts"() {
|
|
@@ -1182,7 +1197,7 @@ var init_dlp = __esm({
|
|
|
1182
1197
|
regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
|
|
1183
1198
|
severity: "block"
|
|
1184
1199
|
},
|
|
1185
|
-
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]
|
|
1200
|
+
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i, severity: "review" }
|
|
1186
1201
|
];
|
|
1187
1202
|
SENSITIVE_PATH_PATTERNS = [
|
|
1188
1203
|
/[/\\]\.ssh[/\\]/i,
|
|
@@ -1750,9 +1765,21 @@ function matchesPattern(text, patterns) {
|
|
|
1750
1765
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1751
1766
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1752
1767
|
}
|
|
1753
|
-
function getNestedValue(obj,
|
|
1768
|
+
function getNestedValue(obj, path43) {
|
|
1754
1769
|
if (!obj || typeof obj !== "object") return null;
|
|
1755
|
-
return
|
|
1770
|
+
return path43.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1771
|
+
}
|
|
1772
|
+
function stripStringArguments(cmd) {
|
|
1773
|
+
let result = cmd;
|
|
1774
|
+
result = result.replace(
|
|
1775
|
+
/\b(node|python3?|ruby|perl|php|deno)\s+(-[ecr]|eval)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/gi,
|
|
1776
|
+
'$1 $2 ""'
|
|
1777
|
+
);
|
|
1778
|
+
result = result.replace(
|
|
1779
|
+
/\s(-m|--message|--body|--title|--description)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
|
|
1780
|
+
' $1 ""'
|
|
1781
|
+
);
|
|
1782
|
+
return result;
|
|
1756
1783
|
}
|
|
1757
1784
|
function shouldSnapshot(toolName, args, config) {
|
|
1758
1785
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1771,7 +1798,8 @@ function evaluateSmartConditions(args, rule) {
|
|
|
1771
1798
|
const mode = rule.conditionMode ?? "all";
|
|
1772
1799
|
const results = rule.conditions.map((cond) => {
|
|
1773
1800
|
const rawVal = getNestedValue(args, cond.field);
|
|
1774
|
-
const
|
|
1801
|
+
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
1802
|
+
const val = cond.field === "command" && normalized !== null ? stripStringArguments(normalized) : normalized;
|
|
1775
1803
|
switch (cond.op) {
|
|
1776
1804
|
case "exists":
|
|
1777
1805
|
return val !== null && val !== "";
|
|
@@ -2877,13 +2905,30 @@ ${smartTruncate(str, 500)}`
|
|
|
2877
2905
|
}
|
|
2878
2906
|
return { intent: "EXEC", message: smartTruncate(JSON.stringify(parsed), 200) };
|
|
2879
2907
|
}
|
|
2908
|
+
function sendDesktopNotification(title, body) {
|
|
2909
|
+
if (isTestEnv()) return;
|
|
2910
|
+
try {
|
|
2911
|
+
if (process.platform === "darwin") {
|
|
2912
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2913
|
+
const script = `display notification "${esc(body)}" with title "${esc(title)}"`;
|
|
2914
|
+
spawn("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
|
|
2915
|
+
} else if (process.platform === "linux") {
|
|
2916
|
+
spawn("notify-send", [title, body, "--icon=dialog-warning"], {
|
|
2917
|
+
detached: true,
|
|
2918
|
+
stdio: "ignore"
|
|
2919
|
+
}).unref();
|
|
2920
|
+
}
|
|
2921
|
+
} catch {
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2880
2924
|
function escapePango(text) {
|
|
2881
2925
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2882
2926
|
}
|
|
2883
2927
|
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2884
2928
|
const lines = [];
|
|
2885
2929
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2886
|
-
|
|
2930
|
+
const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
|
|
2931
|
+
lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
|
|
2887
2932
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2888
2933
|
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2889
2934
|
lines.push("");
|
|
@@ -3268,7 +3313,16 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
3268
3313
|
if (!options?.calledFromDaemon) {
|
|
3269
3314
|
const actId = randomUUID();
|
|
3270
3315
|
const actTs = Date.now();
|
|
3271
|
-
await notifyActivity({
|
|
3316
|
+
await notifyActivity({
|
|
3317
|
+
id: actId,
|
|
3318
|
+
ts: actTs,
|
|
3319
|
+
tool: toolName,
|
|
3320
|
+
args,
|
|
3321
|
+
status: "pending",
|
|
3322
|
+
// Strip ANSI escape sequences — agent name comes from caller-supplied metadata
|
|
3323
|
+
// and may be displayed in a terminal (node9 tail/watch), enabling injection.
|
|
3324
|
+
agent: meta?.agent ? meta.agent.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80) : void 0
|
|
3325
|
+
});
|
|
3272
3326
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
3273
3327
|
...options,
|
|
3274
3328
|
activityId: actId
|
|
@@ -5990,7 +6044,8 @@ function startActivitySocket() {
|
|
|
5990
6044
|
ts: data.ts,
|
|
5991
6045
|
tool: data.tool,
|
|
5992
6046
|
args: redactArgs(data.args),
|
|
5993
|
-
status: "pending"
|
|
6047
|
+
status: "pending",
|
|
6048
|
+
agent: data.agent
|
|
5994
6049
|
});
|
|
5995
6050
|
} else {
|
|
5996
6051
|
if (data.status === "allow") {
|
|
@@ -6448,16 +6503,167 @@ var init_sync = __esm({
|
|
|
6448
6503
|
}
|
|
6449
6504
|
});
|
|
6450
6505
|
|
|
6451
|
-
// src/daemon/
|
|
6452
|
-
import http from "http";
|
|
6506
|
+
// src/daemon/dlp-scanner.ts
|
|
6453
6507
|
import fs18 from "fs";
|
|
6454
6508
|
import path21 from "path";
|
|
6509
|
+
import os16 from "os";
|
|
6510
|
+
function loadIndex() {
|
|
6511
|
+
try {
|
|
6512
|
+
return JSON.parse(fs18.readFileSync(INDEX_FILE, "utf-8"));
|
|
6513
|
+
} catch {
|
|
6514
|
+
return {};
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
function saveIndex(index) {
|
|
6518
|
+
try {
|
|
6519
|
+
fs18.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
|
|
6520
|
+
} catch {
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
function appendAuditEntry(entry) {
|
|
6524
|
+
try {
|
|
6525
|
+
fs18.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
6526
|
+
} catch {
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
function runDlpScan() {
|
|
6530
|
+
if (!fs18.existsSync(PROJECTS_DIR)) return;
|
|
6531
|
+
const index = loadIndex();
|
|
6532
|
+
let updated = false;
|
|
6533
|
+
let projDirs;
|
|
6534
|
+
try {
|
|
6535
|
+
projDirs = fs18.readdirSync(PROJECTS_DIR);
|
|
6536
|
+
} catch {
|
|
6537
|
+
return;
|
|
6538
|
+
}
|
|
6539
|
+
for (const proj of projDirs) {
|
|
6540
|
+
const projPath = path21.join(PROJECTS_DIR, proj);
|
|
6541
|
+
try {
|
|
6542
|
+
if (!fs18.lstatSync(projPath).isDirectory()) continue;
|
|
6543
|
+
const real = fs18.realpathSync(projPath);
|
|
6544
|
+
if (!real.startsWith(PROJECTS_DIR + path21.sep) && real !== PROJECTS_DIR) continue;
|
|
6545
|
+
} catch {
|
|
6546
|
+
continue;
|
|
6547
|
+
}
|
|
6548
|
+
let files;
|
|
6549
|
+
try {
|
|
6550
|
+
files = fs18.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
6551
|
+
} catch {
|
|
6552
|
+
continue;
|
|
6553
|
+
}
|
|
6554
|
+
for (const file of files) {
|
|
6555
|
+
const filePath = path21.join(projPath, file);
|
|
6556
|
+
const lastOffset = index[filePath] ?? 0;
|
|
6557
|
+
let size;
|
|
6558
|
+
try {
|
|
6559
|
+
size = fs18.statSync(filePath).size;
|
|
6560
|
+
} catch {
|
|
6561
|
+
continue;
|
|
6562
|
+
}
|
|
6563
|
+
if (size <= lastOffset) continue;
|
|
6564
|
+
let fd;
|
|
6565
|
+
try {
|
|
6566
|
+
fd = fs18.openSync(filePath, "r");
|
|
6567
|
+
} catch {
|
|
6568
|
+
continue;
|
|
6569
|
+
}
|
|
6570
|
+
try {
|
|
6571
|
+
const chunkSize = size - lastOffset;
|
|
6572
|
+
const buf = Buffer.alloc(chunkSize);
|
|
6573
|
+
fs18.readSync(fd, buf, 0, chunkSize, lastOffset);
|
|
6574
|
+
const chunk = buf.toString("utf-8");
|
|
6575
|
+
for (const line of chunk.split("\n")) {
|
|
6576
|
+
if (!line.trim()) continue;
|
|
6577
|
+
let entry;
|
|
6578
|
+
try {
|
|
6579
|
+
entry = JSON.parse(line);
|
|
6580
|
+
} catch {
|
|
6581
|
+
continue;
|
|
6582
|
+
}
|
|
6583
|
+
if (entry.type !== "assistant") continue;
|
|
6584
|
+
const content = entry.message?.content;
|
|
6585
|
+
if (!Array.isArray(content)) continue;
|
|
6586
|
+
for (const block of content) {
|
|
6587
|
+
if (typeof block !== "object" || block === null || block.type !== "text")
|
|
6588
|
+
continue;
|
|
6589
|
+
const text = block.text;
|
|
6590
|
+
if (typeof text !== "string") continue;
|
|
6591
|
+
const match = scanText(text);
|
|
6592
|
+
if (!match) continue;
|
|
6593
|
+
const projLabel = decodeURIComponent(proj).replace(os16.homedir(), "~").slice(0, 40);
|
|
6594
|
+
const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6595
|
+
appendAuditEntry({
|
|
6596
|
+
ts,
|
|
6597
|
+
tool: "response-text",
|
|
6598
|
+
decision: "dlp",
|
|
6599
|
+
checkedBy: "response-dlp",
|
|
6600
|
+
source: "response-dlp",
|
|
6601
|
+
dlpPattern: match.patternName,
|
|
6602
|
+
dlpSample: match.redactedSample,
|
|
6603
|
+
project: projLabel
|
|
6604
|
+
});
|
|
6605
|
+
sendDesktopNotification(
|
|
6606
|
+
"\u26A0\uFE0F node9 DLP Alert",
|
|
6607
|
+
`${match.patternName} found in Claude response
|
|
6608
|
+
Sample: ${match.redactedSample}
|
|
6609
|
+
Project: ${projLabel}
|
|
6610
|
+
Run: node9 report --period 30d`
|
|
6611
|
+
);
|
|
6612
|
+
}
|
|
6613
|
+
}
|
|
6614
|
+
index[filePath] = size;
|
|
6615
|
+
updated = true;
|
|
6616
|
+
} finally {
|
|
6617
|
+
try {
|
|
6618
|
+
fs18.closeSync(fd);
|
|
6619
|
+
} catch {
|
|
6620
|
+
}
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
if (updated) saveIndex(index);
|
|
6625
|
+
}
|
|
6626
|
+
function startDlpScanner() {
|
|
6627
|
+
setImmediate(() => {
|
|
6628
|
+
try {
|
|
6629
|
+
runDlpScan();
|
|
6630
|
+
} catch {
|
|
6631
|
+
}
|
|
6632
|
+
});
|
|
6633
|
+
const timer = setInterval(
|
|
6634
|
+
() => {
|
|
6635
|
+
try {
|
|
6636
|
+
runDlpScan();
|
|
6637
|
+
} catch {
|
|
6638
|
+
}
|
|
6639
|
+
},
|
|
6640
|
+
60 * 60 * 1e3
|
|
6641
|
+
);
|
|
6642
|
+
timer.unref();
|
|
6643
|
+
}
|
|
6644
|
+
var INDEX_FILE, PROJECTS_DIR;
|
|
6645
|
+
var init_dlp_scanner = __esm({
|
|
6646
|
+
"src/daemon/dlp-scanner.ts"() {
|
|
6647
|
+
"use strict";
|
|
6648
|
+
init_dlp();
|
|
6649
|
+
init_native();
|
|
6650
|
+
init_state2();
|
|
6651
|
+
INDEX_FILE = path21.join(os16.homedir(), ".node9", "dlp-index.json");
|
|
6652
|
+
PROJECTS_DIR = path21.join(os16.homedir(), ".claude", "projects");
|
|
6653
|
+
}
|
|
6654
|
+
});
|
|
6655
|
+
|
|
6656
|
+
// src/daemon/server.ts
|
|
6657
|
+
import http from "http";
|
|
6658
|
+
import fs19 from "fs";
|
|
6659
|
+
import path22 from "path";
|
|
6455
6660
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6456
6661
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6457
6662
|
import chalk2 from "chalk";
|
|
6458
6663
|
function startDaemon() {
|
|
6459
6664
|
startCostSync();
|
|
6460
6665
|
startCloudSync();
|
|
6666
|
+
startDlpScanner();
|
|
6461
6667
|
loadInsightCounts();
|
|
6462
6668
|
const csrfToken = randomUUID4();
|
|
6463
6669
|
const internalToken = randomUUID4();
|
|
@@ -6473,7 +6679,7 @@ function startDaemon() {
|
|
|
6473
6679
|
idleTimer = setTimeout(() => {
|
|
6474
6680
|
if (autoStarted) {
|
|
6475
6681
|
try {
|
|
6476
|
-
|
|
6682
|
+
fs19.unlinkSync(DAEMON_PID_FILE);
|
|
6477
6683
|
} catch {
|
|
6478
6684
|
}
|
|
6479
6685
|
}
|
|
@@ -6636,7 +6842,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6636
6842
|
status: "pending"
|
|
6637
6843
|
});
|
|
6638
6844
|
}
|
|
6639
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6845
|
+
const projectCwd = typeof cwd === "string" && path22.isAbsolute(cwd) ? cwd : void 0;
|
|
6640
6846
|
const projectConfig = getConfig(projectCwd);
|
|
6641
6847
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6642
6848
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -7027,8 +7233,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
7027
7233
|
const body = await readBody(req);
|
|
7028
7234
|
const data = body ? JSON.parse(body) : {};
|
|
7029
7235
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
7030
|
-
const node9Dir =
|
|
7031
|
-
if (!
|
|
7236
|
+
const node9Dir = path22.dirname(GLOBAL_CONFIG_PATH);
|
|
7237
|
+
if (!path22.resolve(configPath).startsWith(node9Dir + path22.sep)) {
|
|
7032
7238
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7033
7239
|
return res.end(
|
|
7034
7240
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -7139,14 +7345,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
7139
7345
|
server.on("error", (e) => {
|
|
7140
7346
|
if (e.code === "EADDRINUSE") {
|
|
7141
7347
|
try {
|
|
7142
|
-
if (
|
|
7143
|
-
const { pid } = JSON.parse(
|
|
7348
|
+
if (fs19.existsSync(DAEMON_PID_FILE)) {
|
|
7349
|
+
const { pid } = JSON.parse(fs19.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7144
7350
|
process.kill(pid, 0);
|
|
7145
7351
|
return process.exit(0);
|
|
7146
7352
|
}
|
|
7147
7353
|
} catch {
|
|
7148
7354
|
try {
|
|
7149
|
-
|
|
7355
|
+
fs19.unlinkSync(DAEMON_PID_FILE);
|
|
7150
7356
|
} catch {
|
|
7151
7357
|
}
|
|
7152
7358
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -7217,19 +7423,20 @@ var init_server = __esm({
|
|
|
7217
7423
|
init_config_schema();
|
|
7218
7424
|
init_costSync();
|
|
7219
7425
|
init_sync();
|
|
7426
|
+
init_dlp_scanner();
|
|
7220
7427
|
}
|
|
7221
7428
|
});
|
|
7222
7429
|
|
|
7223
7430
|
// src/daemon/service.ts
|
|
7224
|
-
import
|
|
7225
|
-
import
|
|
7226
|
-
import
|
|
7431
|
+
import fs20 from "fs";
|
|
7432
|
+
import path23 from "path";
|
|
7433
|
+
import os17 from "os";
|
|
7227
7434
|
import { spawnSync as spawnSync3, execFileSync } from "child_process";
|
|
7228
7435
|
function resolveNode9Binary() {
|
|
7229
7436
|
try {
|
|
7230
7437
|
const script = process.argv[1];
|
|
7231
|
-
if (typeof script === "string" &&
|
|
7232
|
-
return
|
|
7438
|
+
if (typeof script === "string" && path23.isAbsolute(script) && fs20.existsSync(script)) {
|
|
7439
|
+
return fs20.realpathSync(script);
|
|
7233
7440
|
}
|
|
7234
7441
|
} catch {
|
|
7235
7442
|
}
|
|
@@ -7247,11 +7454,11 @@ function xmlEscape(s) {
|
|
|
7247
7454
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7248
7455
|
}
|
|
7249
7456
|
function launchdPlist(binaryPath) {
|
|
7250
|
-
const logDir =
|
|
7457
|
+
const logDir = path23.join(os17.homedir(), ".node9");
|
|
7251
7458
|
const nodePath = xmlEscape(process.execPath);
|
|
7252
7459
|
const scriptPath = xmlEscape(binaryPath);
|
|
7253
|
-
const outLog = xmlEscape(
|
|
7254
|
-
const errLog = xmlEscape(
|
|
7460
|
+
const outLog = xmlEscape(path23.join(logDir, "daemon.log"));
|
|
7461
|
+
const errLog = xmlEscape(path23.join(logDir, "daemon-error.log"));
|
|
7255
7462
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
7256
7463
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7257
7464
|
<plist version="1.0">
|
|
@@ -7286,9 +7493,9 @@ function launchdPlist(binaryPath) {
|
|
|
7286
7493
|
`;
|
|
7287
7494
|
}
|
|
7288
7495
|
function installLaunchd(binaryPath) {
|
|
7289
|
-
const dir =
|
|
7290
|
-
if (!
|
|
7291
|
-
|
|
7496
|
+
const dir = path23.dirname(LAUNCHD_PLIST);
|
|
7497
|
+
if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
|
|
7498
|
+
fs20.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
|
|
7292
7499
|
spawnSync3("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
7293
7500
|
const r = spawnSync3("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
7294
7501
|
encoding: "utf8",
|
|
@@ -7299,13 +7506,13 @@ function installLaunchd(binaryPath) {
|
|
|
7299
7506
|
}
|
|
7300
7507
|
}
|
|
7301
7508
|
function uninstallLaunchd() {
|
|
7302
|
-
if (
|
|
7509
|
+
if (fs20.existsSync(LAUNCHD_PLIST)) {
|
|
7303
7510
|
spawnSync3("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
7304
|
-
|
|
7511
|
+
fs20.unlinkSync(LAUNCHD_PLIST);
|
|
7305
7512
|
}
|
|
7306
7513
|
}
|
|
7307
7514
|
function isLaunchdInstalled() {
|
|
7308
|
-
return
|
|
7515
|
+
return fs20.existsSync(LAUNCHD_PLIST);
|
|
7309
7516
|
}
|
|
7310
7517
|
function systemdUnit(binaryPath) {
|
|
7311
7518
|
return `[Unit]
|
|
@@ -7325,12 +7532,12 @@ WantedBy=default.target
|
|
|
7325
7532
|
`;
|
|
7326
7533
|
}
|
|
7327
7534
|
function installSystemd(binaryPath) {
|
|
7328
|
-
if (!
|
|
7329
|
-
|
|
7535
|
+
if (!fs20.existsSync(SYSTEMD_UNIT_DIR)) {
|
|
7536
|
+
fs20.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
7330
7537
|
}
|
|
7331
|
-
|
|
7538
|
+
fs20.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
|
|
7332
7539
|
try {
|
|
7333
|
-
execFileSync("loginctl", ["enable-linger",
|
|
7540
|
+
execFileSync("loginctl", ["enable-linger", os17.userInfo().username], { timeout: 3e3 });
|
|
7334
7541
|
} catch {
|
|
7335
7542
|
}
|
|
7336
7543
|
const reload = spawnSync3("systemctl", ["--user", "daemon-reload"], {
|
|
@@ -7350,23 +7557,23 @@ function installSystemd(binaryPath) {
|
|
|
7350
7557
|
}
|
|
7351
7558
|
}
|
|
7352
7559
|
function uninstallSystemd() {
|
|
7353
|
-
if (
|
|
7560
|
+
if (fs20.existsSync(SYSTEMD_UNIT)) {
|
|
7354
7561
|
spawnSync3("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
7355
7562
|
encoding: "utf8",
|
|
7356
7563
|
timeout: 5e3
|
|
7357
7564
|
});
|
|
7358
7565
|
spawnSync3("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
7359
|
-
|
|
7566
|
+
fs20.unlinkSync(SYSTEMD_UNIT);
|
|
7360
7567
|
}
|
|
7361
7568
|
}
|
|
7362
7569
|
function isSystemdInstalled() {
|
|
7363
|
-
return
|
|
7570
|
+
return fs20.existsSync(SYSTEMD_UNIT);
|
|
7364
7571
|
}
|
|
7365
7572
|
function stopRunningDaemon() {
|
|
7366
|
-
const pidFile =
|
|
7367
|
-
if (!
|
|
7573
|
+
const pidFile = path23.join(os17.homedir(), ".node9", "daemon.pid");
|
|
7574
|
+
if (!fs20.existsSync(pidFile)) return;
|
|
7368
7575
|
try {
|
|
7369
|
-
const data = JSON.parse(
|
|
7576
|
+
const data = JSON.parse(fs20.readFileSync(pidFile, "utf-8"));
|
|
7370
7577
|
const pid = data.pid;
|
|
7371
7578
|
const MAX_PID2 = 4194304;
|
|
7372
7579
|
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
|
|
@@ -7386,7 +7593,7 @@ function stopRunningDaemon() {
|
|
|
7386
7593
|
}
|
|
7387
7594
|
}
|
|
7388
7595
|
try {
|
|
7389
|
-
|
|
7596
|
+
fs20.unlinkSync(pidFile);
|
|
7390
7597
|
} catch {
|
|
7391
7598
|
}
|
|
7392
7599
|
} catch {
|
|
@@ -7461,20 +7668,20 @@ var init_service = __esm({
|
|
|
7461
7668
|
"src/daemon/service.ts"() {
|
|
7462
7669
|
"use strict";
|
|
7463
7670
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
7464
|
-
LAUNCHD_PLIST =
|
|
7465
|
-
SYSTEMD_UNIT_DIR =
|
|
7466
|
-
SYSTEMD_UNIT =
|
|
7671
|
+
LAUNCHD_PLIST = path23.join(os17.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
7672
|
+
SYSTEMD_UNIT_DIR = path23.join(os17.homedir(), ".config", "systemd", "user");
|
|
7673
|
+
SYSTEMD_UNIT = path23.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
|
|
7467
7674
|
}
|
|
7468
7675
|
});
|
|
7469
7676
|
|
|
7470
7677
|
// src/daemon/index.ts
|
|
7471
|
-
import
|
|
7678
|
+
import fs21 from "fs";
|
|
7472
7679
|
import chalk3 from "chalk";
|
|
7473
7680
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
7474
7681
|
function stopDaemon() {
|
|
7475
|
-
if (!
|
|
7682
|
+
if (!fs21.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
|
|
7476
7683
|
try {
|
|
7477
|
-
const data = JSON.parse(
|
|
7684
|
+
const data = JSON.parse(fs21.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7478
7685
|
const pid = data.pid;
|
|
7479
7686
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
7480
7687
|
console.log(chalk3.gray("Cleaned up invalid PID file."));
|
|
@@ -7486,7 +7693,7 @@ function stopDaemon() {
|
|
|
7486
7693
|
console.log(chalk3.gray("Cleaned up stale PID file."));
|
|
7487
7694
|
} finally {
|
|
7488
7695
|
try {
|
|
7489
|
-
|
|
7696
|
+
fs21.unlinkSync(DAEMON_PID_FILE);
|
|
7490
7697
|
} catch {
|
|
7491
7698
|
}
|
|
7492
7699
|
}
|
|
@@ -7495,9 +7702,9 @@ function daemonStatus() {
|
|
|
7495
7702
|
const serviceInstalled = isDaemonServiceInstalled();
|
|
7496
7703
|
const serviceLabel = serviceInstalled ? chalk3.green("installed (starts on login)") : chalk3.yellow("not installed \u2014 run: node9 daemon install");
|
|
7497
7704
|
let processStatus;
|
|
7498
|
-
if (
|
|
7705
|
+
if (fs21.existsSync(DAEMON_PID_FILE)) {
|
|
7499
7706
|
try {
|
|
7500
|
-
const data = JSON.parse(
|
|
7707
|
+
const data = JSON.parse(fs21.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7501
7708
|
const pid = data.pid;
|
|
7502
7709
|
const port = data.port;
|
|
7503
7710
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
@@ -7546,10 +7753,10 @@ __export(tail_exports, {
|
|
|
7546
7753
|
startTail: () => startTail
|
|
7547
7754
|
});
|
|
7548
7755
|
import http2 from "http";
|
|
7549
|
-
import
|
|
7550
|
-
import
|
|
7551
|
-
import
|
|
7552
|
-
import
|
|
7756
|
+
import chalk25 from "chalk";
|
|
7757
|
+
import fs37 from "fs";
|
|
7758
|
+
import os33 from "os";
|
|
7759
|
+
import path40 from "path";
|
|
7553
7760
|
import readline5 from "readline";
|
|
7554
7761
|
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
7555
7762
|
function getIcon(tool) {
|
|
@@ -7559,6 +7766,74 @@ function getIcon(tool) {
|
|
|
7559
7766
|
}
|
|
7560
7767
|
return "\u{1F6E0}\uFE0F";
|
|
7561
7768
|
}
|
|
7769
|
+
function getModelContextLimit(model) {
|
|
7770
|
+
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
7771
|
+
for (const [key, limit] of Object.entries(MODEL_CONTEXT_LIMITS)) {
|
|
7772
|
+
if (base.startsWith(key)) return limit;
|
|
7773
|
+
}
|
|
7774
|
+
return 2e5;
|
|
7775
|
+
}
|
|
7776
|
+
function readSessionUsage() {
|
|
7777
|
+
const projectsDir = path40.join(os33.homedir(), ".claude", "projects");
|
|
7778
|
+
if (!fs37.existsSync(projectsDir)) return null;
|
|
7779
|
+
let latestFile = null;
|
|
7780
|
+
let latestMtime = 0;
|
|
7781
|
+
try {
|
|
7782
|
+
for (const dir of fs37.readdirSync(projectsDir)) {
|
|
7783
|
+
const dirPath = path40.join(projectsDir, dir);
|
|
7784
|
+
try {
|
|
7785
|
+
if (!fs37.statSync(dirPath).isDirectory()) continue;
|
|
7786
|
+
for (const file of fs37.readdirSync(dirPath)) {
|
|
7787
|
+
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
7788
|
+
const filePath = path40.join(dirPath, file);
|
|
7789
|
+
try {
|
|
7790
|
+
const mtime = fs37.statSync(filePath).mtimeMs;
|
|
7791
|
+
if (mtime > latestMtime) {
|
|
7792
|
+
latestMtime = mtime;
|
|
7793
|
+
latestFile = filePath;
|
|
7794
|
+
}
|
|
7795
|
+
} catch {
|
|
7796
|
+
}
|
|
7797
|
+
}
|
|
7798
|
+
} catch {
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
} catch {
|
|
7802
|
+
}
|
|
7803
|
+
if (!latestFile) return null;
|
|
7804
|
+
try {
|
|
7805
|
+
const lines = fs37.readFileSync(latestFile, "utf-8").split("\n");
|
|
7806
|
+
let lastModel = "";
|
|
7807
|
+
let lastInput = 0;
|
|
7808
|
+
let lastOutput = 0;
|
|
7809
|
+
for (const line of lines) {
|
|
7810
|
+
if (!line.trim()) continue;
|
|
7811
|
+
try {
|
|
7812
|
+
const entry = JSON.parse(line);
|
|
7813
|
+
if (entry.type !== "assistant" || !entry.message?.usage) continue;
|
|
7814
|
+
const u = entry.message.usage;
|
|
7815
|
+
lastInput = (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
7816
|
+
lastOutput = u.output_tokens ?? 0;
|
|
7817
|
+
if (entry.message.model) lastModel = entry.message.model;
|
|
7818
|
+
} catch {
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
if (!lastModel || lastInput === 0) return null;
|
|
7822
|
+
const limit = getModelContextLimit(lastModel);
|
|
7823
|
+
const fillPct = Math.round(lastInput / limit * 100);
|
|
7824
|
+
return { inputTokens: lastInput, outputTokens: lastOutput, model: lastModel, fillPct };
|
|
7825
|
+
} catch {
|
|
7826
|
+
return null;
|
|
7827
|
+
}
|
|
7828
|
+
}
|
|
7829
|
+
function formatContextStat(stat) {
|
|
7830
|
+
const pctColor = stat.fillPct >= 80 ? chalk25.red : stat.fillPct >= 50 ? chalk25.yellow : chalk25.cyan;
|
|
7831
|
+
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
7832
|
+
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
7833
|
+
return chalk25.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk25.dim(
|
|
7834
|
+
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
7835
|
+
);
|
|
7836
|
+
}
|
|
7562
7837
|
function visibleLength(s) {
|
|
7563
7838
|
return s.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
7564
7839
|
}
|
|
@@ -7568,26 +7843,31 @@ function wrappedLineCount(text) {
|
|
|
7568
7843
|
const len = visibleLength(text);
|
|
7569
7844
|
return Math.max(1, Math.ceil(len / cols));
|
|
7570
7845
|
}
|
|
7846
|
+
function agentLabel(agent) {
|
|
7847
|
+
if (!agent || agent === "Terminal") return "";
|
|
7848
|
+
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
7849
|
+
return short ? chalk25.dim(`[${short}] `) : "";
|
|
7850
|
+
}
|
|
7571
7851
|
function formatBase(activity) {
|
|
7572
7852
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
7573
7853
|
const icon = getIcon(activity.tool);
|
|
7574
7854
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
7575
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7855
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os33.homedir(), "~");
|
|
7576
7856
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
7577
|
-
return `${
|
|
7857
|
+
return `${chalk25.gray(time)} ${icon} ${agentLabel(activity.agent)}${chalk25.white.bold(toolName)} ${chalk25.dim(argsPreview)}`;
|
|
7578
7858
|
}
|
|
7579
7859
|
function renderResult(activity, result) {
|
|
7580
7860
|
const base = formatBase(activity);
|
|
7581
7861
|
let status;
|
|
7582
7862
|
if (result.status === "allow") {
|
|
7583
|
-
status =
|
|
7863
|
+
status = chalk25.green("\u2713 ALLOW");
|
|
7584
7864
|
} else if (result.status === "dlp") {
|
|
7585
|
-
status =
|
|
7865
|
+
status = chalk25.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
7586
7866
|
} else {
|
|
7587
|
-
status =
|
|
7867
|
+
status = chalk25.red("\u2717 BLOCK");
|
|
7588
7868
|
}
|
|
7589
7869
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
7590
|
-
const costSuffix = cost == null ? "" :
|
|
7870
|
+
const costSuffix = cost == null ? "" : chalk25.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
7591
7871
|
if (process.stdout.isTTY) {
|
|
7592
7872
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7593
7873
|
readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -7604,19 +7884,19 @@ function renderResult(activity, result) {
|
|
|
7604
7884
|
}
|
|
7605
7885
|
function renderPending(activity) {
|
|
7606
7886
|
if (!process.stdout.isTTY) return;
|
|
7607
|
-
const line = `${formatBase(activity)} ${
|
|
7887
|
+
const line = `${formatBase(activity)} ${chalk25.yellow("\u25CF \u2026")}`;
|
|
7608
7888
|
pendingShownForId = activity.id;
|
|
7609
7889
|
pendingWrappedLines = wrappedLineCount(line);
|
|
7610
7890
|
process.stdout.write(`${line}\r`);
|
|
7611
7891
|
}
|
|
7612
7892
|
async function ensureDaemon() {
|
|
7613
7893
|
let pidPort = null;
|
|
7614
|
-
if (
|
|
7894
|
+
if (fs37.existsSync(PID_FILE)) {
|
|
7615
7895
|
try {
|
|
7616
|
-
const { port } = JSON.parse(
|
|
7896
|
+
const { port } = JSON.parse(fs37.readFileSync(PID_FILE, "utf-8"));
|
|
7617
7897
|
pidPort = port;
|
|
7618
7898
|
} catch {
|
|
7619
|
-
console.error(
|
|
7899
|
+
console.error(chalk25.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
7620
7900
|
}
|
|
7621
7901
|
}
|
|
7622
7902
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -7627,7 +7907,7 @@ async function ensureDaemon() {
|
|
|
7627
7907
|
if (res.ok) return checkPort;
|
|
7628
7908
|
} catch {
|
|
7629
7909
|
}
|
|
7630
|
-
console.log(
|
|
7910
|
+
console.log(chalk25.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
7631
7911
|
const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
|
|
7632
7912
|
detached: true,
|
|
7633
7913
|
stdio: "ignore",
|
|
@@ -7644,7 +7924,7 @@ async function ensureDaemon() {
|
|
|
7644
7924
|
} catch {
|
|
7645
7925
|
}
|
|
7646
7926
|
}
|
|
7647
|
-
console.error(
|
|
7927
|
+
console.error(chalk25.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
7648
7928
|
process.exit(1);
|
|
7649
7929
|
}
|
|
7650
7930
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -7710,10 +7990,11 @@ function buildCardLines(req, localCount = 0) {
|
|
|
7710
7990
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
7711
7991
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
7712
7992
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
7993
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk25.dim(`(${req.agent})`)}` : "";
|
|
7713
7994
|
const lines = [
|
|
7714
7995
|
``,
|
|
7715
7996
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
7716
|
-
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
7997
|
+
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}${agentSuffix}`,
|
|
7717
7998
|
`${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
|
|
7718
7999
|
];
|
|
7719
8000
|
if (description) {
|
|
@@ -7765,9 +8046,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7765
8046
|
];
|
|
7766
8047
|
}
|
|
7767
8048
|
function readApproversFromDisk() {
|
|
7768
|
-
const configPath =
|
|
8049
|
+
const configPath = path40.join(os33.homedir(), ".node9", "config.json");
|
|
7769
8050
|
try {
|
|
7770
|
-
const raw = JSON.parse(
|
|
8051
|
+
const raw = JSON.parse(fs37.readFileSync(configPath, "utf-8"));
|
|
7771
8052
|
const settings = raw.settings ?? {};
|
|
7772
8053
|
return settings.approvers ?? {};
|
|
7773
8054
|
} catch {
|
|
@@ -7778,20 +8059,20 @@ function approverStatusLine() {
|
|
|
7778
8059
|
const a = readApproversFromDisk();
|
|
7779
8060
|
const fmt = (label, key) => {
|
|
7780
8061
|
const on = a[key] !== false;
|
|
7781
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
8062
|
+
return `[${key[0]}]${label.slice(1)} ${on ? chalk25.green("\u2713") : chalk25.dim("\u2717")}`;
|
|
7782
8063
|
};
|
|
7783
8064
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7784
8065
|
}
|
|
7785
8066
|
function toggleApprover(channel) {
|
|
7786
|
-
const configPath =
|
|
8067
|
+
const configPath = path40.join(os33.homedir(), ".node9", "config.json");
|
|
7787
8068
|
try {
|
|
7788
|
-
const raw = JSON.parse(
|
|
8069
|
+
const raw = JSON.parse(fs37.readFileSync(configPath, "utf-8"));
|
|
7789
8070
|
const settings = raw.settings ?? {};
|
|
7790
8071
|
const approvers = settings.approvers ?? {};
|
|
7791
8072
|
approvers[channel] = approvers[channel] === false;
|
|
7792
8073
|
settings.approvers = approvers;
|
|
7793
8074
|
raw.settings = settings;
|
|
7794
|
-
|
|
8075
|
+
fs37.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7795
8076
|
} catch (err2) {
|
|
7796
8077
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7797
8078
|
`);
|
|
@@ -7823,7 +8104,7 @@ async function startTail(options = {}) {
|
|
|
7823
8104
|
req2.end();
|
|
7824
8105
|
});
|
|
7825
8106
|
if (result.ok) {
|
|
7826
|
-
console.log(
|
|
8107
|
+
console.log(chalk25.green("\u2713 Flight Recorder buffer cleared."));
|
|
7827
8108
|
} else if (result.code === "ECONNREFUSED") {
|
|
7828
8109
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7829
8110
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7867,7 +8148,7 @@ async function startTail(options = {}) {
|
|
|
7867
8148
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7868
8149
|
if (channel) {
|
|
7869
8150
|
toggleApprover(channel);
|
|
7870
|
-
console.log(
|
|
8151
|
+
console.log(chalk25.dim(` Approvers: ${approverStatusLine()}`));
|
|
7871
8152
|
}
|
|
7872
8153
|
};
|
|
7873
8154
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7933,7 +8214,7 @@ async function startTail(options = {}) {
|
|
|
7933
8214
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7934
8215
|
)
|
|
7935
8216
|
);
|
|
7936
|
-
const decisionStamp = action === "always-allow" ?
|
|
8217
|
+
const decisionStamp = action === "always-allow" ? chalk25.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk25.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk25.green("\u2713 ALLOWED") : action === "redirect" ? chalk25.yellow("\u21A9 REDIRECT AI") : chalk25.red("\u2717 DENIED");
|
|
7937
8218
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7938
8219
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7939
8220
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7961,8 +8242,8 @@ async function startTail(options = {}) {
|
|
|
7961
8242
|
}
|
|
7962
8243
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7963
8244
|
try {
|
|
7964
|
-
|
|
7965
|
-
|
|
8245
|
+
fs37.appendFileSync(
|
|
8246
|
+
path40.join(os33.homedir(), ".node9", "hook-debug.log"),
|
|
7966
8247
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7967
8248
|
`
|
|
7968
8249
|
);
|
|
@@ -7984,7 +8265,7 @@ async function startTail(options = {}) {
|
|
|
7984
8265
|
);
|
|
7985
8266
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7986
8267
|
if (externalDecision) {
|
|
7987
|
-
const source = externalDecision === "allow" ?
|
|
8268
|
+
const source = externalDecision === "allow" ? chalk25.green("\u2713 ALLOWED") : chalk25.red("\u2717 DENIED");
|
|
7988
8269
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7989
8270
|
}
|
|
7990
8271
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -8043,16 +8324,31 @@ async function startTail(options = {}) {
|
|
|
8043
8324
|
}
|
|
8044
8325
|
} catch {
|
|
8045
8326
|
}
|
|
8046
|
-
|
|
8047
|
-
|
|
8327
|
+
const auditLog = path40.join(os33.homedir(), ".node9", "audit.log");
|
|
8328
|
+
try {
|
|
8329
|
+
const unackedDlp = fs37.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
8330
|
+
if (unackedDlp > 0) {
|
|
8331
|
+
console.log("");
|
|
8332
|
+
console.log(
|
|
8333
|
+
chalk25.bgRed.white.bold(
|
|
8334
|
+
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
8335
|
+
)
|
|
8336
|
+
);
|
|
8337
|
+
}
|
|
8338
|
+
} catch {
|
|
8339
|
+
}
|
|
8340
|
+
console.log(chalk25.cyan.bold(`
|
|
8341
|
+
\u{1F6F0}\uFE0F Node9 tail `) + chalk25.dim(`\u2192 ${dashboardUrl}`));
|
|
8048
8342
|
if (canApprove) {
|
|
8049
|
-
console.log(
|
|
8050
|
-
console.log(
|
|
8343
|
+
console.log(chalk25.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
8344
|
+
console.log(chalk25.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
8051
8345
|
}
|
|
8346
|
+
const ctxStat = readSessionUsage();
|
|
8347
|
+
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
8052
8348
|
if (options.history) {
|
|
8053
|
-
console.log(
|
|
8349
|
+
console.log(chalk25.dim("Showing history + live events.\n"));
|
|
8054
8350
|
} else {
|
|
8055
|
-
console.log(
|
|
8351
|
+
console.log(chalk25.dim("Showing live events only. Use --history to include past.\n"));
|
|
8056
8352
|
}
|
|
8057
8353
|
process.on("SIGINT", () => {
|
|
8058
8354
|
exitIdleMode();
|
|
@@ -8062,13 +8358,13 @@ async function startTail(options = {}) {
|
|
|
8062
8358
|
readline5.clearLine(process.stdout, 0);
|
|
8063
8359
|
readline5.cursorTo(process.stdout, 0);
|
|
8064
8360
|
}
|
|
8065
|
-
console.log(
|
|
8361
|
+
console.log(chalk25.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
8066
8362
|
process.exit(0);
|
|
8067
8363
|
});
|
|
8068
8364
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
8069
8365
|
const req = http2.get(sseUrl, (res) => {
|
|
8070
8366
|
if (res.statusCode !== 200) {
|
|
8071
|
-
console.error(
|
|
8367
|
+
console.error(chalk25.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
8072
8368
|
process.exit(1);
|
|
8073
8369
|
}
|
|
8074
8370
|
if (canApprove) enterIdleMode();
|
|
@@ -8099,7 +8395,7 @@ async function startTail(options = {}) {
|
|
|
8099
8395
|
readline5.clearLine(process.stdout, 0);
|
|
8100
8396
|
readline5.cursorTo(process.stdout, 0);
|
|
8101
8397
|
}
|
|
8102
|
-
console.log(
|
|
8398
|
+
console.log(chalk25.red("\n\u274C Daemon disconnected."));
|
|
8103
8399
|
process.exit(1);
|
|
8104
8400
|
});
|
|
8105
8401
|
});
|
|
@@ -8191,9 +8487,9 @@ async function startTail(options = {}) {
|
|
|
8191
8487
|
const hash = data.hash ?? "";
|
|
8192
8488
|
const summary = data.argsSummary ?? data.tool;
|
|
8193
8489
|
const fileCount = data.fileCount ?? 0;
|
|
8194
|
-
const files = fileCount > 0 ?
|
|
8490
|
+
const files = fileCount > 0 ? chalk25.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
8195
8491
|
process.stdout.write(
|
|
8196
|
-
`${
|
|
8492
|
+
`${chalk25.dim(time)} ${chalk25.cyan("\u{1F4F8} snapshot")} ${chalk25.dim(hash)} ${summary}${files}
|
|
8197
8493
|
`
|
|
8198
8494
|
);
|
|
8199
8495
|
return;
|
|
@@ -8210,19 +8506,19 @@ async function startTail(options = {}) {
|
|
|
8210
8506
|
}
|
|
8211
8507
|
req.on("error", (err2) => {
|
|
8212
8508
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
8213
|
-
console.error(
|
|
8509
|
+
console.error(chalk25.red(`
|
|
8214
8510
|
\u274C ${msg}`));
|
|
8215
8511
|
process.exit(1);
|
|
8216
8512
|
});
|
|
8217
8513
|
}
|
|
8218
|
-
var PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
8514
|
+
var PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
8219
8515
|
var init_tail = __esm({
|
|
8220
8516
|
"src/tui/tail.ts"() {
|
|
8221
8517
|
"use strict";
|
|
8222
8518
|
init_daemon2();
|
|
8223
8519
|
init_daemon();
|
|
8224
8520
|
init_core();
|
|
8225
|
-
PID_FILE =
|
|
8521
|
+
PID_FILE = path40.join(os33.homedir(), ".node9", "daemon.pid");
|
|
8226
8522
|
ICONS = {
|
|
8227
8523
|
bash: "\u{1F4BB}",
|
|
8228
8524
|
shell: "\u{1F4BB}",
|
|
@@ -8240,6 +8536,13 @@ var init_tail = __esm({
|
|
|
8240
8536
|
delete: "\u{1F5D1}\uFE0F",
|
|
8241
8537
|
web: "\u{1F310}"
|
|
8242
8538
|
};
|
|
8539
|
+
MODEL_CONTEXT_LIMITS = {
|
|
8540
|
+
"claude-opus-4": 2e5,
|
|
8541
|
+
"claude-sonnet-4": 2e5,
|
|
8542
|
+
"claude-haiku-4": 2e5,
|
|
8543
|
+
"claude-3-7": 2e5,
|
|
8544
|
+
"claude-3-5": 2e5
|
|
8545
|
+
};
|
|
8243
8546
|
RESET2 = "\x1B[0m";
|
|
8244
8547
|
BOLD2 = "\x1B[1m";
|
|
8245
8548
|
RED = "\x1B[31m";
|
|
@@ -8263,9 +8566,9 @@ __export(hud_exports, {
|
|
|
8263
8566
|
main: () => main,
|
|
8264
8567
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
8265
8568
|
});
|
|
8266
|
-
import
|
|
8267
|
-
import
|
|
8268
|
-
import
|
|
8569
|
+
import fs38 from "fs";
|
|
8570
|
+
import path41 from "path";
|
|
8571
|
+
import os34 from "os";
|
|
8269
8572
|
import http3 from "http";
|
|
8270
8573
|
async function readStdin() {
|
|
8271
8574
|
const chunks = [];
|
|
@@ -8341,9 +8644,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
8341
8644
|
return ` (${m}m left)`;
|
|
8342
8645
|
}
|
|
8343
8646
|
function safeReadJson(filePath) {
|
|
8344
|
-
if (!
|
|
8647
|
+
if (!fs38.existsSync(filePath)) return null;
|
|
8345
8648
|
try {
|
|
8346
|
-
return JSON.parse(
|
|
8649
|
+
return JSON.parse(fs38.readFileSync(filePath, "utf-8"));
|
|
8347
8650
|
} catch {
|
|
8348
8651
|
return null;
|
|
8349
8652
|
}
|
|
@@ -8364,12 +8667,12 @@ function countHooksInFile(filePath) {
|
|
|
8364
8667
|
return Object.keys(cfg.hooks).length;
|
|
8365
8668
|
}
|
|
8366
8669
|
function countRulesInDir(rulesDir) {
|
|
8367
|
-
if (!
|
|
8670
|
+
if (!fs38.existsSync(rulesDir)) return 0;
|
|
8368
8671
|
let count = 0;
|
|
8369
8672
|
try {
|
|
8370
|
-
for (const entry of
|
|
8673
|
+
for (const entry of fs38.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
8371
8674
|
if (entry.isDirectory()) {
|
|
8372
|
-
count += countRulesInDir(
|
|
8675
|
+
count += countRulesInDir(path41.join(rulesDir, entry.name));
|
|
8373
8676
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8374
8677
|
count++;
|
|
8375
8678
|
}
|
|
@@ -8380,46 +8683,46 @@ function countRulesInDir(rulesDir) {
|
|
|
8380
8683
|
}
|
|
8381
8684
|
function isSamePath(a, b) {
|
|
8382
8685
|
try {
|
|
8383
|
-
return
|
|
8686
|
+
return path41.resolve(a) === path41.resolve(b);
|
|
8384
8687
|
} catch {
|
|
8385
8688
|
return false;
|
|
8386
8689
|
}
|
|
8387
8690
|
}
|
|
8388
8691
|
function countConfigs(cwd) {
|
|
8389
|
-
const homeDir2 =
|
|
8390
|
-
const claudeDir =
|
|
8692
|
+
const homeDir2 = os34.homedir();
|
|
8693
|
+
const claudeDir = path41.join(homeDir2, ".claude");
|
|
8391
8694
|
let claudeMdCount = 0;
|
|
8392
8695
|
let rulesCount = 0;
|
|
8393
8696
|
let hooksCount = 0;
|
|
8394
8697
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
8395
8698
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
8396
|
-
if (
|
|
8397
|
-
rulesCount += countRulesInDir(
|
|
8398
|
-
const userSettings =
|
|
8699
|
+
if (fs38.existsSync(path41.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8700
|
+
rulesCount += countRulesInDir(path41.join(claudeDir, "rules"));
|
|
8701
|
+
const userSettings = path41.join(claudeDir, "settings.json");
|
|
8399
8702
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
8400
8703
|
hooksCount += countHooksInFile(userSettings);
|
|
8401
|
-
const userClaudeJson =
|
|
8704
|
+
const userClaudeJson = path41.join(homeDir2, ".claude.json");
|
|
8402
8705
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
8403
8706
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
8404
8707
|
userMcpServers.delete(name);
|
|
8405
8708
|
}
|
|
8406
8709
|
if (cwd) {
|
|
8407
|
-
if (
|
|
8408
|
-
if (
|
|
8409
|
-
const projectClaudeDir =
|
|
8710
|
+
if (fs38.existsSync(path41.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
8711
|
+
if (fs38.existsSync(path41.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8712
|
+
const projectClaudeDir = path41.join(cwd, ".claude");
|
|
8410
8713
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
8411
8714
|
if (!overlapsUserScope) {
|
|
8412
|
-
if (
|
|
8413
|
-
rulesCount += countRulesInDir(
|
|
8414
|
-
const projSettings =
|
|
8715
|
+
if (fs38.existsSync(path41.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8716
|
+
rulesCount += countRulesInDir(path41.join(projectClaudeDir, "rules"));
|
|
8717
|
+
const projSettings = path41.join(projectClaudeDir, "settings.json");
|
|
8415
8718
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
8416
8719
|
hooksCount += countHooksInFile(projSettings);
|
|
8417
8720
|
}
|
|
8418
|
-
if (
|
|
8419
|
-
const localSettings =
|
|
8721
|
+
if (fs38.existsSync(path41.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8722
|
+
const localSettings = path41.join(projectClaudeDir, "settings.local.json");
|
|
8420
8723
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
8421
8724
|
hooksCount += countHooksInFile(localSettings);
|
|
8422
|
-
const mcpJsonServers = getMcpServerNames(
|
|
8725
|
+
const mcpJsonServers = getMcpServerNames(path41.join(cwd, ".mcp.json"));
|
|
8423
8726
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
8424
8727
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
8425
8728
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -8452,12 +8755,12 @@ function readActiveShieldsHud() {
|
|
|
8452
8755
|
return shieldsCache.value;
|
|
8453
8756
|
}
|
|
8454
8757
|
try {
|
|
8455
|
-
const shieldsPath =
|
|
8456
|
-
if (!
|
|
8758
|
+
const shieldsPath = path41.join(os34.homedir(), ".node9", "shields.json");
|
|
8759
|
+
if (!fs38.existsSync(shieldsPath)) {
|
|
8457
8760
|
shieldsCache = { value: [], ts: now };
|
|
8458
8761
|
return [];
|
|
8459
8762
|
}
|
|
8460
|
-
const parsed = JSON.parse(
|
|
8763
|
+
const parsed = JSON.parse(fs38.readFileSync(shieldsPath, "utf-8"));
|
|
8461
8764
|
if (!Array.isArray(parsed.active)) {
|
|
8462
8765
|
shieldsCache = { value: [], ts: now };
|
|
8463
8766
|
return [];
|
|
@@ -8559,17 +8862,17 @@ function renderContextLine(stdin) {
|
|
|
8559
8862
|
async function main() {
|
|
8560
8863
|
try {
|
|
8561
8864
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
8562
|
-
if (
|
|
8865
|
+
if (fs38.existsSync(path41.join(os34.homedir(), ".node9", "hud-debug"))) {
|
|
8563
8866
|
try {
|
|
8564
|
-
const logPath =
|
|
8867
|
+
const logPath = path41.join(os34.homedir(), ".node9", "hud-debug.log");
|
|
8565
8868
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8566
8869
|
let size = 0;
|
|
8567
8870
|
try {
|
|
8568
|
-
size =
|
|
8871
|
+
size = fs38.statSync(logPath).size;
|
|
8569
8872
|
} catch {
|
|
8570
8873
|
}
|
|
8571
8874
|
if (size < MAX_LOG_SIZE) {
|
|
8572
|
-
|
|
8875
|
+
fs38.appendFileSync(
|
|
8573
8876
|
logPath,
|
|
8574
8877
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
8575
8878
|
);
|
|
@@ -8590,11 +8893,11 @@ async function main() {
|
|
|
8590
8893
|
try {
|
|
8591
8894
|
const cwd = stdin.cwd ?? process.cwd();
|
|
8592
8895
|
for (const configPath of [
|
|
8593
|
-
|
|
8594
|
-
|
|
8896
|
+
path41.join(cwd, "node9.config.json"),
|
|
8897
|
+
path41.join(os34.homedir(), ".node9", "config.json")
|
|
8595
8898
|
]) {
|
|
8596
|
-
if (!
|
|
8597
|
-
const cfg = JSON.parse(
|
|
8899
|
+
if (!fs38.existsSync(configPath)) continue;
|
|
8900
|
+
const cfg = JSON.parse(fs38.readFileSync(configPath, "utf-8"));
|
|
8598
8901
|
const hud = cfg.settings?.hud;
|
|
8599
8902
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
8600
8903
|
}
|
|
@@ -9529,10 +9832,10 @@ function getAgentsStatus(homeDir2 = os11.homedir()) {
|
|
|
9529
9832
|
|
|
9530
9833
|
// src/cli.ts
|
|
9531
9834
|
init_daemon2();
|
|
9532
|
-
import
|
|
9533
|
-
import
|
|
9534
|
-
import
|
|
9535
|
-
import
|
|
9835
|
+
import chalk26 from "chalk";
|
|
9836
|
+
import fs39 from "fs";
|
|
9837
|
+
import path42 from "path";
|
|
9838
|
+
import os35 from "os";
|
|
9536
9839
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
9537
9840
|
|
|
9538
9841
|
// src/utils/duration.ts
|
|
@@ -9761,19 +10064,19 @@ init_daemon();
|
|
|
9761
10064
|
init_config();
|
|
9762
10065
|
init_policy();
|
|
9763
10066
|
import chalk5 from "chalk";
|
|
9764
|
-
import
|
|
10067
|
+
import fs24 from "fs";
|
|
9765
10068
|
import { spawn as spawn6 } from "child_process";
|
|
9766
|
-
import
|
|
9767
|
-
import
|
|
10069
|
+
import path26 from "path";
|
|
10070
|
+
import os20 from "os";
|
|
9768
10071
|
|
|
9769
10072
|
// src/undo.ts
|
|
9770
10073
|
import { spawnSync as spawnSync5, spawn as spawn5 } from "child_process";
|
|
9771
10074
|
import crypto3 from "crypto";
|
|
9772
|
-
import
|
|
10075
|
+
import fs22 from "fs";
|
|
9773
10076
|
import net3 from "net";
|
|
9774
|
-
import
|
|
9775
|
-
import
|
|
9776
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
10077
|
+
import path24 from "path";
|
|
10078
|
+
import os18 from "os";
|
|
10079
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path24.join(os18.tmpdir(), "node9-activity.sock");
|
|
9777
10080
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
9778
10081
|
try {
|
|
9779
10082
|
const payload = JSON.stringify({
|
|
@@ -9793,22 +10096,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
9793
10096
|
} catch {
|
|
9794
10097
|
}
|
|
9795
10098
|
}
|
|
9796
|
-
var SNAPSHOT_STACK_PATH =
|
|
9797
|
-
var UNDO_LATEST_PATH =
|
|
10099
|
+
var SNAPSHOT_STACK_PATH = path24.join(os18.homedir(), ".node9", "snapshots.json");
|
|
10100
|
+
var UNDO_LATEST_PATH = path24.join(os18.homedir(), ".node9", "undo_latest.txt");
|
|
9798
10101
|
var MAX_SNAPSHOTS = 10;
|
|
9799
10102
|
var GIT_TIMEOUT = 15e3;
|
|
9800
10103
|
function readStack() {
|
|
9801
10104
|
try {
|
|
9802
|
-
if (
|
|
9803
|
-
return JSON.parse(
|
|
10105
|
+
if (fs22.existsSync(SNAPSHOT_STACK_PATH))
|
|
10106
|
+
return JSON.parse(fs22.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
9804
10107
|
} catch {
|
|
9805
10108
|
}
|
|
9806
10109
|
return [];
|
|
9807
10110
|
}
|
|
9808
10111
|
function writeStack(stack) {
|
|
9809
|
-
const dir =
|
|
9810
|
-
if (!
|
|
9811
|
-
|
|
10112
|
+
const dir = path24.dirname(SNAPSHOT_STACK_PATH);
|
|
10113
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
10114
|
+
fs22.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
9812
10115
|
}
|
|
9813
10116
|
function extractFilePath(args) {
|
|
9814
10117
|
if (!args || typeof args !== "object") return null;
|
|
@@ -9828,12 +10131,12 @@ function buildArgsSummary(tool, args) {
|
|
|
9828
10131
|
return "";
|
|
9829
10132
|
}
|
|
9830
10133
|
function findProjectRoot(filePath) {
|
|
9831
|
-
let dir =
|
|
10134
|
+
let dir = path24.dirname(filePath);
|
|
9832
10135
|
while (true) {
|
|
9833
|
-
if (
|
|
10136
|
+
if (fs22.existsSync(path24.join(dir, ".git")) || fs22.existsSync(path24.join(dir, "package.json"))) {
|
|
9834
10137
|
return dir;
|
|
9835
10138
|
}
|
|
9836
|
-
const parent =
|
|
10139
|
+
const parent = path24.dirname(dir);
|
|
9837
10140
|
if (parent === dir) return process.cwd();
|
|
9838
10141
|
dir = parent;
|
|
9839
10142
|
}
|
|
@@ -9841,7 +10144,7 @@ function findProjectRoot(filePath) {
|
|
|
9841
10144
|
function normalizeCwdForHash(cwd) {
|
|
9842
10145
|
let normalized;
|
|
9843
10146
|
try {
|
|
9844
|
-
normalized =
|
|
10147
|
+
normalized = fs22.realpathSync(cwd);
|
|
9845
10148
|
} catch {
|
|
9846
10149
|
normalized = cwd;
|
|
9847
10150
|
}
|
|
@@ -9851,16 +10154,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
9851
10154
|
}
|
|
9852
10155
|
function getShadowRepoDir(cwd) {
|
|
9853
10156
|
const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
9854
|
-
return
|
|
10157
|
+
return path24.join(os18.homedir(), ".node9", "snapshots", hash);
|
|
9855
10158
|
}
|
|
9856
10159
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
9857
10160
|
try {
|
|
9858
10161
|
const cutoff = Date.now() - 6e4;
|
|
9859
|
-
for (const f of
|
|
10162
|
+
for (const f of fs22.readdirSync(shadowDir)) {
|
|
9860
10163
|
if (f.startsWith("index_")) {
|
|
9861
|
-
const fp =
|
|
10164
|
+
const fp = path24.join(shadowDir, f);
|
|
9862
10165
|
try {
|
|
9863
|
-
if (
|
|
10166
|
+
if (fs22.statSync(fp).mtimeMs < cutoff) fs22.unlinkSync(fp);
|
|
9864
10167
|
} catch {
|
|
9865
10168
|
}
|
|
9866
10169
|
}
|
|
@@ -9872,7 +10175,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
9872
10175
|
const hardcoded = [".git", ".node9"];
|
|
9873
10176
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
9874
10177
|
try {
|
|
9875
|
-
|
|
10178
|
+
fs22.writeFileSync(path24.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
9876
10179
|
} catch {
|
|
9877
10180
|
}
|
|
9878
10181
|
}
|
|
@@ -9885,25 +10188,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9885
10188
|
timeout: 3e3
|
|
9886
10189
|
});
|
|
9887
10190
|
if (check.status === 0) {
|
|
9888
|
-
const ptPath =
|
|
10191
|
+
const ptPath = path24.join(shadowDir, "project-path.txt");
|
|
9889
10192
|
try {
|
|
9890
|
-
const stored =
|
|
10193
|
+
const stored = fs22.readFileSync(ptPath, "utf8").trim();
|
|
9891
10194
|
if (stored === normalizedCwd) return true;
|
|
9892
10195
|
if (process.env.NODE9_DEBUG === "1")
|
|
9893
10196
|
console.error(
|
|
9894
10197
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
9895
10198
|
);
|
|
9896
|
-
|
|
10199
|
+
fs22.rmSync(shadowDir, { recursive: true, force: true });
|
|
9897
10200
|
} catch {
|
|
9898
10201
|
try {
|
|
9899
|
-
|
|
10202
|
+
fs22.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
9900
10203
|
} catch {
|
|
9901
10204
|
}
|
|
9902
10205
|
return true;
|
|
9903
10206
|
}
|
|
9904
10207
|
}
|
|
9905
10208
|
try {
|
|
9906
|
-
|
|
10209
|
+
fs22.mkdirSync(shadowDir, { recursive: true });
|
|
9907
10210
|
} catch {
|
|
9908
10211
|
}
|
|
9909
10212
|
const init = spawnSync5("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -9912,7 +10215,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9912
10215
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
9913
10216
|
return false;
|
|
9914
10217
|
}
|
|
9915
|
-
const configFile =
|
|
10218
|
+
const configFile = path24.join(shadowDir, "config");
|
|
9916
10219
|
spawnSync5("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
9917
10220
|
timeout: 3e3
|
|
9918
10221
|
});
|
|
@@ -9920,7 +10223,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9920
10223
|
timeout: 3e3
|
|
9921
10224
|
});
|
|
9922
10225
|
try {
|
|
9923
|
-
|
|
10226
|
+
fs22.writeFileSync(path24.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
9924
10227
|
} catch {
|
|
9925
10228
|
}
|
|
9926
10229
|
return true;
|
|
@@ -9940,12 +10243,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9940
10243
|
let indexFile = null;
|
|
9941
10244
|
try {
|
|
9942
10245
|
const rawFilePath = extractFilePath(args);
|
|
9943
|
-
const absFilePath = rawFilePath &&
|
|
10246
|
+
const absFilePath = rawFilePath && path24.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
9944
10247
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
9945
10248
|
const shadowDir = getShadowRepoDir(cwd);
|
|
9946
10249
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
9947
10250
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
9948
|
-
indexFile =
|
|
10251
|
+
indexFile = path24.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
9949
10252
|
const shadowEnv = {
|
|
9950
10253
|
...process.env,
|
|
9951
10254
|
GIT_DIR: shadowDir,
|
|
@@ -10017,7 +10320,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
10017
10320
|
writeStack(stack);
|
|
10018
10321
|
const entry = stack[stack.length - 1];
|
|
10019
10322
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
10020
|
-
|
|
10323
|
+
fs22.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
10021
10324
|
if (shouldGc) {
|
|
10022
10325
|
spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
10023
10326
|
}
|
|
@@ -10028,7 +10331,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
10028
10331
|
} finally {
|
|
10029
10332
|
if (indexFile) {
|
|
10030
10333
|
try {
|
|
10031
|
-
|
|
10334
|
+
fs22.unlinkSync(indexFile);
|
|
10032
10335
|
} catch {
|
|
10033
10336
|
}
|
|
10034
10337
|
}
|
|
@@ -10104,9 +10407,9 @@ function applyUndo(hash, cwd) {
|
|
|
10104
10407
|
timeout: GIT_TIMEOUT
|
|
10105
10408
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
10106
10409
|
for (const file of [...tracked, ...untracked]) {
|
|
10107
|
-
const fullPath =
|
|
10108
|
-
if (!snapshotFiles.has(file) &&
|
|
10109
|
-
|
|
10410
|
+
const fullPath = path24.join(dir, file);
|
|
10411
|
+
if (!snapshotFiles.has(file) && fs22.existsSync(fullPath)) {
|
|
10412
|
+
fs22.unlinkSync(fullPath);
|
|
10110
10413
|
}
|
|
10111
10414
|
}
|
|
10112
10415
|
return true;
|
|
@@ -10116,12 +10419,12 @@ function applyUndo(hash, cwd) {
|
|
|
10116
10419
|
}
|
|
10117
10420
|
|
|
10118
10421
|
// src/skill-pin.ts
|
|
10119
|
-
import
|
|
10120
|
-
import
|
|
10121
|
-
import
|
|
10422
|
+
import fs23 from "fs";
|
|
10423
|
+
import path25 from "path";
|
|
10424
|
+
import os19 from "os";
|
|
10122
10425
|
import crypto4 from "crypto";
|
|
10123
10426
|
function getPinsFilePath() {
|
|
10124
|
-
return
|
|
10427
|
+
return path25.join(os19.homedir(), ".node9", "skill-pins.json");
|
|
10125
10428
|
}
|
|
10126
10429
|
var MAX_FILES = 5e3;
|
|
10127
10430
|
var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
@@ -10135,18 +10438,18 @@ function walkDir(root) {
|
|
|
10135
10438
|
if (out.length >= MAX_FILES) return;
|
|
10136
10439
|
let entries;
|
|
10137
10440
|
try {
|
|
10138
|
-
entries =
|
|
10441
|
+
entries = fs23.readdirSync(dir, { withFileTypes: true });
|
|
10139
10442
|
} catch {
|
|
10140
10443
|
return;
|
|
10141
10444
|
}
|
|
10142
10445
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
10143
10446
|
for (const entry of entries) {
|
|
10144
10447
|
if (out.length >= MAX_FILES) return;
|
|
10145
|
-
const full =
|
|
10146
|
-
const rel = relDir ?
|
|
10448
|
+
const full = path25.join(dir, entry.name);
|
|
10449
|
+
const rel = relDir ? path25.posix.join(relDir, entry.name) : entry.name;
|
|
10147
10450
|
let lst;
|
|
10148
10451
|
try {
|
|
10149
|
-
lst =
|
|
10452
|
+
lst = fs23.lstatSync(full);
|
|
10150
10453
|
} catch {
|
|
10151
10454
|
continue;
|
|
10152
10455
|
}
|
|
@@ -10158,7 +10461,7 @@ function walkDir(root) {
|
|
|
10158
10461
|
if (!lst.isFile()) continue;
|
|
10159
10462
|
if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
|
|
10160
10463
|
try {
|
|
10161
|
-
const buf =
|
|
10464
|
+
const buf = fs23.readFileSync(full);
|
|
10162
10465
|
totalBytes += buf.length;
|
|
10163
10466
|
out.push({ rel, hash: sha256Bytes(buf) });
|
|
10164
10467
|
} catch {
|
|
@@ -10172,14 +10475,14 @@ function walkDir(root) {
|
|
|
10172
10475
|
function hashSkillRoot(absPath) {
|
|
10173
10476
|
let lst;
|
|
10174
10477
|
try {
|
|
10175
|
-
lst =
|
|
10478
|
+
lst = fs23.lstatSync(absPath);
|
|
10176
10479
|
} catch {
|
|
10177
10480
|
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10178
10481
|
}
|
|
10179
10482
|
if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
|
|
10180
10483
|
if (lst.isFile()) {
|
|
10181
10484
|
try {
|
|
10182
|
-
return { exists: true, contentHash: sha256Bytes(
|
|
10485
|
+
return { exists: true, contentHash: sha256Bytes(fs23.readFileSync(absPath)), fileCount: 1 };
|
|
10183
10486
|
} catch {
|
|
10184
10487
|
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10185
10488
|
}
|
|
@@ -10197,7 +10500,7 @@ function getRootKey(absPath) {
|
|
|
10197
10500
|
function readSkillPinsSafe() {
|
|
10198
10501
|
const filePath = getPinsFilePath();
|
|
10199
10502
|
try {
|
|
10200
|
-
const raw =
|
|
10503
|
+
const raw = fs23.readFileSync(filePath, "utf-8");
|
|
10201
10504
|
if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10202
10505
|
const parsed = JSON.parse(raw);
|
|
10203
10506
|
if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
|
|
@@ -10217,10 +10520,10 @@ function readSkillPins() {
|
|
|
10217
10520
|
}
|
|
10218
10521
|
function writeSkillPins(data) {
|
|
10219
10522
|
const filePath = getPinsFilePath();
|
|
10220
|
-
|
|
10523
|
+
fs23.mkdirSync(path25.dirname(filePath), { recursive: true });
|
|
10221
10524
|
const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
|
|
10222
|
-
|
|
10223
|
-
|
|
10525
|
+
fs23.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
10526
|
+
fs23.renameSync(tmp, filePath);
|
|
10224
10527
|
}
|
|
10225
10528
|
function removePin(rootKey) {
|
|
10226
10529
|
const pins = readSkillPins();
|
|
@@ -10264,36 +10567,36 @@ function verifyAndPinRoots(roots) {
|
|
|
10264
10567
|
return { kind: "verified" };
|
|
10265
10568
|
}
|
|
10266
10569
|
function defaultSkillRoots(_cwd) {
|
|
10267
|
-
const marketplaces =
|
|
10570
|
+
const marketplaces = path25.join(os19.homedir(), ".claude", "plugins", "marketplaces");
|
|
10268
10571
|
const roots = [];
|
|
10269
10572
|
let registries;
|
|
10270
10573
|
try {
|
|
10271
|
-
registries =
|
|
10574
|
+
registries = fs23.readdirSync(marketplaces, { withFileTypes: true });
|
|
10272
10575
|
} catch {
|
|
10273
10576
|
return [];
|
|
10274
10577
|
}
|
|
10275
10578
|
for (const registry of registries) {
|
|
10276
10579
|
if (!registry.isDirectory()) continue;
|
|
10277
|
-
const pluginsDir =
|
|
10580
|
+
const pluginsDir = path25.join(marketplaces, registry.name, "plugins");
|
|
10278
10581
|
let plugins;
|
|
10279
10582
|
try {
|
|
10280
|
-
plugins =
|
|
10583
|
+
plugins = fs23.readdirSync(pluginsDir, { withFileTypes: true });
|
|
10281
10584
|
} catch {
|
|
10282
10585
|
continue;
|
|
10283
10586
|
}
|
|
10284
10587
|
for (const plugin of plugins) {
|
|
10285
10588
|
if (!plugin.isDirectory()) continue;
|
|
10286
|
-
roots.push(
|
|
10589
|
+
roots.push(path25.join(pluginsDir, plugin.name));
|
|
10287
10590
|
}
|
|
10288
10591
|
}
|
|
10289
10592
|
return roots;
|
|
10290
10593
|
}
|
|
10291
10594
|
function resolveUserSkillRoot(entry, cwd) {
|
|
10292
10595
|
if (!entry) return null;
|
|
10293
|
-
if (entry.startsWith("~/") || entry === "~") return
|
|
10294
|
-
if (
|
|
10295
|
-
if (!cwd || !
|
|
10296
|
-
return
|
|
10596
|
+
if (entry.startsWith("~/") || entry === "~") return path25.join(os19.homedir(), entry.slice(1));
|
|
10597
|
+
if (path25.isAbsolute(entry)) return entry;
|
|
10598
|
+
if (!cwd || !path25.isAbsolute(cwd)) return null;
|
|
10599
|
+
return path25.join(cwd, entry);
|
|
10297
10600
|
}
|
|
10298
10601
|
|
|
10299
10602
|
// src/cli/commands/check.ts
|
|
@@ -10311,9 +10614,9 @@ function registerCheckCommand(program2) {
|
|
|
10311
10614
|
} catch (err2) {
|
|
10312
10615
|
const tempConfig = getConfig();
|
|
10313
10616
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
10314
|
-
const logPath =
|
|
10617
|
+
const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
|
|
10315
10618
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10316
|
-
|
|
10619
|
+
fs24.appendFileSync(
|
|
10317
10620
|
logPath,
|
|
10318
10621
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
10319
10622
|
RAW: ${raw}
|
|
@@ -10326,11 +10629,11 @@ RAW: ${raw}
|
|
|
10326
10629
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
10327
10630
|
try {
|
|
10328
10631
|
const scriptPath = process.argv[1];
|
|
10329
|
-
if (typeof scriptPath !== "string" || !
|
|
10632
|
+
if (typeof scriptPath !== "string" || !path26.isAbsolute(scriptPath))
|
|
10330
10633
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
10331
|
-
const resolvedScript =
|
|
10332
|
-
const packageDist =
|
|
10333
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
10634
|
+
const resolvedScript = fs24.realpathSync(scriptPath);
|
|
10635
|
+
const packageDist = fs24.realpathSync(path26.resolve(__dirname, "../.."));
|
|
10636
|
+
if (!resolvedScript.startsWith(packageDist + path26.sep) && resolvedScript !== packageDist)
|
|
10334
10637
|
throw new Error(
|
|
10335
10638
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
10336
10639
|
);
|
|
@@ -10352,10 +10655,10 @@ RAW: ${raw}
|
|
|
10352
10655
|
});
|
|
10353
10656
|
d.unref();
|
|
10354
10657
|
} catch (spawnErr) {
|
|
10355
|
-
const logPath =
|
|
10658
|
+
const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
|
|
10356
10659
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
10357
10660
|
try {
|
|
10358
|
-
|
|
10661
|
+
fs24.appendFileSync(
|
|
10359
10662
|
logPath,
|
|
10360
10663
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
10361
10664
|
`
|
|
@@ -10365,10 +10668,10 @@ RAW: ${raw}
|
|
|
10365
10668
|
}
|
|
10366
10669
|
}
|
|
10367
10670
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
10368
|
-
const logPath =
|
|
10369
|
-
if (!
|
|
10370
|
-
|
|
10371
|
-
|
|
10671
|
+
const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
|
|
10672
|
+
if (!fs24.existsSync(path26.dirname(logPath)))
|
|
10673
|
+
fs24.mkdirSync(path26.dirname(logPath), { recursive: true });
|
|
10674
|
+
fs24.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
10372
10675
|
`);
|
|
10373
10676
|
}
|
|
10374
10677
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -10381,8 +10684,8 @@ RAW: ${raw}
|
|
|
10381
10684
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
10382
10685
|
let ttyFd = null;
|
|
10383
10686
|
try {
|
|
10384
|
-
ttyFd =
|
|
10385
|
-
const writeTty = (line) =>
|
|
10687
|
+
ttyFd = fs24.openSync("/dev/tty", "w");
|
|
10688
|
+
const writeTty = (line) => fs24.writeSync(ttyFd, line + "\n");
|
|
10386
10689
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
10387
10690
|
writeTty(chalk5.bgRed.white.bold(`
|
|
10388
10691
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -10401,7 +10704,7 @@ RAW: ${raw}
|
|
|
10401
10704
|
} finally {
|
|
10402
10705
|
if (ttyFd !== null)
|
|
10403
10706
|
try {
|
|
10404
|
-
|
|
10707
|
+
fs24.closeSync(ttyFd);
|
|
10405
10708
|
} catch {
|
|
10406
10709
|
}
|
|
10407
10710
|
}
|
|
@@ -10435,17 +10738,17 @@ RAW: ${raw}
|
|
|
10435
10738
|
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
10436
10739
|
if (skillPinCfg.enabled && safeSessionId) {
|
|
10437
10740
|
try {
|
|
10438
|
-
const sessionsDir =
|
|
10439
|
-
const flagPath =
|
|
10741
|
+
const sessionsDir = path26.join(os20.homedir(), ".node9", "skill-sessions");
|
|
10742
|
+
const flagPath = path26.join(sessionsDir, `${safeSessionId}.json`);
|
|
10440
10743
|
let flag = null;
|
|
10441
10744
|
try {
|
|
10442
|
-
flag = JSON.parse(
|
|
10745
|
+
flag = JSON.parse(fs24.readFileSync(flagPath, "utf-8"));
|
|
10443
10746
|
} catch {
|
|
10444
10747
|
}
|
|
10445
10748
|
const writeFlag = (data2) => {
|
|
10446
10749
|
try {
|
|
10447
|
-
|
|
10448
|
-
|
|
10750
|
+
fs24.mkdirSync(sessionsDir, { recursive: true });
|
|
10751
|
+
fs24.writeFileSync(
|
|
10449
10752
|
flagPath,
|
|
10450
10753
|
JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
10451
10754
|
{ mode: 384 }
|
|
@@ -10456,8 +10759,8 @@ RAW: ${raw}
|
|
|
10456
10759
|
const sendSkillWarn = (detail, recoveryCmd) => {
|
|
10457
10760
|
let ttyFd = null;
|
|
10458
10761
|
try {
|
|
10459
|
-
ttyFd =
|
|
10460
|
-
const w = (line) =>
|
|
10762
|
+
ttyFd = fs24.openSync("/dev/tty", "w");
|
|
10763
|
+
const w = (line) => fs24.writeSync(ttyFd, line + "\n");
|
|
10461
10764
|
w(chalk5.yellow(`
|
|
10462
10765
|
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
10463
10766
|
w(chalk5.gray(` ${detail}`));
|
|
@@ -10472,7 +10775,7 @@ RAW: ${raw}
|
|
|
10472
10775
|
} finally {
|
|
10473
10776
|
if (ttyFd !== null)
|
|
10474
10777
|
try {
|
|
10475
|
-
|
|
10778
|
+
fs24.closeSync(ttyFd);
|
|
10476
10779
|
} catch {
|
|
10477
10780
|
}
|
|
10478
10781
|
}
|
|
@@ -10488,7 +10791,7 @@ RAW: ${raw}
|
|
|
10488
10791
|
return;
|
|
10489
10792
|
}
|
|
10490
10793
|
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
10491
|
-
const absoluteCwd = typeof payload.cwd === "string" &&
|
|
10794
|
+
const absoluteCwd = typeof payload.cwd === "string" && path26.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10492
10795
|
const extraRoots = skillPinCfg.roots;
|
|
10493
10796
|
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
10494
10797
|
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
@@ -10529,10 +10832,10 @@ RAW: ${raw}
|
|
|
10529
10832
|
}
|
|
10530
10833
|
try {
|
|
10531
10834
|
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
10532
|
-
for (const name of
|
|
10533
|
-
const p =
|
|
10835
|
+
for (const name of fs24.readdirSync(sessionsDir)) {
|
|
10836
|
+
const p = path26.join(sessionsDir, name);
|
|
10534
10837
|
try {
|
|
10535
|
-
if (
|
|
10838
|
+
if (fs24.statSync(p).mtimeMs < cutoff) fs24.unlinkSync(p);
|
|
10536
10839
|
} catch {
|
|
10537
10840
|
}
|
|
10538
10841
|
}
|
|
@@ -10542,9 +10845,9 @@ RAW: ${raw}
|
|
|
10542
10845
|
} catch (err2) {
|
|
10543
10846
|
if (process.env.NODE9_DEBUG === "1") {
|
|
10544
10847
|
try {
|
|
10545
|
-
const dbg =
|
|
10848
|
+
const dbg = path26.join(os20.homedir(), ".node9", "hook-debug.log");
|
|
10546
10849
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10547
|
-
|
|
10850
|
+
fs24.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
|
|
10548
10851
|
`);
|
|
10549
10852
|
} catch {
|
|
10550
10853
|
}
|
|
@@ -10554,7 +10857,7 @@ RAW: ${raw}
|
|
|
10554
10857
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
10555
10858
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
10556
10859
|
}
|
|
10557
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
10860
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && path26.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10558
10861
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
10559
10862
|
cwd: safeCwdForAuth
|
|
10560
10863
|
});
|
|
@@ -10566,12 +10869,12 @@ RAW: ${raw}
|
|
|
10566
10869
|
}
|
|
10567
10870
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
10568
10871
|
try {
|
|
10569
|
-
const tty =
|
|
10570
|
-
|
|
10872
|
+
const tty = fs24.openSync("/dev/tty", "w");
|
|
10873
|
+
fs24.writeSync(
|
|
10571
10874
|
tty,
|
|
10572
10875
|
chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
10573
10876
|
);
|
|
10574
|
-
|
|
10877
|
+
fs24.closeSync(tty);
|
|
10575
10878
|
} catch {
|
|
10576
10879
|
}
|
|
10577
10880
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -10598,9 +10901,9 @@ RAW: ${raw}
|
|
|
10598
10901
|
});
|
|
10599
10902
|
} catch (err2) {
|
|
10600
10903
|
if (process.env.NODE9_DEBUG === "1") {
|
|
10601
|
-
const logPath =
|
|
10904
|
+
const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
|
|
10602
10905
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10603
|
-
|
|
10906
|
+
fs24.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
10604
10907
|
`);
|
|
10605
10908
|
}
|
|
10606
10909
|
process.exit(0);
|
|
@@ -10637,9 +10940,9 @@ RAW: ${raw}
|
|
|
10637
10940
|
init_audit();
|
|
10638
10941
|
init_config();
|
|
10639
10942
|
init_policy();
|
|
10640
|
-
import
|
|
10641
|
-
import
|
|
10642
|
-
import
|
|
10943
|
+
import fs25 from "fs";
|
|
10944
|
+
import path27 from "path";
|
|
10945
|
+
import os21 from "os";
|
|
10643
10946
|
init_daemon();
|
|
10644
10947
|
|
|
10645
10948
|
// src/utils/cp-mv-parser.ts
|
|
@@ -10712,10 +11015,10 @@ function registerLogCommand(program2) {
|
|
|
10712
11015
|
decision: "allowed",
|
|
10713
11016
|
source: "post-hook"
|
|
10714
11017
|
};
|
|
10715
|
-
const logPath =
|
|
10716
|
-
if (!
|
|
10717
|
-
|
|
10718
|
-
|
|
11018
|
+
const logPath = path27.join(os21.homedir(), ".node9", "audit.log");
|
|
11019
|
+
if (!fs25.existsSync(path27.dirname(logPath)))
|
|
11020
|
+
fs25.mkdirSync(path27.dirname(logPath), { recursive: true });
|
|
11021
|
+
fs25.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
10719
11022
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
10720
11023
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
10721
11024
|
if (command) {
|
|
@@ -10748,7 +11051,7 @@ function registerLogCommand(program2) {
|
|
|
10748
11051
|
}
|
|
10749
11052
|
}
|
|
10750
11053
|
}
|
|
10751
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
11054
|
+
const safeCwd = typeof payload.cwd === "string" && path27.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10752
11055
|
const config = getConfig(safeCwd);
|
|
10753
11056
|
if (shouldSnapshot(tool, {}, config)) {
|
|
10754
11057
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -10757,9 +11060,9 @@ function registerLogCommand(program2) {
|
|
|
10757
11060
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10758
11061
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
10759
11062
|
`);
|
|
10760
|
-
const debugPath =
|
|
11063
|
+
const debugPath = path27.join(os21.homedir(), ".node9", "hook-debug.log");
|
|
10761
11064
|
try {
|
|
10762
|
-
|
|
11065
|
+
fs25.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
10763
11066
|
`);
|
|
10764
11067
|
} catch {
|
|
10765
11068
|
}
|
|
@@ -11160,13 +11463,13 @@ function registerConfigShowCommand(program2) {
|
|
|
11160
11463
|
// src/cli/commands/doctor.ts
|
|
11161
11464
|
init_daemon();
|
|
11162
11465
|
import chalk7 from "chalk";
|
|
11163
|
-
import
|
|
11164
|
-
import
|
|
11165
|
-
import
|
|
11466
|
+
import fs26 from "fs";
|
|
11467
|
+
import path28 from "path";
|
|
11468
|
+
import os22 from "os";
|
|
11166
11469
|
import { execSync as execSync2 } from "child_process";
|
|
11167
11470
|
function registerDoctorCommand(program2, version2) {
|
|
11168
11471
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
11169
|
-
const homeDir2 =
|
|
11472
|
+
const homeDir2 = os22.homedir();
|
|
11170
11473
|
let failures = 0;
|
|
11171
11474
|
function pass(msg) {
|
|
11172
11475
|
console.log(chalk7.green(" \u2705 ") + msg);
|
|
@@ -11215,10 +11518,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11215
11518
|
);
|
|
11216
11519
|
}
|
|
11217
11520
|
section("Configuration");
|
|
11218
|
-
const globalConfigPath =
|
|
11219
|
-
if (
|
|
11521
|
+
const globalConfigPath = path28.join(homeDir2, ".node9", "config.json");
|
|
11522
|
+
if (fs26.existsSync(globalConfigPath)) {
|
|
11220
11523
|
try {
|
|
11221
|
-
JSON.parse(
|
|
11524
|
+
JSON.parse(fs26.readFileSync(globalConfigPath, "utf-8"));
|
|
11222
11525
|
pass("~/.node9/config.json found and valid");
|
|
11223
11526
|
} catch {
|
|
11224
11527
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -11226,10 +11529,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11226
11529
|
} else {
|
|
11227
11530
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
11228
11531
|
}
|
|
11229
|
-
const projectConfigPath =
|
|
11230
|
-
if (
|
|
11532
|
+
const projectConfigPath = path28.join(process.cwd(), "node9.config.json");
|
|
11533
|
+
if (fs26.existsSync(projectConfigPath)) {
|
|
11231
11534
|
try {
|
|
11232
|
-
JSON.parse(
|
|
11535
|
+
JSON.parse(fs26.readFileSync(projectConfigPath, "utf-8"));
|
|
11233
11536
|
pass("node9.config.json found and valid (project)");
|
|
11234
11537
|
} catch {
|
|
11235
11538
|
fail(
|
|
@@ -11238,8 +11541,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11238
11541
|
);
|
|
11239
11542
|
}
|
|
11240
11543
|
}
|
|
11241
|
-
const credsPath =
|
|
11242
|
-
if (
|
|
11544
|
+
const credsPath = path28.join(homeDir2, ".node9", "credentials.json");
|
|
11545
|
+
if (fs26.existsSync(credsPath)) {
|
|
11243
11546
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
11244
11547
|
} else {
|
|
11245
11548
|
warn(
|
|
@@ -11248,10 +11551,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11248
11551
|
);
|
|
11249
11552
|
}
|
|
11250
11553
|
section("Agent Hooks");
|
|
11251
|
-
const claudeSettingsPath =
|
|
11252
|
-
if (
|
|
11554
|
+
const claudeSettingsPath = path28.join(homeDir2, ".claude", "settings.json");
|
|
11555
|
+
if (fs26.existsSync(claudeSettingsPath)) {
|
|
11253
11556
|
try {
|
|
11254
|
-
const cs = JSON.parse(
|
|
11557
|
+
const cs = JSON.parse(fs26.readFileSync(claudeSettingsPath, "utf-8"));
|
|
11255
11558
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
11256
11559
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
11257
11560
|
);
|
|
@@ -11267,10 +11570,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11267
11570
|
} else {
|
|
11268
11571
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
11269
11572
|
}
|
|
11270
|
-
const geminiSettingsPath =
|
|
11271
|
-
if (
|
|
11573
|
+
const geminiSettingsPath = path28.join(homeDir2, ".gemini", "settings.json");
|
|
11574
|
+
if (fs26.existsSync(geminiSettingsPath)) {
|
|
11272
11575
|
try {
|
|
11273
|
-
const gs = JSON.parse(
|
|
11576
|
+
const gs = JSON.parse(fs26.readFileSync(geminiSettingsPath, "utf-8"));
|
|
11274
11577
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
11275
11578
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
11276
11579
|
);
|
|
@@ -11286,10 +11589,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11286
11589
|
} else {
|
|
11287
11590
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
11288
11591
|
}
|
|
11289
|
-
const cursorHooksPath =
|
|
11290
|
-
if (
|
|
11592
|
+
const cursorHooksPath = path28.join(homeDir2, ".cursor", "hooks.json");
|
|
11593
|
+
if (fs26.existsSync(cursorHooksPath)) {
|
|
11291
11594
|
try {
|
|
11292
|
-
const cur = JSON.parse(
|
|
11595
|
+
const cur = JSON.parse(fs26.readFileSync(cursorHooksPath, "utf-8"));
|
|
11293
11596
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
11294
11597
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
11295
11598
|
);
|
|
@@ -11327,9 +11630,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11327
11630
|
|
|
11328
11631
|
// src/cli/commands/audit.ts
|
|
11329
11632
|
import chalk8 from "chalk";
|
|
11330
|
-
import
|
|
11331
|
-
import
|
|
11332
|
-
import
|
|
11633
|
+
import fs27 from "fs";
|
|
11634
|
+
import path29 from "path";
|
|
11635
|
+
import os23 from "os";
|
|
11333
11636
|
function formatRelativeTime(timestamp) {
|
|
11334
11637
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
11335
11638
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -11342,14 +11645,14 @@ function formatRelativeTime(timestamp) {
|
|
|
11342
11645
|
}
|
|
11343
11646
|
function registerAuditCommand(program2) {
|
|
11344
11647
|
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) => {
|
|
11345
|
-
const logPath =
|
|
11346
|
-
if (!
|
|
11648
|
+
const logPath = path29.join(os23.homedir(), ".node9", "audit.log");
|
|
11649
|
+
if (!fs27.existsSync(logPath)) {
|
|
11347
11650
|
console.log(
|
|
11348
11651
|
chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
11349
11652
|
);
|
|
11350
11653
|
return;
|
|
11351
11654
|
}
|
|
11352
|
-
const raw =
|
|
11655
|
+
const raw = fs27.readFileSync(logPath, "utf-8");
|
|
11353
11656
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
11354
11657
|
let entries = lines.flatMap((line) => {
|
|
11355
11658
|
try {
|
|
@@ -11403,9 +11706,9 @@ function registerAuditCommand(program2) {
|
|
|
11403
11706
|
|
|
11404
11707
|
// src/cli/commands/report.ts
|
|
11405
11708
|
import chalk9 from "chalk";
|
|
11406
|
-
import
|
|
11407
|
-
import
|
|
11408
|
-
import
|
|
11709
|
+
import fs28 from "fs";
|
|
11710
|
+
import path30 from "path";
|
|
11711
|
+
import os24 from "os";
|
|
11409
11712
|
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;
|
|
11410
11713
|
function buildTestTimestamps(allEntries) {
|
|
11411
11714
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -11452,8 +11755,8 @@ function getDateRange(period) {
|
|
|
11452
11755
|
}
|
|
11453
11756
|
}
|
|
11454
11757
|
function parseAuditLog(logPath) {
|
|
11455
|
-
if (!
|
|
11456
|
-
const raw =
|
|
11758
|
+
if (!fs28.existsSync(logPath)) return [];
|
|
11759
|
+
const raw = fs28.readFileSync(logPath, "utf-8");
|
|
11457
11760
|
return raw.split("\n").flatMap((line) => {
|
|
11458
11761
|
if (!line.trim()) return [];
|
|
11459
11762
|
try {
|
|
@@ -11520,34 +11823,38 @@ function loadClaudeCost(start, end) {
|
|
|
11520
11823
|
byDay: /* @__PURE__ */ new Map(),
|
|
11521
11824
|
byModel: /* @__PURE__ */ new Map(),
|
|
11522
11825
|
inputTokens: 0,
|
|
11826
|
+
outputTokens: 0,
|
|
11827
|
+
cacheWriteTokens: 0,
|
|
11523
11828
|
cacheReadTokens: 0
|
|
11524
11829
|
};
|
|
11525
|
-
const projectsDir =
|
|
11526
|
-
if (!
|
|
11830
|
+
const projectsDir = path30.join(os24.homedir(), ".claude", "projects");
|
|
11831
|
+
if (!fs28.existsSync(projectsDir)) return empty;
|
|
11527
11832
|
let dirs;
|
|
11528
11833
|
try {
|
|
11529
|
-
dirs =
|
|
11834
|
+
dirs = fs28.readdirSync(projectsDir);
|
|
11530
11835
|
} catch {
|
|
11531
11836
|
return empty;
|
|
11532
11837
|
}
|
|
11533
11838
|
let total = 0;
|
|
11534
11839
|
let inputTokens = 0;
|
|
11840
|
+
let outputTokens = 0;
|
|
11841
|
+
let cacheWriteTokens = 0;
|
|
11535
11842
|
let cacheReadTokens = 0;
|
|
11536
11843
|
const byDay = /* @__PURE__ */ new Map();
|
|
11537
11844
|
const byModel = /* @__PURE__ */ new Map();
|
|
11538
11845
|
for (const proj of dirs) {
|
|
11539
|
-
const projPath =
|
|
11846
|
+
const projPath = path30.join(projectsDir, proj);
|
|
11540
11847
|
let files;
|
|
11541
11848
|
try {
|
|
11542
|
-
const stat =
|
|
11849
|
+
const stat = fs28.statSync(projPath);
|
|
11543
11850
|
if (!stat.isDirectory()) continue;
|
|
11544
|
-
files =
|
|
11851
|
+
files = fs28.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
11545
11852
|
} catch {
|
|
11546
11853
|
continue;
|
|
11547
11854
|
}
|
|
11548
11855
|
for (const file of files) {
|
|
11549
11856
|
try {
|
|
11550
|
-
const raw =
|
|
11857
|
+
const raw = fs28.readFileSync(path30.join(projPath, file), "utf-8");
|
|
11551
11858
|
for (const line of raw.split("\n")) {
|
|
11552
11859
|
if (!line.trim()) continue;
|
|
11553
11860
|
let entry;
|
|
@@ -11572,6 +11879,8 @@ function loadClaudeCost(start, end) {
|
|
|
11572
11879
|
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
11573
11880
|
total += cost;
|
|
11574
11881
|
inputTokens += inp;
|
|
11882
|
+
outputTokens += out;
|
|
11883
|
+
cacheWriteTokens += cw;
|
|
11575
11884
|
cacheReadTokens += cr;
|
|
11576
11885
|
const dateKey = entry.timestamp.slice(0, 10);
|
|
11577
11886
|
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
@@ -11583,15 +11892,24 @@ function loadClaudeCost(start, end) {
|
|
|
11583
11892
|
}
|
|
11584
11893
|
}
|
|
11585
11894
|
}
|
|
11586
|
-
return { total, byDay, byModel, inputTokens, cacheReadTokens };
|
|
11895
|
+
return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
|
|
11587
11896
|
}
|
|
11588
11897
|
function registerReportCommand(program2) {
|
|
11589
11898
|
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").action((options) => {
|
|
11590
11899
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
11591
11900
|
options.period
|
|
11592
11901
|
) ? options.period : "7d";
|
|
11593
|
-
const logPath =
|
|
11902
|
+
const logPath = path30.join(os24.homedir(), ".node9", "audit.log");
|
|
11594
11903
|
const allEntries = parseAuditLog(logPath);
|
|
11904
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
11905
|
+
if (unackedDlp.length > 0) {
|
|
11906
|
+
console.log("");
|
|
11907
|
+
console.log(
|
|
11908
|
+
chalk9.bgRed.white.bold(
|
|
11909
|
+
` \u26A0\uFE0F DLP ALERT: ${unackedDlp.length} secret${unackedDlp.length !== 1 ? "s" : ""} found in Claude response text `
|
|
11910
|
+
) + " " + chalk9.yellow("\u2192 run: node9 dlp")
|
|
11911
|
+
);
|
|
11912
|
+
}
|
|
11595
11913
|
if (allEntries.length === 0) {
|
|
11596
11914
|
console.log(
|
|
11597
11915
|
chalk9.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
@@ -11604,6 +11922,8 @@ function registerReportCommand(program2) {
|
|
|
11604
11922
|
byDay: costByDay,
|
|
11605
11923
|
byModel: costByModel,
|
|
11606
11924
|
inputTokens: costInputTokens,
|
|
11925
|
+
outputTokens: costOutputTokens,
|
|
11926
|
+
cacheWriteTokens: costCacheWrite,
|
|
11607
11927
|
cacheReadTokens: costCacheRead
|
|
11608
11928
|
} = loadClaudeCost(start, end);
|
|
11609
11929
|
const periodMs = end.getTime() - start.getTime();
|
|
@@ -11621,6 +11941,7 @@ function registerReportCommand(program2) {
|
|
|
11621
11941
|
let filteredTestCount = 0;
|
|
11622
11942
|
const entries = allEntries.filter((e) => {
|
|
11623
11943
|
if (e.source === "post-hook") return false;
|
|
11944
|
+
if (e.source === "response-dlp") return false;
|
|
11624
11945
|
const ts = new Date(e.ts);
|
|
11625
11946
|
if (ts < start || ts > end) return false;
|
|
11626
11947
|
if (excludeTests && isTestEntry(e, testTs)) {
|
|
@@ -11754,7 +12075,7 @@ function registerReportCommand(program2) {
|
|
|
11754
12075
|
if (topBlocks.length === 0) {
|
|
11755
12076
|
console.log(" " + " ".repeat(COL) + " " + chalk9.dim("nothing blocked \u2713"));
|
|
11756
12077
|
}
|
|
11757
|
-
if (agentMap.size
|
|
12078
|
+
if (agentMap.size >= 1) {
|
|
11758
12079
|
console.log("");
|
|
11759
12080
|
console.log(" " + chalk9.bold("Agents"));
|
|
11760
12081
|
console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
@@ -11804,6 +12125,40 @@ function registerReportCommand(program2) {
|
|
|
11804
12125
|
);
|
|
11805
12126
|
}
|
|
11806
12127
|
}
|
|
12128
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
12129
|
+
if (totalTokens > 0) {
|
|
12130
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
12131
|
+
console.log("");
|
|
12132
|
+
console.log(" " + chalk9.bold("Tokens") + " " + chalk9.dim(`${num(totalTokens)} total`));
|
|
12133
|
+
console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
12134
|
+
const tokenRows = [
|
|
12135
|
+
["Input", costInputTokens, chalk9.cyan(num(costInputTokens))],
|
|
12136
|
+
["Output", costOutputTokens, chalk9.white(num(costOutputTokens))],
|
|
12137
|
+
["Cache write", costCacheWrite, chalk9.yellow(num(costCacheWrite))],
|
|
12138
|
+
["Cache read", costCacheRead, chalk9.green(num(costCacheRead))]
|
|
12139
|
+
];
|
|
12140
|
+
const maxTok = Math.max(
|
|
12141
|
+
costInputTokens,
|
|
12142
|
+
costOutputTokens,
|
|
12143
|
+
costCacheWrite,
|
|
12144
|
+
costCacheRead,
|
|
12145
|
+
1
|
|
12146
|
+
);
|
|
12147
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
12148
|
+
const TOK_LABEL = 14;
|
|
12149
|
+
for (const [label, count, colored] of tokenRows) {
|
|
12150
|
+
if (count === 0) continue;
|
|
12151
|
+
const b = colorBar(count, maxTok, TOK_BAR);
|
|
12152
|
+
console.log(" " + chalk9.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
12153
|
+
}
|
|
12154
|
+
if (cacheHitPct > 0) {
|
|
12155
|
+
console.log(
|
|
12156
|
+
" " + chalk9.dim(
|
|
12157
|
+
`Cache hit rate: ${cacheHitPct}% (saves ~${fmtCost(costCacheRead * 27e-7)} vs fresh input)`
|
|
12158
|
+
)
|
|
12159
|
+
);
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
11807
12162
|
if (costUSD > 0) {
|
|
11808
12163
|
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
11809
12164
|
const avgPerDay = costUSD / periodDays;
|
|
@@ -11828,6 +12183,33 @@ function registerReportCommand(program2) {
|
|
|
11828
12183
|
);
|
|
11829
12184
|
}
|
|
11830
12185
|
}
|
|
12186
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
12187
|
+
if (e.source !== "response-dlp") return false;
|
|
12188
|
+
const ts = new Date(e.ts);
|
|
12189
|
+
return ts >= start && ts <= end;
|
|
12190
|
+
});
|
|
12191
|
+
if (responseDlpEntries.length > 0) {
|
|
12192
|
+
console.log("");
|
|
12193
|
+
console.log(
|
|
12194
|
+
" " + chalk9.red.bold("\u26A0\uFE0F Response DLP") + chalk9.dim(" \xB7 ") + chalk9.red(
|
|
12195
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
12196
|
+
)
|
|
12197
|
+
);
|
|
12198
|
+
console.log(" " + chalk9.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
12199
|
+
console.log(
|
|
12200
|
+
" " + chalk9.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
12201
|
+
);
|
|
12202
|
+
console.log(" " + chalk9.yellow("Rotate affected keys immediately."));
|
|
12203
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
12204
|
+
const ts = chalk9.dim(fmtDate(e.ts) + " ");
|
|
12205
|
+
const pattern = chalk9.red(e.dlpPattern ?? "DLP");
|
|
12206
|
+
const sample = chalk9.gray(e.dlpSample ?? "");
|
|
12207
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
12208
|
+
}
|
|
12209
|
+
if (responseDlpEntries.length > 5) {
|
|
12210
|
+
console.log(chalk9.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
11831
12213
|
console.log("");
|
|
11832
12214
|
console.log(
|
|
11833
12215
|
" " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
@@ -11945,12 +12327,12 @@ function registerDaemonCommand(program2) {
|
|
|
11945
12327
|
init_core();
|
|
11946
12328
|
init_daemon();
|
|
11947
12329
|
import chalk11 from "chalk";
|
|
11948
|
-
import
|
|
11949
|
-
import
|
|
11950
|
-
import
|
|
12330
|
+
import fs29 from "fs";
|
|
12331
|
+
import path31 from "path";
|
|
12332
|
+
import os25 from "os";
|
|
11951
12333
|
function readJson2(filePath) {
|
|
11952
12334
|
try {
|
|
11953
|
-
if (
|
|
12335
|
+
if (fs29.existsSync(filePath)) return JSON.parse(fs29.readFileSync(filePath, "utf-8"));
|
|
11954
12336
|
} catch {
|
|
11955
12337
|
}
|
|
11956
12338
|
return null;
|
|
@@ -12015,28 +12397,28 @@ function registerStatusCommand(program2) {
|
|
|
12015
12397
|
console.log("");
|
|
12016
12398
|
const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
|
|
12017
12399
|
console.log(` Mode: ${modeLabel}`);
|
|
12018
|
-
const projectConfig =
|
|
12019
|
-
const globalConfig =
|
|
12400
|
+
const projectConfig = path31.join(process.cwd(), "node9.config.json");
|
|
12401
|
+
const globalConfig = path31.join(os25.homedir(), ".node9", "config.json");
|
|
12020
12402
|
console.log(
|
|
12021
|
-
` Local: ${
|
|
12403
|
+
` Local: ${fs29.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
|
|
12022
12404
|
);
|
|
12023
12405
|
console.log(
|
|
12024
|
-
` Global: ${
|
|
12406
|
+
` Global: ${fs29.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
|
|
12025
12407
|
);
|
|
12026
12408
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
12027
12409
|
console.log(
|
|
12028
12410
|
` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
12029
12411
|
);
|
|
12030
12412
|
}
|
|
12031
|
-
const homeDir2 =
|
|
12413
|
+
const homeDir2 = os25.homedir();
|
|
12032
12414
|
const claudeSettings = readJson2(
|
|
12033
|
-
|
|
12415
|
+
path31.join(homeDir2, ".claude", "settings.json")
|
|
12034
12416
|
);
|
|
12035
|
-
const claudeConfig = readJson2(
|
|
12417
|
+
const claudeConfig = readJson2(path31.join(homeDir2, ".claude.json"));
|
|
12036
12418
|
const geminiSettings = readJson2(
|
|
12037
|
-
|
|
12419
|
+
path31.join(homeDir2, ".gemini", "settings.json")
|
|
12038
12420
|
);
|
|
12039
|
-
const cursorConfig = readJson2(
|
|
12421
|
+
const cursorConfig = readJson2(path31.join(homeDir2, ".cursor", "mcp.json"));
|
|
12040
12422
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
12041
12423
|
if (agentFound) {
|
|
12042
12424
|
console.log("");
|
|
@@ -12096,9 +12478,9 @@ function registerStatusCommand(program2) {
|
|
|
12096
12478
|
// src/cli/commands/init.ts
|
|
12097
12479
|
init_core();
|
|
12098
12480
|
import chalk12 from "chalk";
|
|
12099
|
-
import
|
|
12100
|
-
import
|
|
12101
|
-
import
|
|
12481
|
+
import fs30 from "fs";
|
|
12482
|
+
import path32 from "path";
|
|
12483
|
+
import os26 from "os";
|
|
12102
12484
|
import https3 from "https";
|
|
12103
12485
|
init_shields();
|
|
12104
12486
|
init_service();
|
|
@@ -12158,15 +12540,15 @@ function registerInitCommand(program2) {
|
|
|
12158
12540
|
}
|
|
12159
12541
|
console.log("");
|
|
12160
12542
|
}
|
|
12161
|
-
const configPath =
|
|
12162
|
-
if (
|
|
12543
|
+
const configPath = path32.join(os26.homedir(), ".node9", "config.json");
|
|
12544
|
+
if (fs30.existsSync(configPath) && !options.force) {
|
|
12163
12545
|
try {
|
|
12164
|
-
const existing = JSON.parse(
|
|
12546
|
+
const existing = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
|
|
12165
12547
|
const settings = existing.settings ?? {};
|
|
12166
12548
|
if (settings.mode !== chosenMode) {
|
|
12167
12549
|
settings.mode = chosenMode;
|
|
12168
12550
|
existing.settings = settings;
|
|
12169
|
-
|
|
12551
|
+
fs30.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
12170
12552
|
console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
12171
12553
|
} else {
|
|
12172
12554
|
console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -12179,9 +12561,9 @@ function registerInitCommand(program2) {
|
|
|
12179
12561
|
...DEFAULT_CONFIG,
|
|
12180
12562
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
12181
12563
|
};
|
|
12182
|
-
const dir =
|
|
12183
|
-
if (!
|
|
12184
|
-
|
|
12564
|
+
const dir = path32.dirname(configPath);
|
|
12565
|
+
if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
|
|
12566
|
+
fs30.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
12185
12567
|
console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
|
|
12186
12568
|
console.log(chalk12.gray(` Mode: ${chosenMode}`));
|
|
12187
12569
|
}
|
|
@@ -12265,7 +12647,7 @@ function registerInitCommand(program2) {
|
|
|
12265
12647
|
}
|
|
12266
12648
|
|
|
12267
12649
|
// src/cli/commands/undo.ts
|
|
12268
|
-
import
|
|
12650
|
+
import path33 from "path";
|
|
12269
12651
|
import chalk14 from "chalk";
|
|
12270
12652
|
|
|
12271
12653
|
// src/tui/undo-navigator.ts
|
|
@@ -12424,7 +12806,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
12424
12806
|
let dir = startDir;
|
|
12425
12807
|
while (true) {
|
|
12426
12808
|
if (cwds.has(dir)) return dir;
|
|
12427
|
-
const parent =
|
|
12809
|
+
const parent = path33.dirname(dir);
|
|
12428
12810
|
if (parent === dir) return null;
|
|
12429
12811
|
dir = parent;
|
|
12430
12812
|
}
|
|
@@ -12620,12 +13002,12 @@ import { execa as execa2 } from "execa";
|
|
|
12620
13002
|
init_provenance();
|
|
12621
13003
|
|
|
12622
13004
|
// src/mcp-pin.ts
|
|
12623
|
-
import
|
|
12624
|
-
import
|
|
12625
|
-
import
|
|
13005
|
+
import fs31 from "fs";
|
|
13006
|
+
import path34 from "path";
|
|
13007
|
+
import os27 from "os";
|
|
12626
13008
|
import crypto5 from "crypto";
|
|
12627
13009
|
function getPinsFilePath2() {
|
|
12628
|
-
return
|
|
13010
|
+
return path34.join(os27.homedir(), ".node9", "mcp-pins.json");
|
|
12629
13011
|
}
|
|
12630
13012
|
function hashToolDefinitions(tools) {
|
|
12631
13013
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -12642,7 +13024,7 @@ function getServerKey(upstreamCommand) {
|
|
|
12642
13024
|
function readMcpPinsSafe() {
|
|
12643
13025
|
const filePath = getPinsFilePath2();
|
|
12644
13026
|
try {
|
|
12645
|
-
const raw =
|
|
13027
|
+
const raw = fs31.readFileSync(filePath, "utf-8");
|
|
12646
13028
|
if (!raw.trim()) {
|
|
12647
13029
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
12648
13030
|
}
|
|
@@ -12666,10 +13048,10 @@ function readMcpPins() {
|
|
|
12666
13048
|
}
|
|
12667
13049
|
function writeMcpPins(data) {
|
|
12668
13050
|
const filePath = getPinsFilePath2();
|
|
12669
|
-
|
|
13051
|
+
fs31.mkdirSync(path34.dirname(filePath), { recursive: true });
|
|
12670
13052
|
const tmp = `${filePath}.${crypto5.randomBytes(6).toString("hex")}.tmp`;
|
|
12671
|
-
|
|
12672
|
-
|
|
13053
|
+
fs31.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
13054
|
+
fs31.renameSync(tmp, filePath);
|
|
12673
13055
|
}
|
|
12674
13056
|
function checkPin(serverKey, currentHash) {
|
|
12675
13057
|
const result = readMcpPinsSafe();
|
|
@@ -13041,9 +13423,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
13041
13423
|
|
|
13042
13424
|
// src/mcp-server/index.ts
|
|
13043
13425
|
import readline4 from "readline";
|
|
13044
|
-
import
|
|
13045
|
-
import
|
|
13046
|
-
import
|
|
13426
|
+
import fs32 from "fs";
|
|
13427
|
+
import os28 from "os";
|
|
13428
|
+
import path35 from "path";
|
|
13047
13429
|
init_core();
|
|
13048
13430
|
init_daemon();
|
|
13049
13431
|
init_shields();
|
|
@@ -13218,13 +13600,13 @@ function handleStatus() {
|
|
|
13218
13600
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
13219
13601
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
13220
13602
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
13221
|
-
const projectConfig =
|
|
13222
|
-
const globalConfig =
|
|
13603
|
+
const projectConfig = path35.join(process.cwd(), "node9.config.json");
|
|
13604
|
+
const globalConfig = path35.join(os28.homedir(), ".node9", "config.json");
|
|
13223
13605
|
lines.push(
|
|
13224
|
-
`Project config (node9.config.json): ${
|
|
13606
|
+
`Project config (node9.config.json): ${fs32.existsSync(projectConfig) ? "present" : "not found"}`
|
|
13225
13607
|
);
|
|
13226
13608
|
lines.push(
|
|
13227
|
-
`Global config (~/.node9/config.json): ${
|
|
13609
|
+
`Global config (~/.node9/config.json): ${fs32.existsSync(globalConfig) ? "present" : "not found"}`
|
|
13228
13610
|
);
|
|
13229
13611
|
return lines.join("\n");
|
|
13230
13612
|
}
|
|
@@ -13298,21 +13680,21 @@ function handleShieldDisable(args) {
|
|
|
13298
13680
|
writeActiveShields(active.filter((s) => s !== name));
|
|
13299
13681
|
return `Shield "${name}" disabled.`;
|
|
13300
13682
|
}
|
|
13301
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
13683
|
+
var GLOBAL_CONFIG_PATH2 = path35.join(os28.homedir(), ".node9", "config.json");
|
|
13302
13684
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
13303
13685
|
function readGlobalConfigRaw() {
|
|
13304
13686
|
try {
|
|
13305
|
-
if (
|
|
13306
|
-
return JSON.parse(
|
|
13687
|
+
if (fs32.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
13688
|
+
return JSON.parse(fs32.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
13307
13689
|
}
|
|
13308
13690
|
} catch {
|
|
13309
13691
|
}
|
|
13310
13692
|
return {};
|
|
13311
13693
|
}
|
|
13312
13694
|
function writeGlobalConfigRaw(data) {
|
|
13313
|
-
const dir =
|
|
13314
|
-
if (!
|
|
13315
|
-
|
|
13695
|
+
const dir = path35.dirname(GLOBAL_CONFIG_PATH2);
|
|
13696
|
+
if (!fs32.existsSync(dir)) fs32.mkdirSync(dir, { recursive: true });
|
|
13697
|
+
fs32.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
13316
13698
|
}
|
|
13317
13699
|
function handleApproverList() {
|
|
13318
13700
|
const config = getConfig();
|
|
@@ -13355,9 +13737,9 @@ function handleApproverSet(args) {
|
|
|
13355
13737
|
}
|
|
13356
13738
|
function handleAuditGet(args) {
|
|
13357
13739
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
13358
|
-
const auditPath =
|
|
13359
|
-
if (!
|
|
13360
|
-
const lines =
|
|
13740
|
+
const auditPath = path35.join(os28.homedir(), ".node9", "audit.log");
|
|
13741
|
+
if (!fs32.existsSync(auditPath)) return "No audit log found.";
|
|
13742
|
+
const lines = fs32.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
13361
13743
|
const recent = lines.slice(-limit);
|
|
13362
13744
|
const entries = recent.map((line) => {
|
|
13363
13745
|
try {
|
|
@@ -13819,9 +14201,9 @@ init_config();
|
|
|
13819
14201
|
init_policy();
|
|
13820
14202
|
init_dlp();
|
|
13821
14203
|
import chalk21 from "chalk";
|
|
13822
|
-
import
|
|
13823
|
-
import
|
|
13824
|
-
import
|
|
14204
|
+
import fs33 from "fs";
|
|
14205
|
+
import path36 from "path";
|
|
14206
|
+
import os29 from "os";
|
|
13825
14207
|
var CLAUDE_PRICING2 = {
|
|
13826
14208
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13827
14209
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -13889,7 +14271,7 @@ function buildRuleSources() {
|
|
|
13889
14271
|
return sources;
|
|
13890
14272
|
}
|
|
13891
14273
|
function scanClaudeHistory(startDate) {
|
|
13892
|
-
const projectsDir =
|
|
14274
|
+
const projectsDir = path36.join(os29.homedir(), ".claude", "projects");
|
|
13893
14275
|
const result = {
|
|
13894
14276
|
filesScanned: 0,
|
|
13895
14277
|
sessions: 0,
|
|
@@ -13901,25 +14283,25 @@ function scanClaudeHistory(startDate) {
|
|
|
13901
14283
|
firstDate: null,
|
|
13902
14284
|
lastDate: null
|
|
13903
14285
|
};
|
|
13904
|
-
if (!
|
|
14286
|
+
if (!fs33.existsSync(projectsDir)) return result;
|
|
13905
14287
|
let projDirs;
|
|
13906
14288
|
try {
|
|
13907
|
-
projDirs =
|
|
14289
|
+
projDirs = fs33.readdirSync(projectsDir);
|
|
13908
14290
|
} catch {
|
|
13909
14291
|
return result;
|
|
13910
14292
|
}
|
|
13911
14293
|
const ruleSources = buildRuleSources();
|
|
13912
14294
|
for (const proj of projDirs) {
|
|
13913
|
-
const projPath =
|
|
14295
|
+
const projPath = path36.join(projectsDir, proj);
|
|
13914
14296
|
try {
|
|
13915
|
-
if (!
|
|
14297
|
+
if (!fs33.statSync(projPath).isDirectory()) continue;
|
|
13916
14298
|
} catch {
|
|
13917
14299
|
continue;
|
|
13918
14300
|
}
|
|
13919
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
14301
|
+
const projLabel = decodeURIComponent(proj).replace(os29.homedir(), "~").slice(0, 40);
|
|
13920
14302
|
let files;
|
|
13921
14303
|
try {
|
|
13922
|
-
files =
|
|
14304
|
+
files = fs33.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
13923
14305
|
} catch {
|
|
13924
14306
|
continue;
|
|
13925
14307
|
}
|
|
@@ -13928,7 +14310,7 @@ function scanClaudeHistory(startDate) {
|
|
|
13928
14310
|
result.sessions++;
|
|
13929
14311
|
let raw;
|
|
13930
14312
|
try {
|
|
13931
|
-
raw =
|
|
14313
|
+
raw = fs33.readFileSync(path36.join(projPath, file), "utf-8");
|
|
13932
14314
|
} catch {
|
|
13933
14315
|
continue;
|
|
13934
14316
|
}
|
|
@@ -13969,6 +14351,9 @@ function scanClaudeHistory(startDate) {
|
|
|
13969
14351
|
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
13970
14352
|
result.bashCalls++;
|
|
13971
14353
|
}
|
|
14354
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
14355
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
14356
|
+
continue;
|
|
13972
14357
|
const dlpMatch = scanArgs(input);
|
|
13973
14358
|
if (dlpMatch) {
|
|
13974
14359
|
const isDupe = result.dlpFindings.some(
|
|
@@ -13986,6 +14371,7 @@ function scanClaudeHistory(startDate) {
|
|
|
13986
14371
|
}
|
|
13987
14372
|
for (const source of ruleSources) {
|
|
13988
14373
|
const { rule } = source;
|
|
14374
|
+
if (rule.verdict === "allow") continue;
|
|
13989
14375
|
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
13990
14376
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
13991
14377
|
const inputPreview = preview(input, 120);
|
|
@@ -14021,8 +14407,8 @@ function registerScanCommand(program2) {
|
|
|
14021
14407
|
console.log("");
|
|
14022
14408
|
console.log(chalk21.cyan.bold("\u{1F50D} node9 scan") + chalk21.dim(" \u2014 what would node9 catch?"));
|
|
14023
14409
|
console.log("");
|
|
14024
|
-
const projectsDir =
|
|
14025
|
-
if (!
|
|
14410
|
+
const projectsDir = path36.join(os29.homedir(), ".claude", "projects");
|
|
14411
|
+
if (!fs33.existsSync(projectsDir)) {
|
|
14026
14412
|
console.log(chalk21.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
14027
14413
|
console.log(chalk21.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14028
14414
|
return;
|
|
@@ -14134,8 +14520,8 @@ function registerScanCommand(program2) {
|
|
|
14134
14520
|
);
|
|
14135
14521
|
console.log("");
|
|
14136
14522
|
}
|
|
14137
|
-
const auditLog =
|
|
14138
|
-
if (
|
|
14523
|
+
const auditLog = path36.join(os29.homedir(), ".node9", "audit.log");
|
|
14524
|
+
if (fs33.existsSync(auditLog)) {
|
|
14139
14525
|
console.log(chalk21.green(" \u2705 node9 is active \u2014 future sessions are protected."));
|
|
14140
14526
|
console.log(
|
|
14141
14527
|
chalk21.dim(" Run ") + chalk21.cyan("node9 report") + chalk21.dim(" to see live stats.")
|
|
@@ -14152,9 +14538,9 @@ function registerScanCommand(program2) {
|
|
|
14152
14538
|
|
|
14153
14539
|
// src/cli/commands/sessions.ts
|
|
14154
14540
|
import chalk22 from "chalk";
|
|
14155
|
-
import
|
|
14156
|
-
import
|
|
14157
|
-
import
|
|
14541
|
+
import fs34 from "fs";
|
|
14542
|
+
import path37 from "path";
|
|
14543
|
+
import os30 from "os";
|
|
14158
14544
|
var CLAUDE_PRICING3 = {
|
|
14159
14545
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
14160
14546
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -14179,10 +14565,10 @@ function encodeProjectPath(projectPath) {
|
|
|
14179
14565
|
}
|
|
14180
14566
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
14181
14567
|
const encoded = encodeProjectPath(projectPath);
|
|
14182
|
-
return
|
|
14568
|
+
return path37.join(os30.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
14183
14569
|
}
|
|
14184
14570
|
function projectLabel(projectPath) {
|
|
14185
|
-
return projectPath.replace(
|
|
14571
|
+
return projectPath.replace(os30.homedir(), "~");
|
|
14186
14572
|
}
|
|
14187
14573
|
function parseHistoryLines(lines) {
|
|
14188
14574
|
const entries = [];
|
|
@@ -14251,10 +14637,10 @@ function parseSessionLines(lines) {
|
|
|
14251
14637
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
14252
14638
|
}
|
|
14253
14639
|
function loadAuditEntries(auditPath) {
|
|
14254
|
-
const aPath = auditPath ??
|
|
14640
|
+
const aPath = auditPath ?? path37.join(os30.homedir(), ".node9", "audit.log");
|
|
14255
14641
|
let raw;
|
|
14256
14642
|
try {
|
|
14257
|
-
raw =
|
|
14643
|
+
raw = fs34.readFileSync(aPath, "utf-8");
|
|
14258
14644
|
} catch {
|
|
14259
14645
|
return [];
|
|
14260
14646
|
}
|
|
@@ -14290,10 +14676,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
14290
14676
|
return result;
|
|
14291
14677
|
}
|
|
14292
14678
|
function buildSessions(days, historyPath) {
|
|
14293
|
-
const hPath = historyPath ??
|
|
14679
|
+
const hPath = historyPath ?? path37.join(os30.homedir(), ".claude", "history.jsonl");
|
|
14294
14680
|
let historyRaw;
|
|
14295
14681
|
try {
|
|
14296
|
-
historyRaw =
|
|
14682
|
+
historyRaw = fs34.readFileSync(hPath, "utf-8");
|
|
14297
14683
|
} catch {
|
|
14298
14684
|
return [];
|
|
14299
14685
|
}
|
|
@@ -14318,7 +14704,7 @@ function buildSessions(days, historyPath) {
|
|
|
14318
14704
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
14319
14705
|
let sessionLines = [];
|
|
14320
14706
|
try {
|
|
14321
|
-
sessionLines =
|
|
14707
|
+
sessionLines = fs34.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
14322
14708
|
} catch {
|
|
14323
14709
|
}
|
|
14324
14710
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -14569,8 +14955,8 @@ function registerSessionsCommand(program2) {
|
|
|
14569
14955
|
console.log("");
|
|
14570
14956
|
console.log(chalk22.cyan.bold("\u{1F4CB} node9 sessions") + chalk22.dim(" \u2014 what your AI agent did"));
|
|
14571
14957
|
console.log("");
|
|
14572
|
-
const historyPath =
|
|
14573
|
-
if (!
|
|
14958
|
+
const historyPath = path37.join(os30.homedir(), ".claude", "history.jsonl");
|
|
14959
|
+
if (!fs34.existsSync(historyPath)) {
|
|
14574
14960
|
console.log(chalk22.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
14575
14961
|
console.log(chalk22.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14576
14962
|
return;
|
|
@@ -14602,12 +14988,12 @@ function registerSessionsCommand(program2) {
|
|
|
14602
14988
|
|
|
14603
14989
|
// src/cli/commands/skill-pin.ts
|
|
14604
14990
|
import chalk23 from "chalk";
|
|
14605
|
-
import
|
|
14606
|
-
import
|
|
14607
|
-
import
|
|
14991
|
+
import fs35 from "fs";
|
|
14992
|
+
import os31 from "os";
|
|
14993
|
+
import path38 from "path";
|
|
14608
14994
|
function wipeSkillSessions() {
|
|
14609
14995
|
try {
|
|
14610
|
-
|
|
14996
|
+
fs35.rmSync(path38.join(os31.homedir(), ".node9", "skill-sessions"), {
|
|
14611
14997
|
recursive: true,
|
|
14612
14998
|
force: true
|
|
14613
14999
|
});
|
|
@@ -14688,22 +15074,145 @@ function registerSkillPinCommand(program2) {
|
|
|
14688
15074
|
});
|
|
14689
15075
|
}
|
|
14690
15076
|
|
|
15077
|
+
// src/cli/commands/dlp.ts
|
|
15078
|
+
import chalk24 from "chalk";
|
|
15079
|
+
import fs36 from "fs";
|
|
15080
|
+
import path39 from "path";
|
|
15081
|
+
import os32 from "os";
|
|
15082
|
+
var AUDIT_LOG = path39.join(os32.homedir(), ".node9", "audit.log");
|
|
15083
|
+
var RESOLVED_FILE = path39.join(os32.homedir(), ".node9", "dlp-resolved.json");
|
|
15084
|
+
var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
|
|
15085
|
+
function stripAnsi(s) {
|
|
15086
|
+
return s.replace(ANSI_RE, "");
|
|
15087
|
+
}
|
|
15088
|
+
function loadResolved() {
|
|
15089
|
+
try {
|
|
15090
|
+
const raw = JSON.parse(fs36.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
15091
|
+
return new Set(raw);
|
|
15092
|
+
} catch {
|
|
15093
|
+
return /* @__PURE__ */ new Set();
|
|
15094
|
+
}
|
|
15095
|
+
}
|
|
15096
|
+
function saveResolved(resolved) {
|
|
15097
|
+
try {
|
|
15098
|
+
fs36.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
15099
|
+
} catch {
|
|
15100
|
+
}
|
|
15101
|
+
}
|
|
15102
|
+
function loadDlpFindings() {
|
|
15103
|
+
if (!fs36.existsSync(AUDIT_LOG)) return [];
|
|
15104
|
+
return fs36.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
15105
|
+
if (!line.trim()) return [];
|
|
15106
|
+
try {
|
|
15107
|
+
const e = JSON.parse(line);
|
|
15108
|
+
return e.source === "response-dlp" ? [e] : [];
|
|
15109
|
+
} catch {
|
|
15110
|
+
return [];
|
|
15111
|
+
}
|
|
15112
|
+
});
|
|
15113
|
+
}
|
|
15114
|
+
function entryKey(e) {
|
|
15115
|
+
return `${e.ts}:${e.dlpPattern}:${e.dlpSample}`;
|
|
15116
|
+
}
|
|
15117
|
+
function fmtDate3(ts) {
|
|
15118
|
+
try {
|
|
15119
|
+
return new Date(ts).toLocaleDateString("en-US", {
|
|
15120
|
+
month: "short",
|
|
15121
|
+
day: "numeric",
|
|
15122
|
+
year: "numeric"
|
|
15123
|
+
});
|
|
15124
|
+
} catch {
|
|
15125
|
+
return ts.slice(0, 10);
|
|
15126
|
+
}
|
|
15127
|
+
}
|
|
15128
|
+
function registerDlpCommand(program2) {
|
|
15129
|
+
const cmd = program2.command("dlp").description("Show secrets detected in Claude response text and mark them resolved");
|
|
15130
|
+
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
15131
|
+
const findings = loadDlpFindings();
|
|
15132
|
+
if (findings.length === 0) {
|
|
15133
|
+
console.log(chalk24.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
15134
|
+
return;
|
|
15135
|
+
}
|
|
15136
|
+
const resolved = loadResolved();
|
|
15137
|
+
for (const e of findings) resolved.add(entryKey(e));
|
|
15138
|
+
saveResolved(resolved);
|
|
15139
|
+
console.log(
|
|
15140
|
+
chalk24.green(
|
|
15141
|
+
`
|
|
15142
|
+
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
15143
|
+
`
|
|
15144
|
+
)
|
|
15145
|
+
);
|
|
15146
|
+
});
|
|
15147
|
+
cmd.action(() => {
|
|
15148
|
+
const findings = loadDlpFindings();
|
|
15149
|
+
const resolved = loadResolved();
|
|
15150
|
+
const open = findings.filter((e) => !resolved.has(entryKey(e)));
|
|
15151
|
+
const resolvedCount = findings.length - open.length;
|
|
15152
|
+
console.log("");
|
|
15153
|
+
console.log(
|
|
15154
|
+
chalk24.bold.cyan("\u{1F510} node9 dlp") + chalk24.dim(" \u2014 secrets found in Claude response text")
|
|
15155
|
+
);
|
|
15156
|
+
console.log("");
|
|
15157
|
+
if (open.length === 0) {
|
|
15158
|
+
if (resolvedCount > 0) {
|
|
15159
|
+
console.log(chalk24.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
15160
|
+
} else {
|
|
15161
|
+
console.log(
|
|
15162
|
+
chalk24.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
15163
|
+
);
|
|
15164
|
+
}
|
|
15165
|
+
console.log("");
|
|
15166
|
+
return;
|
|
15167
|
+
}
|
|
15168
|
+
console.log(
|
|
15169
|
+
chalk24.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk24.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
15170
|
+
);
|
|
15171
|
+
console.log("");
|
|
15172
|
+
console.log(
|
|
15173
|
+
chalk24.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
15174
|
+
);
|
|
15175
|
+
console.log(chalk24.dim(" Rotate each affected key immediately.\n"));
|
|
15176
|
+
for (const e of open) {
|
|
15177
|
+
console.log(
|
|
15178
|
+
" " + chalk24.red("\u25CF") + " " + chalk24.white(e.dlpPattern ?? "Secret") + chalk24.dim(" " + fmtDate3(e.ts))
|
|
15179
|
+
);
|
|
15180
|
+
if (e.dlpSample) {
|
|
15181
|
+
console.log(" " + chalk24.dim("Sample: ") + chalk24.yellow(stripAnsi(e.dlpSample)));
|
|
15182
|
+
}
|
|
15183
|
+
if (e.project) {
|
|
15184
|
+
console.log(" " + chalk24.dim("Project: ") + chalk24.dim(stripAnsi(e.project)));
|
|
15185
|
+
}
|
|
15186
|
+
console.log("");
|
|
15187
|
+
}
|
|
15188
|
+
console.log(" " + chalk24.bold("Next steps:"));
|
|
15189
|
+
console.log(" " + chalk24.cyan("1.") + " Rotate any exposed keys shown above");
|
|
15190
|
+
console.log(
|
|
15191
|
+
" " + chalk24.cyan("2.") + " Run " + chalk24.white("node9 dlp resolve") + " to acknowledge"
|
|
15192
|
+
);
|
|
15193
|
+
console.log(
|
|
15194
|
+
" " + chalk24.cyan("3.") + " Run " + chalk24.white("node9 report") + " for full audit history"
|
|
15195
|
+
);
|
|
15196
|
+
console.log("");
|
|
15197
|
+
});
|
|
15198
|
+
}
|
|
15199
|
+
|
|
14691
15200
|
// src/cli.ts
|
|
14692
15201
|
var { version } = JSON.parse(
|
|
14693
|
-
|
|
15202
|
+
fs39.readFileSync(path42.join(__dirname, "../package.json"), "utf-8")
|
|
14694
15203
|
);
|
|
14695
15204
|
var program = new Command();
|
|
14696
15205
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
14697
15206
|
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) => {
|
|
14698
15207
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
14699
|
-
const credPath =
|
|
14700
|
-
if (!
|
|
14701
|
-
|
|
15208
|
+
const credPath = path42.join(os35.homedir(), ".node9", "credentials.json");
|
|
15209
|
+
if (!fs39.existsSync(path42.dirname(credPath)))
|
|
15210
|
+
fs39.mkdirSync(path42.dirname(credPath), { recursive: true });
|
|
14702
15211
|
const profileName = options.profile || "default";
|
|
14703
15212
|
let existingCreds = {};
|
|
14704
15213
|
try {
|
|
14705
|
-
if (
|
|
14706
|
-
const raw = JSON.parse(
|
|
15214
|
+
if (fs39.existsSync(credPath)) {
|
|
15215
|
+
const raw = JSON.parse(fs39.readFileSync(credPath, "utf-8"));
|
|
14707
15216
|
if (raw.apiKey) {
|
|
14708
15217
|
existingCreds = {
|
|
14709
15218
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -14715,13 +15224,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14715
15224
|
} catch {
|
|
14716
15225
|
}
|
|
14717
15226
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
14718
|
-
|
|
15227
|
+
fs39.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
14719
15228
|
if (profileName === "default") {
|
|
14720
|
-
const configPath =
|
|
15229
|
+
const configPath = path42.join(os35.homedir(), ".node9", "config.json");
|
|
14721
15230
|
let config = {};
|
|
14722
15231
|
try {
|
|
14723
|
-
if (
|
|
14724
|
-
config = JSON.parse(
|
|
15232
|
+
if (fs39.existsSync(configPath))
|
|
15233
|
+
config = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
|
|
14725
15234
|
} catch {
|
|
14726
15235
|
}
|
|
14727
15236
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -14736,47 +15245,61 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14736
15245
|
approvers.cloud = false;
|
|
14737
15246
|
}
|
|
14738
15247
|
s.approvers = approvers;
|
|
14739
|
-
if (!
|
|
14740
|
-
|
|
14741
|
-
|
|
15248
|
+
if (!fs39.existsSync(path42.dirname(configPath)))
|
|
15249
|
+
fs39.mkdirSync(path42.dirname(configPath), { recursive: true });
|
|
15250
|
+
fs39.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
14742
15251
|
}
|
|
14743
15252
|
if (options.profile && profileName !== "default") {
|
|
14744
|
-
console.log(
|
|
14745
|
-
console.log(
|
|
15253
|
+
console.log(chalk26.green(`\u2705 Profile "${profileName}" saved`));
|
|
15254
|
+
console.log(chalk26.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
14746
15255
|
} else if (options.local) {
|
|
14747
|
-
console.log(
|
|
14748
|
-
console.log(
|
|
15256
|
+
console.log(chalk26.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
15257
|
+
console.log(chalk26.gray(` All decisions stay on this machine.`));
|
|
14749
15258
|
} else {
|
|
14750
|
-
console.log(
|
|
14751
|
-
console.log(
|
|
15259
|
+
console.log(chalk26.green(`\u2705 Logged in \u2014 agent mode`));
|
|
15260
|
+
console.log(chalk26.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
14752
15261
|
}
|
|
14753
15262
|
});
|
|
14754
|
-
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
15263
|
+
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
15264
|
+
"after",
|
|
15265
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15266
|
+
).argument(
|
|
15267
|
+
"<target>",
|
|
15268
|
+
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
15269
|
+
).action(async (target) => {
|
|
14755
15270
|
if (target === "gemini") return await setupGemini();
|
|
14756
15271
|
if (target === "claude") return await setupClaude();
|
|
14757
15272
|
if (target === "cursor") return await setupCursor();
|
|
15273
|
+
if (target === "codex") return await setupCodex();
|
|
14758
15274
|
if (target === "windsurf") return await setupWindsurf();
|
|
14759
15275
|
if (target === "vscode") return await setupVSCode();
|
|
14760
15276
|
if (target === "hud") return setupHud();
|
|
14761
15277
|
console.error(
|
|
14762
|
-
|
|
14763
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15278
|
+
chalk26.red(
|
|
15279
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14764
15280
|
)
|
|
14765
15281
|
);
|
|
14766
15282
|
process.exit(1);
|
|
14767
15283
|
});
|
|
14768
|
-
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
15284
|
+
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
15285
|
+
"after",
|
|
15286
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15287
|
+
).argument(
|
|
15288
|
+
"[target]",
|
|
15289
|
+
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
15290
|
+
).action(async (target) => {
|
|
14769
15291
|
if (!target) {
|
|
14770
|
-
console.log(
|
|
14771
|
-
console.log(" Usage: " +
|
|
15292
|
+
console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
15293
|
+
console.log(" Usage: " + chalk26.white("node9 setup <target>") + "\n");
|
|
14772
15294
|
console.log(" Targets:");
|
|
14773
|
-
console.log(" " +
|
|
14774
|
-
console.log(" " +
|
|
14775
|
-
console.log(" " +
|
|
14776
|
-
console.log(" " +
|
|
14777
|
-
console.log(" " +
|
|
15295
|
+
console.log(" " + chalk26.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
15296
|
+
console.log(" " + chalk26.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
15297
|
+
console.log(" " + chalk26.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
15298
|
+
console.log(" " + chalk26.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
15299
|
+
console.log(" " + chalk26.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
15300
|
+
console.log(" " + chalk26.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
14778
15301
|
process.stdout.write(
|
|
14779
|
-
" " +
|
|
15302
|
+
" " + chalk26.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
14780
15303
|
);
|
|
14781
15304
|
console.log("");
|
|
14782
15305
|
return;
|
|
@@ -14785,61 +15308,67 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
14785
15308
|
if (t === "gemini") return await setupGemini();
|
|
14786
15309
|
if (t === "claude") return await setupClaude();
|
|
14787
15310
|
if (t === "cursor") return await setupCursor();
|
|
15311
|
+
if (t === "codex") return await setupCodex();
|
|
14788
15312
|
if (t === "windsurf") return await setupWindsurf();
|
|
14789
15313
|
if (t === "vscode") return await setupVSCode();
|
|
14790
15314
|
if (t === "hud") return setupHud();
|
|
14791
15315
|
console.error(
|
|
14792
|
-
|
|
14793
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15316
|
+
chalk26.red(
|
|
15317
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14794
15318
|
)
|
|
14795
15319
|
);
|
|
14796
15320
|
process.exit(1);
|
|
14797
15321
|
});
|
|
14798
|
-
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
15322
|
+
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
15323
|
+
"after",
|
|
15324
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15325
|
+
).argument(
|
|
14799
15326
|
"<target>",
|
|
14800
|
-
"The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
|
|
15327
|
+
"The agent to remove from: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
14801
15328
|
).action((target) => {
|
|
14802
15329
|
let fn;
|
|
14803
15330
|
if (target === "claude") fn = teardownClaude;
|
|
14804
15331
|
else if (target === "gemini") fn = teardownGemini;
|
|
14805
15332
|
else if (target === "cursor") fn = teardownCursor;
|
|
15333
|
+
else if (target === "codex") fn = teardownCodex;
|
|
14806
15334
|
else if (target === "windsurf") fn = teardownWindsurf;
|
|
14807
15335
|
else if (target === "vscode") fn = teardownVSCode;
|
|
14808
15336
|
else if (target === "hud") fn = teardownHud;
|
|
14809
15337
|
else {
|
|
14810
15338
|
console.error(
|
|
14811
|
-
|
|
14812
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15339
|
+
chalk26.red(
|
|
15340
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14813
15341
|
)
|
|
14814
15342
|
);
|
|
14815
15343
|
process.exit(1);
|
|
14816
15344
|
}
|
|
14817
|
-
console.log(
|
|
15345
|
+
console.log(chalk26.cyan(`
|
|
14818
15346
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
14819
15347
|
`));
|
|
14820
15348
|
try {
|
|
14821
15349
|
fn();
|
|
14822
15350
|
} catch (err2) {
|
|
14823
|
-
console.error(
|
|
15351
|
+
console.error(chalk26.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14824
15352
|
process.exit(1);
|
|
14825
15353
|
}
|
|
14826
|
-
console.log(
|
|
15354
|
+
console.log(chalk26.gray("\n Restart the agent for changes to take effect."));
|
|
14827
15355
|
});
|
|
14828
15356
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
14829
|
-
console.log(
|
|
14830
|
-
console.log(
|
|
15357
|
+
console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
15358
|
+
console.log(chalk26.bold("Stopping daemon..."));
|
|
14831
15359
|
try {
|
|
14832
15360
|
stopDaemon();
|
|
14833
|
-
console.log(
|
|
15361
|
+
console.log(chalk26.green(" \u2705 Daemon stopped"));
|
|
14834
15362
|
} catch {
|
|
14835
|
-
console.log(
|
|
15363
|
+
console.log(chalk26.blue(" \u2139\uFE0F Daemon was not running"));
|
|
14836
15364
|
}
|
|
14837
|
-
console.log(
|
|
15365
|
+
console.log(chalk26.bold("\nRemoving hooks..."));
|
|
14838
15366
|
let teardownFailed = false;
|
|
14839
15367
|
for (const [label, fn] of [
|
|
14840
15368
|
["Claude", teardownClaude],
|
|
14841
15369
|
["Gemini", teardownGemini],
|
|
14842
15370
|
["Cursor", teardownCursor],
|
|
15371
|
+
["Codex", teardownCodex],
|
|
14843
15372
|
["Windsurf", teardownWindsurf],
|
|
14844
15373
|
["VSCode", teardownVSCode]
|
|
14845
15374
|
]) {
|
|
@@ -14848,45 +15377,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
14848
15377
|
} catch (err2) {
|
|
14849
15378
|
teardownFailed = true;
|
|
14850
15379
|
console.error(
|
|
14851
|
-
|
|
15380
|
+
chalk26.red(
|
|
14852
15381
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
14853
15382
|
)
|
|
14854
15383
|
);
|
|
14855
15384
|
}
|
|
14856
15385
|
}
|
|
14857
15386
|
if (options.purge) {
|
|
14858
|
-
const node9Dir =
|
|
14859
|
-
if (
|
|
15387
|
+
const node9Dir = path42.join(os35.homedir(), ".node9");
|
|
15388
|
+
if (fs39.existsSync(node9Dir)) {
|
|
14860
15389
|
const confirmed = await confirm2({
|
|
14861
15390
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
14862
15391
|
default: false
|
|
14863
15392
|
});
|
|
14864
15393
|
if (confirmed) {
|
|
14865
|
-
|
|
14866
|
-
if (
|
|
15394
|
+
fs39.rmSync(node9Dir, { recursive: true });
|
|
15395
|
+
if (fs39.existsSync(node9Dir)) {
|
|
14867
15396
|
console.error(
|
|
14868
|
-
|
|
15397
|
+
chalk26.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
14869
15398
|
);
|
|
14870
15399
|
} else {
|
|
14871
|
-
console.log(
|
|
15400
|
+
console.log(chalk26.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
14872
15401
|
}
|
|
14873
15402
|
} else {
|
|
14874
|
-
console.log(
|
|
15403
|
+
console.log(chalk26.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
14875
15404
|
}
|
|
14876
15405
|
} else {
|
|
14877
|
-
console.log(
|
|
15406
|
+
console.log(chalk26.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
14878
15407
|
}
|
|
14879
15408
|
} else {
|
|
14880
15409
|
console.log(
|
|
14881
|
-
|
|
15410
|
+
chalk26.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
14882
15411
|
);
|
|
14883
15412
|
}
|
|
14884
15413
|
if (teardownFailed) {
|
|
14885
|
-
console.error(
|
|
15414
|
+
console.error(chalk26.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
14886
15415
|
process.exit(1);
|
|
14887
15416
|
}
|
|
14888
|
-
console.log(
|
|
14889
|
-
console.log(
|
|
15417
|
+
console.log(chalk26.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
15418
|
+
console.log(chalk26.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
14890
15419
|
});
|
|
14891
15420
|
registerDoctorCommand(program, version);
|
|
14892
15421
|
program.command("explain").description(
|
|
@@ -14899,7 +15428,7 @@ program.command("explain").description(
|
|
|
14899
15428
|
try {
|
|
14900
15429
|
args = JSON.parse(trimmed);
|
|
14901
15430
|
} catch {
|
|
14902
|
-
console.error(
|
|
15431
|
+
console.error(chalk26.red(`
|
|
14903
15432
|
\u274C Invalid JSON: ${trimmed}
|
|
14904
15433
|
`));
|
|
14905
15434
|
process.exit(1);
|
|
@@ -14910,54 +15439,54 @@ program.command("explain").description(
|
|
|
14910
15439
|
}
|
|
14911
15440
|
const result = await explainPolicy(tool, args);
|
|
14912
15441
|
console.log("");
|
|
14913
|
-
console.log(
|
|
15442
|
+
console.log(chalk26.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
14914
15443
|
console.log("");
|
|
14915
|
-
console.log(` ${
|
|
15444
|
+
console.log(` ${chalk26.bold("Tool:")} ${chalk26.white(result.tool)}`);
|
|
14916
15445
|
if (argsRaw) {
|
|
14917
15446
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
14918
|
-
console.log(` ${
|
|
15447
|
+
console.log(` ${chalk26.bold("Input:")} ${chalk26.gray(preview2)}`);
|
|
14919
15448
|
}
|
|
14920
15449
|
console.log("");
|
|
14921
|
-
console.log(
|
|
15450
|
+
console.log(chalk26.bold("Config Sources (Waterfall):"));
|
|
14922
15451
|
for (const tier of result.waterfall) {
|
|
14923
|
-
const num3 =
|
|
15452
|
+
const num3 = chalk26.gray(` ${tier.tier}.`);
|
|
14924
15453
|
const label = tier.label.padEnd(16);
|
|
14925
15454
|
let statusStr;
|
|
14926
15455
|
if (tier.tier === 1) {
|
|
14927
|
-
statusStr =
|
|
15456
|
+
statusStr = chalk26.gray(tier.note ?? "");
|
|
14928
15457
|
} else if (tier.status === "active") {
|
|
14929
|
-
const loc = tier.path ?
|
|
14930
|
-
const note = tier.note ?
|
|
14931
|
-
statusStr =
|
|
15458
|
+
const loc = tier.path ? chalk26.gray(tier.path) : "";
|
|
15459
|
+
const note = tier.note ? chalk26.gray(`(${tier.note})`) : "";
|
|
15460
|
+
statusStr = chalk26.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
14932
15461
|
} else {
|
|
14933
|
-
statusStr =
|
|
15462
|
+
statusStr = chalk26.gray("\u25CB " + (tier.note ?? "not found"));
|
|
14934
15463
|
}
|
|
14935
|
-
console.log(`${num3} ${
|
|
15464
|
+
console.log(`${num3} ${chalk26.white(label)} ${statusStr}`);
|
|
14936
15465
|
}
|
|
14937
15466
|
console.log("");
|
|
14938
|
-
console.log(
|
|
15467
|
+
console.log(chalk26.bold("Policy Evaluation:"));
|
|
14939
15468
|
for (const step of result.steps) {
|
|
14940
15469
|
const isFinal = step.isFinal;
|
|
14941
15470
|
let icon;
|
|
14942
|
-
if (step.outcome === "allow") icon =
|
|
14943
|
-
else if (step.outcome === "review") icon =
|
|
14944
|
-
else if (step.outcome === "skip") icon =
|
|
14945
|
-
else icon =
|
|
15471
|
+
if (step.outcome === "allow") icon = chalk26.green(" \u2705");
|
|
15472
|
+
else if (step.outcome === "review") icon = chalk26.red(" \u{1F534}");
|
|
15473
|
+
else if (step.outcome === "skip") icon = chalk26.gray(" \u2500 ");
|
|
15474
|
+
else icon = chalk26.gray(" \u25CB ");
|
|
14946
15475
|
const name = step.name.padEnd(18);
|
|
14947
|
-
const nameStr = isFinal ?
|
|
14948
|
-
const detail = isFinal ?
|
|
14949
|
-
const arrow = isFinal ?
|
|
15476
|
+
const nameStr = isFinal ? chalk26.white.bold(name) : chalk26.white(name);
|
|
15477
|
+
const detail = isFinal ? chalk26.white(step.detail) : chalk26.gray(step.detail);
|
|
15478
|
+
const arrow = isFinal ? chalk26.yellow(" \u2190 STOP") : "";
|
|
14950
15479
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
14951
15480
|
}
|
|
14952
15481
|
console.log("");
|
|
14953
15482
|
if (result.decision === "allow") {
|
|
14954
|
-
console.log(
|
|
15483
|
+
console.log(chalk26.green.bold(" Decision: \u2705 ALLOW") + chalk26.gray(" \u2014 no approval needed"));
|
|
14955
15484
|
} else {
|
|
14956
15485
|
console.log(
|
|
14957
|
-
|
|
15486
|
+
chalk26.red.bold(" Decision: \u{1F534} REVIEW") + chalk26.gray(" \u2014 human approval required")
|
|
14958
15487
|
);
|
|
14959
15488
|
if (result.blockedByLabel) {
|
|
14960
|
-
console.log(
|
|
15489
|
+
console.log(chalk26.gray(` Reason: ${result.blockedByLabel}`));
|
|
14961
15490
|
}
|
|
14962
15491
|
}
|
|
14963
15492
|
console.log("");
|
|
@@ -14972,7 +15501,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
14972
15501
|
try {
|
|
14973
15502
|
await startTail2(options);
|
|
14974
15503
|
} catch (err2) {
|
|
14975
|
-
console.error(
|
|
15504
|
+
console.error(chalk26.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14976
15505
|
process.exit(1);
|
|
14977
15506
|
}
|
|
14978
15507
|
});
|
|
@@ -15005,14 +15534,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
15005
15534
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
15006
15535
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
15007
15536
|
if (subcommand === "debug") {
|
|
15008
|
-
const flagFile =
|
|
15537
|
+
const flagFile = path42.join(os35.homedir(), ".node9", "hud-debug");
|
|
15009
15538
|
if (state === "on") {
|
|
15010
|
-
|
|
15011
|
-
|
|
15539
|
+
fs39.mkdirSync(path42.dirname(flagFile), { recursive: true });
|
|
15540
|
+
fs39.writeFileSync(flagFile, "");
|
|
15012
15541
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
15013
15542
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
15014
15543
|
} else if (state === "off") {
|
|
15015
|
-
if (
|
|
15544
|
+
if (fs39.existsSync(flagFile)) fs39.unlinkSync(flagFile);
|
|
15016
15545
|
console.log("HUD debug logging disabled.");
|
|
15017
15546
|
} else {
|
|
15018
15547
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -15027,7 +15556,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
15027
15556
|
const ms = parseDuration(options.duration);
|
|
15028
15557
|
if (ms === null) {
|
|
15029
15558
|
console.error(
|
|
15030
|
-
|
|
15559
|
+
chalk26.red(`
|
|
15031
15560
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
15032
15561
|
`)
|
|
15033
15562
|
);
|
|
@@ -15035,20 +15564,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
15035
15564
|
}
|
|
15036
15565
|
pauseNode9(ms, options.duration);
|
|
15037
15566
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
15038
|
-
console.log(
|
|
15567
|
+
console.log(chalk26.yellow(`
|
|
15039
15568
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
15040
|
-
console.log(
|
|
15041
|
-
console.log(
|
|
15569
|
+
console.log(chalk26.gray(` All tool calls will be allowed without review.`));
|
|
15570
|
+
console.log(chalk26.gray(` Run "node9 resume" to re-enable early.
|
|
15042
15571
|
`));
|
|
15043
15572
|
});
|
|
15044
15573
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
15045
15574
|
const { paused } = checkPause();
|
|
15046
15575
|
if (!paused) {
|
|
15047
|
-
console.log(
|
|
15576
|
+
console.log(chalk26.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
15048
15577
|
return;
|
|
15049
15578
|
}
|
|
15050
15579
|
resumeNode9();
|
|
15051
|
-
console.log(
|
|
15580
|
+
console.log(chalk26.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
15052
15581
|
});
|
|
15053
15582
|
var HOOK_BASED_AGENTS = {
|
|
15054
15583
|
claude: "claude",
|
|
@@ -15061,15 +15590,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
15061
15590
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
15062
15591
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
15063
15592
|
console.error(
|
|
15064
|
-
|
|
15593
|
+
chalk26.yellow(`
|
|
15065
15594
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
15066
15595
|
);
|
|
15067
|
-
console.error(
|
|
15596
|
+
console.error(chalk26.white(`
|
|
15068
15597
|
"${target}" uses its own hook system. Use:`));
|
|
15069
15598
|
console.error(
|
|
15070
|
-
|
|
15599
|
+
chalk26.green(` node9 addto ${target} `) + chalk26.gray("# one-time setup")
|
|
15071
15600
|
);
|
|
15072
|
-
console.error(
|
|
15601
|
+
console.error(chalk26.green(` ${target} `) + chalk26.gray("# run normally"));
|
|
15073
15602
|
process.exit(1);
|
|
15074
15603
|
}
|
|
15075
15604
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -15086,7 +15615,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
15086
15615
|
}
|
|
15087
15616
|
);
|
|
15088
15617
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
15089
|
-
console.error(
|
|
15618
|
+
console.error(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
15090
15619
|
const daemonReady = await autoStartDaemonAndWait();
|
|
15091
15620
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
15092
15621
|
}
|
|
@@ -15099,12 +15628,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
15099
15628
|
}
|
|
15100
15629
|
if (!result.approved) {
|
|
15101
15630
|
console.error(
|
|
15102
|
-
|
|
15631
|
+
chalk26.red(`
|
|
15103
15632
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
15104
15633
|
);
|
|
15105
15634
|
process.exit(1);
|
|
15106
15635
|
}
|
|
15107
|
-
console.error(
|
|
15636
|
+
console.error(chalk26.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
15108
15637
|
await runProxy(fullCommand);
|
|
15109
15638
|
} else {
|
|
15110
15639
|
program.help();
|
|
@@ -15118,14 +15647,15 @@ registerSyncCommand(program);
|
|
|
15118
15647
|
registerAgentsCommand(program);
|
|
15119
15648
|
registerScanCommand(program);
|
|
15120
15649
|
registerSessionsCommand(program);
|
|
15650
|
+
registerDlpCommand(program);
|
|
15121
15651
|
if (process.argv[2] !== "daemon") {
|
|
15122
15652
|
process.on("unhandledRejection", (reason) => {
|
|
15123
15653
|
const isCheckHook = process.argv[2] === "check";
|
|
15124
15654
|
if (isCheckHook) {
|
|
15125
15655
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
15126
|
-
const logPath =
|
|
15656
|
+
const logPath = path42.join(os35.homedir(), ".node9", "hook-debug.log");
|
|
15127
15657
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
15128
|
-
|
|
15658
|
+
fs39.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
15129
15659
|
`);
|
|
15130
15660
|
}
|
|
15131
15661
|
process.exit(0);
|