@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.js
CHANGED
|
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
const lines = result.error.issues.map((issue) => {
|
|
171
|
-
const
|
|
172
|
-
return ` \u2022 ${
|
|
171
|
+
const path43 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
172
|
+
return ` \u2022 ${path43}: ${issue.message}`;
|
|
173
173
|
});
|
|
174
174
|
return {
|
|
175
175
|
sanitized,
|
|
@@ -842,9 +842,8 @@ var init_config = __esm({
|
|
|
842
842
|
{
|
|
843
843
|
field: "command",
|
|
844
844
|
op: "matches",
|
|
845
|
-
//
|
|
846
|
-
|
|
847
|
-
value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
845
|
+
// Anchor rm as a shell command (not inside a string arg like a git commit message).
|
|
846
|
+
value: "(^|&&|\\|\\||;)\\s*rm\\b[^;&|]*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
848
847
|
},
|
|
849
848
|
{
|
|
850
849
|
field: "command",
|
|
@@ -873,6 +872,13 @@ var init_config = __esm({
|
|
|
873
872
|
name: "review-drop-truncate-shell",
|
|
874
873
|
tool: "bash",
|
|
875
874
|
conditions: [
|
|
875
|
+
{
|
|
876
|
+
field: "command",
|
|
877
|
+
op: "matches",
|
|
878
|
+
// Require a DB CLI in the command so grep/cat/echo of SQL strings don't trigger.
|
|
879
|
+
value: "(^|&&|\\|\\||;|\\|)\\s*(psql|mysql|sqlite3|sqlplus|cockroach|clickhouse-client|mongo)\\b",
|
|
880
|
+
flags: "i"
|
|
881
|
+
},
|
|
876
882
|
{
|
|
877
883
|
field: "command",
|
|
878
884
|
op: "matches",
|
|
@@ -893,7 +899,9 @@ var init_config = __esm({
|
|
|
893
899
|
{
|
|
894
900
|
field: "command",
|
|
895
901
|
op: "matches",
|
|
896
|
-
|
|
902
|
+
// Anchor git as a shell command so node -e / python -c scripts containing
|
|
903
|
+
// "git push --force" as a string don't false-positive.
|
|
904
|
+
value: "(^|&&|\\|\\||;)\\s*git\\s+push[^;&|]*(--force|--force-with-lease|-f\\b)",
|
|
897
905
|
flags: "i"
|
|
898
906
|
}
|
|
899
907
|
],
|
|
@@ -903,29 +911,20 @@ var init_config = __esm({
|
|
|
903
911
|
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."
|
|
904
912
|
},
|
|
905
913
|
{
|
|
906
|
-
name: "review-git-
|
|
914
|
+
name: "review-git-destructive",
|
|
907
915
|
tool: "bash",
|
|
908
916
|
conditions: [
|
|
909
917
|
{
|
|
910
918
|
field: "command",
|
|
911
919
|
op: "matches",
|
|
912
|
-
value: "\\bgit\\
|
|
920
|
+
value: "\\bgit\\s+(reset\\s+--hard|clean\\s+-[fdxX]|rebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
913
921
|
flags: "i"
|
|
914
|
-
}
|
|
915
|
-
],
|
|
916
|
-
conditionMode: "all",
|
|
917
|
-
verdict: "review",
|
|
918
|
-
reason: "git push sends changes to a shared remote",
|
|
919
|
-
description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
|
|
920
|
-
},
|
|
921
|
-
{
|
|
922
|
-
name: "review-git-destructive",
|
|
923
|
-
tool: "bash",
|
|
924
|
-
conditions: [
|
|
922
|
+
},
|
|
925
923
|
{
|
|
926
924
|
field: "command",
|
|
927
|
-
op: "
|
|
928
|
-
|
|
925
|
+
op: "notMatches",
|
|
926
|
+
// Exclude recovery ops — these resolve a conflict, not start a destructive action.
|
|
927
|
+
value: "\\bgit\\s+rebase\\s+--(abort|continue|skip)\\b",
|
|
929
928
|
flags: "i"
|
|
930
929
|
}
|
|
931
930
|
],
|
|
@@ -951,7 +950,9 @@ var init_config = __esm({
|
|
|
951
950
|
{
|
|
952
951
|
field: "command",
|
|
953
952
|
op: "matches",
|
|
954
|
-
|
|
953
|
+
// Anchor curl/wget as a shell command so node -e scripts testing this
|
|
954
|
+
// regex pattern don't self-match as a false positive.
|
|
955
|
+
value: "(^|&&|\\|\\||;)\\s*(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
|
|
955
956
|
flags: "i"
|
|
956
957
|
}
|
|
957
958
|
],
|
|
@@ -1173,6 +1174,20 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
|
1173
1174
|
}
|
|
1174
1175
|
return null;
|
|
1175
1176
|
}
|
|
1177
|
+
function scanText(text) {
|
|
1178
|
+
const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
|
|
1179
|
+
for (const pattern of DLP_PATTERNS) {
|
|
1180
|
+
if (pattern.regex.test(t)) {
|
|
1181
|
+
return {
|
|
1182
|
+
patternName: pattern.name,
|
|
1183
|
+
fieldPath: "response-text",
|
|
1184
|
+
redactedSample: maskSecret(t, pattern.regex),
|
|
1185
|
+
severity: pattern.severity
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1176
1191
|
var import_fs4, import_path4, DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
1177
1192
|
var init_dlp = __esm({
|
|
1178
1193
|
"src/dlp.ts"() {
|
|
@@ -1204,7 +1219,7 @@ var init_dlp = __esm({
|
|
|
1204
1219
|
regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
|
|
1205
1220
|
severity: "block"
|
|
1206
1221
|
},
|
|
1207
|
-
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]
|
|
1222
|
+
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i, severity: "review" }
|
|
1208
1223
|
];
|
|
1209
1224
|
SENSITIVE_PATH_PATTERNS = [
|
|
1210
1225
|
/[/\\]\.ssh[/\\]/i,
|
|
@@ -1767,9 +1782,21 @@ function matchesPattern(text, patterns) {
|
|
|
1767
1782
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1768
1783
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1769
1784
|
}
|
|
1770
|
-
function getNestedValue(obj,
|
|
1785
|
+
function getNestedValue(obj, path43) {
|
|
1771
1786
|
if (!obj || typeof obj !== "object") return null;
|
|
1772
|
-
return
|
|
1787
|
+
return path43.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1788
|
+
}
|
|
1789
|
+
function stripStringArguments(cmd) {
|
|
1790
|
+
let result = cmd;
|
|
1791
|
+
result = result.replace(
|
|
1792
|
+
/\b(node|python3?|ruby|perl|php|deno)\s+(-[ecr]|eval)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/gi,
|
|
1793
|
+
'$1 $2 ""'
|
|
1794
|
+
);
|
|
1795
|
+
result = result.replace(
|
|
1796
|
+
/\s(-m|--message|--body|--title|--description)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
|
|
1797
|
+
' $1 ""'
|
|
1798
|
+
);
|
|
1799
|
+
return result;
|
|
1773
1800
|
}
|
|
1774
1801
|
function shouldSnapshot(toolName, args, config) {
|
|
1775
1802
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1788,7 +1815,8 @@ function evaluateSmartConditions(args, rule) {
|
|
|
1788
1815
|
const mode = rule.conditionMode ?? "all";
|
|
1789
1816
|
const results = rule.conditions.map((cond) => {
|
|
1790
1817
|
const rawVal = getNestedValue(args, cond.field);
|
|
1791
|
-
const
|
|
1818
|
+
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
1819
|
+
const val = cond.field === "command" && normalized !== null ? stripStringArguments(normalized) : normalized;
|
|
1792
1820
|
switch (cond.op) {
|
|
1793
1821
|
case "exists":
|
|
1794
1822
|
return val !== null && val !== "";
|
|
@@ -2897,13 +2925,30 @@ ${smartTruncate(str, 500)}`
|
|
|
2897
2925
|
}
|
|
2898
2926
|
return { intent: "EXEC", message: smartTruncate(JSON.stringify(parsed), 200) };
|
|
2899
2927
|
}
|
|
2928
|
+
function sendDesktopNotification(title, body) {
|
|
2929
|
+
if (isTestEnv()) return;
|
|
2930
|
+
try {
|
|
2931
|
+
if (process.platform === "darwin") {
|
|
2932
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2933
|
+
const script = `display notification "${esc(body)}" with title "${esc(title)}"`;
|
|
2934
|
+
(0, import_child_process2.spawn)("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
|
|
2935
|
+
} else if (process.platform === "linux") {
|
|
2936
|
+
(0, import_child_process2.spawn)("notify-send", [title, body, "--icon=dialog-warning"], {
|
|
2937
|
+
detached: true,
|
|
2938
|
+
stdio: "ignore"
|
|
2939
|
+
}).unref();
|
|
2940
|
+
}
|
|
2941
|
+
} catch {
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2900
2944
|
function escapePango(text) {
|
|
2901
2945
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2902
2946
|
}
|
|
2903
2947
|
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2904
2948
|
const lines = [];
|
|
2905
2949
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2906
|
-
|
|
2950
|
+
const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
|
|
2951
|
+
lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
|
|
2907
2952
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2908
2953
|
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2909
2954
|
lines.push("");
|
|
@@ -3290,7 +3335,16 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
3290
3335
|
if (!options?.calledFromDaemon) {
|
|
3291
3336
|
const actId = (0, import_crypto4.randomUUID)();
|
|
3292
3337
|
const actTs = Date.now();
|
|
3293
|
-
await notifyActivity({
|
|
3338
|
+
await notifyActivity({
|
|
3339
|
+
id: actId,
|
|
3340
|
+
ts: actTs,
|
|
3341
|
+
tool: toolName,
|
|
3342
|
+
args,
|
|
3343
|
+
status: "pending",
|
|
3344
|
+
// Strip ANSI escape sequences — agent name comes from caller-supplied metadata
|
|
3345
|
+
// and may be displayed in a terminal (node9 tail/watch), enabling injection.
|
|
3346
|
+
agent: meta?.agent ? meta.agent.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80) : void 0
|
|
3347
|
+
});
|
|
3294
3348
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
3295
3349
|
...options,
|
|
3296
3350
|
activityId: actId
|
|
@@ -6007,7 +6061,8 @@ function startActivitySocket() {
|
|
|
6007
6061
|
ts: data.ts,
|
|
6008
6062
|
tool: data.tool,
|
|
6009
6063
|
args: redactArgs(data.args),
|
|
6010
|
-
status: "pending"
|
|
6064
|
+
status: "pending",
|
|
6065
|
+
agent: data.agent
|
|
6011
6066
|
});
|
|
6012
6067
|
} else {
|
|
6013
6068
|
if (data.status === "allow") {
|
|
@@ -6471,10 +6526,161 @@ var init_sync = __esm({
|
|
|
6471
6526
|
}
|
|
6472
6527
|
});
|
|
6473
6528
|
|
|
6529
|
+
// src/daemon/dlp-scanner.ts
|
|
6530
|
+
function loadIndex() {
|
|
6531
|
+
try {
|
|
6532
|
+
return JSON.parse(import_fs18.default.readFileSync(INDEX_FILE, "utf-8"));
|
|
6533
|
+
} catch {
|
|
6534
|
+
return {};
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
function saveIndex(index) {
|
|
6538
|
+
try {
|
|
6539
|
+
import_fs18.default.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
|
|
6540
|
+
} catch {
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
function appendAuditEntry(entry) {
|
|
6544
|
+
try {
|
|
6545
|
+
import_fs18.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
6546
|
+
} catch {
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
function runDlpScan() {
|
|
6550
|
+
if (!import_fs18.default.existsSync(PROJECTS_DIR)) return;
|
|
6551
|
+
const index = loadIndex();
|
|
6552
|
+
let updated = false;
|
|
6553
|
+
let projDirs;
|
|
6554
|
+
try {
|
|
6555
|
+
projDirs = import_fs18.default.readdirSync(PROJECTS_DIR);
|
|
6556
|
+
} catch {
|
|
6557
|
+
return;
|
|
6558
|
+
}
|
|
6559
|
+
for (const proj of projDirs) {
|
|
6560
|
+
const projPath = import_path21.default.join(PROJECTS_DIR, proj);
|
|
6561
|
+
try {
|
|
6562
|
+
if (!import_fs18.default.lstatSync(projPath).isDirectory()) continue;
|
|
6563
|
+
const real = import_fs18.default.realpathSync(projPath);
|
|
6564
|
+
if (!real.startsWith(PROJECTS_DIR + import_path21.default.sep) && real !== PROJECTS_DIR) continue;
|
|
6565
|
+
} catch {
|
|
6566
|
+
continue;
|
|
6567
|
+
}
|
|
6568
|
+
let files;
|
|
6569
|
+
try {
|
|
6570
|
+
files = import_fs18.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
6571
|
+
} catch {
|
|
6572
|
+
continue;
|
|
6573
|
+
}
|
|
6574
|
+
for (const file of files) {
|
|
6575
|
+
const filePath = import_path21.default.join(projPath, file);
|
|
6576
|
+
const lastOffset = index[filePath] ?? 0;
|
|
6577
|
+
let size;
|
|
6578
|
+
try {
|
|
6579
|
+
size = import_fs18.default.statSync(filePath).size;
|
|
6580
|
+
} catch {
|
|
6581
|
+
continue;
|
|
6582
|
+
}
|
|
6583
|
+
if (size <= lastOffset) continue;
|
|
6584
|
+
let fd;
|
|
6585
|
+
try {
|
|
6586
|
+
fd = import_fs18.default.openSync(filePath, "r");
|
|
6587
|
+
} catch {
|
|
6588
|
+
continue;
|
|
6589
|
+
}
|
|
6590
|
+
try {
|
|
6591
|
+
const chunkSize = size - lastOffset;
|
|
6592
|
+
const buf = Buffer.alloc(chunkSize);
|
|
6593
|
+
import_fs18.default.readSync(fd, buf, 0, chunkSize, lastOffset);
|
|
6594
|
+
const chunk = buf.toString("utf-8");
|
|
6595
|
+
for (const line of chunk.split("\n")) {
|
|
6596
|
+
if (!line.trim()) continue;
|
|
6597
|
+
let entry;
|
|
6598
|
+
try {
|
|
6599
|
+
entry = JSON.parse(line);
|
|
6600
|
+
} catch {
|
|
6601
|
+
continue;
|
|
6602
|
+
}
|
|
6603
|
+
if (entry.type !== "assistant") continue;
|
|
6604
|
+
const content = entry.message?.content;
|
|
6605
|
+
if (!Array.isArray(content)) continue;
|
|
6606
|
+
for (const block of content) {
|
|
6607
|
+
if (typeof block !== "object" || block === null || block.type !== "text")
|
|
6608
|
+
continue;
|
|
6609
|
+
const text = block.text;
|
|
6610
|
+
if (typeof text !== "string") continue;
|
|
6611
|
+
const match = scanText(text);
|
|
6612
|
+
if (!match) continue;
|
|
6613
|
+
const projLabel = decodeURIComponent(proj).replace(import_os16.default.homedir(), "~").slice(0, 40);
|
|
6614
|
+
const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6615
|
+
appendAuditEntry({
|
|
6616
|
+
ts,
|
|
6617
|
+
tool: "response-text",
|
|
6618
|
+
decision: "dlp",
|
|
6619
|
+
checkedBy: "response-dlp",
|
|
6620
|
+
source: "response-dlp",
|
|
6621
|
+
dlpPattern: match.patternName,
|
|
6622
|
+
dlpSample: match.redactedSample,
|
|
6623
|
+
project: projLabel
|
|
6624
|
+
});
|
|
6625
|
+
sendDesktopNotification(
|
|
6626
|
+
"\u26A0\uFE0F node9 DLP Alert",
|
|
6627
|
+
`${match.patternName} found in Claude response
|
|
6628
|
+
Sample: ${match.redactedSample}
|
|
6629
|
+
Project: ${projLabel}
|
|
6630
|
+
Run: node9 report --period 30d`
|
|
6631
|
+
);
|
|
6632
|
+
}
|
|
6633
|
+
}
|
|
6634
|
+
index[filePath] = size;
|
|
6635
|
+
updated = true;
|
|
6636
|
+
} finally {
|
|
6637
|
+
try {
|
|
6638
|
+
import_fs18.default.closeSync(fd);
|
|
6639
|
+
} catch {
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6644
|
+
if (updated) saveIndex(index);
|
|
6645
|
+
}
|
|
6646
|
+
function startDlpScanner() {
|
|
6647
|
+
setImmediate(() => {
|
|
6648
|
+
try {
|
|
6649
|
+
runDlpScan();
|
|
6650
|
+
} catch {
|
|
6651
|
+
}
|
|
6652
|
+
});
|
|
6653
|
+
const timer = setInterval(
|
|
6654
|
+
() => {
|
|
6655
|
+
try {
|
|
6656
|
+
runDlpScan();
|
|
6657
|
+
} catch {
|
|
6658
|
+
}
|
|
6659
|
+
},
|
|
6660
|
+
60 * 60 * 1e3
|
|
6661
|
+
);
|
|
6662
|
+
timer.unref();
|
|
6663
|
+
}
|
|
6664
|
+
var import_fs18, import_path21, import_os16, INDEX_FILE, PROJECTS_DIR;
|
|
6665
|
+
var init_dlp_scanner = __esm({
|
|
6666
|
+
"src/daemon/dlp-scanner.ts"() {
|
|
6667
|
+
"use strict";
|
|
6668
|
+
import_fs18 = __toESM(require("fs"));
|
|
6669
|
+
import_path21 = __toESM(require("path"));
|
|
6670
|
+
import_os16 = __toESM(require("os"));
|
|
6671
|
+
init_dlp();
|
|
6672
|
+
init_native();
|
|
6673
|
+
init_state2();
|
|
6674
|
+
INDEX_FILE = import_path21.default.join(import_os16.default.homedir(), ".node9", "dlp-index.json");
|
|
6675
|
+
PROJECTS_DIR = import_path21.default.join(import_os16.default.homedir(), ".claude", "projects");
|
|
6676
|
+
}
|
|
6677
|
+
});
|
|
6678
|
+
|
|
6474
6679
|
// src/daemon/server.ts
|
|
6475
6680
|
function startDaemon() {
|
|
6476
6681
|
startCostSync();
|
|
6477
6682
|
startCloudSync();
|
|
6683
|
+
startDlpScanner();
|
|
6478
6684
|
loadInsightCounts();
|
|
6479
6685
|
const csrfToken = (0, import_crypto7.randomUUID)();
|
|
6480
6686
|
const internalToken = (0, import_crypto7.randomUUID)();
|
|
@@ -6490,7 +6696,7 @@ function startDaemon() {
|
|
|
6490
6696
|
idleTimer = setTimeout(() => {
|
|
6491
6697
|
if (autoStarted) {
|
|
6492
6698
|
try {
|
|
6493
|
-
|
|
6699
|
+
import_fs19.default.unlinkSync(DAEMON_PID_FILE);
|
|
6494
6700
|
} catch {
|
|
6495
6701
|
}
|
|
6496
6702
|
}
|
|
@@ -6653,7 +6859,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6653
6859
|
status: "pending"
|
|
6654
6860
|
});
|
|
6655
6861
|
}
|
|
6656
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6862
|
+
const projectCwd = typeof cwd === "string" && import_path22.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6657
6863
|
const projectConfig = getConfig(projectCwd);
|
|
6658
6864
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6659
6865
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -7044,8 +7250,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
7044
7250
|
const body = await readBody(req);
|
|
7045
7251
|
const data = body ? JSON.parse(body) : {};
|
|
7046
7252
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
7047
|
-
const node9Dir =
|
|
7048
|
-
if (!
|
|
7253
|
+
const node9Dir = import_path22.default.dirname(GLOBAL_CONFIG_PATH);
|
|
7254
|
+
if (!import_path22.default.resolve(configPath).startsWith(node9Dir + import_path22.default.sep)) {
|
|
7049
7255
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7050
7256
|
return res.end(
|
|
7051
7257
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -7156,14 +7362,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
7156
7362
|
server.on("error", (e) => {
|
|
7157
7363
|
if (e.code === "EADDRINUSE") {
|
|
7158
7364
|
try {
|
|
7159
|
-
if (
|
|
7160
|
-
const { pid } = JSON.parse(
|
|
7365
|
+
if (import_fs19.default.existsSync(DAEMON_PID_FILE)) {
|
|
7366
|
+
const { pid } = JSON.parse(import_fs19.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7161
7367
|
process.kill(pid, 0);
|
|
7162
7368
|
return process.exit(0);
|
|
7163
7369
|
}
|
|
7164
7370
|
} catch {
|
|
7165
7371
|
try {
|
|
7166
|
-
|
|
7372
|
+
import_fs19.default.unlinkSync(DAEMON_PID_FILE);
|
|
7167
7373
|
} catch {
|
|
7168
7374
|
}
|
|
7169
7375
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -7222,13 +7428,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
7222
7428
|
}
|
|
7223
7429
|
startActivitySocket();
|
|
7224
7430
|
}
|
|
7225
|
-
var import_http,
|
|
7431
|
+
var import_http, import_fs19, import_path22, import_crypto7, import_child_process4, import_chalk2;
|
|
7226
7432
|
var init_server = __esm({
|
|
7227
7433
|
"src/daemon/server.ts"() {
|
|
7228
7434
|
"use strict";
|
|
7229
7435
|
import_http = __toESM(require("http"));
|
|
7230
|
-
|
|
7231
|
-
|
|
7436
|
+
import_fs19 = __toESM(require("fs"));
|
|
7437
|
+
import_path22 = __toESM(require("path"));
|
|
7232
7438
|
import_crypto7 = require("crypto");
|
|
7233
7439
|
import_child_process4 = require("child_process");
|
|
7234
7440
|
import_chalk2 = __toESM(require("chalk"));
|
|
@@ -7241,6 +7447,7 @@ var init_server = __esm({
|
|
|
7241
7447
|
init_config_schema();
|
|
7242
7448
|
init_costSync();
|
|
7243
7449
|
init_sync();
|
|
7450
|
+
init_dlp_scanner();
|
|
7244
7451
|
}
|
|
7245
7452
|
});
|
|
7246
7453
|
|
|
@@ -7248,8 +7455,8 @@ var init_server = __esm({
|
|
|
7248
7455
|
function resolveNode9Binary() {
|
|
7249
7456
|
try {
|
|
7250
7457
|
const script = process.argv[1];
|
|
7251
|
-
if (typeof script === "string" &&
|
|
7252
|
-
return
|
|
7458
|
+
if (typeof script === "string" && import_path23.default.isAbsolute(script) && import_fs20.default.existsSync(script)) {
|
|
7459
|
+
return import_fs20.default.realpathSync(script);
|
|
7253
7460
|
}
|
|
7254
7461
|
} catch {
|
|
7255
7462
|
}
|
|
@@ -7267,11 +7474,11 @@ function xmlEscape(s) {
|
|
|
7267
7474
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7268
7475
|
}
|
|
7269
7476
|
function launchdPlist(binaryPath) {
|
|
7270
|
-
const logDir =
|
|
7477
|
+
const logDir = import_path23.default.join(import_os17.default.homedir(), ".node9");
|
|
7271
7478
|
const nodePath = xmlEscape(process.execPath);
|
|
7272
7479
|
const scriptPath = xmlEscape(binaryPath);
|
|
7273
|
-
const outLog = xmlEscape(
|
|
7274
|
-
const errLog = xmlEscape(
|
|
7480
|
+
const outLog = xmlEscape(import_path23.default.join(logDir, "daemon.log"));
|
|
7481
|
+
const errLog = xmlEscape(import_path23.default.join(logDir, "daemon-error.log"));
|
|
7275
7482
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
7276
7483
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7277
7484
|
<plist version="1.0">
|
|
@@ -7306,9 +7513,9 @@ function launchdPlist(binaryPath) {
|
|
|
7306
7513
|
`;
|
|
7307
7514
|
}
|
|
7308
7515
|
function installLaunchd(binaryPath) {
|
|
7309
|
-
const dir =
|
|
7310
|
-
if (!
|
|
7311
|
-
|
|
7516
|
+
const dir = import_path23.default.dirname(LAUNCHD_PLIST);
|
|
7517
|
+
if (!import_fs20.default.existsSync(dir)) import_fs20.default.mkdirSync(dir, { recursive: true });
|
|
7518
|
+
import_fs20.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
|
|
7312
7519
|
(0, import_child_process5.spawnSync)("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
7313
7520
|
const r = (0, import_child_process5.spawnSync)("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
7314
7521
|
encoding: "utf8",
|
|
@@ -7319,13 +7526,13 @@ function installLaunchd(binaryPath) {
|
|
|
7319
7526
|
}
|
|
7320
7527
|
}
|
|
7321
7528
|
function uninstallLaunchd() {
|
|
7322
|
-
if (
|
|
7529
|
+
if (import_fs20.default.existsSync(LAUNCHD_PLIST)) {
|
|
7323
7530
|
(0, import_child_process5.spawnSync)("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
7324
|
-
|
|
7531
|
+
import_fs20.default.unlinkSync(LAUNCHD_PLIST);
|
|
7325
7532
|
}
|
|
7326
7533
|
}
|
|
7327
7534
|
function isLaunchdInstalled() {
|
|
7328
|
-
return
|
|
7535
|
+
return import_fs20.default.existsSync(LAUNCHD_PLIST);
|
|
7329
7536
|
}
|
|
7330
7537
|
function systemdUnit(binaryPath) {
|
|
7331
7538
|
return `[Unit]
|
|
@@ -7345,12 +7552,12 @@ WantedBy=default.target
|
|
|
7345
7552
|
`;
|
|
7346
7553
|
}
|
|
7347
7554
|
function installSystemd(binaryPath) {
|
|
7348
|
-
if (!
|
|
7349
|
-
|
|
7555
|
+
if (!import_fs20.default.existsSync(SYSTEMD_UNIT_DIR)) {
|
|
7556
|
+
import_fs20.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
7350
7557
|
}
|
|
7351
|
-
|
|
7558
|
+
import_fs20.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
|
|
7352
7559
|
try {
|
|
7353
|
-
(0, import_child_process5.execFileSync)("loginctl", ["enable-linger",
|
|
7560
|
+
(0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os17.default.userInfo().username], { timeout: 3e3 });
|
|
7354
7561
|
} catch {
|
|
7355
7562
|
}
|
|
7356
7563
|
const reload = (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], {
|
|
@@ -7370,23 +7577,23 @@ function installSystemd(binaryPath) {
|
|
|
7370
7577
|
}
|
|
7371
7578
|
}
|
|
7372
7579
|
function uninstallSystemd() {
|
|
7373
|
-
if (
|
|
7580
|
+
if (import_fs20.default.existsSync(SYSTEMD_UNIT)) {
|
|
7374
7581
|
(0, import_child_process5.spawnSync)("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
7375
7582
|
encoding: "utf8",
|
|
7376
7583
|
timeout: 5e3
|
|
7377
7584
|
});
|
|
7378
7585
|
(0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
7379
|
-
|
|
7586
|
+
import_fs20.default.unlinkSync(SYSTEMD_UNIT);
|
|
7380
7587
|
}
|
|
7381
7588
|
}
|
|
7382
7589
|
function isSystemdInstalled() {
|
|
7383
|
-
return
|
|
7590
|
+
return import_fs20.default.existsSync(SYSTEMD_UNIT);
|
|
7384
7591
|
}
|
|
7385
7592
|
function stopRunningDaemon() {
|
|
7386
|
-
const pidFile =
|
|
7387
|
-
if (!
|
|
7593
|
+
const pidFile = import_path23.default.join(import_os17.default.homedir(), ".node9", "daemon.pid");
|
|
7594
|
+
if (!import_fs20.default.existsSync(pidFile)) return;
|
|
7388
7595
|
try {
|
|
7389
|
-
const data = JSON.parse(
|
|
7596
|
+
const data = JSON.parse(import_fs20.default.readFileSync(pidFile, "utf-8"));
|
|
7390
7597
|
const pid = data.pid;
|
|
7391
7598
|
const MAX_PID2 = 4194304;
|
|
7392
7599
|
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
|
|
@@ -7406,7 +7613,7 @@ function stopRunningDaemon() {
|
|
|
7406
7613
|
}
|
|
7407
7614
|
}
|
|
7408
7615
|
try {
|
|
7409
|
-
|
|
7616
|
+
import_fs20.default.unlinkSync(pidFile);
|
|
7410
7617
|
} catch {
|
|
7411
7618
|
}
|
|
7412
7619
|
} catch {
|
|
@@ -7476,26 +7683,26 @@ function isDaemonServiceInstalled() {
|
|
|
7476
7683
|
if (process.platform === "linux") return isSystemdInstalled();
|
|
7477
7684
|
return false;
|
|
7478
7685
|
}
|
|
7479
|
-
var
|
|
7686
|
+
var import_fs20, import_path23, import_os17, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
|
|
7480
7687
|
var init_service = __esm({
|
|
7481
7688
|
"src/daemon/service.ts"() {
|
|
7482
7689
|
"use strict";
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7690
|
+
import_fs20 = __toESM(require("fs"));
|
|
7691
|
+
import_path23 = __toESM(require("path"));
|
|
7692
|
+
import_os17 = __toESM(require("os"));
|
|
7486
7693
|
import_child_process5 = require("child_process");
|
|
7487
7694
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
7488
|
-
LAUNCHD_PLIST =
|
|
7489
|
-
SYSTEMD_UNIT_DIR =
|
|
7490
|
-
SYSTEMD_UNIT =
|
|
7695
|
+
LAUNCHD_PLIST = import_path23.default.join(import_os17.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
7696
|
+
SYSTEMD_UNIT_DIR = import_path23.default.join(import_os17.default.homedir(), ".config", "systemd", "user");
|
|
7697
|
+
SYSTEMD_UNIT = import_path23.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
|
|
7491
7698
|
}
|
|
7492
7699
|
});
|
|
7493
7700
|
|
|
7494
7701
|
// src/daemon/index.ts
|
|
7495
7702
|
function stopDaemon() {
|
|
7496
|
-
if (!
|
|
7703
|
+
if (!import_fs21.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
7497
7704
|
try {
|
|
7498
|
-
const data = JSON.parse(
|
|
7705
|
+
const data = JSON.parse(import_fs21.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7499
7706
|
const pid = data.pid;
|
|
7500
7707
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
7501
7708
|
console.log(import_chalk3.default.gray("Cleaned up invalid PID file."));
|
|
@@ -7507,7 +7714,7 @@ function stopDaemon() {
|
|
|
7507
7714
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
7508
7715
|
} finally {
|
|
7509
7716
|
try {
|
|
7510
|
-
|
|
7717
|
+
import_fs21.default.unlinkSync(DAEMON_PID_FILE);
|
|
7511
7718
|
} catch {
|
|
7512
7719
|
}
|
|
7513
7720
|
}
|
|
@@ -7516,9 +7723,9 @@ function daemonStatus() {
|
|
|
7516
7723
|
const serviceInstalled = isDaemonServiceInstalled();
|
|
7517
7724
|
const serviceLabel = serviceInstalled ? import_chalk3.default.green("installed (starts on login)") : import_chalk3.default.yellow("not installed \u2014 run: node9 daemon install");
|
|
7518
7725
|
let processStatus;
|
|
7519
|
-
if (
|
|
7726
|
+
if (import_fs21.default.existsSync(DAEMON_PID_FILE)) {
|
|
7520
7727
|
try {
|
|
7521
|
-
const data = JSON.parse(
|
|
7728
|
+
const data = JSON.parse(import_fs21.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7522
7729
|
const pid = data.pid;
|
|
7523
7730
|
const port = data.port;
|
|
7524
7731
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
@@ -7548,11 +7755,11 @@ function daemonStatus() {
|
|
|
7548
7755
|
console.log(` Service : ${serviceLabel}
|
|
7549
7756
|
`);
|
|
7550
7757
|
}
|
|
7551
|
-
var
|
|
7758
|
+
var import_fs21, import_chalk3, import_child_process6, MAX_PID;
|
|
7552
7759
|
var init_daemon2 = __esm({
|
|
7553
7760
|
"src/daemon/index.ts"() {
|
|
7554
7761
|
"use strict";
|
|
7555
|
-
|
|
7762
|
+
import_fs21 = __toESM(require("fs"));
|
|
7556
7763
|
import_chalk3 = __toESM(require("chalk"));
|
|
7557
7764
|
import_child_process6 = require("child_process");
|
|
7558
7765
|
init_server();
|
|
@@ -7576,6 +7783,74 @@ function getIcon(tool) {
|
|
|
7576
7783
|
}
|
|
7577
7784
|
return "\u{1F6E0}\uFE0F";
|
|
7578
7785
|
}
|
|
7786
|
+
function getModelContextLimit(model) {
|
|
7787
|
+
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
7788
|
+
for (const [key, limit] of Object.entries(MODEL_CONTEXT_LIMITS)) {
|
|
7789
|
+
if (base.startsWith(key)) return limit;
|
|
7790
|
+
}
|
|
7791
|
+
return 2e5;
|
|
7792
|
+
}
|
|
7793
|
+
function readSessionUsage() {
|
|
7794
|
+
const projectsDir = import_path40.default.join(import_os33.default.homedir(), ".claude", "projects");
|
|
7795
|
+
if (!import_fs37.default.existsSync(projectsDir)) return null;
|
|
7796
|
+
let latestFile = null;
|
|
7797
|
+
let latestMtime = 0;
|
|
7798
|
+
try {
|
|
7799
|
+
for (const dir of import_fs37.default.readdirSync(projectsDir)) {
|
|
7800
|
+
const dirPath = import_path40.default.join(projectsDir, dir);
|
|
7801
|
+
try {
|
|
7802
|
+
if (!import_fs37.default.statSync(dirPath).isDirectory()) continue;
|
|
7803
|
+
for (const file of import_fs37.default.readdirSync(dirPath)) {
|
|
7804
|
+
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
7805
|
+
const filePath = import_path40.default.join(dirPath, file);
|
|
7806
|
+
try {
|
|
7807
|
+
const mtime = import_fs37.default.statSync(filePath).mtimeMs;
|
|
7808
|
+
if (mtime > latestMtime) {
|
|
7809
|
+
latestMtime = mtime;
|
|
7810
|
+
latestFile = filePath;
|
|
7811
|
+
}
|
|
7812
|
+
} catch {
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
} catch {
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
} catch {
|
|
7819
|
+
}
|
|
7820
|
+
if (!latestFile) return null;
|
|
7821
|
+
try {
|
|
7822
|
+
const lines = import_fs37.default.readFileSync(latestFile, "utf-8").split("\n");
|
|
7823
|
+
let lastModel = "";
|
|
7824
|
+
let lastInput = 0;
|
|
7825
|
+
let lastOutput = 0;
|
|
7826
|
+
for (const line of lines) {
|
|
7827
|
+
if (!line.trim()) continue;
|
|
7828
|
+
try {
|
|
7829
|
+
const entry = JSON.parse(line);
|
|
7830
|
+
if (entry.type !== "assistant" || !entry.message?.usage) continue;
|
|
7831
|
+
const u = entry.message.usage;
|
|
7832
|
+
lastInput = (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
7833
|
+
lastOutput = u.output_tokens ?? 0;
|
|
7834
|
+
if (entry.message.model) lastModel = entry.message.model;
|
|
7835
|
+
} catch {
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
if (!lastModel || lastInput === 0) return null;
|
|
7839
|
+
const limit = getModelContextLimit(lastModel);
|
|
7840
|
+
const fillPct = Math.round(lastInput / limit * 100);
|
|
7841
|
+
return { inputTokens: lastInput, outputTokens: lastOutput, model: lastModel, fillPct };
|
|
7842
|
+
} catch {
|
|
7843
|
+
return null;
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
function formatContextStat(stat) {
|
|
7847
|
+
const pctColor = stat.fillPct >= 80 ? import_chalk25.default.red : stat.fillPct >= 50 ? import_chalk25.default.yellow : import_chalk25.default.cyan;
|
|
7848
|
+
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
7849
|
+
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
7850
|
+
return import_chalk25.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk25.default.dim(
|
|
7851
|
+
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
7852
|
+
);
|
|
7853
|
+
}
|
|
7579
7854
|
function visibleLength(s) {
|
|
7580
7855
|
return s.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
7581
7856
|
}
|
|
@@ -7585,26 +7860,31 @@ function wrappedLineCount(text) {
|
|
|
7585
7860
|
const len = visibleLength(text);
|
|
7586
7861
|
return Math.max(1, Math.ceil(len / cols));
|
|
7587
7862
|
}
|
|
7863
|
+
function agentLabel(agent) {
|
|
7864
|
+
if (!agent || agent === "Terminal") return "";
|
|
7865
|
+
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
7866
|
+
return short ? import_chalk25.default.dim(`[${short}] `) : "";
|
|
7867
|
+
}
|
|
7588
7868
|
function formatBase(activity) {
|
|
7589
7869
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
7590
7870
|
const icon = getIcon(activity.tool);
|
|
7591
7871
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
7592
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7872
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os33.default.homedir(), "~");
|
|
7593
7873
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
7594
|
-
return `${
|
|
7874
|
+
return `${import_chalk25.default.gray(time)} ${icon} ${agentLabel(activity.agent)}${import_chalk25.default.white.bold(toolName)} ${import_chalk25.default.dim(argsPreview)}`;
|
|
7595
7875
|
}
|
|
7596
7876
|
function renderResult(activity, result) {
|
|
7597
7877
|
const base = formatBase(activity);
|
|
7598
7878
|
let status;
|
|
7599
7879
|
if (result.status === "allow") {
|
|
7600
|
-
status =
|
|
7880
|
+
status = import_chalk25.default.green("\u2713 ALLOW");
|
|
7601
7881
|
} else if (result.status === "dlp") {
|
|
7602
|
-
status =
|
|
7882
|
+
status = import_chalk25.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
7603
7883
|
} else {
|
|
7604
|
-
status =
|
|
7884
|
+
status = import_chalk25.default.red("\u2717 BLOCK");
|
|
7605
7885
|
}
|
|
7606
7886
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
7607
|
-
const costSuffix = cost == null ? "" :
|
|
7887
|
+
const costSuffix = cost == null ? "" : import_chalk25.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
7608
7888
|
if (process.stdout.isTTY) {
|
|
7609
7889
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7610
7890
|
import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -7621,19 +7901,19 @@ function renderResult(activity, result) {
|
|
|
7621
7901
|
}
|
|
7622
7902
|
function renderPending(activity) {
|
|
7623
7903
|
if (!process.stdout.isTTY) return;
|
|
7624
|
-
const line = `${formatBase(activity)} ${
|
|
7904
|
+
const line = `${formatBase(activity)} ${import_chalk25.default.yellow("\u25CF \u2026")}`;
|
|
7625
7905
|
pendingShownForId = activity.id;
|
|
7626
7906
|
pendingWrappedLines = wrappedLineCount(line);
|
|
7627
7907
|
process.stdout.write(`${line}\r`);
|
|
7628
7908
|
}
|
|
7629
7909
|
async function ensureDaemon() {
|
|
7630
7910
|
let pidPort = null;
|
|
7631
|
-
if (
|
|
7911
|
+
if (import_fs37.default.existsSync(PID_FILE)) {
|
|
7632
7912
|
try {
|
|
7633
|
-
const { port } = JSON.parse(
|
|
7913
|
+
const { port } = JSON.parse(import_fs37.default.readFileSync(PID_FILE, "utf-8"));
|
|
7634
7914
|
pidPort = port;
|
|
7635
7915
|
} catch {
|
|
7636
|
-
console.error(
|
|
7916
|
+
console.error(import_chalk25.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
7637
7917
|
}
|
|
7638
7918
|
}
|
|
7639
7919
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -7644,7 +7924,7 @@ async function ensureDaemon() {
|
|
|
7644
7924
|
if (res.ok) return checkPort;
|
|
7645
7925
|
} catch {
|
|
7646
7926
|
}
|
|
7647
|
-
console.log(
|
|
7927
|
+
console.log(import_chalk25.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
7648
7928
|
const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
7649
7929
|
detached: true,
|
|
7650
7930
|
stdio: "ignore",
|
|
@@ -7661,7 +7941,7 @@ async function ensureDaemon() {
|
|
|
7661
7941
|
} catch {
|
|
7662
7942
|
}
|
|
7663
7943
|
}
|
|
7664
|
-
console.error(
|
|
7944
|
+
console.error(import_chalk25.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
7665
7945
|
process.exit(1);
|
|
7666
7946
|
}
|
|
7667
7947
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -7727,10 +8007,11 @@ function buildCardLines(req, localCount = 0) {
|
|
|
7727
8007
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
7728
8008
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
7729
8009
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
8010
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk25.default.dim(`(${req.agent})`)}` : "";
|
|
7730
8011
|
const lines = [
|
|
7731
8012
|
``,
|
|
7732
8013
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
7733
|
-
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
8014
|
+
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}${agentSuffix}`,
|
|
7734
8015
|
`${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
|
|
7735
8016
|
];
|
|
7736
8017
|
if (description) {
|
|
@@ -7782,9 +8063,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7782
8063
|
];
|
|
7783
8064
|
}
|
|
7784
8065
|
function readApproversFromDisk() {
|
|
7785
|
-
const configPath =
|
|
8066
|
+
const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
|
|
7786
8067
|
try {
|
|
7787
|
-
const raw = JSON.parse(
|
|
8068
|
+
const raw = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
|
|
7788
8069
|
const settings = raw.settings ?? {};
|
|
7789
8070
|
return settings.approvers ?? {};
|
|
7790
8071
|
} catch {
|
|
@@ -7795,20 +8076,20 @@ function approverStatusLine() {
|
|
|
7795
8076
|
const a = readApproversFromDisk();
|
|
7796
8077
|
const fmt = (label, key) => {
|
|
7797
8078
|
const on = a[key] !== false;
|
|
7798
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
8079
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk25.default.green("\u2713") : import_chalk25.default.dim("\u2717")}`;
|
|
7799
8080
|
};
|
|
7800
8081
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7801
8082
|
}
|
|
7802
8083
|
function toggleApprover(channel) {
|
|
7803
|
-
const configPath =
|
|
8084
|
+
const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
|
|
7804
8085
|
try {
|
|
7805
|
-
const raw = JSON.parse(
|
|
8086
|
+
const raw = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
|
|
7806
8087
|
const settings = raw.settings ?? {};
|
|
7807
8088
|
const approvers = settings.approvers ?? {};
|
|
7808
8089
|
approvers[channel] = approvers[channel] === false;
|
|
7809
8090
|
settings.approvers = approvers;
|
|
7810
8091
|
raw.settings = settings;
|
|
7811
|
-
|
|
8092
|
+
import_fs37.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7812
8093
|
} catch (err2) {
|
|
7813
8094
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7814
8095
|
`);
|
|
@@ -7840,7 +8121,7 @@ async function startTail(options = {}) {
|
|
|
7840
8121
|
req2.end();
|
|
7841
8122
|
});
|
|
7842
8123
|
if (result.ok) {
|
|
7843
|
-
console.log(
|
|
8124
|
+
console.log(import_chalk25.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
7844
8125
|
} else if (result.code === "ECONNREFUSED") {
|
|
7845
8126
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7846
8127
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7884,7 +8165,7 @@ async function startTail(options = {}) {
|
|
|
7884
8165
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7885
8166
|
if (channel) {
|
|
7886
8167
|
toggleApprover(channel);
|
|
7887
|
-
console.log(
|
|
8168
|
+
console.log(import_chalk25.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7888
8169
|
}
|
|
7889
8170
|
};
|
|
7890
8171
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7950,7 +8231,7 @@ async function startTail(options = {}) {
|
|
|
7950
8231
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7951
8232
|
)
|
|
7952
8233
|
);
|
|
7953
|
-
const decisionStamp = action === "always-allow" ?
|
|
8234
|
+
const decisionStamp = action === "always-allow" ? import_chalk25.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk25.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk25.default.yellow("\u21A9 REDIRECT AI") : import_chalk25.default.red("\u2717 DENIED");
|
|
7954
8235
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7955
8236
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7956
8237
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7978,8 +8259,8 @@ async function startTail(options = {}) {
|
|
|
7978
8259
|
}
|
|
7979
8260
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7980
8261
|
try {
|
|
7981
|
-
|
|
7982
|
-
|
|
8262
|
+
import_fs37.default.appendFileSync(
|
|
8263
|
+
import_path40.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log"),
|
|
7983
8264
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7984
8265
|
`
|
|
7985
8266
|
);
|
|
@@ -8001,7 +8282,7 @@ async function startTail(options = {}) {
|
|
|
8001
8282
|
);
|
|
8002
8283
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
8003
8284
|
if (externalDecision) {
|
|
8004
|
-
const source = externalDecision === "allow" ?
|
|
8285
|
+
const source = externalDecision === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : import_chalk25.default.red("\u2717 DENIED");
|
|
8005
8286
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
8006
8287
|
}
|
|
8007
8288
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -8060,16 +8341,31 @@ async function startTail(options = {}) {
|
|
|
8060
8341
|
}
|
|
8061
8342
|
} catch {
|
|
8062
8343
|
}
|
|
8063
|
-
|
|
8064
|
-
|
|
8344
|
+
const auditLog = import_path40.default.join(import_os33.default.homedir(), ".node9", "audit.log");
|
|
8345
|
+
try {
|
|
8346
|
+
const unackedDlp = import_fs37.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
8347
|
+
if (unackedDlp > 0) {
|
|
8348
|
+
console.log("");
|
|
8349
|
+
console.log(
|
|
8350
|
+
import_chalk25.default.bgRed.white.bold(
|
|
8351
|
+
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
8352
|
+
)
|
|
8353
|
+
);
|
|
8354
|
+
}
|
|
8355
|
+
} catch {
|
|
8356
|
+
}
|
|
8357
|
+
console.log(import_chalk25.default.cyan.bold(`
|
|
8358
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk25.default.dim(`\u2192 ${dashboardUrl}`));
|
|
8065
8359
|
if (canApprove) {
|
|
8066
|
-
console.log(
|
|
8067
|
-
console.log(
|
|
8360
|
+
console.log(import_chalk25.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
8361
|
+
console.log(import_chalk25.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
8068
8362
|
}
|
|
8363
|
+
const ctxStat = readSessionUsage();
|
|
8364
|
+
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
8069
8365
|
if (options.history) {
|
|
8070
|
-
console.log(
|
|
8366
|
+
console.log(import_chalk25.default.dim("Showing history + live events.\n"));
|
|
8071
8367
|
} else {
|
|
8072
|
-
console.log(
|
|
8368
|
+
console.log(import_chalk25.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
8073
8369
|
}
|
|
8074
8370
|
process.on("SIGINT", () => {
|
|
8075
8371
|
exitIdleMode();
|
|
@@ -8079,13 +8375,13 @@ async function startTail(options = {}) {
|
|
|
8079
8375
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
8080
8376
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
8081
8377
|
}
|
|
8082
|
-
console.log(
|
|
8378
|
+
console.log(import_chalk25.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
8083
8379
|
process.exit(0);
|
|
8084
8380
|
});
|
|
8085
8381
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
8086
8382
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
8087
8383
|
if (res.statusCode !== 200) {
|
|
8088
|
-
console.error(
|
|
8384
|
+
console.error(import_chalk25.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
8089
8385
|
process.exit(1);
|
|
8090
8386
|
}
|
|
8091
8387
|
if (canApprove) enterIdleMode();
|
|
@@ -8116,7 +8412,7 @@ async function startTail(options = {}) {
|
|
|
8116
8412
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
8117
8413
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
8118
8414
|
}
|
|
8119
|
-
console.log(
|
|
8415
|
+
console.log(import_chalk25.default.red("\n\u274C Daemon disconnected."));
|
|
8120
8416
|
process.exit(1);
|
|
8121
8417
|
});
|
|
8122
8418
|
});
|
|
@@ -8208,9 +8504,9 @@ async function startTail(options = {}) {
|
|
|
8208
8504
|
const hash = data.hash ?? "";
|
|
8209
8505
|
const summary = data.argsSummary ?? data.tool;
|
|
8210
8506
|
const fileCount = data.fileCount ?? 0;
|
|
8211
|
-
const files = fileCount > 0 ?
|
|
8507
|
+
const files = fileCount > 0 ? import_chalk25.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
8212
8508
|
process.stdout.write(
|
|
8213
|
-
`${
|
|
8509
|
+
`${import_chalk25.default.dim(time)} ${import_chalk25.default.cyan("\u{1F4F8} snapshot")} ${import_chalk25.default.dim(hash)} ${summary}${files}
|
|
8214
8510
|
`
|
|
8215
8511
|
);
|
|
8216
8512
|
return;
|
|
@@ -8227,26 +8523,26 @@ async function startTail(options = {}) {
|
|
|
8227
8523
|
}
|
|
8228
8524
|
req.on("error", (err2) => {
|
|
8229
8525
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
8230
|
-
console.error(
|
|
8526
|
+
console.error(import_chalk25.default.red(`
|
|
8231
8527
|
\u274C ${msg}`));
|
|
8232
8528
|
process.exit(1);
|
|
8233
8529
|
});
|
|
8234
8530
|
}
|
|
8235
|
-
var import_http2,
|
|
8531
|
+
var import_http2, import_chalk25, import_fs37, import_os33, import_path40, import_readline5, import_child_process15, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
8236
8532
|
var init_tail = __esm({
|
|
8237
8533
|
"src/tui/tail.ts"() {
|
|
8238
8534
|
"use strict";
|
|
8239
8535
|
import_http2 = __toESM(require("http"));
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8536
|
+
import_chalk25 = __toESM(require("chalk"));
|
|
8537
|
+
import_fs37 = __toESM(require("fs"));
|
|
8538
|
+
import_os33 = __toESM(require("os"));
|
|
8539
|
+
import_path40 = __toESM(require("path"));
|
|
8244
8540
|
import_readline5 = __toESM(require("readline"));
|
|
8245
8541
|
import_child_process15 = require("child_process");
|
|
8246
8542
|
init_daemon2();
|
|
8247
8543
|
init_daemon();
|
|
8248
8544
|
init_core();
|
|
8249
|
-
PID_FILE =
|
|
8545
|
+
PID_FILE = import_path40.default.join(import_os33.default.homedir(), ".node9", "daemon.pid");
|
|
8250
8546
|
ICONS = {
|
|
8251
8547
|
bash: "\u{1F4BB}",
|
|
8252
8548
|
shell: "\u{1F4BB}",
|
|
@@ -8264,6 +8560,13 @@ var init_tail = __esm({
|
|
|
8264
8560
|
delete: "\u{1F5D1}\uFE0F",
|
|
8265
8561
|
web: "\u{1F310}"
|
|
8266
8562
|
};
|
|
8563
|
+
MODEL_CONTEXT_LIMITS = {
|
|
8564
|
+
"claude-opus-4": 2e5,
|
|
8565
|
+
"claude-sonnet-4": 2e5,
|
|
8566
|
+
"claude-haiku-4": 2e5,
|
|
8567
|
+
"claude-3-7": 2e5,
|
|
8568
|
+
"claude-3-5": 2e5
|
|
8569
|
+
};
|
|
8267
8570
|
RESET2 = "\x1B[0m";
|
|
8268
8571
|
BOLD2 = "\x1B[1m";
|
|
8269
8572
|
RED = "\x1B[31m";
|
|
@@ -8361,9 +8664,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
8361
8664
|
return ` (${m}m left)`;
|
|
8362
8665
|
}
|
|
8363
8666
|
function safeReadJson(filePath) {
|
|
8364
|
-
if (!
|
|
8667
|
+
if (!import_fs38.default.existsSync(filePath)) return null;
|
|
8365
8668
|
try {
|
|
8366
|
-
return JSON.parse(
|
|
8669
|
+
return JSON.parse(import_fs38.default.readFileSync(filePath, "utf-8"));
|
|
8367
8670
|
} catch {
|
|
8368
8671
|
return null;
|
|
8369
8672
|
}
|
|
@@ -8384,12 +8687,12 @@ function countHooksInFile(filePath) {
|
|
|
8384
8687
|
return Object.keys(cfg.hooks).length;
|
|
8385
8688
|
}
|
|
8386
8689
|
function countRulesInDir(rulesDir) {
|
|
8387
|
-
if (!
|
|
8690
|
+
if (!import_fs38.default.existsSync(rulesDir)) return 0;
|
|
8388
8691
|
let count = 0;
|
|
8389
8692
|
try {
|
|
8390
|
-
for (const entry of
|
|
8693
|
+
for (const entry of import_fs38.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
8391
8694
|
if (entry.isDirectory()) {
|
|
8392
|
-
count += countRulesInDir(
|
|
8695
|
+
count += countRulesInDir(import_path41.default.join(rulesDir, entry.name));
|
|
8393
8696
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8394
8697
|
count++;
|
|
8395
8698
|
}
|
|
@@ -8400,46 +8703,46 @@ function countRulesInDir(rulesDir) {
|
|
|
8400
8703
|
}
|
|
8401
8704
|
function isSamePath(a, b) {
|
|
8402
8705
|
try {
|
|
8403
|
-
return
|
|
8706
|
+
return import_path41.default.resolve(a) === import_path41.default.resolve(b);
|
|
8404
8707
|
} catch {
|
|
8405
8708
|
return false;
|
|
8406
8709
|
}
|
|
8407
8710
|
}
|
|
8408
8711
|
function countConfigs(cwd) {
|
|
8409
|
-
const homeDir2 =
|
|
8410
|
-
const claudeDir =
|
|
8712
|
+
const homeDir2 = import_os34.default.homedir();
|
|
8713
|
+
const claudeDir = import_path41.default.join(homeDir2, ".claude");
|
|
8411
8714
|
let claudeMdCount = 0;
|
|
8412
8715
|
let rulesCount = 0;
|
|
8413
8716
|
let hooksCount = 0;
|
|
8414
8717
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
8415
8718
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
8416
|
-
if (
|
|
8417
|
-
rulesCount += countRulesInDir(
|
|
8418
|
-
const userSettings =
|
|
8719
|
+
if (import_fs38.default.existsSync(import_path41.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8720
|
+
rulesCount += countRulesInDir(import_path41.default.join(claudeDir, "rules"));
|
|
8721
|
+
const userSettings = import_path41.default.join(claudeDir, "settings.json");
|
|
8419
8722
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
8420
8723
|
hooksCount += countHooksInFile(userSettings);
|
|
8421
|
-
const userClaudeJson =
|
|
8724
|
+
const userClaudeJson = import_path41.default.join(homeDir2, ".claude.json");
|
|
8422
8725
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
8423
8726
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
8424
8727
|
userMcpServers.delete(name);
|
|
8425
8728
|
}
|
|
8426
8729
|
if (cwd) {
|
|
8427
|
-
if (
|
|
8428
|
-
if (
|
|
8429
|
-
const projectClaudeDir =
|
|
8730
|
+
if (import_fs38.default.existsSync(import_path41.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
8731
|
+
if (import_fs38.default.existsSync(import_path41.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8732
|
+
const projectClaudeDir = import_path41.default.join(cwd, ".claude");
|
|
8430
8733
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
8431
8734
|
if (!overlapsUserScope) {
|
|
8432
|
-
if (
|
|
8433
|
-
rulesCount += countRulesInDir(
|
|
8434
|
-
const projSettings =
|
|
8735
|
+
if (import_fs38.default.existsSync(import_path41.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8736
|
+
rulesCount += countRulesInDir(import_path41.default.join(projectClaudeDir, "rules"));
|
|
8737
|
+
const projSettings = import_path41.default.join(projectClaudeDir, "settings.json");
|
|
8435
8738
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
8436
8739
|
hooksCount += countHooksInFile(projSettings);
|
|
8437
8740
|
}
|
|
8438
|
-
if (
|
|
8439
|
-
const localSettings =
|
|
8741
|
+
if (import_fs38.default.existsSync(import_path41.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8742
|
+
const localSettings = import_path41.default.join(projectClaudeDir, "settings.local.json");
|
|
8440
8743
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
8441
8744
|
hooksCount += countHooksInFile(localSettings);
|
|
8442
|
-
const mcpJsonServers = getMcpServerNames(
|
|
8745
|
+
const mcpJsonServers = getMcpServerNames(import_path41.default.join(cwd, ".mcp.json"));
|
|
8443
8746
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
8444
8747
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
8445
8748
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -8472,12 +8775,12 @@ function readActiveShieldsHud() {
|
|
|
8472
8775
|
return shieldsCache.value;
|
|
8473
8776
|
}
|
|
8474
8777
|
try {
|
|
8475
|
-
const shieldsPath =
|
|
8476
|
-
if (!
|
|
8778
|
+
const shieldsPath = import_path41.default.join(import_os34.default.homedir(), ".node9", "shields.json");
|
|
8779
|
+
if (!import_fs38.default.existsSync(shieldsPath)) {
|
|
8477
8780
|
shieldsCache = { value: [], ts: now };
|
|
8478
8781
|
return [];
|
|
8479
8782
|
}
|
|
8480
|
-
const parsed = JSON.parse(
|
|
8783
|
+
const parsed = JSON.parse(import_fs38.default.readFileSync(shieldsPath, "utf-8"));
|
|
8481
8784
|
if (!Array.isArray(parsed.active)) {
|
|
8482
8785
|
shieldsCache = { value: [], ts: now };
|
|
8483
8786
|
return [];
|
|
@@ -8579,17 +8882,17 @@ function renderContextLine(stdin) {
|
|
|
8579
8882
|
async function main() {
|
|
8580
8883
|
try {
|
|
8581
8884
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
8582
|
-
if (
|
|
8885
|
+
if (import_fs38.default.existsSync(import_path41.default.join(import_os34.default.homedir(), ".node9", "hud-debug"))) {
|
|
8583
8886
|
try {
|
|
8584
|
-
const logPath =
|
|
8887
|
+
const logPath = import_path41.default.join(import_os34.default.homedir(), ".node9", "hud-debug.log");
|
|
8585
8888
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8586
8889
|
let size = 0;
|
|
8587
8890
|
try {
|
|
8588
|
-
size =
|
|
8891
|
+
size = import_fs38.default.statSync(logPath).size;
|
|
8589
8892
|
} catch {
|
|
8590
8893
|
}
|
|
8591
8894
|
if (size < MAX_LOG_SIZE) {
|
|
8592
|
-
|
|
8895
|
+
import_fs38.default.appendFileSync(
|
|
8593
8896
|
logPath,
|
|
8594
8897
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
8595
8898
|
);
|
|
@@ -8610,11 +8913,11 @@ async function main() {
|
|
|
8610
8913
|
try {
|
|
8611
8914
|
const cwd = stdin.cwd ?? process.cwd();
|
|
8612
8915
|
for (const configPath of [
|
|
8613
|
-
|
|
8614
|
-
|
|
8916
|
+
import_path41.default.join(cwd, "node9.config.json"),
|
|
8917
|
+
import_path41.default.join(import_os34.default.homedir(), ".node9", "config.json")
|
|
8615
8918
|
]) {
|
|
8616
|
-
if (!
|
|
8617
|
-
const cfg = JSON.parse(
|
|
8919
|
+
if (!import_fs38.default.existsSync(configPath)) continue;
|
|
8920
|
+
const cfg = JSON.parse(import_fs38.default.readFileSync(configPath, "utf-8"));
|
|
8618
8921
|
const hud = cfg.settings?.hud;
|
|
8619
8922
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
8620
8923
|
}
|
|
@@ -8632,13 +8935,13 @@ async function main() {
|
|
|
8632
8935
|
renderOffline();
|
|
8633
8936
|
}
|
|
8634
8937
|
}
|
|
8635
|
-
var
|
|
8938
|
+
var import_fs38, import_path41, import_os34, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
8636
8939
|
var init_hud = __esm({
|
|
8637
8940
|
"src/cli/hud.ts"() {
|
|
8638
8941
|
"use strict";
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8942
|
+
import_fs38 = __toESM(require("fs"));
|
|
8943
|
+
import_path41 = __toESM(require("path"));
|
|
8944
|
+
import_os34 = __toESM(require("os"));
|
|
8642
8945
|
import_http3 = __toESM(require("http"));
|
|
8643
8946
|
init_daemon();
|
|
8644
8947
|
RESET3 = "\x1B[0m";
|
|
@@ -9553,10 +9856,10 @@ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
|
|
|
9553
9856
|
|
|
9554
9857
|
// src/cli.ts
|
|
9555
9858
|
init_daemon2();
|
|
9556
|
-
var
|
|
9557
|
-
var
|
|
9558
|
-
var
|
|
9559
|
-
var
|
|
9859
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
9860
|
+
var import_fs39 = __toESM(require("fs"));
|
|
9861
|
+
var import_path42 = __toESM(require("path"));
|
|
9862
|
+
var import_os35 = __toESM(require("os"));
|
|
9560
9863
|
var import_prompts2 = require("@inquirer/prompts");
|
|
9561
9864
|
|
|
9562
9865
|
// src/utils/duration.ts
|
|
@@ -9781,10 +10084,10 @@ async function autoStartDaemonAndWait() {
|
|
|
9781
10084
|
|
|
9782
10085
|
// src/cli/commands/check.ts
|
|
9783
10086
|
var import_chalk5 = __toESM(require("chalk"));
|
|
9784
|
-
var
|
|
10087
|
+
var import_fs24 = __toESM(require("fs"));
|
|
9785
10088
|
var import_child_process10 = require("child_process");
|
|
9786
|
-
var
|
|
9787
|
-
var
|
|
10089
|
+
var import_path26 = __toESM(require("path"));
|
|
10090
|
+
var import_os20 = __toESM(require("os"));
|
|
9788
10091
|
init_orchestrator();
|
|
9789
10092
|
init_daemon();
|
|
9790
10093
|
init_config();
|
|
@@ -9793,11 +10096,11 @@ init_policy();
|
|
|
9793
10096
|
// src/undo.ts
|
|
9794
10097
|
var import_child_process9 = require("child_process");
|
|
9795
10098
|
var import_crypto8 = __toESM(require("crypto"));
|
|
9796
|
-
var
|
|
10099
|
+
var import_fs22 = __toESM(require("fs"));
|
|
9797
10100
|
var import_net3 = __toESM(require("net"));
|
|
9798
|
-
var
|
|
9799
|
-
var
|
|
9800
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
10101
|
+
var import_path24 = __toESM(require("path"));
|
|
10102
|
+
var import_os18 = __toESM(require("os"));
|
|
10103
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path24.default.join(import_os18.default.tmpdir(), "node9-activity.sock");
|
|
9801
10104
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
9802
10105
|
try {
|
|
9803
10106
|
const payload = JSON.stringify({
|
|
@@ -9817,22 +10120,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
9817
10120
|
} catch {
|
|
9818
10121
|
}
|
|
9819
10122
|
}
|
|
9820
|
-
var SNAPSHOT_STACK_PATH =
|
|
9821
|
-
var UNDO_LATEST_PATH =
|
|
10123
|
+
var SNAPSHOT_STACK_PATH = import_path24.default.join(import_os18.default.homedir(), ".node9", "snapshots.json");
|
|
10124
|
+
var UNDO_LATEST_PATH = import_path24.default.join(import_os18.default.homedir(), ".node9", "undo_latest.txt");
|
|
9822
10125
|
var MAX_SNAPSHOTS = 10;
|
|
9823
10126
|
var GIT_TIMEOUT = 15e3;
|
|
9824
10127
|
function readStack() {
|
|
9825
10128
|
try {
|
|
9826
|
-
if (
|
|
9827
|
-
return JSON.parse(
|
|
10129
|
+
if (import_fs22.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
10130
|
+
return JSON.parse(import_fs22.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
9828
10131
|
} catch {
|
|
9829
10132
|
}
|
|
9830
10133
|
return [];
|
|
9831
10134
|
}
|
|
9832
10135
|
function writeStack(stack) {
|
|
9833
|
-
const dir =
|
|
9834
|
-
if (!
|
|
9835
|
-
|
|
10136
|
+
const dir = import_path24.default.dirname(SNAPSHOT_STACK_PATH);
|
|
10137
|
+
if (!import_fs22.default.existsSync(dir)) import_fs22.default.mkdirSync(dir, { recursive: true });
|
|
10138
|
+
import_fs22.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
9836
10139
|
}
|
|
9837
10140
|
function extractFilePath(args) {
|
|
9838
10141
|
if (!args || typeof args !== "object") return null;
|
|
@@ -9852,12 +10155,12 @@ function buildArgsSummary(tool, args) {
|
|
|
9852
10155
|
return "";
|
|
9853
10156
|
}
|
|
9854
10157
|
function findProjectRoot(filePath) {
|
|
9855
|
-
let dir =
|
|
10158
|
+
let dir = import_path24.default.dirname(filePath);
|
|
9856
10159
|
while (true) {
|
|
9857
|
-
if (
|
|
10160
|
+
if (import_fs22.default.existsSync(import_path24.default.join(dir, ".git")) || import_fs22.default.existsSync(import_path24.default.join(dir, "package.json"))) {
|
|
9858
10161
|
return dir;
|
|
9859
10162
|
}
|
|
9860
|
-
const parent =
|
|
10163
|
+
const parent = import_path24.default.dirname(dir);
|
|
9861
10164
|
if (parent === dir) return process.cwd();
|
|
9862
10165
|
dir = parent;
|
|
9863
10166
|
}
|
|
@@ -9865,7 +10168,7 @@ function findProjectRoot(filePath) {
|
|
|
9865
10168
|
function normalizeCwdForHash(cwd) {
|
|
9866
10169
|
let normalized;
|
|
9867
10170
|
try {
|
|
9868
|
-
normalized =
|
|
10171
|
+
normalized = import_fs22.default.realpathSync(cwd);
|
|
9869
10172
|
} catch {
|
|
9870
10173
|
normalized = cwd;
|
|
9871
10174
|
}
|
|
@@ -9875,16 +10178,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
9875
10178
|
}
|
|
9876
10179
|
function getShadowRepoDir(cwd) {
|
|
9877
10180
|
const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
9878
|
-
return
|
|
10181
|
+
return import_path24.default.join(import_os18.default.homedir(), ".node9", "snapshots", hash);
|
|
9879
10182
|
}
|
|
9880
10183
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
9881
10184
|
try {
|
|
9882
10185
|
const cutoff = Date.now() - 6e4;
|
|
9883
|
-
for (const f of
|
|
10186
|
+
for (const f of import_fs22.default.readdirSync(shadowDir)) {
|
|
9884
10187
|
if (f.startsWith("index_")) {
|
|
9885
|
-
const fp =
|
|
10188
|
+
const fp = import_path24.default.join(shadowDir, f);
|
|
9886
10189
|
try {
|
|
9887
|
-
if (
|
|
10190
|
+
if (import_fs22.default.statSync(fp).mtimeMs < cutoff) import_fs22.default.unlinkSync(fp);
|
|
9888
10191
|
} catch {
|
|
9889
10192
|
}
|
|
9890
10193
|
}
|
|
@@ -9896,7 +10199,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
9896
10199
|
const hardcoded = [".git", ".node9"];
|
|
9897
10200
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
9898
10201
|
try {
|
|
9899
|
-
|
|
10202
|
+
import_fs22.default.writeFileSync(import_path24.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
9900
10203
|
} catch {
|
|
9901
10204
|
}
|
|
9902
10205
|
}
|
|
@@ -9909,25 +10212,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9909
10212
|
timeout: 3e3
|
|
9910
10213
|
});
|
|
9911
10214
|
if (check.status === 0) {
|
|
9912
|
-
const ptPath =
|
|
10215
|
+
const ptPath = import_path24.default.join(shadowDir, "project-path.txt");
|
|
9913
10216
|
try {
|
|
9914
|
-
const stored =
|
|
10217
|
+
const stored = import_fs22.default.readFileSync(ptPath, "utf8").trim();
|
|
9915
10218
|
if (stored === normalizedCwd) return true;
|
|
9916
10219
|
if (process.env.NODE9_DEBUG === "1")
|
|
9917
10220
|
console.error(
|
|
9918
10221
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
9919
10222
|
);
|
|
9920
|
-
|
|
10223
|
+
import_fs22.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
9921
10224
|
} catch {
|
|
9922
10225
|
try {
|
|
9923
|
-
|
|
10226
|
+
import_fs22.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
9924
10227
|
} catch {
|
|
9925
10228
|
}
|
|
9926
10229
|
return true;
|
|
9927
10230
|
}
|
|
9928
10231
|
}
|
|
9929
10232
|
try {
|
|
9930
|
-
|
|
10233
|
+
import_fs22.default.mkdirSync(shadowDir, { recursive: true });
|
|
9931
10234
|
} catch {
|
|
9932
10235
|
}
|
|
9933
10236
|
const init = (0, import_child_process9.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -9936,7 +10239,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9936
10239
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
9937
10240
|
return false;
|
|
9938
10241
|
}
|
|
9939
|
-
const configFile =
|
|
10242
|
+
const configFile = import_path24.default.join(shadowDir, "config");
|
|
9940
10243
|
(0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
9941
10244
|
timeout: 3e3
|
|
9942
10245
|
});
|
|
@@ -9944,7 +10247,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9944
10247
|
timeout: 3e3
|
|
9945
10248
|
});
|
|
9946
10249
|
try {
|
|
9947
|
-
|
|
10250
|
+
import_fs22.default.writeFileSync(import_path24.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
9948
10251
|
} catch {
|
|
9949
10252
|
}
|
|
9950
10253
|
return true;
|
|
@@ -9964,12 +10267,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9964
10267
|
let indexFile = null;
|
|
9965
10268
|
try {
|
|
9966
10269
|
const rawFilePath = extractFilePath(args);
|
|
9967
|
-
const absFilePath = rawFilePath &&
|
|
10270
|
+
const absFilePath = rawFilePath && import_path24.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
9968
10271
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
9969
10272
|
const shadowDir = getShadowRepoDir(cwd);
|
|
9970
10273
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
9971
10274
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
9972
|
-
indexFile =
|
|
10275
|
+
indexFile = import_path24.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
9973
10276
|
const shadowEnv = {
|
|
9974
10277
|
...process.env,
|
|
9975
10278
|
GIT_DIR: shadowDir,
|
|
@@ -10041,7 +10344,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
10041
10344
|
writeStack(stack);
|
|
10042
10345
|
const entry = stack[stack.length - 1];
|
|
10043
10346
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
10044
|
-
|
|
10347
|
+
import_fs22.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
10045
10348
|
if (shouldGc) {
|
|
10046
10349
|
(0, import_child_process9.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
10047
10350
|
}
|
|
@@ -10052,7 +10355,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
10052
10355
|
} finally {
|
|
10053
10356
|
if (indexFile) {
|
|
10054
10357
|
try {
|
|
10055
|
-
|
|
10358
|
+
import_fs22.default.unlinkSync(indexFile);
|
|
10056
10359
|
} catch {
|
|
10057
10360
|
}
|
|
10058
10361
|
}
|
|
@@ -10128,9 +10431,9 @@ function applyUndo(hash, cwd) {
|
|
|
10128
10431
|
timeout: GIT_TIMEOUT
|
|
10129
10432
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
10130
10433
|
for (const file of [...tracked, ...untracked]) {
|
|
10131
|
-
const fullPath =
|
|
10132
|
-
if (!snapshotFiles.has(file) &&
|
|
10133
|
-
|
|
10434
|
+
const fullPath = import_path24.default.join(dir, file);
|
|
10435
|
+
if (!snapshotFiles.has(file) && import_fs22.default.existsSync(fullPath)) {
|
|
10436
|
+
import_fs22.default.unlinkSync(fullPath);
|
|
10134
10437
|
}
|
|
10135
10438
|
}
|
|
10136
10439
|
return true;
|
|
@@ -10140,12 +10443,12 @@ function applyUndo(hash, cwd) {
|
|
|
10140
10443
|
}
|
|
10141
10444
|
|
|
10142
10445
|
// src/skill-pin.ts
|
|
10143
|
-
var
|
|
10144
|
-
var
|
|
10145
|
-
var
|
|
10446
|
+
var import_fs23 = __toESM(require("fs"));
|
|
10447
|
+
var import_path25 = __toESM(require("path"));
|
|
10448
|
+
var import_os19 = __toESM(require("os"));
|
|
10146
10449
|
var import_crypto9 = __toESM(require("crypto"));
|
|
10147
10450
|
function getPinsFilePath() {
|
|
10148
|
-
return
|
|
10451
|
+
return import_path25.default.join(import_os19.default.homedir(), ".node9", "skill-pins.json");
|
|
10149
10452
|
}
|
|
10150
10453
|
var MAX_FILES = 5e3;
|
|
10151
10454
|
var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
@@ -10159,18 +10462,18 @@ function walkDir(root) {
|
|
|
10159
10462
|
if (out.length >= MAX_FILES) return;
|
|
10160
10463
|
let entries;
|
|
10161
10464
|
try {
|
|
10162
|
-
entries =
|
|
10465
|
+
entries = import_fs23.default.readdirSync(dir, { withFileTypes: true });
|
|
10163
10466
|
} catch {
|
|
10164
10467
|
return;
|
|
10165
10468
|
}
|
|
10166
10469
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
10167
10470
|
for (const entry of entries) {
|
|
10168
10471
|
if (out.length >= MAX_FILES) return;
|
|
10169
|
-
const full =
|
|
10170
|
-
const rel = relDir ?
|
|
10472
|
+
const full = import_path25.default.join(dir, entry.name);
|
|
10473
|
+
const rel = relDir ? import_path25.default.posix.join(relDir, entry.name) : entry.name;
|
|
10171
10474
|
let lst;
|
|
10172
10475
|
try {
|
|
10173
|
-
lst =
|
|
10476
|
+
lst = import_fs23.default.lstatSync(full);
|
|
10174
10477
|
} catch {
|
|
10175
10478
|
continue;
|
|
10176
10479
|
}
|
|
@@ -10182,7 +10485,7 @@ function walkDir(root) {
|
|
|
10182
10485
|
if (!lst.isFile()) continue;
|
|
10183
10486
|
if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
|
|
10184
10487
|
try {
|
|
10185
|
-
const buf =
|
|
10488
|
+
const buf = import_fs23.default.readFileSync(full);
|
|
10186
10489
|
totalBytes += buf.length;
|
|
10187
10490
|
out.push({ rel, hash: sha256Bytes(buf) });
|
|
10188
10491
|
} catch {
|
|
@@ -10196,14 +10499,14 @@ function walkDir(root) {
|
|
|
10196
10499
|
function hashSkillRoot(absPath) {
|
|
10197
10500
|
let lst;
|
|
10198
10501
|
try {
|
|
10199
|
-
lst =
|
|
10502
|
+
lst = import_fs23.default.lstatSync(absPath);
|
|
10200
10503
|
} catch {
|
|
10201
10504
|
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10202
10505
|
}
|
|
10203
10506
|
if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
|
|
10204
10507
|
if (lst.isFile()) {
|
|
10205
10508
|
try {
|
|
10206
|
-
return { exists: true, contentHash: sha256Bytes(
|
|
10509
|
+
return { exists: true, contentHash: sha256Bytes(import_fs23.default.readFileSync(absPath)), fileCount: 1 };
|
|
10207
10510
|
} catch {
|
|
10208
10511
|
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10209
10512
|
}
|
|
@@ -10221,7 +10524,7 @@ function getRootKey(absPath) {
|
|
|
10221
10524
|
function readSkillPinsSafe() {
|
|
10222
10525
|
const filePath = getPinsFilePath();
|
|
10223
10526
|
try {
|
|
10224
|
-
const raw =
|
|
10527
|
+
const raw = import_fs23.default.readFileSync(filePath, "utf-8");
|
|
10225
10528
|
if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10226
10529
|
const parsed = JSON.parse(raw);
|
|
10227
10530
|
if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
|
|
@@ -10241,10 +10544,10 @@ function readSkillPins() {
|
|
|
10241
10544
|
}
|
|
10242
10545
|
function writeSkillPins(data) {
|
|
10243
10546
|
const filePath = getPinsFilePath();
|
|
10244
|
-
|
|
10547
|
+
import_fs23.default.mkdirSync(import_path25.default.dirname(filePath), { recursive: true });
|
|
10245
10548
|
const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
|
|
10246
|
-
|
|
10247
|
-
|
|
10549
|
+
import_fs23.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
10550
|
+
import_fs23.default.renameSync(tmp, filePath);
|
|
10248
10551
|
}
|
|
10249
10552
|
function removePin(rootKey) {
|
|
10250
10553
|
const pins = readSkillPins();
|
|
@@ -10288,36 +10591,36 @@ function verifyAndPinRoots(roots) {
|
|
|
10288
10591
|
return { kind: "verified" };
|
|
10289
10592
|
}
|
|
10290
10593
|
function defaultSkillRoots(_cwd) {
|
|
10291
|
-
const marketplaces =
|
|
10594
|
+
const marketplaces = import_path25.default.join(import_os19.default.homedir(), ".claude", "plugins", "marketplaces");
|
|
10292
10595
|
const roots = [];
|
|
10293
10596
|
let registries;
|
|
10294
10597
|
try {
|
|
10295
|
-
registries =
|
|
10598
|
+
registries = import_fs23.default.readdirSync(marketplaces, { withFileTypes: true });
|
|
10296
10599
|
} catch {
|
|
10297
10600
|
return [];
|
|
10298
10601
|
}
|
|
10299
10602
|
for (const registry of registries) {
|
|
10300
10603
|
if (!registry.isDirectory()) continue;
|
|
10301
|
-
const pluginsDir =
|
|
10604
|
+
const pluginsDir = import_path25.default.join(marketplaces, registry.name, "plugins");
|
|
10302
10605
|
let plugins;
|
|
10303
10606
|
try {
|
|
10304
|
-
plugins =
|
|
10607
|
+
plugins = import_fs23.default.readdirSync(pluginsDir, { withFileTypes: true });
|
|
10305
10608
|
} catch {
|
|
10306
10609
|
continue;
|
|
10307
10610
|
}
|
|
10308
10611
|
for (const plugin of plugins) {
|
|
10309
10612
|
if (!plugin.isDirectory()) continue;
|
|
10310
|
-
roots.push(
|
|
10613
|
+
roots.push(import_path25.default.join(pluginsDir, plugin.name));
|
|
10311
10614
|
}
|
|
10312
10615
|
}
|
|
10313
10616
|
return roots;
|
|
10314
10617
|
}
|
|
10315
10618
|
function resolveUserSkillRoot(entry, cwd) {
|
|
10316
10619
|
if (!entry) return null;
|
|
10317
|
-
if (entry.startsWith("~/") || entry === "~") return
|
|
10318
|
-
if (
|
|
10319
|
-
if (!cwd || !
|
|
10320
|
-
return
|
|
10620
|
+
if (entry.startsWith("~/") || entry === "~") return import_path25.default.join(import_os19.default.homedir(), entry.slice(1));
|
|
10621
|
+
if (import_path25.default.isAbsolute(entry)) return entry;
|
|
10622
|
+
if (!cwd || !import_path25.default.isAbsolute(cwd)) return null;
|
|
10623
|
+
return import_path25.default.join(cwd, entry);
|
|
10321
10624
|
}
|
|
10322
10625
|
|
|
10323
10626
|
// src/cli/commands/check.ts
|
|
@@ -10335,9 +10638,9 @@ function registerCheckCommand(program2) {
|
|
|
10335
10638
|
} catch (err2) {
|
|
10336
10639
|
const tempConfig = getConfig();
|
|
10337
10640
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
10338
|
-
const logPath =
|
|
10641
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10339
10642
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10340
|
-
|
|
10643
|
+
import_fs24.default.appendFileSync(
|
|
10341
10644
|
logPath,
|
|
10342
10645
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
10343
10646
|
RAW: ${raw}
|
|
@@ -10350,11 +10653,11 @@ RAW: ${raw}
|
|
|
10350
10653
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
10351
10654
|
try {
|
|
10352
10655
|
const scriptPath = process.argv[1];
|
|
10353
|
-
if (typeof scriptPath !== "string" || !
|
|
10656
|
+
if (typeof scriptPath !== "string" || !import_path26.default.isAbsolute(scriptPath))
|
|
10354
10657
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
10355
|
-
const resolvedScript =
|
|
10356
|
-
const packageDist =
|
|
10357
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
10658
|
+
const resolvedScript = import_fs24.default.realpathSync(scriptPath);
|
|
10659
|
+
const packageDist = import_fs24.default.realpathSync(import_path26.default.resolve(__dirname, "../.."));
|
|
10660
|
+
if (!resolvedScript.startsWith(packageDist + import_path26.default.sep) && resolvedScript !== packageDist)
|
|
10358
10661
|
throw new Error(
|
|
10359
10662
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
10360
10663
|
);
|
|
@@ -10376,10 +10679,10 @@ RAW: ${raw}
|
|
|
10376
10679
|
});
|
|
10377
10680
|
d.unref();
|
|
10378
10681
|
} catch (spawnErr) {
|
|
10379
|
-
const logPath =
|
|
10682
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10380
10683
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
10381
10684
|
try {
|
|
10382
|
-
|
|
10685
|
+
import_fs24.default.appendFileSync(
|
|
10383
10686
|
logPath,
|
|
10384
10687
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
10385
10688
|
`
|
|
@@ -10389,10 +10692,10 @@ RAW: ${raw}
|
|
|
10389
10692
|
}
|
|
10390
10693
|
}
|
|
10391
10694
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
10392
|
-
const logPath =
|
|
10393
|
-
if (!
|
|
10394
|
-
|
|
10395
|
-
|
|
10695
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10696
|
+
if (!import_fs24.default.existsSync(import_path26.default.dirname(logPath)))
|
|
10697
|
+
import_fs24.default.mkdirSync(import_path26.default.dirname(logPath), { recursive: true });
|
|
10698
|
+
import_fs24.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
10396
10699
|
`);
|
|
10397
10700
|
}
|
|
10398
10701
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -10405,8 +10708,8 @@ RAW: ${raw}
|
|
|
10405
10708
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
10406
10709
|
let ttyFd = null;
|
|
10407
10710
|
try {
|
|
10408
|
-
ttyFd =
|
|
10409
|
-
const writeTty = (line) =>
|
|
10711
|
+
ttyFd = import_fs24.default.openSync("/dev/tty", "w");
|
|
10712
|
+
const writeTty = (line) => import_fs24.default.writeSync(ttyFd, line + "\n");
|
|
10410
10713
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
10411
10714
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
10412
10715
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -10425,7 +10728,7 @@ RAW: ${raw}
|
|
|
10425
10728
|
} finally {
|
|
10426
10729
|
if (ttyFd !== null)
|
|
10427
10730
|
try {
|
|
10428
|
-
|
|
10731
|
+
import_fs24.default.closeSync(ttyFd);
|
|
10429
10732
|
} catch {
|
|
10430
10733
|
}
|
|
10431
10734
|
}
|
|
@@ -10459,17 +10762,17 @@ RAW: ${raw}
|
|
|
10459
10762
|
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
10460
10763
|
if (skillPinCfg.enabled && safeSessionId) {
|
|
10461
10764
|
try {
|
|
10462
|
-
const sessionsDir =
|
|
10463
|
-
const flagPath =
|
|
10765
|
+
const sessionsDir = import_path26.default.join(import_os20.default.homedir(), ".node9", "skill-sessions");
|
|
10766
|
+
const flagPath = import_path26.default.join(sessionsDir, `${safeSessionId}.json`);
|
|
10464
10767
|
let flag = null;
|
|
10465
10768
|
try {
|
|
10466
|
-
flag = JSON.parse(
|
|
10769
|
+
flag = JSON.parse(import_fs24.default.readFileSync(flagPath, "utf-8"));
|
|
10467
10770
|
} catch {
|
|
10468
10771
|
}
|
|
10469
10772
|
const writeFlag = (data2) => {
|
|
10470
10773
|
try {
|
|
10471
|
-
|
|
10472
|
-
|
|
10774
|
+
import_fs24.default.mkdirSync(sessionsDir, { recursive: true });
|
|
10775
|
+
import_fs24.default.writeFileSync(
|
|
10473
10776
|
flagPath,
|
|
10474
10777
|
JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
10475
10778
|
{ mode: 384 }
|
|
@@ -10480,8 +10783,8 @@ RAW: ${raw}
|
|
|
10480
10783
|
const sendSkillWarn = (detail, recoveryCmd) => {
|
|
10481
10784
|
let ttyFd = null;
|
|
10482
10785
|
try {
|
|
10483
|
-
ttyFd =
|
|
10484
|
-
const w = (line) =>
|
|
10786
|
+
ttyFd = import_fs24.default.openSync("/dev/tty", "w");
|
|
10787
|
+
const w = (line) => import_fs24.default.writeSync(ttyFd, line + "\n");
|
|
10485
10788
|
w(import_chalk5.default.yellow(`
|
|
10486
10789
|
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
10487
10790
|
w(import_chalk5.default.gray(` ${detail}`));
|
|
@@ -10496,7 +10799,7 @@ RAW: ${raw}
|
|
|
10496
10799
|
} finally {
|
|
10497
10800
|
if (ttyFd !== null)
|
|
10498
10801
|
try {
|
|
10499
|
-
|
|
10802
|
+
import_fs24.default.closeSync(ttyFd);
|
|
10500
10803
|
} catch {
|
|
10501
10804
|
}
|
|
10502
10805
|
}
|
|
@@ -10512,7 +10815,7 @@ RAW: ${raw}
|
|
|
10512
10815
|
return;
|
|
10513
10816
|
}
|
|
10514
10817
|
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
10515
|
-
const absoluteCwd = typeof payload.cwd === "string" &&
|
|
10818
|
+
const absoluteCwd = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10516
10819
|
const extraRoots = skillPinCfg.roots;
|
|
10517
10820
|
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
10518
10821
|
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
@@ -10553,10 +10856,10 @@ RAW: ${raw}
|
|
|
10553
10856
|
}
|
|
10554
10857
|
try {
|
|
10555
10858
|
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
10556
|
-
for (const name of
|
|
10557
|
-
const p =
|
|
10859
|
+
for (const name of import_fs24.default.readdirSync(sessionsDir)) {
|
|
10860
|
+
const p = import_path26.default.join(sessionsDir, name);
|
|
10558
10861
|
try {
|
|
10559
|
-
if (
|
|
10862
|
+
if (import_fs24.default.statSync(p).mtimeMs < cutoff) import_fs24.default.unlinkSync(p);
|
|
10560
10863
|
} catch {
|
|
10561
10864
|
}
|
|
10562
10865
|
}
|
|
@@ -10566,9 +10869,9 @@ RAW: ${raw}
|
|
|
10566
10869
|
} catch (err2) {
|
|
10567
10870
|
if (process.env.NODE9_DEBUG === "1") {
|
|
10568
10871
|
try {
|
|
10569
|
-
const dbg =
|
|
10872
|
+
const dbg = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10570
10873
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10571
|
-
|
|
10874
|
+
import_fs24.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
|
|
10572
10875
|
`);
|
|
10573
10876
|
} catch {
|
|
10574
10877
|
}
|
|
@@ -10578,7 +10881,7 @@ RAW: ${raw}
|
|
|
10578
10881
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
10579
10882
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
10580
10883
|
}
|
|
10581
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
10884
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10582
10885
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
10583
10886
|
cwd: safeCwdForAuth
|
|
10584
10887
|
});
|
|
@@ -10590,12 +10893,12 @@ RAW: ${raw}
|
|
|
10590
10893
|
}
|
|
10591
10894
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
10592
10895
|
try {
|
|
10593
|
-
const tty =
|
|
10594
|
-
|
|
10896
|
+
const tty = import_fs24.default.openSync("/dev/tty", "w");
|
|
10897
|
+
import_fs24.default.writeSync(
|
|
10595
10898
|
tty,
|
|
10596
10899
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
10597
10900
|
);
|
|
10598
|
-
|
|
10901
|
+
import_fs24.default.closeSync(tty);
|
|
10599
10902
|
} catch {
|
|
10600
10903
|
}
|
|
10601
10904
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -10622,9 +10925,9 @@ RAW: ${raw}
|
|
|
10622
10925
|
});
|
|
10623
10926
|
} catch (err2) {
|
|
10624
10927
|
if (process.env.NODE9_DEBUG === "1") {
|
|
10625
|
-
const logPath =
|
|
10928
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10626
10929
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10627
|
-
|
|
10930
|
+
import_fs24.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
10628
10931
|
`);
|
|
10629
10932
|
}
|
|
10630
10933
|
process.exit(0);
|
|
@@ -10658,9 +10961,9 @@ RAW: ${raw}
|
|
|
10658
10961
|
}
|
|
10659
10962
|
|
|
10660
10963
|
// src/cli/commands/log.ts
|
|
10661
|
-
var
|
|
10662
|
-
var
|
|
10663
|
-
var
|
|
10964
|
+
var import_fs25 = __toESM(require("fs"));
|
|
10965
|
+
var import_path27 = __toESM(require("path"));
|
|
10966
|
+
var import_os21 = __toESM(require("os"));
|
|
10664
10967
|
init_audit();
|
|
10665
10968
|
init_config();
|
|
10666
10969
|
init_policy();
|
|
@@ -10736,10 +11039,10 @@ function registerLogCommand(program2) {
|
|
|
10736
11039
|
decision: "allowed",
|
|
10737
11040
|
source: "post-hook"
|
|
10738
11041
|
};
|
|
10739
|
-
const logPath =
|
|
10740
|
-
if (!
|
|
10741
|
-
|
|
10742
|
-
|
|
11042
|
+
const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
|
|
11043
|
+
if (!import_fs25.default.existsSync(import_path27.default.dirname(logPath)))
|
|
11044
|
+
import_fs25.default.mkdirSync(import_path27.default.dirname(logPath), { recursive: true });
|
|
11045
|
+
import_fs25.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
10743
11046
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
10744
11047
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
10745
11048
|
if (command) {
|
|
@@ -10772,7 +11075,7 @@ function registerLogCommand(program2) {
|
|
|
10772
11075
|
}
|
|
10773
11076
|
}
|
|
10774
11077
|
}
|
|
10775
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
11078
|
+
const safeCwd = typeof payload.cwd === "string" && import_path27.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10776
11079
|
const config = getConfig(safeCwd);
|
|
10777
11080
|
if (shouldSnapshot(tool, {}, config)) {
|
|
10778
11081
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -10781,9 +11084,9 @@ function registerLogCommand(program2) {
|
|
|
10781
11084
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10782
11085
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
10783
11086
|
`);
|
|
10784
|
-
const debugPath =
|
|
11087
|
+
const debugPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "hook-debug.log");
|
|
10785
11088
|
try {
|
|
10786
|
-
|
|
11089
|
+
import_fs25.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
10787
11090
|
`);
|
|
10788
11091
|
} catch {
|
|
10789
11092
|
}
|
|
@@ -11183,14 +11486,14 @@ function registerConfigShowCommand(program2) {
|
|
|
11183
11486
|
|
|
11184
11487
|
// src/cli/commands/doctor.ts
|
|
11185
11488
|
var import_chalk7 = __toESM(require("chalk"));
|
|
11186
|
-
var
|
|
11187
|
-
var
|
|
11188
|
-
var
|
|
11489
|
+
var import_fs26 = __toESM(require("fs"));
|
|
11490
|
+
var import_path28 = __toESM(require("path"));
|
|
11491
|
+
var import_os22 = __toESM(require("os"));
|
|
11189
11492
|
var import_child_process11 = require("child_process");
|
|
11190
11493
|
init_daemon();
|
|
11191
11494
|
function registerDoctorCommand(program2, version2) {
|
|
11192
11495
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
11193
|
-
const homeDir2 =
|
|
11496
|
+
const homeDir2 = import_os22.default.homedir();
|
|
11194
11497
|
let failures = 0;
|
|
11195
11498
|
function pass(msg) {
|
|
11196
11499
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -11239,10 +11542,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11239
11542
|
);
|
|
11240
11543
|
}
|
|
11241
11544
|
section("Configuration");
|
|
11242
|
-
const globalConfigPath =
|
|
11243
|
-
if (
|
|
11545
|
+
const globalConfigPath = import_path28.default.join(homeDir2, ".node9", "config.json");
|
|
11546
|
+
if (import_fs26.default.existsSync(globalConfigPath)) {
|
|
11244
11547
|
try {
|
|
11245
|
-
JSON.parse(
|
|
11548
|
+
JSON.parse(import_fs26.default.readFileSync(globalConfigPath, "utf-8"));
|
|
11246
11549
|
pass("~/.node9/config.json found and valid");
|
|
11247
11550
|
} catch {
|
|
11248
11551
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -11250,10 +11553,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11250
11553
|
} else {
|
|
11251
11554
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
11252
11555
|
}
|
|
11253
|
-
const projectConfigPath =
|
|
11254
|
-
if (
|
|
11556
|
+
const projectConfigPath = import_path28.default.join(process.cwd(), "node9.config.json");
|
|
11557
|
+
if (import_fs26.default.existsSync(projectConfigPath)) {
|
|
11255
11558
|
try {
|
|
11256
|
-
JSON.parse(
|
|
11559
|
+
JSON.parse(import_fs26.default.readFileSync(projectConfigPath, "utf-8"));
|
|
11257
11560
|
pass("node9.config.json found and valid (project)");
|
|
11258
11561
|
} catch {
|
|
11259
11562
|
fail(
|
|
@@ -11262,8 +11565,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11262
11565
|
);
|
|
11263
11566
|
}
|
|
11264
11567
|
}
|
|
11265
|
-
const credsPath =
|
|
11266
|
-
if (
|
|
11568
|
+
const credsPath = import_path28.default.join(homeDir2, ".node9", "credentials.json");
|
|
11569
|
+
if (import_fs26.default.existsSync(credsPath)) {
|
|
11267
11570
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
11268
11571
|
} else {
|
|
11269
11572
|
warn(
|
|
@@ -11272,10 +11575,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11272
11575
|
);
|
|
11273
11576
|
}
|
|
11274
11577
|
section("Agent Hooks");
|
|
11275
|
-
const claudeSettingsPath =
|
|
11276
|
-
if (
|
|
11578
|
+
const claudeSettingsPath = import_path28.default.join(homeDir2, ".claude", "settings.json");
|
|
11579
|
+
if (import_fs26.default.existsSync(claudeSettingsPath)) {
|
|
11277
11580
|
try {
|
|
11278
|
-
const cs = JSON.parse(
|
|
11581
|
+
const cs = JSON.parse(import_fs26.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
11279
11582
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
11280
11583
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
11281
11584
|
);
|
|
@@ -11291,10 +11594,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11291
11594
|
} else {
|
|
11292
11595
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
11293
11596
|
}
|
|
11294
|
-
const geminiSettingsPath =
|
|
11295
|
-
if (
|
|
11597
|
+
const geminiSettingsPath = import_path28.default.join(homeDir2, ".gemini", "settings.json");
|
|
11598
|
+
if (import_fs26.default.existsSync(geminiSettingsPath)) {
|
|
11296
11599
|
try {
|
|
11297
|
-
const gs = JSON.parse(
|
|
11600
|
+
const gs = JSON.parse(import_fs26.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
11298
11601
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
11299
11602
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
11300
11603
|
);
|
|
@@ -11310,10 +11613,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11310
11613
|
} else {
|
|
11311
11614
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
11312
11615
|
}
|
|
11313
|
-
const cursorHooksPath =
|
|
11314
|
-
if (
|
|
11616
|
+
const cursorHooksPath = import_path28.default.join(homeDir2, ".cursor", "hooks.json");
|
|
11617
|
+
if (import_fs26.default.existsSync(cursorHooksPath)) {
|
|
11315
11618
|
try {
|
|
11316
|
-
const cur = JSON.parse(
|
|
11619
|
+
const cur = JSON.parse(import_fs26.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
11317
11620
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
11318
11621
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
11319
11622
|
);
|
|
@@ -11351,9 +11654,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
11351
11654
|
|
|
11352
11655
|
// src/cli/commands/audit.ts
|
|
11353
11656
|
var import_chalk8 = __toESM(require("chalk"));
|
|
11354
|
-
var
|
|
11355
|
-
var
|
|
11356
|
-
var
|
|
11657
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11658
|
+
var import_path29 = __toESM(require("path"));
|
|
11659
|
+
var import_os23 = __toESM(require("os"));
|
|
11357
11660
|
function formatRelativeTime(timestamp) {
|
|
11358
11661
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
11359
11662
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -11366,14 +11669,14 @@ function formatRelativeTime(timestamp) {
|
|
|
11366
11669
|
}
|
|
11367
11670
|
function registerAuditCommand(program2) {
|
|
11368
11671
|
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) => {
|
|
11369
|
-
const logPath =
|
|
11370
|
-
if (!
|
|
11672
|
+
const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "audit.log");
|
|
11673
|
+
if (!import_fs27.default.existsSync(logPath)) {
|
|
11371
11674
|
console.log(
|
|
11372
11675
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
11373
11676
|
);
|
|
11374
11677
|
return;
|
|
11375
11678
|
}
|
|
11376
|
-
const raw =
|
|
11679
|
+
const raw = import_fs27.default.readFileSync(logPath, "utf-8");
|
|
11377
11680
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
11378
11681
|
let entries = lines.flatMap((line) => {
|
|
11379
11682
|
try {
|
|
@@ -11427,9 +11730,9 @@ function registerAuditCommand(program2) {
|
|
|
11427
11730
|
|
|
11428
11731
|
// src/cli/commands/report.ts
|
|
11429
11732
|
var import_chalk9 = __toESM(require("chalk"));
|
|
11430
|
-
var
|
|
11431
|
-
var
|
|
11432
|
-
var
|
|
11733
|
+
var import_fs28 = __toESM(require("fs"));
|
|
11734
|
+
var import_path30 = __toESM(require("path"));
|
|
11735
|
+
var import_os24 = __toESM(require("os"));
|
|
11433
11736
|
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;
|
|
11434
11737
|
function buildTestTimestamps(allEntries) {
|
|
11435
11738
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -11476,8 +11779,8 @@ function getDateRange(period) {
|
|
|
11476
11779
|
}
|
|
11477
11780
|
}
|
|
11478
11781
|
function parseAuditLog(logPath) {
|
|
11479
|
-
if (!
|
|
11480
|
-
const raw =
|
|
11782
|
+
if (!import_fs28.default.existsSync(logPath)) return [];
|
|
11783
|
+
const raw = import_fs28.default.readFileSync(logPath, "utf-8");
|
|
11481
11784
|
return raw.split("\n").flatMap((line) => {
|
|
11482
11785
|
if (!line.trim()) return [];
|
|
11483
11786
|
try {
|
|
@@ -11544,34 +11847,38 @@ function loadClaudeCost(start, end) {
|
|
|
11544
11847
|
byDay: /* @__PURE__ */ new Map(),
|
|
11545
11848
|
byModel: /* @__PURE__ */ new Map(),
|
|
11546
11849
|
inputTokens: 0,
|
|
11850
|
+
outputTokens: 0,
|
|
11851
|
+
cacheWriteTokens: 0,
|
|
11547
11852
|
cacheReadTokens: 0
|
|
11548
11853
|
};
|
|
11549
|
-
const projectsDir =
|
|
11550
|
-
if (!
|
|
11854
|
+
const projectsDir = import_path30.default.join(import_os24.default.homedir(), ".claude", "projects");
|
|
11855
|
+
if (!import_fs28.default.existsSync(projectsDir)) return empty;
|
|
11551
11856
|
let dirs;
|
|
11552
11857
|
try {
|
|
11553
|
-
dirs =
|
|
11858
|
+
dirs = import_fs28.default.readdirSync(projectsDir);
|
|
11554
11859
|
} catch {
|
|
11555
11860
|
return empty;
|
|
11556
11861
|
}
|
|
11557
11862
|
let total = 0;
|
|
11558
11863
|
let inputTokens = 0;
|
|
11864
|
+
let outputTokens = 0;
|
|
11865
|
+
let cacheWriteTokens = 0;
|
|
11559
11866
|
let cacheReadTokens = 0;
|
|
11560
11867
|
const byDay = /* @__PURE__ */ new Map();
|
|
11561
11868
|
const byModel = /* @__PURE__ */ new Map();
|
|
11562
11869
|
for (const proj of dirs) {
|
|
11563
|
-
const projPath =
|
|
11870
|
+
const projPath = import_path30.default.join(projectsDir, proj);
|
|
11564
11871
|
let files;
|
|
11565
11872
|
try {
|
|
11566
|
-
const stat =
|
|
11873
|
+
const stat = import_fs28.default.statSync(projPath);
|
|
11567
11874
|
if (!stat.isDirectory()) continue;
|
|
11568
|
-
files =
|
|
11875
|
+
files = import_fs28.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
11569
11876
|
} catch {
|
|
11570
11877
|
continue;
|
|
11571
11878
|
}
|
|
11572
11879
|
for (const file of files) {
|
|
11573
11880
|
try {
|
|
11574
|
-
const raw =
|
|
11881
|
+
const raw = import_fs28.default.readFileSync(import_path30.default.join(projPath, file), "utf-8");
|
|
11575
11882
|
for (const line of raw.split("\n")) {
|
|
11576
11883
|
if (!line.trim()) continue;
|
|
11577
11884
|
let entry;
|
|
@@ -11596,6 +11903,8 @@ function loadClaudeCost(start, end) {
|
|
|
11596
11903
|
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
11597
11904
|
total += cost;
|
|
11598
11905
|
inputTokens += inp;
|
|
11906
|
+
outputTokens += out;
|
|
11907
|
+
cacheWriteTokens += cw;
|
|
11599
11908
|
cacheReadTokens += cr;
|
|
11600
11909
|
const dateKey = entry.timestamp.slice(0, 10);
|
|
11601
11910
|
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
@@ -11607,15 +11916,24 @@ function loadClaudeCost(start, end) {
|
|
|
11607
11916
|
}
|
|
11608
11917
|
}
|
|
11609
11918
|
}
|
|
11610
|
-
return { total, byDay, byModel, inputTokens, cacheReadTokens };
|
|
11919
|
+
return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
|
|
11611
11920
|
}
|
|
11612
11921
|
function registerReportCommand(program2) {
|
|
11613
11922
|
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) => {
|
|
11614
11923
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
11615
11924
|
options.period
|
|
11616
11925
|
) ? options.period : "7d";
|
|
11617
|
-
const logPath =
|
|
11926
|
+
const logPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "audit.log");
|
|
11618
11927
|
const allEntries = parseAuditLog(logPath);
|
|
11928
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
11929
|
+
if (unackedDlp.length > 0) {
|
|
11930
|
+
console.log("");
|
|
11931
|
+
console.log(
|
|
11932
|
+
import_chalk9.default.bgRed.white.bold(
|
|
11933
|
+
` \u26A0\uFE0F DLP ALERT: ${unackedDlp.length} secret${unackedDlp.length !== 1 ? "s" : ""} found in Claude response text `
|
|
11934
|
+
) + " " + import_chalk9.default.yellow("\u2192 run: node9 dlp")
|
|
11935
|
+
);
|
|
11936
|
+
}
|
|
11619
11937
|
if (allEntries.length === 0) {
|
|
11620
11938
|
console.log(
|
|
11621
11939
|
import_chalk9.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
@@ -11628,6 +11946,8 @@ function registerReportCommand(program2) {
|
|
|
11628
11946
|
byDay: costByDay,
|
|
11629
11947
|
byModel: costByModel,
|
|
11630
11948
|
inputTokens: costInputTokens,
|
|
11949
|
+
outputTokens: costOutputTokens,
|
|
11950
|
+
cacheWriteTokens: costCacheWrite,
|
|
11631
11951
|
cacheReadTokens: costCacheRead
|
|
11632
11952
|
} = loadClaudeCost(start, end);
|
|
11633
11953
|
const periodMs = end.getTime() - start.getTime();
|
|
@@ -11645,6 +11965,7 @@ function registerReportCommand(program2) {
|
|
|
11645
11965
|
let filteredTestCount = 0;
|
|
11646
11966
|
const entries = allEntries.filter((e) => {
|
|
11647
11967
|
if (e.source === "post-hook") return false;
|
|
11968
|
+
if (e.source === "response-dlp") return false;
|
|
11648
11969
|
const ts = new Date(e.ts);
|
|
11649
11970
|
if (ts < start || ts > end) return false;
|
|
11650
11971
|
if (excludeTests && isTestEntry(e, testTs)) {
|
|
@@ -11778,7 +12099,7 @@ function registerReportCommand(program2) {
|
|
|
11778
12099
|
if (topBlocks.length === 0) {
|
|
11779
12100
|
console.log(" " + " ".repeat(COL) + " " + import_chalk9.default.dim("nothing blocked \u2713"));
|
|
11780
12101
|
}
|
|
11781
|
-
if (agentMap.size
|
|
12102
|
+
if (agentMap.size >= 1) {
|
|
11782
12103
|
console.log("");
|
|
11783
12104
|
console.log(" " + import_chalk9.default.bold("Agents"));
|
|
11784
12105
|
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
@@ -11828,6 +12149,40 @@ function registerReportCommand(program2) {
|
|
|
11828
12149
|
);
|
|
11829
12150
|
}
|
|
11830
12151
|
}
|
|
12152
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
12153
|
+
if (totalTokens > 0) {
|
|
12154
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
12155
|
+
console.log("");
|
|
12156
|
+
console.log(" " + import_chalk9.default.bold("Tokens") + " " + import_chalk9.default.dim(`${num(totalTokens)} total`));
|
|
12157
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
12158
|
+
const tokenRows = [
|
|
12159
|
+
["Input", costInputTokens, import_chalk9.default.cyan(num(costInputTokens))],
|
|
12160
|
+
["Output", costOutputTokens, import_chalk9.default.white(num(costOutputTokens))],
|
|
12161
|
+
["Cache write", costCacheWrite, import_chalk9.default.yellow(num(costCacheWrite))],
|
|
12162
|
+
["Cache read", costCacheRead, import_chalk9.default.green(num(costCacheRead))]
|
|
12163
|
+
];
|
|
12164
|
+
const maxTok = Math.max(
|
|
12165
|
+
costInputTokens,
|
|
12166
|
+
costOutputTokens,
|
|
12167
|
+
costCacheWrite,
|
|
12168
|
+
costCacheRead,
|
|
12169
|
+
1
|
|
12170
|
+
);
|
|
12171
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
12172
|
+
const TOK_LABEL = 14;
|
|
12173
|
+
for (const [label, count, colored] of tokenRows) {
|
|
12174
|
+
if (count === 0) continue;
|
|
12175
|
+
const b = colorBar(count, maxTok, TOK_BAR);
|
|
12176
|
+
console.log(" " + import_chalk9.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
12177
|
+
}
|
|
12178
|
+
if (cacheHitPct > 0) {
|
|
12179
|
+
console.log(
|
|
12180
|
+
" " + import_chalk9.default.dim(
|
|
12181
|
+
`Cache hit rate: ${cacheHitPct}% (saves ~${fmtCost(costCacheRead * 27e-7)} vs fresh input)`
|
|
12182
|
+
)
|
|
12183
|
+
);
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
11831
12186
|
if (costUSD > 0) {
|
|
11832
12187
|
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
11833
12188
|
const avgPerDay = costUSD / periodDays;
|
|
@@ -11852,6 +12207,33 @@ function registerReportCommand(program2) {
|
|
|
11852
12207
|
);
|
|
11853
12208
|
}
|
|
11854
12209
|
}
|
|
12210
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
12211
|
+
if (e.source !== "response-dlp") return false;
|
|
12212
|
+
const ts = new Date(e.ts);
|
|
12213
|
+
return ts >= start && ts <= end;
|
|
12214
|
+
});
|
|
12215
|
+
if (responseDlpEntries.length > 0) {
|
|
12216
|
+
console.log("");
|
|
12217
|
+
console.log(
|
|
12218
|
+
" " + import_chalk9.default.red.bold("\u26A0\uFE0F Response DLP") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.red(
|
|
12219
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
12220
|
+
)
|
|
12221
|
+
);
|
|
12222
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
12223
|
+
console.log(
|
|
12224
|
+
" " + import_chalk9.default.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
12225
|
+
);
|
|
12226
|
+
console.log(" " + import_chalk9.default.yellow("Rotate affected keys immediately."));
|
|
12227
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
12228
|
+
const ts = import_chalk9.default.dim(fmtDate(e.ts) + " ");
|
|
12229
|
+
const pattern = import_chalk9.default.red(e.dlpPattern ?? "DLP");
|
|
12230
|
+
const sample = import_chalk9.default.gray(e.dlpSample ?? "");
|
|
12231
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
12232
|
+
}
|
|
12233
|
+
if (responseDlpEntries.length > 5) {
|
|
12234
|
+
console.log(import_chalk9.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
12235
|
+
}
|
|
12236
|
+
}
|
|
11855
12237
|
console.log("");
|
|
11856
12238
|
console.log(
|
|
11857
12239
|
" " + import_chalk9.default.dim("node9 audit --deny") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
@@ -11967,14 +12349,14 @@ function registerDaemonCommand(program2) {
|
|
|
11967
12349
|
|
|
11968
12350
|
// src/cli/commands/status.ts
|
|
11969
12351
|
var import_chalk11 = __toESM(require("chalk"));
|
|
11970
|
-
var
|
|
11971
|
-
var
|
|
11972
|
-
var
|
|
12352
|
+
var import_fs29 = __toESM(require("fs"));
|
|
12353
|
+
var import_path31 = __toESM(require("path"));
|
|
12354
|
+
var import_os25 = __toESM(require("os"));
|
|
11973
12355
|
init_core();
|
|
11974
12356
|
init_daemon();
|
|
11975
12357
|
function readJson2(filePath) {
|
|
11976
12358
|
try {
|
|
11977
|
-
if (
|
|
12359
|
+
if (import_fs29.default.existsSync(filePath)) return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
|
|
11978
12360
|
} catch {
|
|
11979
12361
|
}
|
|
11980
12362
|
return null;
|
|
@@ -12039,28 +12421,28 @@ function registerStatusCommand(program2) {
|
|
|
12039
12421
|
console.log("");
|
|
12040
12422
|
const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
|
|
12041
12423
|
console.log(` Mode: ${modeLabel}`);
|
|
12042
|
-
const projectConfig =
|
|
12043
|
-
const globalConfig =
|
|
12424
|
+
const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
|
|
12425
|
+
const globalConfig = import_path31.default.join(import_os25.default.homedir(), ".node9", "config.json");
|
|
12044
12426
|
console.log(
|
|
12045
|
-
` Local: ${
|
|
12427
|
+
` Local: ${import_fs29.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
12046
12428
|
);
|
|
12047
12429
|
console.log(
|
|
12048
|
-
` Global: ${
|
|
12430
|
+
` Global: ${import_fs29.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
12049
12431
|
);
|
|
12050
12432
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
12051
12433
|
console.log(
|
|
12052
12434
|
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
12053
12435
|
);
|
|
12054
12436
|
}
|
|
12055
|
-
const homeDir2 =
|
|
12437
|
+
const homeDir2 = import_os25.default.homedir();
|
|
12056
12438
|
const claudeSettings = readJson2(
|
|
12057
|
-
|
|
12439
|
+
import_path31.default.join(homeDir2, ".claude", "settings.json")
|
|
12058
12440
|
);
|
|
12059
|
-
const claudeConfig = readJson2(
|
|
12441
|
+
const claudeConfig = readJson2(import_path31.default.join(homeDir2, ".claude.json"));
|
|
12060
12442
|
const geminiSettings = readJson2(
|
|
12061
|
-
|
|
12443
|
+
import_path31.default.join(homeDir2, ".gemini", "settings.json")
|
|
12062
12444
|
);
|
|
12063
|
-
const cursorConfig = readJson2(
|
|
12445
|
+
const cursorConfig = readJson2(import_path31.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
12064
12446
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
12065
12447
|
if (agentFound) {
|
|
12066
12448
|
console.log("");
|
|
@@ -12119,9 +12501,9 @@ function registerStatusCommand(program2) {
|
|
|
12119
12501
|
|
|
12120
12502
|
// src/cli/commands/init.ts
|
|
12121
12503
|
var import_chalk12 = __toESM(require("chalk"));
|
|
12122
|
-
var
|
|
12123
|
-
var
|
|
12124
|
-
var
|
|
12504
|
+
var import_fs30 = __toESM(require("fs"));
|
|
12505
|
+
var import_path32 = __toESM(require("path"));
|
|
12506
|
+
var import_os26 = __toESM(require("os"));
|
|
12125
12507
|
var import_https3 = __toESM(require("https"));
|
|
12126
12508
|
init_core();
|
|
12127
12509
|
init_shields();
|
|
@@ -12182,15 +12564,15 @@ function registerInitCommand(program2) {
|
|
|
12182
12564
|
}
|
|
12183
12565
|
console.log("");
|
|
12184
12566
|
}
|
|
12185
|
-
const configPath =
|
|
12186
|
-
if (
|
|
12567
|
+
const configPath = import_path32.default.join(import_os26.default.homedir(), ".node9", "config.json");
|
|
12568
|
+
if (import_fs30.default.existsSync(configPath) && !options.force) {
|
|
12187
12569
|
try {
|
|
12188
|
-
const existing = JSON.parse(
|
|
12570
|
+
const existing = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
|
|
12189
12571
|
const settings = existing.settings ?? {};
|
|
12190
12572
|
if (settings.mode !== chosenMode) {
|
|
12191
12573
|
settings.mode = chosenMode;
|
|
12192
12574
|
existing.settings = settings;
|
|
12193
|
-
|
|
12575
|
+
import_fs30.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
12194
12576
|
console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
12195
12577
|
} else {
|
|
12196
12578
|
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -12203,9 +12585,9 @@ function registerInitCommand(program2) {
|
|
|
12203
12585
|
...DEFAULT_CONFIG,
|
|
12204
12586
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
12205
12587
|
};
|
|
12206
|
-
const dir =
|
|
12207
|
-
if (!
|
|
12208
|
-
|
|
12588
|
+
const dir = import_path32.default.dirname(configPath);
|
|
12589
|
+
if (!import_fs30.default.existsSync(dir)) import_fs30.default.mkdirSync(dir, { recursive: true });
|
|
12590
|
+
import_fs30.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
12209
12591
|
console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
|
|
12210
12592
|
console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
|
|
12211
12593
|
}
|
|
@@ -12289,7 +12671,7 @@ function registerInitCommand(program2) {
|
|
|
12289
12671
|
}
|
|
12290
12672
|
|
|
12291
12673
|
// src/cli/commands/undo.ts
|
|
12292
|
-
var
|
|
12674
|
+
var import_path33 = __toESM(require("path"));
|
|
12293
12675
|
var import_chalk14 = __toESM(require("chalk"));
|
|
12294
12676
|
|
|
12295
12677
|
// src/tui/undo-navigator.ts
|
|
@@ -12448,7 +12830,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
12448
12830
|
let dir = startDir;
|
|
12449
12831
|
while (true) {
|
|
12450
12832
|
if (cwds.has(dir)) return dir;
|
|
12451
|
-
const parent =
|
|
12833
|
+
const parent = import_path33.default.dirname(dir);
|
|
12452
12834
|
if (parent === dir) return null;
|
|
12453
12835
|
dir = parent;
|
|
12454
12836
|
}
|
|
@@ -12644,12 +13026,12 @@ init_orchestrator();
|
|
|
12644
13026
|
init_provenance();
|
|
12645
13027
|
|
|
12646
13028
|
// src/mcp-pin.ts
|
|
12647
|
-
var
|
|
12648
|
-
var
|
|
12649
|
-
var
|
|
13029
|
+
var import_fs31 = __toESM(require("fs"));
|
|
13030
|
+
var import_path34 = __toESM(require("path"));
|
|
13031
|
+
var import_os27 = __toESM(require("os"));
|
|
12650
13032
|
var import_crypto10 = __toESM(require("crypto"));
|
|
12651
13033
|
function getPinsFilePath2() {
|
|
12652
|
-
return
|
|
13034
|
+
return import_path34.default.join(import_os27.default.homedir(), ".node9", "mcp-pins.json");
|
|
12653
13035
|
}
|
|
12654
13036
|
function hashToolDefinitions(tools) {
|
|
12655
13037
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -12666,7 +13048,7 @@ function getServerKey(upstreamCommand) {
|
|
|
12666
13048
|
function readMcpPinsSafe() {
|
|
12667
13049
|
const filePath = getPinsFilePath2();
|
|
12668
13050
|
try {
|
|
12669
|
-
const raw =
|
|
13051
|
+
const raw = import_fs31.default.readFileSync(filePath, "utf-8");
|
|
12670
13052
|
if (!raw.trim()) {
|
|
12671
13053
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
12672
13054
|
}
|
|
@@ -12690,10 +13072,10 @@ function readMcpPins() {
|
|
|
12690
13072
|
}
|
|
12691
13073
|
function writeMcpPins(data) {
|
|
12692
13074
|
const filePath = getPinsFilePath2();
|
|
12693
|
-
|
|
13075
|
+
import_fs31.default.mkdirSync(import_path34.default.dirname(filePath), { recursive: true });
|
|
12694
13076
|
const tmp = `${filePath}.${import_crypto10.default.randomBytes(6).toString("hex")}.tmp`;
|
|
12695
|
-
|
|
12696
|
-
|
|
13077
|
+
import_fs31.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
13078
|
+
import_fs31.default.renameSync(tmp, filePath);
|
|
12697
13079
|
}
|
|
12698
13080
|
function checkPin(serverKey, currentHash) {
|
|
12699
13081
|
const result = readMcpPinsSafe();
|
|
@@ -13065,9 +13447,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
13065
13447
|
|
|
13066
13448
|
// src/mcp-server/index.ts
|
|
13067
13449
|
var import_readline4 = __toESM(require("readline"));
|
|
13068
|
-
var
|
|
13069
|
-
var
|
|
13070
|
-
var
|
|
13450
|
+
var import_fs32 = __toESM(require("fs"));
|
|
13451
|
+
var import_os28 = __toESM(require("os"));
|
|
13452
|
+
var import_path35 = __toESM(require("path"));
|
|
13071
13453
|
init_core();
|
|
13072
13454
|
init_daemon();
|
|
13073
13455
|
init_shields();
|
|
@@ -13242,13 +13624,13 @@ function handleStatus() {
|
|
|
13242
13624
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
13243
13625
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
13244
13626
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
13245
|
-
const projectConfig =
|
|
13246
|
-
const globalConfig =
|
|
13627
|
+
const projectConfig = import_path35.default.join(process.cwd(), "node9.config.json");
|
|
13628
|
+
const globalConfig = import_path35.default.join(import_os28.default.homedir(), ".node9", "config.json");
|
|
13247
13629
|
lines.push(
|
|
13248
|
-
`Project config (node9.config.json): ${
|
|
13630
|
+
`Project config (node9.config.json): ${import_fs32.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
13249
13631
|
);
|
|
13250
13632
|
lines.push(
|
|
13251
|
-
`Global config (~/.node9/config.json): ${
|
|
13633
|
+
`Global config (~/.node9/config.json): ${import_fs32.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
13252
13634
|
);
|
|
13253
13635
|
return lines.join("\n");
|
|
13254
13636
|
}
|
|
@@ -13322,21 +13704,21 @@ function handleShieldDisable(args) {
|
|
|
13322
13704
|
writeActiveShields(active.filter((s) => s !== name));
|
|
13323
13705
|
return `Shield "${name}" disabled.`;
|
|
13324
13706
|
}
|
|
13325
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
13707
|
+
var GLOBAL_CONFIG_PATH2 = import_path35.default.join(import_os28.default.homedir(), ".node9", "config.json");
|
|
13326
13708
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
13327
13709
|
function readGlobalConfigRaw() {
|
|
13328
13710
|
try {
|
|
13329
|
-
if (
|
|
13330
|
-
return JSON.parse(
|
|
13711
|
+
if (import_fs32.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
13712
|
+
return JSON.parse(import_fs32.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
13331
13713
|
}
|
|
13332
13714
|
} catch {
|
|
13333
13715
|
}
|
|
13334
13716
|
return {};
|
|
13335
13717
|
}
|
|
13336
13718
|
function writeGlobalConfigRaw(data) {
|
|
13337
|
-
const dir =
|
|
13338
|
-
if (!
|
|
13339
|
-
|
|
13719
|
+
const dir = import_path35.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
13720
|
+
if (!import_fs32.default.existsSync(dir)) import_fs32.default.mkdirSync(dir, { recursive: true });
|
|
13721
|
+
import_fs32.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
13340
13722
|
}
|
|
13341
13723
|
function handleApproverList() {
|
|
13342
13724
|
const config = getConfig();
|
|
@@ -13379,9 +13761,9 @@ function handleApproverSet(args) {
|
|
|
13379
13761
|
}
|
|
13380
13762
|
function handleAuditGet(args) {
|
|
13381
13763
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
13382
|
-
const auditPath =
|
|
13383
|
-
if (!
|
|
13384
|
-
const lines =
|
|
13764
|
+
const auditPath = import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
|
|
13765
|
+
if (!import_fs32.default.existsSync(auditPath)) return "No audit log found.";
|
|
13766
|
+
const lines = import_fs32.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
13385
13767
|
const recent = lines.slice(-limit);
|
|
13386
13768
|
const entries = recent.map((line) => {
|
|
13387
13769
|
try {
|
|
@@ -13839,9 +14221,9 @@ function registerAgentsCommand(program2) {
|
|
|
13839
14221
|
|
|
13840
14222
|
// src/cli/commands/scan.ts
|
|
13841
14223
|
var import_chalk21 = __toESM(require("chalk"));
|
|
13842
|
-
var
|
|
13843
|
-
var
|
|
13844
|
-
var
|
|
14224
|
+
var import_fs33 = __toESM(require("fs"));
|
|
14225
|
+
var import_path36 = __toESM(require("path"));
|
|
14226
|
+
var import_os29 = __toESM(require("os"));
|
|
13845
14227
|
init_shields();
|
|
13846
14228
|
init_config();
|
|
13847
14229
|
init_policy();
|
|
@@ -13913,7 +14295,7 @@ function buildRuleSources() {
|
|
|
13913
14295
|
return sources;
|
|
13914
14296
|
}
|
|
13915
14297
|
function scanClaudeHistory(startDate) {
|
|
13916
|
-
const projectsDir =
|
|
14298
|
+
const projectsDir = import_path36.default.join(import_os29.default.homedir(), ".claude", "projects");
|
|
13917
14299
|
const result = {
|
|
13918
14300
|
filesScanned: 0,
|
|
13919
14301
|
sessions: 0,
|
|
@@ -13925,25 +14307,25 @@ function scanClaudeHistory(startDate) {
|
|
|
13925
14307
|
firstDate: null,
|
|
13926
14308
|
lastDate: null
|
|
13927
14309
|
};
|
|
13928
|
-
if (!
|
|
14310
|
+
if (!import_fs33.default.existsSync(projectsDir)) return result;
|
|
13929
14311
|
let projDirs;
|
|
13930
14312
|
try {
|
|
13931
|
-
projDirs =
|
|
14313
|
+
projDirs = import_fs33.default.readdirSync(projectsDir);
|
|
13932
14314
|
} catch {
|
|
13933
14315
|
return result;
|
|
13934
14316
|
}
|
|
13935
14317
|
const ruleSources = buildRuleSources();
|
|
13936
14318
|
for (const proj of projDirs) {
|
|
13937
|
-
const projPath =
|
|
14319
|
+
const projPath = import_path36.default.join(projectsDir, proj);
|
|
13938
14320
|
try {
|
|
13939
|
-
if (!
|
|
14321
|
+
if (!import_fs33.default.statSync(projPath).isDirectory()) continue;
|
|
13940
14322
|
} catch {
|
|
13941
14323
|
continue;
|
|
13942
14324
|
}
|
|
13943
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
14325
|
+
const projLabel = decodeURIComponent(proj).replace(import_os29.default.homedir(), "~").slice(0, 40);
|
|
13944
14326
|
let files;
|
|
13945
14327
|
try {
|
|
13946
|
-
files =
|
|
14328
|
+
files = import_fs33.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
13947
14329
|
} catch {
|
|
13948
14330
|
continue;
|
|
13949
14331
|
}
|
|
@@ -13952,7 +14334,7 @@ function scanClaudeHistory(startDate) {
|
|
|
13952
14334
|
result.sessions++;
|
|
13953
14335
|
let raw;
|
|
13954
14336
|
try {
|
|
13955
|
-
raw =
|
|
14337
|
+
raw = import_fs33.default.readFileSync(import_path36.default.join(projPath, file), "utf-8");
|
|
13956
14338
|
} catch {
|
|
13957
14339
|
continue;
|
|
13958
14340
|
}
|
|
@@ -13993,6 +14375,9 @@ function scanClaudeHistory(startDate) {
|
|
|
13993
14375
|
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
13994
14376
|
result.bashCalls++;
|
|
13995
14377
|
}
|
|
14378
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
14379
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
14380
|
+
continue;
|
|
13996
14381
|
const dlpMatch = scanArgs(input);
|
|
13997
14382
|
if (dlpMatch) {
|
|
13998
14383
|
const isDupe = result.dlpFindings.some(
|
|
@@ -14010,6 +14395,7 @@ function scanClaudeHistory(startDate) {
|
|
|
14010
14395
|
}
|
|
14011
14396
|
for (const source of ruleSources) {
|
|
14012
14397
|
const { rule } = source;
|
|
14398
|
+
if (rule.verdict === "allow") continue;
|
|
14013
14399
|
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
14014
14400
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
14015
14401
|
const inputPreview = preview(input, 120);
|
|
@@ -14045,8 +14431,8 @@ function registerScanCommand(program2) {
|
|
|
14045
14431
|
console.log("");
|
|
14046
14432
|
console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
|
|
14047
14433
|
console.log("");
|
|
14048
|
-
const projectsDir =
|
|
14049
|
-
if (!
|
|
14434
|
+
const projectsDir = import_path36.default.join(import_os29.default.homedir(), ".claude", "projects");
|
|
14435
|
+
if (!import_fs33.default.existsSync(projectsDir)) {
|
|
14050
14436
|
console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
14051
14437
|
console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14052
14438
|
return;
|
|
@@ -14158,8 +14544,8 @@ function registerScanCommand(program2) {
|
|
|
14158
14544
|
);
|
|
14159
14545
|
console.log("");
|
|
14160
14546
|
}
|
|
14161
|
-
const auditLog =
|
|
14162
|
-
if (
|
|
14547
|
+
const auditLog = import_path36.default.join(import_os29.default.homedir(), ".node9", "audit.log");
|
|
14548
|
+
if (import_fs33.default.existsSync(auditLog)) {
|
|
14163
14549
|
console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
|
|
14164
14550
|
console.log(
|
|
14165
14551
|
import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
|
|
@@ -14176,9 +14562,9 @@ function registerScanCommand(program2) {
|
|
|
14176
14562
|
|
|
14177
14563
|
// src/cli/commands/sessions.ts
|
|
14178
14564
|
var import_chalk22 = __toESM(require("chalk"));
|
|
14179
|
-
var
|
|
14180
|
-
var
|
|
14181
|
-
var
|
|
14565
|
+
var import_fs34 = __toESM(require("fs"));
|
|
14566
|
+
var import_path37 = __toESM(require("path"));
|
|
14567
|
+
var import_os30 = __toESM(require("os"));
|
|
14182
14568
|
var CLAUDE_PRICING3 = {
|
|
14183
14569
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
14184
14570
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -14203,10 +14589,10 @@ function encodeProjectPath(projectPath) {
|
|
|
14203
14589
|
}
|
|
14204
14590
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
14205
14591
|
const encoded = encodeProjectPath(projectPath);
|
|
14206
|
-
return
|
|
14592
|
+
return import_path37.default.join(import_os30.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
14207
14593
|
}
|
|
14208
14594
|
function projectLabel(projectPath) {
|
|
14209
|
-
return projectPath.replace(
|
|
14595
|
+
return projectPath.replace(import_os30.default.homedir(), "~");
|
|
14210
14596
|
}
|
|
14211
14597
|
function parseHistoryLines(lines) {
|
|
14212
14598
|
const entries = [];
|
|
@@ -14275,10 +14661,10 @@ function parseSessionLines(lines) {
|
|
|
14275
14661
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
14276
14662
|
}
|
|
14277
14663
|
function loadAuditEntries(auditPath) {
|
|
14278
|
-
const aPath = auditPath ??
|
|
14664
|
+
const aPath = auditPath ?? import_path37.default.join(import_os30.default.homedir(), ".node9", "audit.log");
|
|
14279
14665
|
let raw;
|
|
14280
14666
|
try {
|
|
14281
|
-
raw =
|
|
14667
|
+
raw = import_fs34.default.readFileSync(aPath, "utf-8");
|
|
14282
14668
|
} catch {
|
|
14283
14669
|
return [];
|
|
14284
14670
|
}
|
|
@@ -14314,10 +14700,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
14314
14700
|
return result;
|
|
14315
14701
|
}
|
|
14316
14702
|
function buildSessions(days, historyPath) {
|
|
14317
|
-
const hPath = historyPath ??
|
|
14703
|
+
const hPath = historyPath ?? import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
|
|
14318
14704
|
let historyRaw;
|
|
14319
14705
|
try {
|
|
14320
|
-
historyRaw =
|
|
14706
|
+
historyRaw = import_fs34.default.readFileSync(hPath, "utf-8");
|
|
14321
14707
|
} catch {
|
|
14322
14708
|
return [];
|
|
14323
14709
|
}
|
|
@@ -14342,7 +14728,7 @@ function buildSessions(days, historyPath) {
|
|
|
14342
14728
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
14343
14729
|
let sessionLines = [];
|
|
14344
14730
|
try {
|
|
14345
|
-
sessionLines =
|
|
14731
|
+
sessionLines = import_fs34.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
14346
14732
|
} catch {
|
|
14347
14733
|
}
|
|
14348
14734
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -14593,8 +14979,8 @@ function registerSessionsCommand(program2) {
|
|
|
14593
14979
|
console.log("");
|
|
14594
14980
|
console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
|
|
14595
14981
|
console.log("");
|
|
14596
|
-
const historyPath =
|
|
14597
|
-
if (!
|
|
14982
|
+
const historyPath = import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
|
|
14983
|
+
if (!import_fs34.default.existsSync(historyPath)) {
|
|
14598
14984
|
console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
14599
14985
|
console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14600
14986
|
return;
|
|
@@ -14626,12 +15012,12 @@ function registerSessionsCommand(program2) {
|
|
|
14626
15012
|
|
|
14627
15013
|
// src/cli/commands/skill-pin.ts
|
|
14628
15014
|
var import_chalk23 = __toESM(require("chalk"));
|
|
14629
|
-
var
|
|
14630
|
-
var
|
|
14631
|
-
var
|
|
15015
|
+
var import_fs35 = __toESM(require("fs"));
|
|
15016
|
+
var import_os31 = __toESM(require("os"));
|
|
15017
|
+
var import_path38 = __toESM(require("path"));
|
|
14632
15018
|
function wipeSkillSessions() {
|
|
14633
15019
|
try {
|
|
14634
|
-
|
|
15020
|
+
import_fs35.default.rmSync(import_path38.default.join(import_os31.default.homedir(), ".node9", "skill-sessions"), {
|
|
14635
15021
|
recursive: true,
|
|
14636
15022
|
force: true
|
|
14637
15023
|
});
|
|
@@ -14712,22 +15098,145 @@ function registerSkillPinCommand(program2) {
|
|
|
14712
15098
|
});
|
|
14713
15099
|
}
|
|
14714
15100
|
|
|
15101
|
+
// src/cli/commands/dlp.ts
|
|
15102
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
15103
|
+
var import_fs36 = __toESM(require("fs"));
|
|
15104
|
+
var import_path39 = __toESM(require("path"));
|
|
15105
|
+
var import_os32 = __toESM(require("os"));
|
|
15106
|
+
var AUDIT_LOG = import_path39.default.join(import_os32.default.homedir(), ".node9", "audit.log");
|
|
15107
|
+
var RESOLVED_FILE = import_path39.default.join(import_os32.default.homedir(), ".node9", "dlp-resolved.json");
|
|
15108
|
+
var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
|
|
15109
|
+
function stripAnsi(s) {
|
|
15110
|
+
return s.replace(ANSI_RE, "");
|
|
15111
|
+
}
|
|
15112
|
+
function loadResolved() {
|
|
15113
|
+
try {
|
|
15114
|
+
const raw = JSON.parse(import_fs36.default.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
15115
|
+
return new Set(raw);
|
|
15116
|
+
} catch {
|
|
15117
|
+
return /* @__PURE__ */ new Set();
|
|
15118
|
+
}
|
|
15119
|
+
}
|
|
15120
|
+
function saveResolved(resolved) {
|
|
15121
|
+
try {
|
|
15122
|
+
import_fs36.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
15123
|
+
} catch {
|
|
15124
|
+
}
|
|
15125
|
+
}
|
|
15126
|
+
function loadDlpFindings() {
|
|
15127
|
+
if (!import_fs36.default.existsSync(AUDIT_LOG)) return [];
|
|
15128
|
+
return import_fs36.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
15129
|
+
if (!line.trim()) return [];
|
|
15130
|
+
try {
|
|
15131
|
+
const e = JSON.parse(line);
|
|
15132
|
+
return e.source === "response-dlp" ? [e] : [];
|
|
15133
|
+
} catch {
|
|
15134
|
+
return [];
|
|
15135
|
+
}
|
|
15136
|
+
});
|
|
15137
|
+
}
|
|
15138
|
+
function entryKey(e) {
|
|
15139
|
+
return `${e.ts}:${e.dlpPattern}:${e.dlpSample}`;
|
|
15140
|
+
}
|
|
15141
|
+
function fmtDate3(ts) {
|
|
15142
|
+
try {
|
|
15143
|
+
return new Date(ts).toLocaleDateString("en-US", {
|
|
15144
|
+
month: "short",
|
|
15145
|
+
day: "numeric",
|
|
15146
|
+
year: "numeric"
|
|
15147
|
+
});
|
|
15148
|
+
} catch {
|
|
15149
|
+
return ts.slice(0, 10);
|
|
15150
|
+
}
|
|
15151
|
+
}
|
|
15152
|
+
function registerDlpCommand(program2) {
|
|
15153
|
+
const cmd = program2.command("dlp").description("Show secrets detected in Claude response text and mark them resolved");
|
|
15154
|
+
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
15155
|
+
const findings = loadDlpFindings();
|
|
15156
|
+
if (findings.length === 0) {
|
|
15157
|
+
console.log(import_chalk24.default.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
15158
|
+
return;
|
|
15159
|
+
}
|
|
15160
|
+
const resolved = loadResolved();
|
|
15161
|
+
for (const e of findings) resolved.add(entryKey(e));
|
|
15162
|
+
saveResolved(resolved);
|
|
15163
|
+
console.log(
|
|
15164
|
+
import_chalk24.default.green(
|
|
15165
|
+
`
|
|
15166
|
+
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
15167
|
+
`
|
|
15168
|
+
)
|
|
15169
|
+
);
|
|
15170
|
+
});
|
|
15171
|
+
cmd.action(() => {
|
|
15172
|
+
const findings = loadDlpFindings();
|
|
15173
|
+
const resolved = loadResolved();
|
|
15174
|
+
const open = findings.filter((e) => !resolved.has(entryKey(e)));
|
|
15175
|
+
const resolvedCount = findings.length - open.length;
|
|
15176
|
+
console.log("");
|
|
15177
|
+
console.log(
|
|
15178
|
+
import_chalk24.default.bold.cyan("\u{1F510} node9 dlp") + import_chalk24.default.dim(" \u2014 secrets found in Claude response text")
|
|
15179
|
+
);
|
|
15180
|
+
console.log("");
|
|
15181
|
+
if (open.length === 0) {
|
|
15182
|
+
if (resolvedCount > 0) {
|
|
15183
|
+
console.log(import_chalk24.default.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
15184
|
+
} else {
|
|
15185
|
+
console.log(
|
|
15186
|
+
import_chalk24.default.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
15187
|
+
);
|
|
15188
|
+
}
|
|
15189
|
+
console.log("");
|
|
15190
|
+
return;
|
|
15191
|
+
}
|
|
15192
|
+
console.log(
|
|
15193
|
+
import_chalk24.default.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + import_chalk24.default.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
15194
|
+
);
|
|
15195
|
+
console.log("");
|
|
15196
|
+
console.log(
|
|
15197
|
+
import_chalk24.default.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
15198
|
+
);
|
|
15199
|
+
console.log(import_chalk24.default.dim(" Rotate each affected key immediately.\n"));
|
|
15200
|
+
for (const e of open) {
|
|
15201
|
+
console.log(
|
|
15202
|
+
" " + import_chalk24.default.red("\u25CF") + " " + import_chalk24.default.white(e.dlpPattern ?? "Secret") + import_chalk24.default.dim(" " + fmtDate3(e.ts))
|
|
15203
|
+
);
|
|
15204
|
+
if (e.dlpSample) {
|
|
15205
|
+
console.log(" " + import_chalk24.default.dim("Sample: ") + import_chalk24.default.yellow(stripAnsi(e.dlpSample)));
|
|
15206
|
+
}
|
|
15207
|
+
if (e.project) {
|
|
15208
|
+
console.log(" " + import_chalk24.default.dim("Project: ") + import_chalk24.default.dim(stripAnsi(e.project)));
|
|
15209
|
+
}
|
|
15210
|
+
console.log("");
|
|
15211
|
+
}
|
|
15212
|
+
console.log(" " + import_chalk24.default.bold("Next steps:"));
|
|
15213
|
+
console.log(" " + import_chalk24.default.cyan("1.") + " Rotate any exposed keys shown above");
|
|
15214
|
+
console.log(
|
|
15215
|
+
" " + import_chalk24.default.cyan("2.") + " Run " + import_chalk24.default.white("node9 dlp resolve") + " to acknowledge"
|
|
15216
|
+
);
|
|
15217
|
+
console.log(
|
|
15218
|
+
" " + import_chalk24.default.cyan("3.") + " Run " + import_chalk24.default.white("node9 report") + " for full audit history"
|
|
15219
|
+
);
|
|
15220
|
+
console.log("");
|
|
15221
|
+
});
|
|
15222
|
+
}
|
|
15223
|
+
|
|
14715
15224
|
// src/cli.ts
|
|
14716
15225
|
var { version } = JSON.parse(
|
|
14717
|
-
|
|
15226
|
+
import_fs39.default.readFileSync(import_path42.default.join(__dirname, "../package.json"), "utf-8")
|
|
14718
15227
|
);
|
|
14719
15228
|
var program = new import_commander.Command();
|
|
14720
15229
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
14721
15230
|
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) => {
|
|
14722
15231
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
14723
|
-
const credPath =
|
|
14724
|
-
if (!
|
|
14725
|
-
|
|
15232
|
+
const credPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "credentials.json");
|
|
15233
|
+
if (!import_fs39.default.existsSync(import_path42.default.dirname(credPath)))
|
|
15234
|
+
import_fs39.default.mkdirSync(import_path42.default.dirname(credPath), { recursive: true });
|
|
14726
15235
|
const profileName = options.profile || "default";
|
|
14727
15236
|
let existingCreds = {};
|
|
14728
15237
|
try {
|
|
14729
|
-
if (
|
|
14730
|
-
const raw = JSON.parse(
|
|
15238
|
+
if (import_fs39.default.existsSync(credPath)) {
|
|
15239
|
+
const raw = JSON.parse(import_fs39.default.readFileSync(credPath, "utf-8"));
|
|
14731
15240
|
if (raw.apiKey) {
|
|
14732
15241
|
existingCreds = {
|
|
14733
15242
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -14739,13 +15248,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14739
15248
|
} catch {
|
|
14740
15249
|
}
|
|
14741
15250
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
14742
|
-
|
|
15251
|
+
import_fs39.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
14743
15252
|
if (profileName === "default") {
|
|
14744
|
-
const configPath =
|
|
15253
|
+
const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
|
|
14745
15254
|
let config = {};
|
|
14746
15255
|
try {
|
|
14747
|
-
if (
|
|
14748
|
-
config = JSON.parse(
|
|
15256
|
+
if (import_fs39.default.existsSync(configPath))
|
|
15257
|
+
config = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
|
|
14749
15258
|
} catch {
|
|
14750
15259
|
}
|
|
14751
15260
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -14760,47 +15269,61 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14760
15269
|
approvers.cloud = false;
|
|
14761
15270
|
}
|
|
14762
15271
|
s.approvers = approvers;
|
|
14763
|
-
if (!
|
|
14764
|
-
|
|
14765
|
-
|
|
15272
|
+
if (!import_fs39.default.existsSync(import_path42.default.dirname(configPath)))
|
|
15273
|
+
import_fs39.default.mkdirSync(import_path42.default.dirname(configPath), { recursive: true });
|
|
15274
|
+
import_fs39.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
14766
15275
|
}
|
|
14767
15276
|
if (options.profile && profileName !== "default") {
|
|
14768
|
-
console.log(
|
|
14769
|
-
console.log(
|
|
15277
|
+
console.log(import_chalk26.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
15278
|
+
console.log(import_chalk26.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
14770
15279
|
} else if (options.local) {
|
|
14771
|
-
console.log(
|
|
14772
|
-
console.log(
|
|
15280
|
+
console.log(import_chalk26.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
15281
|
+
console.log(import_chalk26.default.gray(` All decisions stay on this machine.`));
|
|
14773
15282
|
} else {
|
|
14774
|
-
console.log(
|
|
14775
|
-
console.log(
|
|
15283
|
+
console.log(import_chalk26.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
15284
|
+
console.log(import_chalk26.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
14776
15285
|
}
|
|
14777
15286
|
});
|
|
14778
|
-
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
15287
|
+
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
15288
|
+
"after",
|
|
15289
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15290
|
+
).argument(
|
|
15291
|
+
"<target>",
|
|
15292
|
+
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
15293
|
+
).action(async (target) => {
|
|
14779
15294
|
if (target === "gemini") return await setupGemini();
|
|
14780
15295
|
if (target === "claude") return await setupClaude();
|
|
14781
15296
|
if (target === "cursor") return await setupCursor();
|
|
15297
|
+
if (target === "codex") return await setupCodex();
|
|
14782
15298
|
if (target === "windsurf") return await setupWindsurf();
|
|
14783
15299
|
if (target === "vscode") return await setupVSCode();
|
|
14784
15300
|
if (target === "hud") return setupHud();
|
|
14785
15301
|
console.error(
|
|
14786
|
-
|
|
14787
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15302
|
+
import_chalk26.default.red(
|
|
15303
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14788
15304
|
)
|
|
14789
15305
|
);
|
|
14790
15306
|
process.exit(1);
|
|
14791
15307
|
});
|
|
14792
|
-
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
15308
|
+
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
15309
|
+
"after",
|
|
15310
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15311
|
+
).argument(
|
|
15312
|
+
"[target]",
|
|
15313
|
+
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
15314
|
+
).action(async (target) => {
|
|
14793
15315
|
if (!target) {
|
|
14794
|
-
console.log(
|
|
14795
|
-
console.log(" Usage: " +
|
|
15316
|
+
console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
15317
|
+
console.log(" Usage: " + import_chalk26.default.white("node9 setup <target>") + "\n");
|
|
14796
15318
|
console.log(" Targets:");
|
|
14797
|
-
console.log(" " +
|
|
14798
|
-
console.log(" " +
|
|
14799
|
-
console.log(" " +
|
|
14800
|
-
console.log(" " +
|
|
14801
|
-
console.log(" " +
|
|
15319
|
+
console.log(" " + import_chalk26.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
15320
|
+
console.log(" " + import_chalk26.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
15321
|
+
console.log(" " + import_chalk26.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
15322
|
+
console.log(" " + import_chalk26.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
15323
|
+
console.log(" " + import_chalk26.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
15324
|
+
console.log(" " + import_chalk26.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
14802
15325
|
process.stdout.write(
|
|
14803
|
-
" " +
|
|
15326
|
+
" " + import_chalk26.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
14804
15327
|
);
|
|
14805
15328
|
console.log("");
|
|
14806
15329
|
return;
|
|
@@ -14809,61 +15332,67 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
14809
15332
|
if (t === "gemini") return await setupGemini();
|
|
14810
15333
|
if (t === "claude") return await setupClaude();
|
|
14811
15334
|
if (t === "cursor") return await setupCursor();
|
|
15335
|
+
if (t === "codex") return await setupCodex();
|
|
14812
15336
|
if (t === "windsurf") return await setupWindsurf();
|
|
14813
15337
|
if (t === "vscode") return await setupVSCode();
|
|
14814
15338
|
if (t === "hud") return setupHud();
|
|
14815
15339
|
console.error(
|
|
14816
|
-
|
|
14817
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15340
|
+
import_chalk26.default.red(
|
|
15341
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14818
15342
|
)
|
|
14819
15343
|
);
|
|
14820
15344
|
process.exit(1);
|
|
14821
15345
|
});
|
|
14822
|
-
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
15346
|
+
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
15347
|
+
"after",
|
|
15348
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15349
|
+
).argument(
|
|
14823
15350
|
"<target>",
|
|
14824
|
-
"The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
|
|
15351
|
+
"The agent to remove from: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
14825
15352
|
).action((target) => {
|
|
14826
15353
|
let fn;
|
|
14827
15354
|
if (target === "claude") fn = teardownClaude;
|
|
14828
15355
|
else if (target === "gemini") fn = teardownGemini;
|
|
14829
15356
|
else if (target === "cursor") fn = teardownCursor;
|
|
15357
|
+
else if (target === "codex") fn = teardownCodex;
|
|
14830
15358
|
else if (target === "windsurf") fn = teardownWindsurf;
|
|
14831
15359
|
else if (target === "vscode") fn = teardownVSCode;
|
|
14832
15360
|
else if (target === "hud") fn = teardownHud;
|
|
14833
15361
|
else {
|
|
14834
15362
|
console.error(
|
|
14835
|
-
|
|
14836
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15363
|
+
import_chalk26.default.red(
|
|
15364
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14837
15365
|
)
|
|
14838
15366
|
);
|
|
14839
15367
|
process.exit(1);
|
|
14840
15368
|
}
|
|
14841
|
-
console.log(
|
|
15369
|
+
console.log(import_chalk26.default.cyan(`
|
|
14842
15370
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
14843
15371
|
`));
|
|
14844
15372
|
try {
|
|
14845
15373
|
fn();
|
|
14846
15374
|
} catch (err2) {
|
|
14847
|
-
console.error(
|
|
15375
|
+
console.error(import_chalk26.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14848
15376
|
process.exit(1);
|
|
14849
15377
|
}
|
|
14850
|
-
console.log(
|
|
15378
|
+
console.log(import_chalk26.default.gray("\n Restart the agent for changes to take effect."));
|
|
14851
15379
|
});
|
|
14852
15380
|
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) => {
|
|
14853
|
-
console.log(
|
|
14854
|
-
console.log(
|
|
15381
|
+
console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
15382
|
+
console.log(import_chalk26.default.bold("Stopping daemon..."));
|
|
14855
15383
|
try {
|
|
14856
15384
|
stopDaemon();
|
|
14857
|
-
console.log(
|
|
15385
|
+
console.log(import_chalk26.default.green(" \u2705 Daemon stopped"));
|
|
14858
15386
|
} catch {
|
|
14859
|
-
console.log(
|
|
15387
|
+
console.log(import_chalk26.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
14860
15388
|
}
|
|
14861
|
-
console.log(
|
|
15389
|
+
console.log(import_chalk26.default.bold("\nRemoving hooks..."));
|
|
14862
15390
|
let teardownFailed = false;
|
|
14863
15391
|
for (const [label, fn] of [
|
|
14864
15392
|
["Claude", teardownClaude],
|
|
14865
15393
|
["Gemini", teardownGemini],
|
|
14866
15394
|
["Cursor", teardownCursor],
|
|
15395
|
+
["Codex", teardownCodex],
|
|
14867
15396
|
["Windsurf", teardownWindsurf],
|
|
14868
15397
|
["VSCode", teardownVSCode]
|
|
14869
15398
|
]) {
|
|
@@ -14872,45 +15401,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
14872
15401
|
} catch (err2) {
|
|
14873
15402
|
teardownFailed = true;
|
|
14874
15403
|
console.error(
|
|
14875
|
-
|
|
15404
|
+
import_chalk26.default.red(
|
|
14876
15405
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
14877
15406
|
)
|
|
14878
15407
|
);
|
|
14879
15408
|
}
|
|
14880
15409
|
}
|
|
14881
15410
|
if (options.purge) {
|
|
14882
|
-
const node9Dir =
|
|
14883
|
-
if (
|
|
15411
|
+
const node9Dir = import_path42.default.join(import_os35.default.homedir(), ".node9");
|
|
15412
|
+
if (import_fs39.default.existsSync(node9Dir)) {
|
|
14884
15413
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
14885
15414
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
14886
15415
|
default: false
|
|
14887
15416
|
});
|
|
14888
15417
|
if (confirmed) {
|
|
14889
|
-
|
|
14890
|
-
if (
|
|
15418
|
+
import_fs39.default.rmSync(node9Dir, { recursive: true });
|
|
15419
|
+
if (import_fs39.default.existsSync(node9Dir)) {
|
|
14891
15420
|
console.error(
|
|
14892
|
-
|
|
15421
|
+
import_chalk26.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
14893
15422
|
);
|
|
14894
15423
|
} else {
|
|
14895
|
-
console.log(
|
|
15424
|
+
console.log(import_chalk26.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
14896
15425
|
}
|
|
14897
15426
|
} else {
|
|
14898
|
-
console.log(
|
|
15427
|
+
console.log(import_chalk26.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
14899
15428
|
}
|
|
14900
15429
|
} else {
|
|
14901
|
-
console.log(
|
|
15430
|
+
console.log(import_chalk26.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
14902
15431
|
}
|
|
14903
15432
|
} else {
|
|
14904
15433
|
console.log(
|
|
14905
|
-
|
|
15434
|
+
import_chalk26.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
14906
15435
|
);
|
|
14907
15436
|
}
|
|
14908
15437
|
if (teardownFailed) {
|
|
14909
|
-
console.error(
|
|
15438
|
+
console.error(import_chalk26.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
14910
15439
|
process.exit(1);
|
|
14911
15440
|
}
|
|
14912
|
-
console.log(
|
|
14913
|
-
console.log(
|
|
15441
|
+
console.log(import_chalk26.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
15442
|
+
console.log(import_chalk26.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
14914
15443
|
});
|
|
14915
15444
|
registerDoctorCommand(program, version);
|
|
14916
15445
|
program.command("explain").description(
|
|
@@ -14923,7 +15452,7 @@ program.command("explain").description(
|
|
|
14923
15452
|
try {
|
|
14924
15453
|
args = JSON.parse(trimmed);
|
|
14925
15454
|
} catch {
|
|
14926
|
-
console.error(
|
|
15455
|
+
console.error(import_chalk26.default.red(`
|
|
14927
15456
|
\u274C Invalid JSON: ${trimmed}
|
|
14928
15457
|
`));
|
|
14929
15458
|
process.exit(1);
|
|
@@ -14934,54 +15463,54 @@ program.command("explain").description(
|
|
|
14934
15463
|
}
|
|
14935
15464
|
const result = await explainPolicy(tool, args);
|
|
14936
15465
|
console.log("");
|
|
14937
|
-
console.log(
|
|
15466
|
+
console.log(import_chalk26.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
14938
15467
|
console.log("");
|
|
14939
|
-
console.log(` ${
|
|
15468
|
+
console.log(` ${import_chalk26.default.bold("Tool:")} ${import_chalk26.default.white(result.tool)}`);
|
|
14940
15469
|
if (argsRaw) {
|
|
14941
15470
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
14942
|
-
console.log(` ${
|
|
15471
|
+
console.log(` ${import_chalk26.default.bold("Input:")} ${import_chalk26.default.gray(preview2)}`);
|
|
14943
15472
|
}
|
|
14944
15473
|
console.log("");
|
|
14945
|
-
console.log(
|
|
15474
|
+
console.log(import_chalk26.default.bold("Config Sources (Waterfall):"));
|
|
14946
15475
|
for (const tier of result.waterfall) {
|
|
14947
|
-
const num3 =
|
|
15476
|
+
const num3 = import_chalk26.default.gray(` ${tier.tier}.`);
|
|
14948
15477
|
const label = tier.label.padEnd(16);
|
|
14949
15478
|
let statusStr;
|
|
14950
15479
|
if (tier.tier === 1) {
|
|
14951
|
-
statusStr =
|
|
15480
|
+
statusStr = import_chalk26.default.gray(tier.note ?? "");
|
|
14952
15481
|
} else if (tier.status === "active") {
|
|
14953
|
-
const loc = tier.path ?
|
|
14954
|
-
const note = tier.note ?
|
|
14955
|
-
statusStr =
|
|
15482
|
+
const loc = tier.path ? import_chalk26.default.gray(tier.path) : "";
|
|
15483
|
+
const note = tier.note ? import_chalk26.default.gray(`(${tier.note})`) : "";
|
|
15484
|
+
statusStr = import_chalk26.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
14956
15485
|
} else {
|
|
14957
|
-
statusStr =
|
|
15486
|
+
statusStr = import_chalk26.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
14958
15487
|
}
|
|
14959
|
-
console.log(`${num3} ${
|
|
15488
|
+
console.log(`${num3} ${import_chalk26.default.white(label)} ${statusStr}`);
|
|
14960
15489
|
}
|
|
14961
15490
|
console.log("");
|
|
14962
|
-
console.log(
|
|
15491
|
+
console.log(import_chalk26.default.bold("Policy Evaluation:"));
|
|
14963
15492
|
for (const step of result.steps) {
|
|
14964
15493
|
const isFinal = step.isFinal;
|
|
14965
15494
|
let icon;
|
|
14966
|
-
if (step.outcome === "allow") icon =
|
|
14967
|
-
else if (step.outcome === "review") icon =
|
|
14968
|
-
else if (step.outcome === "skip") icon =
|
|
14969
|
-
else icon =
|
|
15495
|
+
if (step.outcome === "allow") icon = import_chalk26.default.green(" \u2705");
|
|
15496
|
+
else if (step.outcome === "review") icon = import_chalk26.default.red(" \u{1F534}");
|
|
15497
|
+
else if (step.outcome === "skip") icon = import_chalk26.default.gray(" \u2500 ");
|
|
15498
|
+
else icon = import_chalk26.default.gray(" \u25CB ");
|
|
14970
15499
|
const name = step.name.padEnd(18);
|
|
14971
|
-
const nameStr = isFinal ?
|
|
14972
|
-
const detail = isFinal ?
|
|
14973
|
-
const arrow = isFinal ?
|
|
15500
|
+
const nameStr = isFinal ? import_chalk26.default.white.bold(name) : import_chalk26.default.white(name);
|
|
15501
|
+
const detail = isFinal ? import_chalk26.default.white(step.detail) : import_chalk26.default.gray(step.detail);
|
|
15502
|
+
const arrow = isFinal ? import_chalk26.default.yellow(" \u2190 STOP") : "";
|
|
14974
15503
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
14975
15504
|
}
|
|
14976
15505
|
console.log("");
|
|
14977
15506
|
if (result.decision === "allow") {
|
|
14978
|
-
console.log(
|
|
15507
|
+
console.log(import_chalk26.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk26.default.gray(" \u2014 no approval needed"));
|
|
14979
15508
|
} else {
|
|
14980
15509
|
console.log(
|
|
14981
|
-
|
|
15510
|
+
import_chalk26.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk26.default.gray(" \u2014 human approval required")
|
|
14982
15511
|
);
|
|
14983
15512
|
if (result.blockedByLabel) {
|
|
14984
|
-
console.log(
|
|
15513
|
+
console.log(import_chalk26.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
14985
15514
|
}
|
|
14986
15515
|
}
|
|
14987
15516
|
console.log("");
|
|
@@ -14996,7 +15525,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
14996
15525
|
try {
|
|
14997
15526
|
await startTail2(options);
|
|
14998
15527
|
} catch (err2) {
|
|
14999
|
-
console.error(
|
|
15528
|
+
console.error(import_chalk26.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
15000
15529
|
process.exit(1);
|
|
15001
15530
|
}
|
|
15002
15531
|
});
|
|
@@ -15029,14 +15558,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
15029
15558
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
15030
15559
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
15031
15560
|
if (subcommand === "debug") {
|
|
15032
|
-
const flagFile =
|
|
15561
|
+
const flagFile = import_path42.default.join(import_os35.default.homedir(), ".node9", "hud-debug");
|
|
15033
15562
|
if (state === "on") {
|
|
15034
|
-
|
|
15035
|
-
|
|
15563
|
+
import_fs39.default.mkdirSync(import_path42.default.dirname(flagFile), { recursive: true });
|
|
15564
|
+
import_fs39.default.writeFileSync(flagFile, "");
|
|
15036
15565
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
15037
15566
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
15038
15567
|
} else if (state === "off") {
|
|
15039
|
-
if (
|
|
15568
|
+
if (import_fs39.default.existsSync(flagFile)) import_fs39.default.unlinkSync(flagFile);
|
|
15040
15569
|
console.log("HUD debug logging disabled.");
|
|
15041
15570
|
} else {
|
|
15042
15571
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -15051,7 +15580,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
15051
15580
|
const ms = parseDuration(options.duration);
|
|
15052
15581
|
if (ms === null) {
|
|
15053
15582
|
console.error(
|
|
15054
|
-
|
|
15583
|
+
import_chalk26.default.red(`
|
|
15055
15584
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
15056
15585
|
`)
|
|
15057
15586
|
);
|
|
@@ -15059,20 +15588,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
15059
15588
|
}
|
|
15060
15589
|
pauseNode9(ms, options.duration);
|
|
15061
15590
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
15062
|
-
console.log(
|
|
15591
|
+
console.log(import_chalk26.default.yellow(`
|
|
15063
15592
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
15064
|
-
console.log(
|
|
15065
|
-
console.log(
|
|
15593
|
+
console.log(import_chalk26.default.gray(` All tool calls will be allowed without review.`));
|
|
15594
|
+
console.log(import_chalk26.default.gray(` Run "node9 resume" to re-enable early.
|
|
15066
15595
|
`));
|
|
15067
15596
|
});
|
|
15068
15597
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
15069
15598
|
const { paused } = checkPause();
|
|
15070
15599
|
if (!paused) {
|
|
15071
|
-
console.log(
|
|
15600
|
+
console.log(import_chalk26.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
15072
15601
|
return;
|
|
15073
15602
|
}
|
|
15074
15603
|
resumeNode9();
|
|
15075
|
-
console.log(
|
|
15604
|
+
console.log(import_chalk26.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
15076
15605
|
});
|
|
15077
15606
|
var HOOK_BASED_AGENTS = {
|
|
15078
15607
|
claude: "claude",
|
|
@@ -15085,15 +15614,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
15085
15614
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
15086
15615
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
15087
15616
|
console.error(
|
|
15088
|
-
|
|
15617
|
+
import_chalk26.default.yellow(`
|
|
15089
15618
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
15090
15619
|
);
|
|
15091
|
-
console.error(
|
|
15620
|
+
console.error(import_chalk26.default.white(`
|
|
15092
15621
|
"${target}" uses its own hook system. Use:`));
|
|
15093
15622
|
console.error(
|
|
15094
|
-
|
|
15623
|
+
import_chalk26.default.green(` node9 addto ${target} `) + import_chalk26.default.gray("# one-time setup")
|
|
15095
15624
|
);
|
|
15096
|
-
console.error(
|
|
15625
|
+
console.error(import_chalk26.default.green(` ${target} `) + import_chalk26.default.gray("# run normally"));
|
|
15097
15626
|
process.exit(1);
|
|
15098
15627
|
}
|
|
15099
15628
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -15110,7 +15639,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
15110
15639
|
}
|
|
15111
15640
|
);
|
|
15112
15641
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
15113
|
-
console.error(
|
|
15642
|
+
console.error(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
15114
15643
|
const daemonReady = await autoStartDaemonAndWait();
|
|
15115
15644
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
15116
15645
|
}
|
|
@@ -15123,12 +15652,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
15123
15652
|
}
|
|
15124
15653
|
if (!result.approved) {
|
|
15125
15654
|
console.error(
|
|
15126
|
-
|
|
15655
|
+
import_chalk26.default.red(`
|
|
15127
15656
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
15128
15657
|
);
|
|
15129
15658
|
process.exit(1);
|
|
15130
15659
|
}
|
|
15131
|
-
console.error(
|
|
15660
|
+
console.error(import_chalk26.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
15132
15661
|
await runProxy(fullCommand);
|
|
15133
15662
|
} else {
|
|
15134
15663
|
program.help();
|
|
@@ -15142,14 +15671,15 @@ registerSyncCommand(program);
|
|
|
15142
15671
|
registerAgentsCommand(program);
|
|
15143
15672
|
registerScanCommand(program);
|
|
15144
15673
|
registerSessionsCommand(program);
|
|
15674
|
+
registerDlpCommand(program);
|
|
15145
15675
|
if (process.argv[2] !== "daemon") {
|
|
15146
15676
|
process.on("unhandledRejection", (reason) => {
|
|
15147
15677
|
const isCheckHook = process.argv[2] === "check";
|
|
15148
15678
|
if (isCheckHook) {
|
|
15149
15679
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
15150
|
-
const logPath =
|
|
15680
|
+
const logPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "hook-debug.log");
|
|
15151
15681
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
15152
|
-
|
|
15682
|
+
import_fs39.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
15153
15683
|
`);
|
|
15154
15684
|
}
|
|
15155
15685
|
process.exit(0);
|