@node9/proxy 1.9.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -7
- package/dist/cli.js +1154 -623
- package/dist/cli.mjs +1136 -605
- package/dist/index.js +127 -23
- package/dist/index.mjs +125 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -76,6 +76,11 @@ __export(audit_exports, {
|
|
|
76
76
|
appendToLog: () => appendToLog,
|
|
77
77
|
redactSecrets: () => redactSecrets
|
|
78
78
|
});
|
|
79
|
+
function isTestCall(toolName, args) {
|
|
80
|
+
if (toolName !== "Bash" && toolName !== "bash") return false;
|
|
81
|
+
const cmd = args?.command;
|
|
82
|
+
return typeof cmd === "string" && TEST_COMMAND_RE.test(cmd);
|
|
83
|
+
}
|
|
79
84
|
function redactSecrets(text) {
|
|
80
85
|
if (!text) return text;
|
|
81
86
|
let redacted = text;
|
|
@@ -111,12 +116,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
111
116
|
}
|
|
112
117
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
113
118
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
119
|
+
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
114
120
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
115
121
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
116
122
|
tool: toolName,
|
|
117
123
|
...argsField,
|
|
118
124
|
decision,
|
|
119
125
|
checkedBy,
|
|
126
|
+
...testRun,
|
|
120
127
|
agent: meta?.agent,
|
|
121
128
|
mcpServer: meta?.mcpServer,
|
|
122
129
|
hostname: import_os.default.hostname()
|
|
@@ -129,7 +136,7 @@ function appendConfigAudit(entry) {
|
|
|
129
136
|
hostname: import_os.default.hostname()
|
|
130
137
|
});
|
|
131
138
|
}
|
|
132
|
-
var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG;
|
|
139
|
+
var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, TEST_COMMAND_RE;
|
|
133
140
|
var init_audit = __esm({
|
|
134
141
|
"src/audit/index.ts"() {
|
|
135
142
|
"use strict";
|
|
@@ -139,6 +146,7 @@ var init_audit = __esm({
|
|
|
139
146
|
init_hasher();
|
|
140
147
|
LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
|
|
141
148
|
HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
|
|
149
|
+
TEST_COMMAND_RE = /(?:^|\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;
|
|
142
150
|
}
|
|
143
151
|
});
|
|
144
152
|
|
|
@@ -160,8 +168,8 @@ function sanitizeConfig(raw) {
|
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
const lines = result.error.issues.map((issue) => {
|
|
163
|
-
const
|
|
164
|
-
return ` \u2022 ${
|
|
171
|
+
const path34 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
172
|
+
return ` \u2022 ${path34}: ${issue.message}`;
|
|
165
173
|
});
|
|
166
174
|
return {
|
|
167
175
|
sanitized,
|
|
@@ -214,6 +222,7 @@ var init_config_schema = __esm({
|
|
|
214
222
|
errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
|
|
215
223
|
}),
|
|
216
224
|
reason: import_zod.z.string().optional(),
|
|
225
|
+
description: import_zod.z.string().optional(),
|
|
217
226
|
// Unknown predicate names are filtered out rather than failing the whole rule.
|
|
218
227
|
// Failing the whole z.array() would cause sanitizeConfig to drop the entire
|
|
219
228
|
// `policy` top-level key, silently disabling ALL smart rules in the config.
|
|
@@ -260,6 +269,11 @@ var init_config_schema = __esm({
|
|
|
260
269
|
dlp: import_zod.z.object({
|
|
261
270
|
enabled: import_zod.z.boolean().optional(),
|
|
262
271
|
scanIgnoredTools: import_zod.z.boolean().optional()
|
|
272
|
+
}).optional(),
|
|
273
|
+
loopDetection: import_zod.z.object({
|
|
274
|
+
enabled: import_zod.z.boolean().optional(),
|
|
275
|
+
threshold: import_zod.z.number().min(2).optional(),
|
|
276
|
+
windowSeconds: import_zod.z.number().min(10).optional()
|
|
263
277
|
}).optional()
|
|
264
278
|
}).optional(),
|
|
265
279
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -554,7 +568,8 @@ function getConfig(cwd) {
|
|
|
554
568
|
onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
|
|
555
569
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
556
570
|
},
|
|
557
|
-
dlp: { ...DEFAULT_CONFIG.policy.dlp }
|
|
571
|
+
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
572
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
558
573
|
};
|
|
559
574
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
560
575
|
const applyLayer = (source) => {
|
|
@@ -593,6 +608,13 @@ function getConfig(cwd) {
|
|
|
593
608
|
if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
|
|
594
609
|
if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
|
|
595
610
|
}
|
|
611
|
+
if (p.loopDetection) {
|
|
612
|
+
const ld = p.loopDetection;
|
|
613
|
+
if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
|
|
614
|
+
if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
|
|
615
|
+
if (ld.windowSeconds !== void 0)
|
|
616
|
+
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
617
|
+
}
|
|
596
618
|
const envs = source.environments || {};
|
|
597
619
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
598
620
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -791,7 +813,8 @@ var init_config = __esm({
|
|
|
791
813
|
}
|
|
792
814
|
],
|
|
793
815
|
verdict: "block",
|
|
794
|
-
reason: "Recursive delete of home directory is irreversible"
|
|
816
|
+
reason: "Recursive delete of home directory is irreversible",
|
|
817
|
+
description: "The AI wants to recursively delete your home directory. This will permanently destroy all your personal files and cannot be undone."
|
|
795
818
|
},
|
|
796
819
|
// ── SQL safety ────────────────────────────────────────────────────────
|
|
797
820
|
{
|
|
@@ -803,7 +826,8 @@ var init_config = __esm({
|
|
|
803
826
|
],
|
|
804
827
|
conditionMode: "all",
|
|
805
828
|
verdict: "review",
|
|
806
|
-
reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table"
|
|
829
|
+
reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table",
|
|
830
|
+
description: "The AI is running a SQL statement that will modify every row in the table \u2014 no WHERE filter was found. This could wipe or corrupt all your data."
|
|
807
831
|
},
|
|
808
832
|
{
|
|
809
833
|
name: "review-drop-truncate-shell",
|
|
@@ -818,7 +842,8 @@ var init_config = __esm({
|
|
|
818
842
|
],
|
|
819
843
|
conditionMode: "all",
|
|
820
844
|
verdict: "review",
|
|
821
|
-
reason: "SQL DDL destructive statement inside a shell command"
|
|
845
|
+
reason: "SQL DDL destructive statement inside a shell command",
|
|
846
|
+
description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
|
|
822
847
|
},
|
|
823
848
|
// ── Git safety ────────────────────────────────────────────────────────
|
|
824
849
|
{
|
|
@@ -834,7 +859,8 @@ var init_config = __esm({
|
|
|
834
859
|
],
|
|
835
860
|
conditionMode: "all",
|
|
836
861
|
verdict: "block",
|
|
837
|
-
reason: "Force push overwrites remote history and cannot be undone"
|
|
862
|
+
reason: "Force push overwrites remote history and cannot be undone",
|
|
863
|
+
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."
|
|
838
864
|
},
|
|
839
865
|
{
|
|
840
866
|
name: "review-git-push",
|
|
@@ -849,7 +875,8 @@ var init_config = __esm({
|
|
|
849
875
|
],
|
|
850
876
|
conditionMode: "all",
|
|
851
877
|
verdict: "review",
|
|
852
|
-
reason: "git push sends changes to a shared remote"
|
|
878
|
+
reason: "git push sends changes to a shared remote",
|
|
879
|
+
description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
|
|
853
880
|
},
|
|
854
881
|
{
|
|
855
882
|
name: "review-git-destructive",
|
|
@@ -864,7 +891,8 @@ var init_config = __esm({
|
|
|
864
891
|
],
|
|
865
892
|
conditionMode: "all",
|
|
866
893
|
verdict: "review",
|
|
867
|
-
reason: "Destructive git operation \u2014 discards history or working-tree changes"
|
|
894
|
+
reason: "Destructive git operation \u2014 discards history or working-tree changes",
|
|
895
|
+
description: "The AI wants to run a destructive git operation (reset, rebase, clean, or branch delete) that can permanently discard commits or uncommitted work."
|
|
868
896
|
},
|
|
869
897
|
// ── Shell safety ──────────────────────────────────────────────────────
|
|
870
898
|
{
|
|
@@ -873,7 +901,8 @@ var init_config = __esm({
|
|
|
873
901
|
conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
|
|
874
902
|
conditionMode: "all",
|
|
875
903
|
verdict: "review",
|
|
876
|
-
reason: "Command requires elevated privileges"
|
|
904
|
+
reason: "Command requires elevated privileges",
|
|
905
|
+
description: "The AI wants to run a command as root (sudo). Commands with root access can modify system files, install software, or change security settings."
|
|
877
906
|
},
|
|
878
907
|
{
|
|
879
908
|
name: "review-curl-pipe-shell",
|
|
@@ -888,10 +917,12 @@ var init_config = __esm({
|
|
|
888
917
|
],
|
|
889
918
|
conditionMode: "all",
|
|
890
919
|
verdict: "block",
|
|
891
|
-
reason: "Piping remote script into a shell is a supply-chain attack vector"
|
|
920
|
+
reason: "Piping remote script into a shell is a supply-chain attack vector",
|
|
921
|
+
description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
|
|
892
922
|
}
|
|
893
923
|
],
|
|
894
|
-
dlp: { enabled: true, scanIgnoredTools: true }
|
|
924
|
+
dlp: { enabled: true, scanIgnoredTools: true },
|
|
925
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
895
926
|
},
|
|
896
927
|
environments: {}
|
|
897
928
|
};
|
|
@@ -921,7 +952,8 @@ var init_config = __esm({
|
|
|
921
952
|
tool: "*",
|
|
922
953
|
conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
|
|
923
954
|
verdict: "review",
|
|
924
|
-
reason: "rm can permanently delete files \u2014 confirm the target path"
|
|
955
|
+
reason: "rm can permanently delete files \u2014 confirm the target path",
|
|
956
|
+
description: "The AI wants to delete files. Unlike moving to trash, rm is permanent \u2014 the files cannot be recovered without a backup."
|
|
925
957
|
},
|
|
926
958
|
// ── SQL safety (Safe by Default) ──────────────────────────────────────────
|
|
927
959
|
// These rules fire when an AI calls a database tool directly (e.g. MCP postgres,
|
|
@@ -933,14 +965,16 @@ var init_config = __esm({
|
|
|
933
965
|
tool: "*",
|
|
934
966
|
conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
|
|
935
967
|
verdict: "review",
|
|
936
|
-
reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead"
|
|
968
|
+
reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead",
|
|
969
|
+
description: "The AI wants to drop a database table. This permanently deletes the table and all its data \u2014 there is no undo."
|
|
937
970
|
},
|
|
938
971
|
{
|
|
939
972
|
name: "review-truncate-sql",
|
|
940
973
|
tool: "*",
|
|
941
974
|
conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
|
|
942
975
|
verdict: "review",
|
|
943
|
-
reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead"
|
|
976
|
+
reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead",
|
|
977
|
+
description: "The AI wants to truncate a database table, which instantly deletes every row. The table structure remains but all data is gone."
|
|
944
978
|
},
|
|
945
979
|
{
|
|
946
980
|
name: "review-drop-column-sql",
|
|
@@ -949,7 +983,8 @@ var init_config = __esm({
|
|
|
949
983
|
{ field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
|
|
950
984
|
],
|
|
951
985
|
verdict: "review",
|
|
952
|
-
reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead"
|
|
986
|
+
reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead",
|
|
987
|
+
description: "The AI wants to drop a column from a database table. This permanently removes the column and all its data from every row."
|
|
953
988
|
}
|
|
954
989
|
];
|
|
955
990
|
cachedConfig = null;
|
|
@@ -1691,9 +1726,9 @@ function matchesPattern(text, patterns) {
|
|
|
1691
1726
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1692
1727
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1693
1728
|
}
|
|
1694
|
-
function getNestedValue(obj,
|
|
1729
|
+
function getNestedValue(obj, path34) {
|
|
1695
1730
|
if (!obj || typeof obj !== "object") return null;
|
|
1696
|
-
return
|
|
1731
|
+
return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1697
1732
|
}
|
|
1698
1733
|
function shouldSnapshot(toolName, args, config) {
|
|
1699
1734
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1844,6 +1879,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
1844
1879
|
reason: matchedRule.reason,
|
|
1845
1880
|
tier: 2,
|
|
1846
1881
|
ruleName: matchedRule.name ?? matchedRule.tool,
|
|
1882
|
+
...(matchedRule.description ?? matchedRule.reason) && {
|
|
1883
|
+
ruleDescription: matchedRule.description ?? matchedRule.reason
|
|
1884
|
+
},
|
|
1847
1885
|
...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
|
|
1848
1886
|
dependsOnStatePredicates: matchedRule.dependsOnState
|
|
1849
1887
|
},
|
|
@@ -3063,6 +3101,58 @@ var init_cloud = __esm({
|
|
|
3063
3101
|
}
|
|
3064
3102
|
});
|
|
3065
3103
|
|
|
3104
|
+
// src/loop-detector.ts
|
|
3105
|
+
function loopStateFile() {
|
|
3106
|
+
return import_path14.default.join(import_os10.default.homedir(), ".node9", "loop-state.json");
|
|
3107
|
+
}
|
|
3108
|
+
function computeArgsHash(args) {
|
|
3109
|
+
const str = JSON.stringify(args ?? "");
|
|
3110
|
+
return import_crypto3.default.createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
3111
|
+
}
|
|
3112
|
+
function readState() {
|
|
3113
|
+
try {
|
|
3114
|
+
if (!import_fs11.default.existsSync(loopStateFile())) return [];
|
|
3115
|
+
const raw = import_fs11.default.readFileSync(loopStateFile(), "utf-8");
|
|
3116
|
+
const parsed = JSON.parse(raw);
|
|
3117
|
+
if (!Array.isArray(parsed)) return [];
|
|
3118
|
+
return parsed;
|
|
3119
|
+
} catch {
|
|
3120
|
+
return [];
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
function writeState(records) {
|
|
3124
|
+
const dir = import_path14.default.dirname(loopStateFile());
|
|
3125
|
+
if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
|
|
3126
|
+
const tmpPath = `${loopStateFile()}.${import_os10.default.hostname()}.${process.pid}.tmp`;
|
|
3127
|
+
import_fs11.default.writeFileSync(tmpPath, JSON.stringify(records));
|
|
3128
|
+
import_fs11.default.renameSync(tmpPath, loopStateFile());
|
|
3129
|
+
}
|
|
3130
|
+
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
3131
|
+
try {
|
|
3132
|
+
const hash = computeArgsHash(args);
|
|
3133
|
+
const now = Date.now();
|
|
3134
|
+
const cutoff = now - windowMs;
|
|
3135
|
+
const records = readState().filter((r) => r.ts >= cutoff);
|
|
3136
|
+
records.push({ t: tool, h: hash, ts: now });
|
|
3137
|
+
const count = records.filter((r) => r.t === tool && r.h === hash).length;
|
|
3138
|
+
writeState(records.slice(-MAX_RECORDS));
|
|
3139
|
+
return { looping: count >= threshold, count };
|
|
3140
|
+
} catch {
|
|
3141
|
+
return { looping: false, count: 0 };
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
var import_fs11, import_path14, import_os10, import_crypto3, MAX_RECORDS;
|
|
3145
|
+
var init_loop_detector = __esm({
|
|
3146
|
+
"src/loop-detector.ts"() {
|
|
3147
|
+
"use strict";
|
|
3148
|
+
import_fs11 = __toESM(require("fs"));
|
|
3149
|
+
import_path14 = __toESM(require("path"));
|
|
3150
|
+
import_os10 = __toESM(require("os"));
|
|
3151
|
+
import_crypto3 = __toESM(require("crypto"));
|
|
3152
|
+
MAX_RECORDS = 500;
|
|
3153
|
+
}
|
|
3154
|
+
});
|
|
3155
|
+
|
|
3066
3156
|
// src/auth/orchestrator.ts
|
|
3067
3157
|
function isWriteTool(toolName) {
|
|
3068
3158
|
const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
|
|
@@ -3103,7 +3193,7 @@ function notifyActivity(data) {
|
|
|
3103
3193
|
}
|
|
3104
3194
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
3105
3195
|
if (!options?.calledFromDaemon) {
|
|
3106
|
-
const actId = (0,
|
|
3196
|
+
const actId = (0, import_crypto4.randomUUID)();
|
|
3107
3197
|
const actTs = Date.now();
|
|
3108
3198
|
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
3109
3199
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
@@ -3150,6 +3240,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3150
3240
|
let explainableLabel = "Local Config";
|
|
3151
3241
|
let policyMatchedField;
|
|
3152
3242
|
let policyMatchedWord;
|
|
3243
|
+
let policyRuleDescription;
|
|
3153
3244
|
let riskMetadata;
|
|
3154
3245
|
let statefulRecoveryCommand;
|
|
3155
3246
|
let localSmartRuleMatched = false;
|
|
@@ -3243,6 +3334,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3243
3334
|
return { approved: true, checkedBy: "audit" };
|
|
3244
3335
|
}
|
|
3245
3336
|
if (!taintWarning && !isIgnoredTool(toolName)) {
|
|
3337
|
+
const ld = config.policy.loopDetection;
|
|
3338
|
+
if (ld.enabled) {
|
|
3339
|
+
const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
|
|
3340
|
+
if (loopResult.looping) {
|
|
3341
|
+
const reason = `It looks like you've called "${toolName}" ${loopResult.count} times with identical arguments in the last ${ld.windowSeconds}s. Are you stuck? Step back and reconsider your approach \u2014 what are you actually trying to accomplish, and is there a different way to get there?`;
|
|
3342
|
+
if (!isManual)
|
|
3343
|
+
appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
|
|
3344
|
+
return {
|
|
3345
|
+
approved: false,
|
|
3346
|
+
reason,
|
|
3347
|
+
blockedBy: "loop-detection",
|
|
3348
|
+
blockedByLabel: "\u{1F504} Loop Detected"
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3246
3352
|
if (getActiveTrustSession(toolName)) {
|
|
3247
3353
|
if (approvers.cloud && creds?.apiKey)
|
|
3248
3354
|
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
@@ -3289,7 +3395,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3289
3395
|
blockedBy: "local-config",
|
|
3290
3396
|
blockedByLabel: policyResult.blockedByLabel,
|
|
3291
3397
|
ruleHit: policyResult.ruleName,
|
|
3292
|
-
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
|
|
3398
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
3399
|
+
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
3293
3400
|
};
|
|
3294
3401
|
}
|
|
3295
3402
|
}
|
|
@@ -3297,6 +3404,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3297
3404
|
policyMatchedField = policyResult.matchedField;
|
|
3298
3405
|
policyMatchedWord = policyResult.matchedWord;
|
|
3299
3406
|
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
3407
|
+
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
3408
|
+
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
3300
3409
|
riskMetadata = computeRiskMetadata(
|
|
3301
3410
|
args,
|
|
3302
3411
|
policyResult.tier ?? 6,
|
|
@@ -3560,13 +3669,14 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3560
3669
|
hashAuditArgs
|
|
3561
3670
|
);
|
|
3562
3671
|
}
|
|
3563
|
-
|
|
3672
|
+
const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
|
|
3673
|
+
return enrichedResult;
|
|
3564
3674
|
}
|
|
3565
|
-
var
|
|
3675
|
+
var import_crypto4, WRITE_TOOLS;
|
|
3566
3676
|
var init_orchestrator = __esm({
|
|
3567
3677
|
"src/auth/orchestrator.ts"() {
|
|
3568
3678
|
"use strict";
|
|
3569
|
-
|
|
3679
|
+
import_crypto4 = require("crypto");
|
|
3570
3680
|
init_native();
|
|
3571
3681
|
init_context_sniper();
|
|
3572
3682
|
init_dlp();
|
|
@@ -3576,6 +3686,7 @@ var init_orchestrator = __esm({
|
|
|
3576
3686
|
init_state();
|
|
3577
3687
|
init_daemon();
|
|
3578
3688
|
init_cloud();
|
|
3689
|
+
init_loop_detector();
|
|
3579
3690
|
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
3580
3691
|
"write",
|
|
3581
3692
|
"write_file",
|
|
@@ -5249,11 +5360,11 @@ function commonPathPrefix(paths) {
|
|
|
5249
5360
|
const prefix = common.join("/").replace(/\/?$/, "/");
|
|
5250
5361
|
return prefix.length > 1 ? prefix : null;
|
|
5251
5362
|
}
|
|
5252
|
-
var
|
|
5363
|
+
var import_crypto5, SuggestionTracker;
|
|
5253
5364
|
var init_suggestion_tracker = __esm({
|
|
5254
5365
|
"src/daemon/suggestion-tracker.ts"() {
|
|
5255
5366
|
"use strict";
|
|
5256
|
-
|
|
5367
|
+
import_crypto5 = require("crypto");
|
|
5257
5368
|
SuggestionTracker = class {
|
|
5258
5369
|
events = /* @__PURE__ */ new Map();
|
|
5259
5370
|
threshold;
|
|
@@ -5299,7 +5410,7 @@ var init_suggestion_tracker = __esm({
|
|
|
5299
5410
|
}
|
|
5300
5411
|
} : { type: "ignoredTool", toolName };
|
|
5301
5412
|
return {
|
|
5302
|
-
id: (0,
|
|
5413
|
+
id: (0, import_crypto5.randomUUID)(),
|
|
5303
5414
|
toolName,
|
|
5304
5415
|
allowCount: events.length,
|
|
5305
5416
|
suggestedRule,
|
|
@@ -5313,12 +5424,12 @@ var init_suggestion_tracker = __esm({
|
|
|
5313
5424
|
});
|
|
5314
5425
|
|
|
5315
5426
|
// src/daemon/taint-store.ts
|
|
5316
|
-
var
|
|
5427
|
+
var import_fs13, import_path16, DEFAULT_TTL_MS, TaintStore;
|
|
5317
5428
|
var init_taint_store = __esm({
|
|
5318
5429
|
"src/daemon/taint-store.ts"() {
|
|
5319
5430
|
"use strict";
|
|
5320
|
-
|
|
5321
|
-
|
|
5431
|
+
import_fs13 = __toESM(require("fs"));
|
|
5432
|
+
import_path16 = __toESM(require("path"));
|
|
5322
5433
|
DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
5323
5434
|
TaintStore = class {
|
|
5324
5435
|
records = /* @__PURE__ */ new Map();
|
|
@@ -5383,9 +5494,9 @@ var init_taint_store = __esm({
|
|
|
5383
5494
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
5384
5495
|
_resolve(filePath) {
|
|
5385
5496
|
try {
|
|
5386
|
-
return
|
|
5497
|
+
return import_fs13.default.realpathSync.native(import_path16.default.resolve(filePath));
|
|
5387
5498
|
} catch {
|
|
5388
|
-
return
|
|
5499
|
+
return import_path16.default.resolve(filePath);
|
|
5389
5500
|
}
|
|
5390
5501
|
}
|
|
5391
5502
|
};
|
|
@@ -5503,8 +5614,8 @@ var init_session_history = __esm({
|
|
|
5503
5614
|
// src/daemon/state.ts
|
|
5504
5615
|
function loadInsightCounts() {
|
|
5505
5616
|
try {
|
|
5506
|
-
if (!
|
|
5507
|
-
const data = JSON.parse(
|
|
5617
|
+
if (!import_fs14.default.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
5618
|
+
const data = JSON.parse(import_fs14.default.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
|
|
5508
5619
|
for (const [tool, count] of Object.entries(data)) {
|
|
5509
5620
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
5510
5621
|
}
|
|
@@ -5543,23 +5654,23 @@ function markRejectionHandlerRegistered() {
|
|
|
5543
5654
|
daemonRejectionHandlerRegistered = true;
|
|
5544
5655
|
}
|
|
5545
5656
|
function atomicWriteSync2(filePath, data, options) {
|
|
5546
|
-
const dir =
|
|
5547
|
-
if (!
|
|
5548
|
-
const tmpPath = `${filePath}.${(0,
|
|
5657
|
+
const dir = import_path17.default.dirname(filePath);
|
|
5658
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
5659
|
+
const tmpPath = `${filePath}.${(0, import_crypto6.randomUUID)()}.tmp`;
|
|
5549
5660
|
try {
|
|
5550
|
-
|
|
5661
|
+
import_fs14.default.writeFileSync(tmpPath, data, options);
|
|
5551
5662
|
} catch (err2) {
|
|
5552
5663
|
try {
|
|
5553
|
-
|
|
5664
|
+
import_fs14.default.unlinkSync(tmpPath);
|
|
5554
5665
|
} catch {
|
|
5555
5666
|
}
|
|
5556
5667
|
throw err2;
|
|
5557
5668
|
}
|
|
5558
5669
|
try {
|
|
5559
|
-
|
|
5670
|
+
import_fs14.default.renameSync(tmpPath, filePath);
|
|
5560
5671
|
} catch (err2) {
|
|
5561
5672
|
try {
|
|
5562
|
-
|
|
5673
|
+
import_fs14.default.unlinkSync(tmpPath);
|
|
5563
5674
|
} catch {
|
|
5564
5675
|
}
|
|
5565
5676
|
throw err2;
|
|
@@ -5583,16 +5694,16 @@ function appendAuditLog(data) {
|
|
|
5583
5694
|
decision: data.decision,
|
|
5584
5695
|
source: "daemon"
|
|
5585
5696
|
};
|
|
5586
|
-
const dir =
|
|
5587
|
-
if (!
|
|
5588
|
-
|
|
5697
|
+
const dir = import_path17.default.dirname(AUDIT_LOG_FILE);
|
|
5698
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
5699
|
+
import_fs14.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
5589
5700
|
} catch {
|
|
5590
5701
|
}
|
|
5591
5702
|
}
|
|
5592
5703
|
function getAuditHistory(limit = 20) {
|
|
5593
5704
|
try {
|
|
5594
|
-
if (!
|
|
5595
|
-
const lines =
|
|
5705
|
+
if (!import_fs14.default.existsSync(AUDIT_LOG_FILE)) return [];
|
|
5706
|
+
const lines = import_fs14.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
5596
5707
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
5597
5708
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
5598
5709
|
} catch {
|
|
@@ -5601,19 +5712,19 @@ function getAuditHistory(limit = 20) {
|
|
|
5601
5712
|
}
|
|
5602
5713
|
function getOrgName() {
|
|
5603
5714
|
try {
|
|
5604
|
-
if (
|
|
5715
|
+
if (import_fs14.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
5605
5716
|
} catch {
|
|
5606
5717
|
}
|
|
5607
5718
|
return null;
|
|
5608
5719
|
}
|
|
5609
5720
|
function hasStoredSlackKey() {
|
|
5610
|
-
return
|
|
5721
|
+
return import_fs14.default.existsSync(CREDENTIALS_FILE);
|
|
5611
5722
|
}
|
|
5612
5723
|
function writeGlobalSetting(key, value) {
|
|
5613
5724
|
let config = {};
|
|
5614
5725
|
try {
|
|
5615
|
-
if (
|
|
5616
|
-
config = JSON.parse(
|
|
5726
|
+
if (import_fs14.default.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
5727
|
+
config = JSON.parse(import_fs14.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
5617
5728
|
}
|
|
5618
5729
|
} catch {
|
|
5619
5730
|
}
|
|
@@ -5625,8 +5736,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5625
5736
|
try {
|
|
5626
5737
|
let trust = { entries: [] };
|
|
5627
5738
|
try {
|
|
5628
|
-
if (
|
|
5629
|
-
trust = JSON.parse(
|
|
5739
|
+
if (import_fs14.default.existsSync(TRUST_FILE2))
|
|
5740
|
+
trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
5630
5741
|
} catch {
|
|
5631
5742
|
}
|
|
5632
5743
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -5637,8 +5748,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5637
5748
|
}
|
|
5638
5749
|
function readPersistentDecisions() {
|
|
5639
5750
|
try {
|
|
5640
|
-
if (
|
|
5641
|
-
return JSON.parse(
|
|
5751
|
+
if (import_fs14.default.existsSync(DECISIONS_FILE)) {
|
|
5752
|
+
return JSON.parse(import_fs14.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
5642
5753
|
}
|
|
5643
5754
|
} catch {
|
|
5644
5755
|
}
|
|
@@ -5675,7 +5786,7 @@ function estimateToolCost(tool, args) {
|
|
|
5675
5786
|
const filePath = a.file_path ?? a.path;
|
|
5676
5787
|
if (filePath) {
|
|
5677
5788
|
try {
|
|
5678
|
-
const bytes =
|
|
5789
|
+
const bytes = import_fs14.default.statSync(filePath).size;
|
|
5679
5790
|
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
5680
5791
|
} catch {
|
|
5681
5792
|
}
|
|
@@ -5733,7 +5844,7 @@ function abandonPending() {
|
|
|
5733
5844
|
});
|
|
5734
5845
|
if (autoStarted) {
|
|
5735
5846
|
try {
|
|
5736
|
-
|
|
5847
|
+
import_fs14.default.unlinkSync(DAEMON_PID_FILE);
|
|
5737
5848
|
} catch {
|
|
5738
5849
|
}
|
|
5739
5850
|
setTimeout(() => {
|
|
@@ -5744,7 +5855,7 @@ function abandonPending() {
|
|
|
5744
5855
|
}
|
|
5745
5856
|
function startActivitySocket() {
|
|
5746
5857
|
try {
|
|
5747
|
-
|
|
5858
|
+
import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5748
5859
|
} catch {
|
|
5749
5860
|
}
|
|
5750
5861
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -5825,34 +5936,34 @@ function startActivitySocket() {
|
|
|
5825
5936
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
5826
5937
|
process.on("exit", () => {
|
|
5827
5938
|
try {
|
|
5828
|
-
|
|
5939
|
+
import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5829
5940
|
} catch {
|
|
5830
5941
|
}
|
|
5831
5942
|
});
|
|
5832
5943
|
}
|
|
5833
|
-
var import_net2,
|
|
5944
|
+
var import_net2, import_fs14, import_path17, import_os12, import_child_process3, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
|
|
5834
5945
|
var init_state2 = __esm({
|
|
5835
5946
|
"src/daemon/state.ts"() {
|
|
5836
5947
|
"use strict";
|
|
5837
5948
|
import_net2 = __toESM(require("net"));
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5949
|
+
import_fs14 = __toESM(require("fs"));
|
|
5950
|
+
import_path17 = __toESM(require("path"));
|
|
5951
|
+
import_os12 = __toESM(require("os"));
|
|
5841
5952
|
import_child_process3 = require("child_process");
|
|
5842
|
-
|
|
5953
|
+
import_crypto6 = require("crypto");
|
|
5843
5954
|
init_daemon();
|
|
5844
5955
|
init_suggestion_tracker();
|
|
5845
5956
|
init_taint_store();
|
|
5846
5957
|
init_session_counters();
|
|
5847
5958
|
init_session_history();
|
|
5848
|
-
homeDir =
|
|
5849
|
-
DAEMON_PID_FILE =
|
|
5850
|
-
DECISIONS_FILE =
|
|
5851
|
-
AUDIT_LOG_FILE =
|
|
5852
|
-
TRUST_FILE2 =
|
|
5853
|
-
GLOBAL_CONFIG_FILE =
|
|
5854
|
-
CREDENTIALS_FILE =
|
|
5855
|
-
INSIGHT_COUNTS_FILE =
|
|
5959
|
+
homeDir = import_os12.default.homedir();
|
|
5960
|
+
DAEMON_PID_FILE = import_path17.default.join(homeDir, ".node9", "daemon.pid");
|
|
5961
|
+
DECISIONS_FILE = import_path17.default.join(homeDir, ".node9", "decisions.json");
|
|
5962
|
+
AUDIT_LOG_FILE = import_path17.default.join(homeDir, ".node9", "audit.log");
|
|
5963
|
+
TRUST_FILE2 = import_path17.default.join(homeDir, ".node9", "trust.json");
|
|
5964
|
+
GLOBAL_CONFIG_FILE = import_path17.default.join(homeDir, ".node9", "config.json");
|
|
5965
|
+
CREDENTIALS_FILE = import_path17.default.join(homeDir, ".node9", "credentials.json");
|
|
5966
|
+
INSIGHT_COUNTS_FILE = import_path17.default.join(homeDir, ".node9", "insight-counts.json");
|
|
5856
5967
|
pending = /* @__PURE__ */ new Map();
|
|
5857
5968
|
sseClients = /* @__PURE__ */ new Set();
|
|
5858
5969
|
suggestionTracker = new SuggestionTracker(3);
|
|
@@ -5870,7 +5981,7 @@ var init_state2 = __esm({
|
|
|
5870
5981
|
"2h": 2 * 60 * 6e4
|
|
5871
5982
|
};
|
|
5872
5983
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
5873
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
5984
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path17.default.join(import_os12.default.tmpdir(), "node9-activity.sock");
|
|
5874
5985
|
ACTIVITY_RING_SIZE = 100;
|
|
5875
5986
|
activityRing = [];
|
|
5876
5987
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -5895,8 +6006,8 @@ var init_state2 = __esm({
|
|
|
5895
6006
|
function patchConfig(configPath, patch) {
|
|
5896
6007
|
let config = {};
|
|
5897
6008
|
try {
|
|
5898
|
-
if (
|
|
5899
|
-
config = JSON.parse(
|
|
6009
|
+
if (import_fs15.default.existsSync(configPath)) {
|
|
6010
|
+
config = JSON.parse(import_fs15.default.readFileSync(configPath, "utf8"));
|
|
5900
6011
|
}
|
|
5901
6012
|
} catch {
|
|
5902
6013
|
throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
|
|
@@ -5915,44 +6026,44 @@ function patchConfig(configPath, patch) {
|
|
|
5915
6026
|
ignored.push(patch.toolName);
|
|
5916
6027
|
}
|
|
5917
6028
|
}
|
|
5918
|
-
const dir =
|
|
5919
|
-
|
|
6029
|
+
const dir = import_path18.default.dirname(configPath);
|
|
6030
|
+
import_fs15.default.mkdirSync(dir, { recursive: true });
|
|
5920
6031
|
const tmp = configPath + ".node9-tmp";
|
|
5921
6032
|
try {
|
|
5922
|
-
|
|
6033
|
+
import_fs15.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5923
6034
|
} catch (err2) {
|
|
5924
6035
|
try {
|
|
5925
|
-
|
|
6036
|
+
import_fs15.default.unlinkSync(tmp);
|
|
5926
6037
|
} catch {
|
|
5927
6038
|
}
|
|
5928
6039
|
throw err2;
|
|
5929
6040
|
}
|
|
5930
6041
|
try {
|
|
5931
|
-
|
|
6042
|
+
import_fs15.default.renameSync(tmp, configPath);
|
|
5932
6043
|
} catch (err2) {
|
|
5933
6044
|
try {
|
|
5934
|
-
|
|
6045
|
+
import_fs15.default.unlinkSync(tmp);
|
|
5935
6046
|
} catch {
|
|
5936
6047
|
}
|
|
5937
6048
|
throw err2;
|
|
5938
6049
|
}
|
|
5939
6050
|
}
|
|
5940
|
-
var
|
|
6051
|
+
var import_fs15, import_path18, import_os13, GLOBAL_CONFIG_PATH;
|
|
5941
6052
|
var init_patch = __esm({
|
|
5942
6053
|
"src/config/patch.ts"() {
|
|
5943
6054
|
"use strict";
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
GLOBAL_CONFIG_PATH =
|
|
6055
|
+
import_fs15 = __toESM(require("fs"));
|
|
6056
|
+
import_path18 = __toESM(require("path"));
|
|
6057
|
+
import_os13 = __toESM(require("os"));
|
|
6058
|
+
GLOBAL_CONFIG_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "config.json");
|
|
5948
6059
|
}
|
|
5949
6060
|
});
|
|
5950
6061
|
|
|
5951
6062
|
// src/daemon/server.ts
|
|
5952
6063
|
function startDaemon() {
|
|
5953
6064
|
loadInsightCounts();
|
|
5954
|
-
const csrfToken = (0,
|
|
5955
|
-
const internalToken = (0,
|
|
6065
|
+
const csrfToken = (0, import_crypto7.randomUUID)();
|
|
6066
|
+
const internalToken = (0, import_crypto7.randomUUID)();
|
|
5956
6067
|
const UI_HTML = UI_HTML_TEMPLATE.replace("{{CSRF_TOKEN}}", csrfToken);
|
|
5957
6068
|
const validToken = (req) => req.headers["x-node9-token"] === csrfToken;
|
|
5958
6069
|
const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
|
|
@@ -5965,7 +6076,7 @@ function startDaemon() {
|
|
|
5965
6076
|
idleTimer = setTimeout(() => {
|
|
5966
6077
|
if (autoStarted) {
|
|
5967
6078
|
try {
|
|
5968
|
-
|
|
6079
|
+
import_fs16.default.unlinkSync(DAEMON_PID_FILE);
|
|
5969
6080
|
} catch {
|
|
5970
6081
|
}
|
|
5971
6082
|
}
|
|
@@ -6086,7 +6197,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6086
6197
|
cwd,
|
|
6087
6198
|
localSmartRuleMatched = false
|
|
6088
6199
|
} = JSON.parse(body);
|
|
6089
|
-
const id = fromCLI && typeof activityId === "string" && activityId || (0,
|
|
6200
|
+
const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto7.randomUUID)();
|
|
6090
6201
|
const entry = {
|
|
6091
6202
|
id,
|
|
6092
6203
|
toolName,
|
|
@@ -6128,7 +6239,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6128
6239
|
status: "pending"
|
|
6129
6240
|
});
|
|
6130
6241
|
}
|
|
6131
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6242
|
+
const projectCwd = typeof cwd === "string" && import_path19.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6132
6243
|
const projectConfig = getConfig(projectCwd);
|
|
6133
6244
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6134
6245
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6518,8 +6629,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6518
6629
|
const body = await readBody(req);
|
|
6519
6630
|
const data = body ? JSON.parse(body) : {};
|
|
6520
6631
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6521
|
-
const node9Dir =
|
|
6522
|
-
if (!
|
|
6632
|
+
const node9Dir = import_path19.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6633
|
+
if (!import_path19.default.resolve(configPath).startsWith(node9Dir + import_path19.default.sep)) {
|
|
6523
6634
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6524
6635
|
return res.end(
|
|
6525
6636
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6630,14 +6741,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6630
6741
|
server.on("error", (e) => {
|
|
6631
6742
|
if (e.code === "EADDRINUSE") {
|
|
6632
6743
|
try {
|
|
6633
|
-
if (
|
|
6634
|
-
const { pid } = JSON.parse(
|
|
6744
|
+
if (import_fs16.default.existsSync(DAEMON_PID_FILE)) {
|
|
6745
|
+
const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6635
6746
|
process.kill(pid, 0);
|
|
6636
6747
|
return process.exit(0);
|
|
6637
6748
|
}
|
|
6638
6749
|
} catch {
|
|
6639
6750
|
try {
|
|
6640
|
-
|
|
6751
|
+
import_fs16.default.unlinkSync(DAEMON_PID_FILE);
|
|
6641
6752
|
} catch {
|
|
6642
6753
|
}
|
|
6643
6754
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -6696,14 +6807,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6696
6807
|
}
|
|
6697
6808
|
startActivitySocket();
|
|
6698
6809
|
}
|
|
6699
|
-
var import_http,
|
|
6810
|
+
var import_http, import_fs16, import_path19, import_crypto7, import_child_process4, import_chalk2;
|
|
6700
6811
|
var init_server = __esm({
|
|
6701
6812
|
"src/daemon/server.ts"() {
|
|
6702
6813
|
"use strict";
|
|
6703
6814
|
import_http = __toESM(require("http"));
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6815
|
+
import_fs16 = __toESM(require("fs"));
|
|
6816
|
+
import_path19 = __toESM(require("path"));
|
|
6817
|
+
import_crypto7 = require("crypto");
|
|
6707
6818
|
import_child_process4 = require("child_process");
|
|
6708
6819
|
import_chalk2 = __toESM(require("chalk"));
|
|
6709
6820
|
init_core();
|
|
@@ -6717,24 +6828,24 @@ var init_server = __esm({
|
|
|
6717
6828
|
|
|
6718
6829
|
// src/daemon/index.ts
|
|
6719
6830
|
function stopDaemon() {
|
|
6720
|
-
if (!
|
|
6831
|
+
if (!import_fs17.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
6721
6832
|
try {
|
|
6722
|
-
const { pid } = JSON.parse(
|
|
6833
|
+
const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6723
6834
|
process.kill(pid, "SIGTERM");
|
|
6724
6835
|
console.log(import_chalk3.default.green("\u2705 Stopped."));
|
|
6725
6836
|
} catch {
|
|
6726
6837
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
6727
6838
|
} finally {
|
|
6728
6839
|
try {
|
|
6729
|
-
|
|
6840
|
+
import_fs17.default.unlinkSync(DAEMON_PID_FILE);
|
|
6730
6841
|
} catch {
|
|
6731
6842
|
}
|
|
6732
6843
|
}
|
|
6733
6844
|
}
|
|
6734
6845
|
function daemonStatus() {
|
|
6735
|
-
if (
|
|
6846
|
+
if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
|
|
6736
6847
|
try {
|
|
6737
|
-
const { pid } = JSON.parse(
|
|
6848
|
+
const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6738
6849
|
process.kill(pid, 0);
|
|
6739
6850
|
console.log(import_chalk3.default.green("Node9 daemon: running"));
|
|
6740
6851
|
return;
|
|
@@ -6753,11 +6864,11 @@ function daemonStatus() {
|
|
|
6753
6864
|
console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
|
|
6754
6865
|
}
|
|
6755
6866
|
}
|
|
6756
|
-
var
|
|
6867
|
+
var import_fs17, import_chalk3, import_child_process5;
|
|
6757
6868
|
var init_daemon2 = __esm({
|
|
6758
6869
|
"src/daemon/index.ts"() {
|
|
6759
6870
|
"use strict";
|
|
6760
|
-
|
|
6871
|
+
import_fs17 = __toESM(require("fs"));
|
|
6761
6872
|
import_chalk3 = __toESM(require("chalk"));
|
|
6762
6873
|
import_child_process5 = require("child_process");
|
|
6763
6874
|
init_server();
|
|
@@ -6778,44 +6889,64 @@ function getIcon(tool) {
|
|
|
6778
6889
|
}
|
|
6779
6890
|
return "\u{1F6E0}\uFE0F";
|
|
6780
6891
|
}
|
|
6892
|
+
function visibleLength(s) {
|
|
6893
|
+
return s.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
6894
|
+
}
|
|
6895
|
+
function wrappedLineCount(text) {
|
|
6896
|
+
const cols = process.stdout.columns;
|
|
6897
|
+
if (!cols) return 1;
|
|
6898
|
+
const len = visibleLength(text);
|
|
6899
|
+
return Math.max(1, Math.ceil(len / cols));
|
|
6900
|
+
}
|
|
6781
6901
|
function formatBase(activity) {
|
|
6782
6902
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
6783
6903
|
const icon = getIcon(activity.tool);
|
|
6784
6904
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6785
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
6905
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os24.default.homedir(), "~");
|
|
6786
6906
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6787
|
-
return `${
|
|
6907
|
+
return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
|
|
6788
6908
|
}
|
|
6789
6909
|
function renderResult(activity, result) {
|
|
6790
6910
|
const base = formatBase(activity);
|
|
6791
6911
|
let status;
|
|
6792
6912
|
if (result.status === "allow") {
|
|
6793
|
-
status =
|
|
6913
|
+
status = import_chalk19.default.green("\u2713 ALLOW");
|
|
6794
6914
|
} else if (result.status === "dlp") {
|
|
6795
|
-
status =
|
|
6915
|
+
status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
6796
6916
|
} else {
|
|
6797
|
-
status =
|
|
6917
|
+
status = import_chalk19.default.red("\u2717 BLOCK");
|
|
6798
6918
|
}
|
|
6799
6919
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
6800
|
-
const costSuffix = cost == null ? "" :
|
|
6920
|
+
const costSuffix = cost == null ? "" : import_chalk19.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
6801
6921
|
if (process.stdout.isTTY) {
|
|
6802
|
-
|
|
6803
|
-
|
|
6922
|
+
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
6923
|
+
import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
6924
|
+
import_readline5.default.cursorTo(process.stdout, 0);
|
|
6925
|
+
process.stdout.write(ERASE_DOWN);
|
|
6926
|
+
} else {
|
|
6927
|
+
import_readline5.default.clearLine(process.stdout, 0);
|
|
6928
|
+
import_readline5.default.cursorTo(process.stdout, 0);
|
|
6929
|
+
}
|
|
6930
|
+
pendingShownForId = null;
|
|
6931
|
+
pendingWrappedLines = 0;
|
|
6804
6932
|
}
|
|
6805
6933
|
console.log(`${base} ${status}${costSuffix}`);
|
|
6806
6934
|
}
|
|
6807
6935
|
function renderPending(activity) {
|
|
6808
6936
|
if (!process.stdout.isTTY) return;
|
|
6809
|
-
|
|
6937
|
+
const line = `${formatBase(activity)} ${import_chalk19.default.yellow("\u25CF \u2026")}`;
|
|
6938
|
+
pendingShownForId = activity.id;
|
|
6939
|
+
pendingWrappedLines = wrappedLineCount(line);
|
|
6940
|
+
process.stdout.write(`${line}\r`);
|
|
6810
6941
|
}
|
|
6811
6942
|
async function ensureDaemon() {
|
|
6812
6943
|
let pidPort = null;
|
|
6813
|
-
if (
|
|
6944
|
+
if (import_fs28.default.existsSync(PID_FILE)) {
|
|
6814
6945
|
try {
|
|
6815
|
-
const { port } = JSON.parse(
|
|
6946
|
+
const { port } = JSON.parse(import_fs28.default.readFileSync(PID_FILE, "utf-8"));
|
|
6816
6947
|
pidPort = port;
|
|
6817
6948
|
} catch {
|
|
6818
|
-
console.error(
|
|
6949
|
+
console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
6819
6950
|
}
|
|
6820
6951
|
}
|
|
6821
6952
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -6826,7 +6957,7 @@ async function ensureDaemon() {
|
|
|
6826
6957
|
if (res.ok) return checkPort;
|
|
6827
6958
|
} catch {
|
|
6828
6959
|
}
|
|
6829
|
-
console.log(
|
|
6960
|
+
console.log(import_chalk19.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6830
6961
|
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6831
6962
|
detached: true,
|
|
6832
6963
|
stdio: "ignore",
|
|
@@ -6843,7 +6974,7 @@ async function ensureDaemon() {
|
|
|
6843
6974
|
} catch {
|
|
6844
6975
|
}
|
|
6845
6976
|
}
|
|
6846
|
-
console.error(
|
|
6977
|
+
console.error(import_chalk19.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
6847
6978
|
process.exit(1);
|
|
6848
6979
|
}
|
|
6849
6980
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -6932,9 +7063,9 @@ function buildRecoveryCardLines(req) {
|
|
|
6932
7063
|
];
|
|
6933
7064
|
}
|
|
6934
7065
|
function readApproversFromDisk() {
|
|
6935
|
-
const configPath =
|
|
7066
|
+
const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
6936
7067
|
try {
|
|
6937
|
-
const raw = JSON.parse(
|
|
7068
|
+
const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
|
|
6938
7069
|
const settings = raw.settings ?? {};
|
|
6939
7070
|
return settings.approvers ?? {};
|
|
6940
7071
|
} catch {
|
|
@@ -6945,20 +7076,20 @@ function approverStatusLine() {
|
|
|
6945
7076
|
const a = readApproversFromDisk();
|
|
6946
7077
|
const fmt = (label, key) => {
|
|
6947
7078
|
const on = a[key] !== false;
|
|
6948
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
7079
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk19.default.green("\u2713") : import_chalk19.default.dim("\u2717")}`;
|
|
6949
7080
|
};
|
|
6950
7081
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
6951
7082
|
}
|
|
6952
7083
|
function toggleApprover(channel) {
|
|
6953
|
-
const configPath =
|
|
7084
|
+
const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
6954
7085
|
try {
|
|
6955
|
-
const raw = JSON.parse(
|
|
7086
|
+
const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
|
|
6956
7087
|
const settings = raw.settings ?? {};
|
|
6957
7088
|
const approvers = settings.approvers ?? {};
|
|
6958
7089
|
approvers[channel] = approvers[channel] === false;
|
|
6959
7090
|
settings.approvers = approvers;
|
|
6960
7091
|
raw.settings = settings;
|
|
6961
|
-
|
|
7092
|
+
import_fs28.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
6962
7093
|
} catch (err2) {
|
|
6963
7094
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
6964
7095
|
`);
|
|
@@ -6990,7 +7121,7 @@ async function startTail(options = {}) {
|
|
|
6990
7121
|
req2.end();
|
|
6991
7122
|
});
|
|
6992
7123
|
if (result.ok) {
|
|
6993
|
-
console.log(
|
|
7124
|
+
console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
6994
7125
|
} else if (result.code === "ECONNREFUSED") {
|
|
6995
7126
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
6996
7127
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7034,7 +7165,7 @@ async function startTail(options = {}) {
|
|
|
7034
7165
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7035
7166
|
if (channel) {
|
|
7036
7167
|
toggleApprover(channel);
|
|
7037
|
-
console.log(
|
|
7168
|
+
console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7038
7169
|
}
|
|
7039
7170
|
};
|
|
7040
7171
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7100,7 +7231,7 @@ async function startTail(options = {}) {
|
|
|
7100
7231
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7101
7232
|
)
|
|
7102
7233
|
);
|
|
7103
|
-
const decisionStamp = action === "always-allow" ?
|
|
7234
|
+
const decisionStamp = action === "always-allow" ? import_chalk19.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk19.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk19.default.yellow("\u21A9 REDIRECT AI") : import_chalk19.default.red("\u2717 DENIED");
|
|
7104
7235
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7105
7236
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7106
7237
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7128,8 +7259,8 @@ async function startTail(options = {}) {
|
|
|
7128
7259
|
}
|
|
7129
7260
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7130
7261
|
try {
|
|
7131
|
-
|
|
7132
|
-
|
|
7262
|
+
import_fs28.default.appendFileSync(
|
|
7263
|
+
import_path31.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log"),
|
|
7133
7264
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7134
7265
|
`
|
|
7135
7266
|
);
|
|
@@ -7151,7 +7282,7 @@ async function startTail(options = {}) {
|
|
|
7151
7282
|
);
|
|
7152
7283
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7153
7284
|
if (externalDecision) {
|
|
7154
|
-
const source = externalDecision === "allow" ?
|
|
7285
|
+
const source = externalDecision === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : import_chalk19.default.red("\u2717 DENIED");
|
|
7155
7286
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7156
7287
|
}
|
|
7157
7288
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -7210,16 +7341,16 @@ async function startTail(options = {}) {
|
|
|
7210
7341
|
}
|
|
7211
7342
|
} catch {
|
|
7212
7343
|
}
|
|
7213
|
-
console.log(
|
|
7214
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
7344
|
+
console.log(import_chalk19.default.cyan.bold(`
|
|
7345
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk19.default.dim(`\u2192 ${dashboardUrl}`));
|
|
7215
7346
|
if (canApprove) {
|
|
7216
|
-
console.log(
|
|
7217
|
-
console.log(
|
|
7347
|
+
console.log(import_chalk19.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
7348
|
+
console.log(import_chalk19.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
7218
7349
|
}
|
|
7219
7350
|
if (options.history) {
|
|
7220
|
-
console.log(
|
|
7351
|
+
console.log(import_chalk19.default.dim("Showing history + live events.\n"));
|
|
7221
7352
|
} else {
|
|
7222
|
-
console.log(
|
|
7353
|
+
console.log(import_chalk19.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
7223
7354
|
}
|
|
7224
7355
|
process.on("SIGINT", () => {
|
|
7225
7356
|
exitIdleMode();
|
|
@@ -7229,13 +7360,13 @@ async function startTail(options = {}) {
|
|
|
7229
7360
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7230
7361
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7231
7362
|
}
|
|
7232
|
-
console.log(
|
|
7363
|
+
console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7233
7364
|
process.exit(0);
|
|
7234
7365
|
});
|
|
7235
7366
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
7236
7367
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
7237
7368
|
if (res.statusCode !== 200) {
|
|
7238
|
-
console.error(
|
|
7369
|
+
console.error(import_chalk19.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7239
7370
|
process.exit(1);
|
|
7240
7371
|
}
|
|
7241
7372
|
if (canApprove) enterIdleMode();
|
|
@@ -7266,7 +7397,7 @@ async function startTail(options = {}) {
|
|
|
7266
7397
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7267
7398
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7268
7399
|
}
|
|
7269
|
-
console.log(
|
|
7400
|
+
console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
|
|
7270
7401
|
process.exit(1);
|
|
7271
7402
|
});
|
|
7272
7403
|
});
|
|
@@ -7358,9 +7489,9 @@ async function startTail(options = {}) {
|
|
|
7358
7489
|
const hash = data.hash ?? "";
|
|
7359
7490
|
const summary = data.argsSummary ?? data.tool;
|
|
7360
7491
|
const fileCount = data.fileCount ?? 0;
|
|
7361
|
-
const files = fileCount > 0 ?
|
|
7492
|
+
const files = fileCount > 0 ? import_chalk19.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
7362
7493
|
process.stdout.write(
|
|
7363
|
-
`${
|
|
7494
|
+
`${import_chalk19.default.dim(time)} ${import_chalk19.default.cyan("\u{1F4F8} snapshot")} ${import_chalk19.default.dim(hash)} ${summary}${files}
|
|
7364
7495
|
`
|
|
7365
7496
|
);
|
|
7366
7497
|
return;
|
|
@@ -7377,26 +7508,26 @@ async function startTail(options = {}) {
|
|
|
7377
7508
|
}
|
|
7378
7509
|
req.on("error", (err2) => {
|
|
7379
7510
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
7380
|
-
console.error(
|
|
7511
|
+
console.error(import_chalk19.default.red(`
|
|
7381
7512
|
\u274C ${msg}`));
|
|
7382
7513
|
process.exit(1);
|
|
7383
7514
|
});
|
|
7384
7515
|
}
|
|
7385
|
-
var import_http2,
|
|
7516
|
+
var import_http2, import_chalk19, import_fs28, import_os24, import_path31, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
7386
7517
|
var init_tail = __esm({
|
|
7387
7518
|
"src/tui/tail.ts"() {
|
|
7388
7519
|
"use strict";
|
|
7389
7520
|
import_http2 = __toESM(require("http"));
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7521
|
+
import_chalk19 = __toESM(require("chalk"));
|
|
7522
|
+
import_fs28 = __toESM(require("fs"));
|
|
7523
|
+
import_os24 = __toESM(require("os"));
|
|
7524
|
+
import_path31 = __toESM(require("path"));
|
|
7394
7525
|
import_readline5 = __toESM(require("readline"));
|
|
7395
7526
|
import_child_process14 = require("child_process");
|
|
7396
7527
|
init_daemon2();
|
|
7397
7528
|
init_daemon();
|
|
7398
7529
|
init_core();
|
|
7399
|
-
PID_FILE =
|
|
7530
|
+
PID_FILE = import_path31.default.join(import_os24.default.homedir(), ".node9", "daemon.pid");
|
|
7400
7531
|
ICONS = {
|
|
7401
7532
|
bash: "\u{1F4BB}",
|
|
7402
7533
|
shell: "\u{1F4BB}",
|
|
@@ -7424,6 +7555,8 @@ var init_tail = __esm({
|
|
|
7424
7555
|
HIDE_CURSOR = "\x1B[?25l";
|
|
7425
7556
|
SHOW_CURSOR = "\x1B[?25h";
|
|
7426
7557
|
ERASE_DOWN = "\x1B[J";
|
|
7558
|
+
pendingShownForId = null;
|
|
7559
|
+
pendingWrappedLines = 0;
|
|
7427
7560
|
DIVIDER = "\u2500".repeat(60);
|
|
7428
7561
|
}
|
|
7429
7562
|
});
|
|
@@ -7492,10 +7625,10 @@ function bold(s) {
|
|
|
7492
7625
|
function color(c, s) {
|
|
7493
7626
|
return `${c}${s}${RESET3}`;
|
|
7494
7627
|
}
|
|
7495
|
-
function progressBar(
|
|
7496
|
-
const filled = Math.round(Math.min(
|
|
7628
|
+
function progressBar(pct2, warnAt = 70, critAt = 85) {
|
|
7629
|
+
const filled = Math.round(Math.min(pct2, 100) / 100 * BAR_WIDTH);
|
|
7497
7630
|
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
7498
|
-
const c =
|
|
7631
|
+
const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
|
|
7499
7632
|
return `${c}${bar}${RESET3}`;
|
|
7500
7633
|
}
|
|
7501
7634
|
function formatTimeLeft(resetsAt) {
|
|
@@ -7509,9 +7642,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7509
7642
|
return ` (${m}m left)`;
|
|
7510
7643
|
}
|
|
7511
7644
|
function safeReadJson(filePath) {
|
|
7512
|
-
if (!
|
|
7645
|
+
if (!import_fs29.default.existsSync(filePath)) return null;
|
|
7513
7646
|
try {
|
|
7514
|
-
return JSON.parse(
|
|
7647
|
+
return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
|
|
7515
7648
|
} catch {
|
|
7516
7649
|
return null;
|
|
7517
7650
|
}
|
|
@@ -7532,12 +7665,12 @@ function countHooksInFile(filePath) {
|
|
|
7532
7665
|
return Object.keys(cfg.hooks).length;
|
|
7533
7666
|
}
|
|
7534
7667
|
function countRulesInDir(rulesDir) {
|
|
7535
|
-
if (!
|
|
7668
|
+
if (!import_fs29.default.existsSync(rulesDir)) return 0;
|
|
7536
7669
|
let count = 0;
|
|
7537
7670
|
try {
|
|
7538
|
-
for (const entry of
|
|
7671
|
+
for (const entry of import_fs29.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7539
7672
|
if (entry.isDirectory()) {
|
|
7540
|
-
count += countRulesInDir(
|
|
7673
|
+
count += countRulesInDir(import_path32.default.join(rulesDir, entry.name));
|
|
7541
7674
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7542
7675
|
count++;
|
|
7543
7676
|
}
|
|
@@ -7548,46 +7681,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7548
7681
|
}
|
|
7549
7682
|
function isSamePath(a, b) {
|
|
7550
7683
|
try {
|
|
7551
|
-
return
|
|
7684
|
+
return import_path32.default.resolve(a) === import_path32.default.resolve(b);
|
|
7552
7685
|
} catch {
|
|
7553
7686
|
return false;
|
|
7554
7687
|
}
|
|
7555
7688
|
}
|
|
7556
7689
|
function countConfigs(cwd) {
|
|
7557
|
-
const homeDir2 =
|
|
7558
|
-
const claudeDir =
|
|
7690
|
+
const homeDir2 = import_os25.default.homedir();
|
|
7691
|
+
const claudeDir = import_path32.default.join(homeDir2, ".claude");
|
|
7559
7692
|
let claudeMdCount = 0;
|
|
7560
7693
|
let rulesCount = 0;
|
|
7561
7694
|
let hooksCount = 0;
|
|
7562
7695
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7563
7696
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7564
|
-
if (
|
|
7565
|
-
rulesCount += countRulesInDir(
|
|
7566
|
-
const userSettings =
|
|
7697
|
+
if (import_fs29.default.existsSync(import_path32.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7698
|
+
rulesCount += countRulesInDir(import_path32.default.join(claudeDir, "rules"));
|
|
7699
|
+
const userSettings = import_path32.default.join(claudeDir, "settings.json");
|
|
7567
7700
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7568
7701
|
hooksCount += countHooksInFile(userSettings);
|
|
7569
|
-
const userClaudeJson =
|
|
7702
|
+
const userClaudeJson = import_path32.default.join(homeDir2, ".claude.json");
|
|
7570
7703
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7571
7704
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7572
7705
|
userMcpServers.delete(name);
|
|
7573
7706
|
}
|
|
7574
7707
|
if (cwd) {
|
|
7575
|
-
if (
|
|
7576
|
-
if (
|
|
7577
|
-
const projectClaudeDir =
|
|
7708
|
+
if (import_fs29.default.existsSync(import_path32.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7709
|
+
if (import_fs29.default.existsSync(import_path32.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7710
|
+
const projectClaudeDir = import_path32.default.join(cwd, ".claude");
|
|
7578
7711
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7579
7712
|
if (!overlapsUserScope) {
|
|
7580
|
-
if (
|
|
7581
|
-
rulesCount += countRulesInDir(
|
|
7582
|
-
const projSettings =
|
|
7713
|
+
if (import_fs29.default.existsSync(import_path32.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7714
|
+
rulesCount += countRulesInDir(import_path32.default.join(projectClaudeDir, "rules"));
|
|
7715
|
+
const projSettings = import_path32.default.join(projectClaudeDir, "settings.json");
|
|
7583
7716
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7584
7717
|
hooksCount += countHooksInFile(projSettings);
|
|
7585
7718
|
}
|
|
7586
|
-
if (
|
|
7587
|
-
const localSettings =
|
|
7719
|
+
if (import_fs29.default.existsSync(import_path32.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7720
|
+
const localSettings = import_path32.default.join(projectClaudeDir, "settings.local.json");
|
|
7588
7721
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7589
7722
|
hooksCount += countHooksInFile(localSettings);
|
|
7590
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7723
|
+
const mcpJsonServers = getMcpServerNames(import_path32.default.join(cwd, ".mcp.json"));
|
|
7591
7724
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7592
7725
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7593
7726
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7620,12 +7753,12 @@ function readActiveShieldsHud() {
|
|
|
7620
7753
|
return shieldsCache.value;
|
|
7621
7754
|
}
|
|
7622
7755
|
try {
|
|
7623
|
-
const shieldsPath =
|
|
7624
|
-
if (!
|
|
7756
|
+
const shieldsPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "shields.json");
|
|
7757
|
+
if (!import_fs29.default.existsSync(shieldsPath)) {
|
|
7625
7758
|
shieldsCache = { value: [], ts: now };
|
|
7626
7759
|
return [];
|
|
7627
7760
|
}
|
|
7628
|
-
const parsed = JSON.parse(
|
|
7761
|
+
const parsed = JSON.parse(import_fs29.default.readFileSync(shieldsPath, "utf-8"));
|
|
7629
7762
|
if (!Array.isArray(parsed.active)) {
|
|
7630
7763
|
shieldsCache = { value: [], ts: now };
|
|
7631
7764
|
return [];
|
|
@@ -7711,15 +7844,15 @@ function renderContextLine(stdin) {
|
|
|
7711
7844
|
}
|
|
7712
7845
|
const rl = stdin.rate_limits;
|
|
7713
7846
|
if (rl?.five_hour?.used_percentage !== void 0) {
|
|
7714
|
-
const
|
|
7715
|
-
const bar = progressBar(
|
|
7847
|
+
const pct2 = Math.round(rl.five_hour.used_percentage);
|
|
7848
|
+
const bar = progressBar(pct2, 60, 80);
|
|
7716
7849
|
const left = formatTimeLeft(rl.five_hour.resets_at);
|
|
7717
|
-
parts.push(`${dim("\u2502")} 5h ${bar} ${
|
|
7850
|
+
parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
|
|
7718
7851
|
}
|
|
7719
7852
|
if (rl?.seven_day?.used_percentage !== void 0) {
|
|
7720
|
-
const
|
|
7721
|
-
const bar = progressBar(
|
|
7722
|
-
parts.push(`${dim("\u2502")} 7d ${bar} ${
|
|
7853
|
+
const pct2 = Math.round(rl.seven_day.used_percentage);
|
|
7854
|
+
const bar = progressBar(pct2, 60, 80);
|
|
7855
|
+
parts.push(`${dim("\u2502")} 7d ${bar} ${pct2}%`);
|
|
7723
7856
|
}
|
|
7724
7857
|
if (parts.length === 0) return null;
|
|
7725
7858
|
return parts.join(" ");
|
|
@@ -7727,17 +7860,17 @@ function renderContextLine(stdin) {
|
|
|
7727
7860
|
async function main() {
|
|
7728
7861
|
try {
|
|
7729
7862
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7730
|
-
if (
|
|
7863
|
+
if (import_fs29.default.existsSync(import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug"))) {
|
|
7731
7864
|
try {
|
|
7732
|
-
const logPath =
|
|
7865
|
+
const logPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug.log");
|
|
7733
7866
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7734
7867
|
let size = 0;
|
|
7735
7868
|
try {
|
|
7736
|
-
size =
|
|
7869
|
+
size = import_fs29.default.statSync(logPath).size;
|
|
7737
7870
|
} catch {
|
|
7738
7871
|
}
|
|
7739
7872
|
if (size < MAX_LOG_SIZE) {
|
|
7740
|
-
|
|
7873
|
+
import_fs29.default.appendFileSync(
|
|
7741
7874
|
logPath,
|
|
7742
7875
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7743
7876
|
);
|
|
@@ -7758,11 +7891,11 @@ async function main() {
|
|
|
7758
7891
|
try {
|
|
7759
7892
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7760
7893
|
for (const configPath of [
|
|
7761
|
-
|
|
7762
|
-
|
|
7894
|
+
import_path32.default.join(cwd, "node9.config.json"),
|
|
7895
|
+
import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json")
|
|
7763
7896
|
]) {
|
|
7764
|
-
if (!
|
|
7765
|
-
const cfg = JSON.parse(
|
|
7897
|
+
if (!import_fs29.default.existsSync(configPath)) continue;
|
|
7898
|
+
const cfg = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
|
|
7766
7899
|
const hud = cfg.settings?.hud;
|
|
7767
7900
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7768
7901
|
}
|
|
@@ -7780,13 +7913,13 @@ async function main() {
|
|
|
7780
7913
|
renderOffline();
|
|
7781
7914
|
}
|
|
7782
7915
|
}
|
|
7783
|
-
var
|
|
7916
|
+
var import_fs29, import_path32, import_os25, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
7784
7917
|
var init_hud = __esm({
|
|
7785
7918
|
"src/cli/hud.ts"() {
|
|
7786
7919
|
"use strict";
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7920
|
+
import_fs29 = __toESM(require("fs"));
|
|
7921
|
+
import_path32 = __toESM(require("path"));
|
|
7922
|
+
import_os25 = __toESM(require("os"));
|
|
7790
7923
|
import_http3 = __toESM(require("http"));
|
|
7791
7924
|
init_daemon();
|
|
7792
7925
|
RESET3 = "\x1B[0m";
|
|
@@ -7812,9 +7945,9 @@ var import_commander = require("commander");
|
|
|
7812
7945
|
init_core();
|
|
7813
7946
|
|
|
7814
7947
|
// src/setup.ts
|
|
7815
|
-
var
|
|
7816
|
-
var
|
|
7817
|
-
var
|
|
7948
|
+
var import_fs12 = __toESM(require("fs"));
|
|
7949
|
+
var import_path15 = __toESM(require("path"));
|
|
7950
|
+
var import_os11 = __toESM(require("os"));
|
|
7818
7951
|
var import_chalk = __toESM(require("chalk"));
|
|
7819
7952
|
var import_prompts = require("@inquirer/prompts");
|
|
7820
7953
|
var import_smol_toml = require("smol-toml");
|
|
@@ -7842,26 +7975,26 @@ function fullPathCommand(subcommand) {
|
|
|
7842
7975
|
}
|
|
7843
7976
|
function readJson(filePath) {
|
|
7844
7977
|
try {
|
|
7845
|
-
if (
|
|
7846
|
-
return JSON.parse(
|
|
7978
|
+
if (import_fs12.default.existsSync(filePath)) {
|
|
7979
|
+
return JSON.parse(import_fs12.default.readFileSync(filePath, "utf-8"));
|
|
7847
7980
|
}
|
|
7848
7981
|
} catch {
|
|
7849
7982
|
}
|
|
7850
7983
|
return null;
|
|
7851
7984
|
}
|
|
7852
7985
|
function writeJson(filePath, data) {
|
|
7853
|
-
const dir =
|
|
7854
|
-
if (!
|
|
7855
|
-
|
|
7986
|
+
const dir = import_path15.default.dirname(filePath);
|
|
7987
|
+
if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
|
|
7988
|
+
import_fs12.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
7856
7989
|
}
|
|
7857
7990
|
function isNode9Hook(cmd) {
|
|
7858
7991
|
if (!cmd) return false;
|
|
7859
7992
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
7860
7993
|
}
|
|
7861
7994
|
function teardownClaude() {
|
|
7862
|
-
const homeDir2 =
|
|
7863
|
-
const hooksPath =
|
|
7864
|
-
const mcpPath =
|
|
7995
|
+
const homeDir2 = import_os11.default.homedir();
|
|
7996
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
7997
|
+
const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
|
|
7865
7998
|
let changed = false;
|
|
7866
7999
|
const settings = readJson(hooksPath);
|
|
7867
8000
|
if (settings?.hooks) {
|
|
@@ -7909,8 +8042,8 @@ function teardownClaude() {
|
|
|
7909
8042
|
}
|
|
7910
8043
|
}
|
|
7911
8044
|
function teardownGemini() {
|
|
7912
|
-
const homeDir2 =
|
|
7913
|
-
const settingsPath =
|
|
8045
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8046
|
+
const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
|
|
7914
8047
|
const settings = readJson(settingsPath);
|
|
7915
8048
|
if (!settings) {
|
|
7916
8049
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -7953,8 +8086,8 @@ function teardownGemini() {
|
|
|
7953
8086
|
}
|
|
7954
8087
|
}
|
|
7955
8088
|
function teardownCursor() {
|
|
7956
|
-
const homeDir2 =
|
|
7957
|
-
const mcpPath =
|
|
8089
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8090
|
+
const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7958
8091
|
const mcpConfig = readJson(mcpPath);
|
|
7959
8092
|
if (!mcpConfig?.mcpServers) {
|
|
7960
8093
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -7985,9 +8118,9 @@ function teardownCursor() {
|
|
|
7985
8118
|
}
|
|
7986
8119
|
}
|
|
7987
8120
|
async function setupClaude() {
|
|
7988
|
-
const homeDir2 =
|
|
7989
|
-
const mcpPath =
|
|
7990
|
-
const hooksPath =
|
|
8121
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8122
|
+
const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
|
|
8123
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
7991
8124
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
7992
8125
|
const settings = readJson(hooksPath) ?? {};
|
|
7993
8126
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -8084,8 +8217,8 @@ async function setupClaude() {
|
|
|
8084
8217
|
}
|
|
8085
8218
|
}
|
|
8086
8219
|
async function setupGemini() {
|
|
8087
|
-
const homeDir2 =
|
|
8088
|
-
const settingsPath =
|
|
8220
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8221
|
+
const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
|
|
8089
8222
|
const settings = readJson(settingsPath) ?? {};
|
|
8090
8223
|
const servers = settings.mcpServers ?? {};
|
|
8091
8224
|
let hooksChanged = false;
|
|
@@ -8180,10 +8313,10 @@ async function setupGemini() {
|
|
|
8180
8313
|
printDaemonTip();
|
|
8181
8314
|
}
|
|
8182
8315
|
}
|
|
8183
|
-
function detectAgents(homeDir2 =
|
|
8316
|
+
function detectAgents(homeDir2 = import_os11.default.homedir()) {
|
|
8184
8317
|
const exists = (p) => {
|
|
8185
8318
|
try {
|
|
8186
|
-
return
|
|
8319
|
+
return import_fs12.default.existsSync(p);
|
|
8187
8320
|
} catch (err2) {
|
|
8188
8321
|
const code = err2.code;
|
|
8189
8322
|
if (code !== "ENOENT") {
|
|
@@ -8194,15 +8327,15 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
|
|
|
8194
8327
|
}
|
|
8195
8328
|
};
|
|
8196
8329
|
return {
|
|
8197
|
-
claude: exists(
|
|
8198
|
-
gemini: exists(
|
|
8199
|
-
cursor: exists(
|
|
8200
|
-
codex: exists(
|
|
8330
|
+
claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
|
|
8331
|
+
gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
|
|
8332
|
+
cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
|
|
8333
|
+
codex: exists(import_path15.default.join(homeDir2, ".codex"))
|
|
8201
8334
|
};
|
|
8202
8335
|
}
|
|
8203
8336
|
async function setupCursor() {
|
|
8204
|
-
const homeDir2 =
|
|
8205
|
-
const mcpPath =
|
|
8337
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8338
|
+
const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
|
|
8206
8339
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
8207
8340
|
const servers = mcpConfig.mcpServers ?? {};
|
|
8208
8341
|
let anythingChanged = false;
|
|
@@ -8268,21 +8401,21 @@ async function setupCursor() {
|
|
|
8268
8401
|
}
|
|
8269
8402
|
function readToml(filePath) {
|
|
8270
8403
|
try {
|
|
8271
|
-
if (
|
|
8272
|
-
return (0, import_smol_toml.parse)(
|
|
8404
|
+
if (import_fs12.default.existsSync(filePath)) {
|
|
8405
|
+
return (0, import_smol_toml.parse)(import_fs12.default.readFileSync(filePath, "utf-8"));
|
|
8273
8406
|
}
|
|
8274
8407
|
} catch {
|
|
8275
8408
|
}
|
|
8276
8409
|
return null;
|
|
8277
8410
|
}
|
|
8278
8411
|
function writeToml(filePath, data) {
|
|
8279
|
-
const dir =
|
|
8280
|
-
if (!
|
|
8281
|
-
|
|
8412
|
+
const dir = import_path15.default.dirname(filePath);
|
|
8413
|
+
if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
|
|
8414
|
+
import_fs12.default.writeFileSync(filePath, (0, import_smol_toml.stringify)(data));
|
|
8282
8415
|
}
|
|
8283
8416
|
async function setupCodex() {
|
|
8284
|
-
const homeDir2 =
|
|
8285
|
-
const configPath =
|
|
8417
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8418
|
+
const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
|
|
8286
8419
|
const config = readToml(configPath) ?? {};
|
|
8287
8420
|
const servers = config.mcp_servers ?? {};
|
|
8288
8421
|
let anythingChanged = false;
|
|
@@ -8347,8 +8480,8 @@ async function setupCodex() {
|
|
|
8347
8480
|
}
|
|
8348
8481
|
}
|
|
8349
8482
|
function setupHud() {
|
|
8350
|
-
const homeDir2 =
|
|
8351
|
-
const hooksPath =
|
|
8483
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8484
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8352
8485
|
const settings = readJson(hooksPath) ?? {};
|
|
8353
8486
|
const hudCommand = fullPathCommand("hud");
|
|
8354
8487
|
const statusLineObj = { type: "command", command: hudCommand };
|
|
@@ -8374,8 +8507,8 @@ function setupHud() {
|
|
|
8374
8507
|
console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
|
|
8375
8508
|
}
|
|
8376
8509
|
function teardownHud() {
|
|
8377
|
-
const homeDir2 =
|
|
8378
|
-
const hooksPath =
|
|
8510
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8511
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8379
8512
|
const settings = readJson(hooksPath);
|
|
8380
8513
|
if (!settings) {
|
|
8381
8514
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
|
|
@@ -8395,10 +8528,10 @@ function teardownHud() {
|
|
|
8395
8528
|
|
|
8396
8529
|
// src/cli.ts
|
|
8397
8530
|
init_daemon2();
|
|
8398
|
-
var
|
|
8399
|
-
var
|
|
8400
|
-
var
|
|
8401
|
-
var
|
|
8531
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
8532
|
+
var import_fs30 = __toESM(require("fs"));
|
|
8533
|
+
var import_path33 = __toESM(require("path"));
|
|
8534
|
+
var import_os26 = __toESM(require("os"));
|
|
8402
8535
|
var import_prompts2 = require("@inquirer/prompts");
|
|
8403
8536
|
|
|
8404
8537
|
// src/utils/duration.ts
|
|
@@ -8623,10 +8756,10 @@ async function autoStartDaemonAndWait() {
|
|
|
8623
8756
|
|
|
8624
8757
|
// src/cli/commands/check.ts
|
|
8625
8758
|
var import_chalk5 = __toESM(require("chalk"));
|
|
8626
|
-
var
|
|
8759
|
+
var import_fs19 = __toESM(require("fs"));
|
|
8627
8760
|
var import_child_process9 = require("child_process");
|
|
8628
|
-
var
|
|
8629
|
-
var
|
|
8761
|
+
var import_path21 = __toESM(require("path"));
|
|
8762
|
+
var import_os15 = __toESM(require("os"));
|
|
8630
8763
|
init_orchestrator();
|
|
8631
8764
|
init_daemon();
|
|
8632
8765
|
init_config();
|
|
@@ -8634,12 +8767,12 @@ init_policy();
|
|
|
8634
8767
|
|
|
8635
8768
|
// src/undo.ts
|
|
8636
8769
|
var import_child_process8 = require("child_process");
|
|
8637
|
-
var
|
|
8638
|
-
var
|
|
8770
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
8771
|
+
var import_fs18 = __toESM(require("fs"));
|
|
8639
8772
|
var import_net3 = __toESM(require("net"));
|
|
8640
|
-
var
|
|
8641
|
-
var
|
|
8642
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
8773
|
+
var import_path20 = __toESM(require("path"));
|
|
8774
|
+
var import_os14 = __toESM(require("os"));
|
|
8775
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path20.default.join(import_os14.default.tmpdir(), "node9-activity.sock");
|
|
8643
8776
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8644
8777
|
try {
|
|
8645
8778
|
const payload = JSON.stringify({
|
|
@@ -8659,22 +8792,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
8659
8792
|
} catch {
|
|
8660
8793
|
}
|
|
8661
8794
|
}
|
|
8662
|
-
var SNAPSHOT_STACK_PATH =
|
|
8663
|
-
var UNDO_LATEST_PATH =
|
|
8795
|
+
var SNAPSHOT_STACK_PATH = import_path20.default.join(import_os14.default.homedir(), ".node9", "snapshots.json");
|
|
8796
|
+
var UNDO_LATEST_PATH = import_path20.default.join(import_os14.default.homedir(), ".node9", "undo_latest.txt");
|
|
8664
8797
|
var MAX_SNAPSHOTS = 10;
|
|
8665
8798
|
var GIT_TIMEOUT = 15e3;
|
|
8666
8799
|
function readStack() {
|
|
8667
8800
|
try {
|
|
8668
|
-
if (
|
|
8669
|
-
return JSON.parse(
|
|
8801
|
+
if (import_fs18.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
8802
|
+
return JSON.parse(import_fs18.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
8670
8803
|
} catch {
|
|
8671
8804
|
}
|
|
8672
8805
|
return [];
|
|
8673
8806
|
}
|
|
8674
8807
|
function writeStack(stack) {
|
|
8675
|
-
const dir =
|
|
8676
|
-
if (!
|
|
8677
|
-
|
|
8808
|
+
const dir = import_path20.default.dirname(SNAPSHOT_STACK_PATH);
|
|
8809
|
+
if (!import_fs18.default.existsSync(dir)) import_fs18.default.mkdirSync(dir, { recursive: true });
|
|
8810
|
+
import_fs18.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
8678
8811
|
}
|
|
8679
8812
|
function extractFilePath(args) {
|
|
8680
8813
|
if (!args || typeof args !== "object") return null;
|
|
@@ -8694,12 +8827,12 @@ function buildArgsSummary(tool, args) {
|
|
|
8694
8827
|
return "";
|
|
8695
8828
|
}
|
|
8696
8829
|
function findProjectRoot(filePath) {
|
|
8697
|
-
let dir =
|
|
8830
|
+
let dir = import_path20.default.dirname(filePath);
|
|
8698
8831
|
while (true) {
|
|
8699
|
-
if (
|
|
8832
|
+
if (import_fs18.default.existsSync(import_path20.default.join(dir, ".git")) || import_fs18.default.existsSync(import_path20.default.join(dir, "package.json"))) {
|
|
8700
8833
|
return dir;
|
|
8701
8834
|
}
|
|
8702
|
-
const parent =
|
|
8835
|
+
const parent = import_path20.default.dirname(dir);
|
|
8703
8836
|
if (parent === dir) return process.cwd();
|
|
8704
8837
|
dir = parent;
|
|
8705
8838
|
}
|
|
@@ -8707,7 +8840,7 @@ function findProjectRoot(filePath) {
|
|
|
8707
8840
|
function normalizeCwdForHash(cwd) {
|
|
8708
8841
|
let normalized;
|
|
8709
8842
|
try {
|
|
8710
|
-
normalized =
|
|
8843
|
+
normalized = import_fs18.default.realpathSync(cwd);
|
|
8711
8844
|
} catch {
|
|
8712
8845
|
normalized = cwd;
|
|
8713
8846
|
}
|
|
@@ -8716,17 +8849,17 @@ function normalizeCwdForHash(cwd) {
|
|
|
8716
8849
|
return normalized;
|
|
8717
8850
|
}
|
|
8718
8851
|
function getShadowRepoDir(cwd) {
|
|
8719
|
-
const hash =
|
|
8720
|
-
return
|
|
8852
|
+
const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
8853
|
+
return import_path20.default.join(import_os14.default.homedir(), ".node9", "snapshots", hash);
|
|
8721
8854
|
}
|
|
8722
8855
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
8723
8856
|
try {
|
|
8724
8857
|
const cutoff = Date.now() - 6e4;
|
|
8725
|
-
for (const f of
|
|
8858
|
+
for (const f of import_fs18.default.readdirSync(shadowDir)) {
|
|
8726
8859
|
if (f.startsWith("index_")) {
|
|
8727
|
-
const fp =
|
|
8860
|
+
const fp = import_path20.default.join(shadowDir, f);
|
|
8728
8861
|
try {
|
|
8729
|
-
if (
|
|
8862
|
+
if (import_fs18.default.statSync(fp).mtimeMs < cutoff) import_fs18.default.unlinkSync(fp);
|
|
8730
8863
|
} catch {
|
|
8731
8864
|
}
|
|
8732
8865
|
}
|
|
@@ -8738,7 +8871,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
8738
8871
|
const hardcoded = [".git", ".node9"];
|
|
8739
8872
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
8740
8873
|
try {
|
|
8741
|
-
|
|
8874
|
+
import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
8742
8875
|
} catch {
|
|
8743
8876
|
}
|
|
8744
8877
|
}
|
|
@@ -8751,25 +8884,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8751
8884
|
timeout: 3e3
|
|
8752
8885
|
});
|
|
8753
8886
|
if (check.status === 0) {
|
|
8754
|
-
const ptPath =
|
|
8887
|
+
const ptPath = import_path20.default.join(shadowDir, "project-path.txt");
|
|
8755
8888
|
try {
|
|
8756
|
-
const stored =
|
|
8889
|
+
const stored = import_fs18.default.readFileSync(ptPath, "utf8").trim();
|
|
8757
8890
|
if (stored === normalizedCwd) return true;
|
|
8758
8891
|
if (process.env.NODE9_DEBUG === "1")
|
|
8759
8892
|
console.error(
|
|
8760
8893
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
8761
8894
|
);
|
|
8762
|
-
|
|
8895
|
+
import_fs18.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
8763
8896
|
} catch {
|
|
8764
8897
|
try {
|
|
8765
|
-
|
|
8898
|
+
import_fs18.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
8766
8899
|
} catch {
|
|
8767
8900
|
}
|
|
8768
8901
|
return true;
|
|
8769
8902
|
}
|
|
8770
8903
|
}
|
|
8771
8904
|
try {
|
|
8772
|
-
|
|
8905
|
+
import_fs18.default.mkdirSync(shadowDir, { recursive: true });
|
|
8773
8906
|
} catch {
|
|
8774
8907
|
}
|
|
8775
8908
|
const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -8778,7 +8911,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8778
8911
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
8779
8912
|
return false;
|
|
8780
8913
|
}
|
|
8781
|
-
const configFile =
|
|
8914
|
+
const configFile = import_path20.default.join(shadowDir, "config");
|
|
8782
8915
|
(0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
8783
8916
|
timeout: 3e3
|
|
8784
8917
|
});
|
|
@@ -8786,7 +8919,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8786
8919
|
timeout: 3e3
|
|
8787
8920
|
});
|
|
8788
8921
|
try {
|
|
8789
|
-
|
|
8922
|
+
import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
8790
8923
|
} catch {
|
|
8791
8924
|
}
|
|
8792
8925
|
return true;
|
|
@@ -8806,12 +8939,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8806
8939
|
let indexFile = null;
|
|
8807
8940
|
try {
|
|
8808
8941
|
const rawFilePath = extractFilePath(args);
|
|
8809
|
-
const absFilePath = rawFilePath &&
|
|
8942
|
+
const absFilePath = rawFilePath && import_path20.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
8810
8943
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
8811
8944
|
const shadowDir = getShadowRepoDir(cwd);
|
|
8812
8945
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
8813
8946
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
8814
|
-
indexFile =
|
|
8947
|
+
indexFile = import_path20.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
8815
8948
|
const shadowEnv = {
|
|
8816
8949
|
...process.env,
|
|
8817
8950
|
GIT_DIR: shadowDir,
|
|
@@ -8883,7 +9016,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8883
9016
|
writeStack(stack);
|
|
8884
9017
|
const entry = stack[stack.length - 1];
|
|
8885
9018
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
8886
|
-
|
|
9019
|
+
import_fs18.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
8887
9020
|
if (shouldGc) {
|
|
8888
9021
|
(0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
8889
9022
|
}
|
|
@@ -8894,7 +9027,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8894
9027
|
} finally {
|
|
8895
9028
|
if (indexFile) {
|
|
8896
9029
|
try {
|
|
8897
|
-
|
|
9030
|
+
import_fs18.default.unlinkSync(indexFile);
|
|
8898
9031
|
} catch {
|
|
8899
9032
|
}
|
|
8900
9033
|
}
|
|
@@ -8970,9 +9103,9 @@ function applyUndo(hash, cwd) {
|
|
|
8970
9103
|
timeout: GIT_TIMEOUT
|
|
8971
9104
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8972
9105
|
for (const file of [...tracked, ...untracked]) {
|
|
8973
|
-
const fullPath =
|
|
8974
|
-
if (!snapshotFiles.has(file) &&
|
|
8975
|
-
|
|
9106
|
+
const fullPath = import_path20.default.join(dir, file);
|
|
9107
|
+
if (!snapshotFiles.has(file) && import_fs18.default.existsSync(fullPath)) {
|
|
9108
|
+
import_fs18.default.unlinkSync(fullPath);
|
|
8976
9109
|
}
|
|
8977
9110
|
}
|
|
8978
9111
|
return true;
|
|
@@ -8996,9 +9129,9 @@ function registerCheckCommand(program2) {
|
|
|
8996
9129
|
} catch (err2) {
|
|
8997
9130
|
const tempConfig = getConfig();
|
|
8998
9131
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
8999
|
-
const logPath =
|
|
9132
|
+
const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
9000
9133
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9001
|
-
|
|
9134
|
+
import_fs19.default.appendFileSync(
|
|
9002
9135
|
logPath,
|
|
9003
9136
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
9004
9137
|
RAW: ${raw}
|
|
@@ -9011,10 +9144,10 @@ RAW: ${raw}
|
|
|
9011
9144
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9012
9145
|
try {
|
|
9013
9146
|
const scriptPath = process.argv[1];
|
|
9014
|
-
if (typeof scriptPath !== "string" || !
|
|
9147
|
+
if (typeof scriptPath !== "string" || !import_path21.default.isAbsolute(scriptPath))
|
|
9015
9148
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
9016
|
-
const resolvedScript =
|
|
9017
|
-
const expectedCli =
|
|
9149
|
+
const resolvedScript = import_fs19.default.realpathSync(scriptPath);
|
|
9150
|
+
const expectedCli = import_fs19.default.realpathSync(import_path21.default.resolve(__dirname, "../../cli.js"));
|
|
9018
9151
|
if (resolvedScript !== expectedCli)
|
|
9019
9152
|
throw new Error(
|
|
9020
9153
|
"node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
|
|
@@ -9040,10 +9173,10 @@ RAW: ${raw}
|
|
|
9040
9173
|
}
|
|
9041
9174
|
}
|
|
9042
9175
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9043
|
-
const logPath =
|
|
9044
|
-
if (!
|
|
9045
|
-
|
|
9046
|
-
|
|
9176
|
+
const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
9177
|
+
if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
|
|
9178
|
+
import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
|
|
9179
|
+
import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
9047
9180
|
`);
|
|
9048
9181
|
}
|
|
9049
9182
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -9056,8 +9189,8 @@ RAW: ${raw}
|
|
|
9056
9189
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
9057
9190
|
let ttyFd = null;
|
|
9058
9191
|
try {
|
|
9059
|
-
ttyFd =
|
|
9060
|
-
const writeTty = (line) =>
|
|
9192
|
+
ttyFd = import_fs19.default.openSync("/dev/tty", "w");
|
|
9193
|
+
const writeTty = (line) => import_fs19.default.writeSync(ttyFd, line + "\n");
|
|
9061
9194
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
9062
9195
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
9063
9196
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -9066,6 +9199,7 @@ RAW: ${raw}
|
|
|
9066
9199
|
writeTty(import_chalk5.default.red(`
|
|
9067
9200
|
\u{1F6D1} Node9 blocked "${toolName}"`));
|
|
9068
9201
|
}
|
|
9202
|
+
if (result2?.ruleDescription) writeTty(import_chalk5.default.white(` ${result2.ruleDescription}`));
|
|
9069
9203
|
writeTty(import_chalk5.default.gray(` Triggered by: ${blockedByContext}`));
|
|
9070
9204
|
if (result2?.changeHint) writeTty(import_chalk5.default.cyan(` To change: ${result2.changeHint}`));
|
|
9071
9205
|
if (result2?.recoveryCommand)
|
|
@@ -9075,7 +9209,7 @@ RAW: ${raw}
|
|
|
9075
9209
|
} finally {
|
|
9076
9210
|
if (ttyFd !== null)
|
|
9077
9211
|
try {
|
|
9078
|
-
|
|
9212
|
+
import_fs19.default.closeSync(ttyFd);
|
|
9079
9213
|
} catch {
|
|
9080
9214
|
}
|
|
9081
9215
|
}
|
|
@@ -9107,7 +9241,7 @@ RAW: ${raw}
|
|
|
9107
9241
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
9108
9242
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
9109
9243
|
}
|
|
9110
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
9244
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9111
9245
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
9112
9246
|
cwd: safeCwdForAuth
|
|
9113
9247
|
});
|
|
@@ -9119,12 +9253,12 @@ RAW: ${raw}
|
|
|
9119
9253
|
}
|
|
9120
9254
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
9121
9255
|
try {
|
|
9122
|
-
const tty =
|
|
9123
|
-
|
|
9256
|
+
const tty = import_fs19.default.openSync("/dev/tty", "w");
|
|
9257
|
+
import_fs19.default.writeSync(
|
|
9124
9258
|
tty,
|
|
9125
9259
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
9126
9260
|
);
|
|
9127
|
-
|
|
9261
|
+
import_fs19.default.closeSync(tty);
|
|
9128
9262
|
} catch {
|
|
9129
9263
|
}
|
|
9130
9264
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -9151,9 +9285,9 @@ RAW: ${raw}
|
|
|
9151
9285
|
});
|
|
9152
9286
|
} catch (err2) {
|
|
9153
9287
|
if (process.env.NODE9_DEBUG === "1") {
|
|
9154
|
-
const logPath =
|
|
9288
|
+
const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
9155
9289
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9156
|
-
|
|
9290
|
+
import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
9157
9291
|
`);
|
|
9158
9292
|
}
|
|
9159
9293
|
process.exit(0);
|
|
@@ -9187,9 +9321,9 @@ RAW: ${raw}
|
|
|
9187
9321
|
}
|
|
9188
9322
|
|
|
9189
9323
|
// src/cli/commands/log.ts
|
|
9190
|
-
var
|
|
9191
|
-
var
|
|
9192
|
-
var
|
|
9324
|
+
var import_fs20 = __toESM(require("fs"));
|
|
9325
|
+
var import_path22 = __toESM(require("path"));
|
|
9326
|
+
var import_os16 = __toESM(require("os"));
|
|
9193
9327
|
init_audit();
|
|
9194
9328
|
init_config();
|
|
9195
9329
|
init_policy();
|
|
@@ -9233,9 +9367,9 @@ function containsShellMetachar(token) {
|
|
|
9233
9367
|
}
|
|
9234
9368
|
|
|
9235
9369
|
// src/cli/commands/log.ts
|
|
9236
|
-
var
|
|
9370
|
+
var TEST_COMMAND_RE2 = /(?:^|\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;
|
|
9237
9371
|
function detectTestResult(command, output) {
|
|
9238
|
-
if (!
|
|
9372
|
+
if (!TEST_COMMAND_RE2.test(command)) return null;
|
|
9239
9373
|
const out = output.toLowerCase();
|
|
9240
9374
|
if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
|
|
9241
9375
|
out
|
|
@@ -9265,10 +9399,10 @@ function registerLogCommand(program2) {
|
|
|
9265
9399
|
decision: "allowed",
|
|
9266
9400
|
source: "post-hook"
|
|
9267
9401
|
};
|
|
9268
|
-
const logPath =
|
|
9269
|
-
if (!
|
|
9270
|
-
|
|
9271
|
-
|
|
9402
|
+
const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "audit.log");
|
|
9403
|
+
if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
|
|
9404
|
+
import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
|
|
9405
|
+
import_fs20.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
9272
9406
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
9273
9407
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
9274
9408
|
if (command) {
|
|
@@ -9284,16 +9418,24 @@ function registerLogCommand(program2) {
|
|
|
9284
9418
|
if (bashCommand && output) {
|
|
9285
9419
|
const testResult = detectTestResult(bashCommand, output);
|
|
9286
9420
|
if (testResult) {
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
ts: Date.now(),
|
|
9421
|
+
appendToLog(LOCAL_AUDIT_LOG, {
|
|
9422
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9290
9423
|
tool,
|
|
9291
|
-
|
|
9424
|
+
testResult,
|
|
9425
|
+
source: "test-result"
|
|
9292
9426
|
});
|
|
9427
|
+
if (isDaemonRunning()) {
|
|
9428
|
+
await notifyActivitySocket({
|
|
9429
|
+
id: "test-result",
|
|
9430
|
+
ts: Date.now(),
|
|
9431
|
+
tool,
|
|
9432
|
+
status: testResult === "pass" ? "test_pass" : "test_fail"
|
|
9433
|
+
});
|
|
9434
|
+
}
|
|
9293
9435
|
}
|
|
9294
9436
|
}
|
|
9295
9437
|
}
|
|
9296
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
9438
|
+
const safeCwd = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9297
9439
|
const config = getConfig(safeCwd);
|
|
9298
9440
|
if (shouldSnapshot(tool, {}, config)) {
|
|
9299
9441
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -9302,9 +9444,9 @@ function registerLogCommand(program2) {
|
|
|
9302
9444
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
9303
9445
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
9304
9446
|
`);
|
|
9305
|
-
const debugPath =
|
|
9447
|
+
const debugPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
|
|
9306
9448
|
try {
|
|
9307
|
-
|
|
9449
|
+
import_fs20.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
9308
9450
|
`);
|
|
9309
9451
|
} catch {
|
|
9310
9452
|
}
|
|
@@ -9704,14 +9846,14 @@ function registerConfigShowCommand(program2) {
|
|
|
9704
9846
|
|
|
9705
9847
|
// src/cli/commands/doctor.ts
|
|
9706
9848
|
var import_chalk7 = __toESM(require("chalk"));
|
|
9707
|
-
var
|
|
9708
|
-
var
|
|
9709
|
-
var
|
|
9849
|
+
var import_fs21 = __toESM(require("fs"));
|
|
9850
|
+
var import_path23 = __toESM(require("path"));
|
|
9851
|
+
var import_os17 = __toESM(require("os"));
|
|
9710
9852
|
var import_child_process10 = require("child_process");
|
|
9711
9853
|
init_daemon();
|
|
9712
9854
|
function registerDoctorCommand(program2, version2) {
|
|
9713
9855
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
9714
|
-
const homeDir2 =
|
|
9856
|
+
const homeDir2 = import_os17.default.homedir();
|
|
9715
9857
|
let failures = 0;
|
|
9716
9858
|
function pass(msg) {
|
|
9717
9859
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -9760,10 +9902,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9760
9902
|
);
|
|
9761
9903
|
}
|
|
9762
9904
|
section("Configuration");
|
|
9763
|
-
const globalConfigPath =
|
|
9764
|
-
if (
|
|
9905
|
+
const globalConfigPath = import_path23.default.join(homeDir2, ".node9", "config.json");
|
|
9906
|
+
if (import_fs21.default.existsSync(globalConfigPath)) {
|
|
9765
9907
|
try {
|
|
9766
|
-
JSON.parse(
|
|
9908
|
+
JSON.parse(import_fs21.default.readFileSync(globalConfigPath, "utf-8"));
|
|
9767
9909
|
pass("~/.node9/config.json found and valid");
|
|
9768
9910
|
} catch {
|
|
9769
9911
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -9771,10 +9913,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9771
9913
|
} else {
|
|
9772
9914
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
9773
9915
|
}
|
|
9774
|
-
const projectConfigPath =
|
|
9775
|
-
if (
|
|
9916
|
+
const projectConfigPath = import_path23.default.join(process.cwd(), "node9.config.json");
|
|
9917
|
+
if (import_fs21.default.existsSync(projectConfigPath)) {
|
|
9776
9918
|
try {
|
|
9777
|
-
JSON.parse(
|
|
9919
|
+
JSON.parse(import_fs21.default.readFileSync(projectConfigPath, "utf-8"));
|
|
9778
9920
|
pass("node9.config.json found and valid (project)");
|
|
9779
9921
|
} catch {
|
|
9780
9922
|
fail(
|
|
@@ -9783,8 +9925,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9783
9925
|
);
|
|
9784
9926
|
}
|
|
9785
9927
|
}
|
|
9786
|
-
const credsPath =
|
|
9787
|
-
if (
|
|
9928
|
+
const credsPath = import_path23.default.join(homeDir2, ".node9", "credentials.json");
|
|
9929
|
+
if (import_fs21.default.existsSync(credsPath)) {
|
|
9788
9930
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
9789
9931
|
} else {
|
|
9790
9932
|
warn(
|
|
@@ -9793,10 +9935,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9793
9935
|
);
|
|
9794
9936
|
}
|
|
9795
9937
|
section("Agent Hooks");
|
|
9796
|
-
const claudeSettingsPath =
|
|
9797
|
-
if (
|
|
9938
|
+
const claudeSettingsPath = import_path23.default.join(homeDir2, ".claude", "settings.json");
|
|
9939
|
+
if (import_fs21.default.existsSync(claudeSettingsPath)) {
|
|
9798
9940
|
try {
|
|
9799
|
-
const cs = JSON.parse(
|
|
9941
|
+
const cs = JSON.parse(import_fs21.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
9800
9942
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
9801
9943
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9802
9944
|
);
|
|
@@ -9812,10 +9954,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9812
9954
|
} else {
|
|
9813
9955
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
9814
9956
|
}
|
|
9815
|
-
const geminiSettingsPath =
|
|
9816
|
-
if (
|
|
9957
|
+
const geminiSettingsPath = import_path23.default.join(homeDir2, ".gemini", "settings.json");
|
|
9958
|
+
if (import_fs21.default.existsSync(geminiSettingsPath)) {
|
|
9817
9959
|
try {
|
|
9818
|
-
const gs = JSON.parse(
|
|
9960
|
+
const gs = JSON.parse(import_fs21.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
9819
9961
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
9820
9962
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9821
9963
|
);
|
|
@@ -9831,10 +9973,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9831
9973
|
} else {
|
|
9832
9974
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
9833
9975
|
}
|
|
9834
|
-
const cursorHooksPath =
|
|
9835
|
-
if (
|
|
9976
|
+
const cursorHooksPath = import_path23.default.join(homeDir2, ".cursor", "hooks.json");
|
|
9977
|
+
if (import_fs21.default.existsSync(cursorHooksPath)) {
|
|
9836
9978
|
try {
|
|
9837
|
-
const cur = JSON.parse(
|
|
9979
|
+
const cur = JSON.parse(import_fs21.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
9838
9980
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
9839
9981
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
9840
9982
|
);
|
|
@@ -9872,9 +10014,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9872
10014
|
|
|
9873
10015
|
// src/cli/commands/audit.ts
|
|
9874
10016
|
var import_chalk8 = __toESM(require("chalk"));
|
|
9875
|
-
var
|
|
9876
|
-
var
|
|
9877
|
-
var
|
|
10017
|
+
var import_fs22 = __toESM(require("fs"));
|
|
10018
|
+
var import_path24 = __toESM(require("path"));
|
|
10019
|
+
var import_os18 = __toESM(require("os"));
|
|
9878
10020
|
function formatRelativeTime(timestamp) {
|
|
9879
10021
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
9880
10022
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -9887,14 +10029,14 @@ function formatRelativeTime(timestamp) {
|
|
|
9887
10029
|
}
|
|
9888
10030
|
function registerAuditCommand(program2) {
|
|
9889
10031
|
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) => {
|
|
9890
|
-
const logPath =
|
|
9891
|
-
if (!
|
|
10032
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "audit.log");
|
|
10033
|
+
if (!import_fs22.default.existsSync(logPath)) {
|
|
9892
10034
|
console.log(
|
|
9893
10035
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
9894
10036
|
);
|
|
9895
10037
|
return;
|
|
9896
10038
|
}
|
|
9897
|
-
const raw =
|
|
10039
|
+
const raw = import_fs22.default.readFileSync(logPath, "utf-8");
|
|
9898
10040
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
9899
10041
|
let entries = lines.flatMap((line) => {
|
|
9900
10042
|
try {
|
|
@@ -9946,8 +10088,396 @@ function registerAuditCommand(program2) {
|
|
|
9946
10088
|
});
|
|
9947
10089
|
}
|
|
9948
10090
|
|
|
9949
|
-
// src/cli/commands/
|
|
10091
|
+
// src/cli/commands/report.ts
|
|
9950
10092
|
var import_chalk9 = __toESM(require("chalk"));
|
|
10093
|
+
var import_fs23 = __toESM(require("fs"));
|
|
10094
|
+
var import_path25 = __toESM(require("path"));
|
|
10095
|
+
var import_os19 = __toESM(require("os"));
|
|
10096
|
+
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;
|
|
10097
|
+
function buildTestTimestamps(allEntries) {
|
|
10098
|
+
const testTs = /* @__PURE__ */ new Set();
|
|
10099
|
+
for (const e of allEntries) {
|
|
10100
|
+
if (e.source !== "post-hook") continue;
|
|
10101
|
+
if (e.tool !== "Bash" && e.tool !== "bash") continue;
|
|
10102
|
+
const cmd = e.args?.command;
|
|
10103
|
+
if (typeof cmd === "string" && TEST_COMMAND_RE3.test(cmd)) {
|
|
10104
|
+
testTs.add(new Date(e.ts).getTime());
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
return testTs;
|
|
10108
|
+
}
|
|
10109
|
+
function isTestEntry(entry, testTs) {
|
|
10110
|
+
if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
|
|
10111
|
+
if (entry.testRun === true) return true;
|
|
10112
|
+
const cmd = entry.args?.command;
|
|
10113
|
+
if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
|
|
10114
|
+
const t = new Date(entry.ts).getTime();
|
|
10115
|
+
for (const ts of testTs) {
|
|
10116
|
+
if (Math.abs(ts - t) <= 3e3) return true;
|
|
10117
|
+
}
|
|
10118
|
+
return false;
|
|
10119
|
+
}
|
|
10120
|
+
function getDateRange(period) {
|
|
10121
|
+
const now = /* @__PURE__ */ new Date();
|
|
10122
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
10123
|
+
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
10124
|
+
switch (period) {
|
|
10125
|
+
case "today":
|
|
10126
|
+
return { start: todayStart, end };
|
|
10127
|
+
case "7d": {
|
|
10128
|
+
const s = new Date(todayStart);
|
|
10129
|
+
s.setDate(s.getDate() - 6);
|
|
10130
|
+
return { start: s, end };
|
|
10131
|
+
}
|
|
10132
|
+
case "30d": {
|
|
10133
|
+
const s = new Date(todayStart);
|
|
10134
|
+
s.setDate(s.getDate() - 29);
|
|
10135
|
+
return { start: s, end };
|
|
10136
|
+
}
|
|
10137
|
+
case "month":
|
|
10138
|
+
return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
|
|
10139
|
+
}
|
|
10140
|
+
}
|
|
10141
|
+
function parseAuditLog(logPath) {
|
|
10142
|
+
if (!import_fs23.default.existsSync(logPath)) return [];
|
|
10143
|
+
const raw = import_fs23.default.readFileSync(logPath, "utf-8");
|
|
10144
|
+
return raw.split("\n").flatMap((line) => {
|
|
10145
|
+
if (!line.trim()) return [];
|
|
10146
|
+
try {
|
|
10147
|
+
return [JSON.parse(line)];
|
|
10148
|
+
} catch {
|
|
10149
|
+
return [];
|
|
10150
|
+
}
|
|
10151
|
+
});
|
|
10152
|
+
}
|
|
10153
|
+
function isAllow(decision) {
|
|
10154
|
+
return decision.startsWith("allow");
|
|
10155
|
+
}
|
|
10156
|
+
function isDlp(checkedBy) {
|
|
10157
|
+
return !!checkedBy?.includes("dlp");
|
|
10158
|
+
}
|
|
10159
|
+
function barStr(value, max, width) {
|
|
10160
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
10161
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
10162
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
10163
|
+
}
|
|
10164
|
+
function colorBar(value, max, width) {
|
|
10165
|
+
const s = barStr(value, max, width);
|
|
10166
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
10167
|
+
return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
|
|
10168
|
+
}
|
|
10169
|
+
function pct(num2, total) {
|
|
10170
|
+
if (total === 0) return "\u2013";
|
|
10171
|
+
return Math.round(num2 / total * 100) + "%";
|
|
10172
|
+
}
|
|
10173
|
+
function fmtDate(d) {
|
|
10174
|
+
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
10175
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
10176
|
+
}
|
|
10177
|
+
function num(n) {
|
|
10178
|
+
return n.toLocaleString();
|
|
10179
|
+
}
|
|
10180
|
+
function fmtCost(usd) {
|
|
10181
|
+
if (usd < 1e-3) return "< $0.001";
|
|
10182
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
10183
|
+
return "$" + usd.toFixed(2);
|
|
10184
|
+
}
|
|
10185
|
+
var CLAUDE_PRICING = {
|
|
10186
|
+
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
10187
|
+
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
10188
|
+
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
10189
|
+
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10190
|
+
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10191
|
+
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10192
|
+
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10193
|
+
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10194
|
+
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
10195
|
+
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
10196
|
+
};
|
|
10197
|
+
function claudeModelPrice(model) {
|
|
10198
|
+
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
10199
|
+
for (const [key, p] of Object.entries(CLAUDE_PRICING)) {
|
|
10200
|
+
if (base === key || base.startsWith(key + "-") || base.startsWith(key)) return p;
|
|
10201
|
+
}
|
|
10202
|
+
return null;
|
|
10203
|
+
}
|
|
10204
|
+
function loadClaudeCost(start, end) {
|
|
10205
|
+
const projectsDir = import_path25.default.join(import_os19.default.homedir(), ".claude", "projects");
|
|
10206
|
+
if (!import_fs23.default.existsSync(projectsDir)) return { total: 0, byDay: /* @__PURE__ */ new Map() };
|
|
10207
|
+
let dirs;
|
|
10208
|
+
try {
|
|
10209
|
+
dirs = import_fs23.default.readdirSync(projectsDir);
|
|
10210
|
+
} catch {
|
|
10211
|
+
return { total: 0, byDay: /* @__PURE__ */ new Map() };
|
|
10212
|
+
}
|
|
10213
|
+
let total = 0;
|
|
10214
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
10215
|
+
for (const proj of dirs) {
|
|
10216
|
+
const projPath = import_path25.default.join(projectsDir, proj);
|
|
10217
|
+
let files;
|
|
10218
|
+
try {
|
|
10219
|
+
const stat = import_fs23.default.statSync(projPath);
|
|
10220
|
+
if (!stat.isDirectory()) continue;
|
|
10221
|
+
files = import_fs23.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
10222
|
+
} catch {
|
|
10223
|
+
continue;
|
|
10224
|
+
}
|
|
10225
|
+
for (const file of files) {
|
|
10226
|
+
try {
|
|
10227
|
+
const raw = import_fs23.default.readFileSync(import_path25.default.join(projPath, file), "utf-8");
|
|
10228
|
+
for (const line of raw.split("\n")) {
|
|
10229
|
+
if (!line.trim()) continue;
|
|
10230
|
+
let entry;
|
|
10231
|
+
try {
|
|
10232
|
+
entry = JSON.parse(line);
|
|
10233
|
+
} catch {
|
|
10234
|
+
continue;
|
|
10235
|
+
}
|
|
10236
|
+
if (entry.type !== "assistant") continue;
|
|
10237
|
+
if (!entry.timestamp) continue;
|
|
10238
|
+
const ts = new Date(entry.timestamp);
|
|
10239
|
+
if (ts < start || ts > end) continue;
|
|
10240
|
+
const usage = entry.message?.usage;
|
|
10241
|
+
const model = entry.message?.model;
|
|
10242
|
+
if (!usage || !model) continue;
|
|
10243
|
+
const p = claudeModelPrice(model);
|
|
10244
|
+
if (!p) continue;
|
|
10245
|
+
const cost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
|
|
10246
|
+
total += cost;
|
|
10247
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
10248
|
+
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
10249
|
+
}
|
|
10250
|
+
} catch {
|
|
10251
|
+
continue;
|
|
10252
|
+
}
|
|
10253
|
+
}
|
|
10254
|
+
}
|
|
10255
|
+
return { total, byDay };
|
|
10256
|
+
}
|
|
10257
|
+
function registerReportCommand(program2) {
|
|
10258
|
+
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) => {
|
|
10259
|
+
const period = ["today", "7d", "30d", "month"].includes(
|
|
10260
|
+
options.period
|
|
10261
|
+
) ? options.period : "7d";
|
|
10262
|
+
const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
|
|
10263
|
+
const allEntries = parseAuditLog(logPath);
|
|
10264
|
+
if (allEntries.length === 0) {
|
|
10265
|
+
console.log(
|
|
10266
|
+
import_chalk9.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
10267
|
+
);
|
|
10268
|
+
return;
|
|
10269
|
+
}
|
|
10270
|
+
const { start, end } = getDateRange(period);
|
|
10271
|
+
const { total: costUSD, byDay: costByDay } = loadClaudeCost(start, end);
|
|
10272
|
+
const periodMs = end.getTime() - start.getTime();
|
|
10273
|
+
const priorEnd = new Date(start.getTime() - 1);
|
|
10274
|
+
const priorStart = new Date(start.getTime() - periodMs);
|
|
10275
|
+
const priorEntries = allEntries.filter((e) => {
|
|
10276
|
+
if (e.source === "post-hook") return false;
|
|
10277
|
+
const ts = new Date(e.ts);
|
|
10278
|
+
return ts >= priorStart && ts <= priorEnd;
|
|
10279
|
+
});
|
|
10280
|
+
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
10281
|
+
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
10282
|
+
const excludeTests = options.tests === false;
|
|
10283
|
+
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
10284
|
+
let filteredTestCount = 0;
|
|
10285
|
+
const entries = allEntries.filter((e) => {
|
|
10286
|
+
if (e.source === "post-hook") return false;
|
|
10287
|
+
const ts = new Date(e.ts);
|
|
10288
|
+
if (ts < start || ts > end) return false;
|
|
10289
|
+
if (excludeTests && isTestEntry(e, testTs)) {
|
|
10290
|
+
filteredTestCount++;
|
|
10291
|
+
return false;
|
|
10292
|
+
}
|
|
10293
|
+
return true;
|
|
10294
|
+
});
|
|
10295
|
+
if (entries.length === 0) {
|
|
10296
|
+
console.log(import_chalk9.default.yellow(`
|
|
10297
|
+
No activity for period "${period}".
|
|
10298
|
+
`));
|
|
10299
|
+
return;
|
|
10300
|
+
}
|
|
10301
|
+
let allowed = 0;
|
|
10302
|
+
let blocked = 0;
|
|
10303
|
+
let dlpHits = 0;
|
|
10304
|
+
let loopHits = 0;
|
|
10305
|
+
let testPasses = 0;
|
|
10306
|
+
let testFails = 0;
|
|
10307
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
10308
|
+
const blockMap = /* @__PURE__ */ new Map();
|
|
10309
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
10310
|
+
const mcpMap = /* @__PURE__ */ new Map();
|
|
10311
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
10312
|
+
const hourMap = /* @__PURE__ */ new Map();
|
|
10313
|
+
for (const e of entries) {
|
|
10314
|
+
const allow = isAllow(e.decision);
|
|
10315
|
+
const dateKey = e.ts.slice(0, 10);
|
|
10316
|
+
if (allow) allowed++;
|
|
10317
|
+
else blocked++;
|
|
10318
|
+
if (isDlp(e.checkedBy)) dlpHits++;
|
|
10319
|
+
if (e.checkedBy === "loop-detected") loopHits++;
|
|
10320
|
+
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
10321
|
+
t.calls++;
|
|
10322
|
+
if (!allow) t.blocked++;
|
|
10323
|
+
toolMap.set(e.tool, t);
|
|
10324
|
+
if (!allow && e.checkedBy) {
|
|
10325
|
+
blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
|
|
10326
|
+
}
|
|
10327
|
+
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
10328
|
+
if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
|
|
10329
|
+
const hour = new Date(e.ts).getHours();
|
|
10330
|
+
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
10331
|
+
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
10332
|
+
d.calls++;
|
|
10333
|
+
if (!allow) d.blocked++;
|
|
10334
|
+
dailyMap.set(dateKey, d);
|
|
10335
|
+
}
|
|
10336
|
+
for (const e of allEntries) {
|
|
10337
|
+
if (e.source !== "test-result") continue;
|
|
10338
|
+
const ts = new Date(e.ts);
|
|
10339
|
+
if (ts < start || ts > end) continue;
|
|
10340
|
+
if (e.testResult === "pass") testPasses++;
|
|
10341
|
+
else if (e.testResult === "fail") testFails++;
|
|
10342
|
+
}
|
|
10343
|
+
const total = entries.length;
|
|
10344
|
+
const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
|
|
10345
|
+
const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
10346
|
+
const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
|
|
10347
|
+
const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
|
|
10348
|
+
const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
|
|
10349
|
+
const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
|
|
10350
|
+
const W = Math.min(process.stdout.columns || 80, 100);
|
|
10351
|
+
const INNER = W - 4;
|
|
10352
|
+
const COL = Math.floor(INNER / 2) - 1;
|
|
10353
|
+
const LABEL = 24;
|
|
10354
|
+
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
10355
|
+
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num(v.calls).length), 1);
|
|
10356
|
+
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num(v).length), 1);
|
|
10357
|
+
const line = import_chalk9.default.dim("\u2500".repeat(W - 2));
|
|
10358
|
+
const periodLabel = {
|
|
10359
|
+
today: "Today",
|
|
10360
|
+
"7d": "Last 7 Days",
|
|
10361
|
+
"30d": "Last 30 Days",
|
|
10362
|
+
month: "This Month"
|
|
10363
|
+
};
|
|
10364
|
+
console.log("");
|
|
10365
|
+
console.log(
|
|
10366
|
+
" " + import_chalk9.default.bold.cyan("\u{1F6E1} node9 Report") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.white(periodLabel[period]) + import_chalk9.default.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + import_chalk9.default.dim(` ${num(total)} events`) + (excludeTests ? import_chalk9.default.dim(` \u2013tests (\u2013${filteredTestCount})`) : "")
|
|
10367
|
+
);
|
|
10368
|
+
console.log(" " + line);
|
|
10369
|
+
console.log("");
|
|
10370
|
+
const blockLabel = blocked > 0 ? import_chalk9.default.red(`\u{1F6D1} ${num(blocked)} blocked`) : import_chalk9.default.dim("\u{1F6D1} 0 blocked");
|
|
10371
|
+
const dlpLabel = dlpHits > 0 ? import_chalk9.default.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : import_chalk9.default.dim("\u{1F6A8} 0 DLP hits");
|
|
10372
|
+
const loopLabel = loopHits > 0 ? import_chalk9.default.yellow(`\u{1F504} ${loopHits} loops`) : import_chalk9.default.dim("\u{1F504} 0 loops");
|
|
10373
|
+
const costLabel = costUSD > 0 ? import_chalk9.default.magenta(`\u{1F4B0} ${fmtCost(costUSD)}`) : import_chalk9.default.dim("\u{1F4B0} \u2013");
|
|
10374
|
+
const currentRate = total > 0 ? blocked / total : 0;
|
|
10375
|
+
const trendLabel = (() => {
|
|
10376
|
+
if (priorBlockRate === null) return import_chalk9.default.dim(`${pct(blocked, total)} block rate`);
|
|
10377
|
+
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
10378
|
+
const arrow = delta > 0 ? import_chalk9.default.red(`\u25B2${delta}%`) : delta < 0 ? import_chalk9.default.green(`\u25BC${Math.abs(delta)}%`) : import_chalk9.default.dim("\u2013");
|
|
10379
|
+
return import_chalk9.default.dim(`${pct(blocked, total)} block rate `) + arrow + import_chalk9.default.dim(" vs prior");
|
|
10380
|
+
})();
|
|
10381
|
+
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
10382
|
+
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
10383
|
+
const ratioLabel = reads > 0 ? import_chalk9.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk9.default.dim("edit/read \u2013");
|
|
10384
|
+
const testLabel = testPasses + testFails > 0 ? import_chalk9.default.dim("tests ") + import_chalk9.default.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + import_chalk9.default.red(`${testFails}\u2717`) : "") : import_chalk9.default.dim("tests \u2013");
|
|
10385
|
+
console.log(
|
|
10386
|
+
" " + import_chalk9.default.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel + " " + costLabel
|
|
10387
|
+
);
|
|
10388
|
+
console.log(" " + ratioLabel + " " + testLabel);
|
|
10389
|
+
console.log("");
|
|
10390
|
+
const toolHeaderRaw = "Top Tools";
|
|
10391
|
+
const blockHeaderRaw = "Top Blocks";
|
|
10392
|
+
console.log(
|
|
10393
|
+
" " + import_chalk9.default.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + import_chalk9.default.bold(blockHeaderRaw)
|
|
10394
|
+
);
|
|
10395
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(COL)) + " " + import_chalk9.default.dim("\u2500".repeat(COL)));
|
|
10396
|
+
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
10397
|
+
for (let i = 0; i < rows; i++) {
|
|
10398
|
+
let leftStyled = " ".repeat(COL);
|
|
10399
|
+
if (i < topTools.length) {
|
|
10400
|
+
const [tool, { calls }] = topTools[i];
|
|
10401
|
+
const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
|
|
10402
|
+
const countStr = num(calls).padStart(TOOL_COUNT_W);
|
|
10403
|
+
const b = colorBar(calls, maxTool, BAR);
|
|
10404
|
+
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
10405
|
+
const pad = Math.max(0, COL - rawLen);
|
|
10406
|
+
leftStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(countStr) + " ".repeat(pad);
|
|
10407
|
+
}
|
|
10408
|
+
let rightStyled = "";
|
|
10409
|
+
if (i < topBlocks.length) {
|
|
10410
|
+
const [reason, count] = topBlocks[i];
|
|
10411
|
+
const label = reason.length > LABEL - 1 ? reason.slice(0, LABEL - 2) + "\u2026" : reason;
|
|
10412
|
+
const countStr = num(count).padStart(BLOCK_COUNT_W);
|
|
10413
|
+
const b = colorBar(count, maxBlock, BAR);
|
|
10414
|
+
rightStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.red(countStr);
|
|
10415
|
+
}
|
|
10416
|
+
console.log(" " + leftStyled + " " + rightStyled);
|
|
10417
|
+
}
|
|
10418
|
+
if (topBlocks.length === 0) {
|
|
10419
|
+
console.log(" " + " ".repeat(COL) + " " + import_chalk9.default.dim("nothing blocked \u2713"));
|
|
10420
|
+
}
|
|
10421
|
+
if (agentMap.size > 1) {
|
|
10422
|
+
console.log("");
|
|
10423
|
+
console.log(" " + import_chalk9.default.bold("Agents"));
|
|
10424
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
10425
|
+
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
10426
|
+
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
10427
|
+
const label = agent.slice(0, LABEL - 1);
|
|
10428
|
+
const b = colorBar(count, maxAgent, BAR);
|
|
10429
|
+
console.log(" " + import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(num(count)));
|
|
10430
|
+
}
|
|
10431
|
+
}
|
|
10432
|
+
if (mcpMap.size > 0) {
|
|
10433
|
+
console.log("");
|
|
10434
|
+
console.log(" " + import_chalk9.default.bold("MCP Servers"));
|
|
10435
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
10436
|
+
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
10437
|
+
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
10438
|
+
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
10439
|
+
const b = colorBar(count, maxMcp, BAR);
|
|
10440
|
+
console.log(" " + import_chalk9.default.white(label) + b + " " + import_chalk9.default.white(num(count)));
|
|
10441
|
+
}
|
|
10442
|
+
}
|
|
10443
|
+
if (hourMap.size > 0) {
|
|
10444
|
+
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
10445
|
+
const maxHour = Math.max(...hourMap.values(), 1);
|
|
10446
|
+
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
10447
|
+
const v = hourMap.get(h) ?? 0;
|
|
10448
|
+
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
10449
|
+
}).join("");
|
|
10450
|
+
console.log("");
|
|
10451
|
+
console.log(" " + import_chalk9.default.bold("Hour of Day") + import_chalk9.default.dim(" (local, 0h \u2013 23h)"));
|
|
10452
|
+
console.log(" " + import_chalk9.default.cyan(bar));
|
|
10453
|
+
console.log(" " + import_chalk9.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
10454
|
+
}
|
|
10455
|
+
if (dailyList.length > 1) {
|
|
10456
|
+
console.log("");
|
|
10457
|
+
console.log(" " + import_chalk9.default.bold("Daily Activity"));
|
|
10458
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(W - 2)));
|
|
10459
|
+
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
10460
|
+
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
10461
|
+
const label = fmtDate(dateKey).padEnd(10);
|
|
10462
|
+
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
10463
|
+
const dayCost = costByDay.get(dateKey);
|
|
10464
|
+
const costNote = dayCost ? import_chalk9.default.magenta(` ${fmtCost(dayCost)}`) : "";
|
|
10465
|
+
const blockNote = db > 0 ? import_chalk9.default.red(` ${db} blocked`) : "";
|
|
10466
|
+
console.log(
|
|
10467
|
+
" " + import_chalk9.default.dim(label) + " " + b + " " + import_chalk9.default.white(num(calls)) + blockNote + costNote
|
|
10468
|
+
);
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
console.log("");
|
|
10472
|
+
console.log(
|
|
10473
|
+
" " + 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")
|
|
10474
|
+
);
|
|
10475
|
+
console.log("");
|
|
10476
|
+
});
|
|
10477
|
+
}
|
|
10478
|
+
|
|
10479
|
+
// src/cli/commands/daemon-cmd.ts
|
|
10480
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
9951
10481
|
var import_child_process11 = require("child_process");
|
|
9952
10482
|
init_daemon2();
|
|
9953
10483
|
init_daemon();
|
|
@@ -9962,7 +10492,7 @@ function registerDaemonCommand(program2) {
|
|
|
9962
10492
|
if (cmd === "status") return daemonStatus();
|
|
9963
10493
|
if (cmd !== "start" && action !== void 0) {
|
|
9964
10494
|
console.error(
|
|
9965
|
-
|
|
10495
|
+
import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
|
|
9966
10496
|
);
|
|
9967
10497
|
process.exit(1);
|
|
9968
10498
|
}
|
|
@@ -9970,7 +10500,7 @@ function registerDaemonCommand(program2) {
|
|
|
9970
10500
|
process.env.NODE9_WATCH_MODE = "1";
|
|
9971
10501
|
setTimeout(() => {
|
|
9972
10502
|
openBrowserLocal();
|
|
9973
|
-
console.log(
|
|
10503
|
+
console.log(import_chalk10.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
9974
10504
|
}, 600);
|
|
9975
10505
|
startDaemon();
|
|
9976
10506
|
return;
|
|
@@ -9978,7 +10508,7 @@ function registerDaemonCommand(program2) {
|
|
|
9978
10508
|
if (options.openui) {
|
|
9979
10509
|
if (isDaemonRunning()) {
|
|
9980
10510
|
openBrowserLocal();
|
|
9981
|
-
console.log(
|
|
10511
|
+
console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
9982
10512
|
process.exit(0);
|
|
9983
10513
|
}
|
|
9984
10514
|
const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
@@ -9991,7 +10521,7 @@ function registerDaemonCommand(program2) {
|
|
|
9991
10521
|
if (isDaemonRunning()) break;
|
|
9992
10522
|
}
|
|
9993
10523
|
openBrowserLocal();
|
|
9994
|
-
console.log(
|
|
10524
|
+
console.log(import_chalk10.default.green(`
|
|
9995
10525
|
\u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
|
|
9996
10526
|
process.exit(0);
|
|
9997
10527
|
}
|
|
@@ -10001,7 +10531,7 @@ function registerDaemonCommand(program2) {
|
|
|
10001
10531
|
stdio: "ignore"
|
|
10002
10532
|
});
|
|
10003
10533
|
child.unref();
|
|
10004
|
-
console.log(
|
|
10534
|
+
console.log(import_chalk10.default.green(`
|
|
10005
10535
|
\u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
|
|
10006
10536
|
process.exit(0);
|
|
10007
10537
|
}
|
|
@@ -10011,15 +10541,15 @@ function registerDaemonCommand(program2) {
|
|
|
10011
10541
|
}
|
|
10012
10542
|
|
|
10013
10543
|
// src/cli/commands/status.ts
|
|
10014
|
-
var
|
|
10015
|
-
var
|
|
10016
|
-
var
|
|
10017
|
-
var
|
|
10544
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
10545
|
+
var import_fs24 = __toESM(require("fs"));
|
|
10546
|
+
var import_path26 = __toESM(require("path"));
|
|
10547
|
+
var import_os20 = __toESM(require("os"));
|
|
10018
10548
|
init_core();
|
|
10019
10549
|
init_daemon();
|
|
10020
10550
|
function readJson2(filePath) {
|
|
10021
10551
|
try {
|
|
10022
|
-
if (
|
|
10552
|
+
if (import_fs24.default.existsSync(filePath)) return JSON.parse(import_fs24.default.readFileSync(filePath, "utf-8"));
|
|
10023
10553
|
} catch {
|
|
10024
10554
|
}
|
|
10025
10555
|
return null;
|
|
@@ -10033,21 +10563,21 @@ function wrappedMcpServers(servers) {
|
|
|
10033
10563
|
return Object.entries(servers).filter(([, s]) => s.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
|
|
10034
10564
|
}
|
|
10035
10565
|
function printAgentSection(label, hookPairs, wrapped) {
|
|
10036
|
-
console.log(
|
|
10566
|
+
console.log(import_chalk11.default.bold(` ${label}`));
|
|
10037
10567
|
for (const { name, present } of hookPairs) {
|
|
10038
10568
|
if (present) {
|
|
10039
|
-
console.log(
|
|
10569
|
+
console.log(import_chalk11.default.green(` \u2713 ${name}`));
|
|
10040
10570
|
} else {
|
|
10041
|
-
console.log(
|
|
10571
|
+
console.log(import_chalk11.default.red(` \u2717 ${name}`) + import_chalk11.default.gray(" (not wired)"));
|
|
10042
10572
|
}
|
|
10043
10573
|
}
|
|
10044
10574
|
if (wrapped.length > 0) {
|
|
10045
|
-
console.log(
|
|
10575
|
+
console.log(import_chalk11.default.cyan(` MCP proxied:`));
|
|
10046
10576
|
for (const entry of wrapped) {
|
|
10047
|
-
console.log(
|
|
10577
|
+
console.log(import_chalk11.default.gray(` \u2022 ${entry}`));
|
|
10048
10578
|
}
|
|
10049
10579
|
} else {
|
|
10050
|
-
console.log(
|
|
10580
|
+
console.log(import_chalk11.default.gray(` MCP proxied: none`));
|
|
10051
10581
|
}
|
|
10052
10582
|
}
|
|
10053
10583
|
function registerStatusCommand(program2) {
|
|
@@ -10058,58 +10588,58 @@ function registerStatusCommand(program2) {
|
|
|
10058
10588
|
const settings = mergedConfig.settings;
|
|
10059
10589
|
console.log("");
|
|
10060
10590
|
if (creds && settings.approvers.cloud) {
|
|
10061
|
-
console.log(
|
|
10591
|
+
console.log(import_chalk11.default.green(" \u25CF Agent mode") + import_chalk11.default.gray(" \u2014 cloud team policy enforced"));
|
|
10062
10592
|
} else if (creds && !settings.approvers.cloud) {
|
|
10063
10593
|
console.log(
|
|
10064
|
-
|
|
10594
|
+
import_chalk11.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 all decisions stay on this machine")
|
|
10065
10595
|
);
|
|
10066
10596
|
} else {
|
|
10067
10597
|
console.log(
|
|
10068
|
-
|
|
10598
|
+
import_chalk11.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 no API key (Local rules only)")
|
|
10069
10599
|
);
|
|
10070
10600
|
}
|
|
10071
10601
|
console.log("");
|
|
10072
10602
|
if (daemonRunning) {
|
|
10073
10603
|
console.log(
|
|
10074
|
-
|
|
10604
|
+
import_chalk11.default.green(" \u25CF Daemon running") + import_chalk11.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
|
|
10075
10605
|
);
|
|
10076
10606
|
} else {
|
|
10077
|
-
console.log(
|
|
10607
|
+
console.log(import_chalk11.default.gray(" \u25CB Daemon stopped"));
|
|
10078
10608
|
}
|
|
10079
10609
|
if (settings.enableUndo) {
|
|
10080
10610
|
console.log(
|
|
10081
|
-
|
|
10611
|
+
import_chalk11.default.magenta(" \u25CF Undo Engine") + import_chalk11.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
|
|
10082
10612
|
);
|
|
10083
10613
|
}
|
|
10084
10614
|
console.log("");
|
|
10085
|
-
const modeLabel = settings.mode === "audit" ?
|
|
10615
|
+
const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
|
|
10086
10616
|
console.log(` Mode: ${modeLabel}`);
|
|
10087
|
-
const projectConfig =
|
|
10088
|
-
const globalConfig =
|
|
10617
|
+
const projectConfig = import_path26.default.join(process.cwd(), "node9.config.json");
|
|
10618
|
+
const globalConfig = import_path26.default.join(import_os20.default.homedir(), ".node9", "config.json");
|
|
10089
10619
|
console.log(
|
|
10090
|
-
` Local: ${
|
|
10620
|
+
` Local: ${import_fs24.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10091
10621
|
);
|
|
10092
10622
|
console.log(
|
|
10093
|
-
` Global: ${
|
|
10623
|
+
` Global: ${import_fs24.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10094
10624
|
);
|
|
10095
10625
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
10096
10626
|
console.log(
|
|
10097
|
-
` Sandbox: ${
|
|
10627
|
+
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
10098
10628
|
);
|
|
10099
10629
|
}
|
|
10100
|
-
const homeDir2 =
|
|
10630
|
+
const homeDir2 = import_os20.default.homedir();
|
|
10101
10631
|
const claudeSettings = readJson2(
|
|
10102
|
-
|
|
10632
|
+
import_path26.default.join(homeDir2, ".claude", "settings.json")
|
|
10103
10633
|
);
|
|
10104
|
-
const claudeConfig = readJson2(
|
|
10634
|
+
const claudeConfig = readJson2(import_path26.default.join(homeDir2, ".claude.json"));
|
|
10105
10635
|
const geminiSettings = readJson2(
|
|
10106
|
-
|
|
10636
|
+
import_path26.default.join(homeDir2, ".gemini", "settings.json")
|
|
10107
10637
|
);
|
|
10108
|
-
const cursorConfig = readJson2(
|
|
10638
|
+
const cursorConfig = readJson2(import_path26.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
10109
10639
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
10110
10640
|
if (agentFound) {
|
|
10111
10641
|
console.log("");
|
|
10112
|
-
console.log(
|
|
10642
|
+
console.log(import_chalk11.default.bold(" Agent Wiring:"));
|
|
10113
10643
|
console.log("");
|
|
10114
10644
|
if (claudeSettings || claudeConfig) {
|
|
10115
10645
|
const preHook = claudeSettings?.hooks?.PreToolUse?.some(
|
|
@@ -10155,7 +10685,7 @@ function registerStatusCommand(program2) {
|
|
|
10155
10685
|
const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
|
|
10156
10686
|
console.log("");
|
|
10157
10687
|
console.log(
|
|
10158
|
-
|
|
10688
|
+
import_chalk11.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk11.default.gray(" \u2014 all tool calls allowed")
|
|
10159
10689
|
);
|
|
10160
10690
|
}
|
|
10161
10691
|
console.log("");
|
|
@@ -10163,10 +10693,10 @@ function registerStatusCommand(program2) {
|
|
|
10163
10693
|
}
|
|
10164
10694
|
|
|
10165
10695
|
// src/cli/commands/init.ts
|
|
10166
|
-
var
|
|
10167
|
-
var
|
|
10168
|
-
var
|
|
10169
|
-
var
|
|
10696
|
+
var import_chalk12 = __toESM(require("chalk"));
|
|
10697
|
+
var import_fs25 = __toESM(require("fs"));
|
|
10698
|
+
var import_path27 = __toESM(require("path"));
|
|
10699
|
+
var import_os21 = __toESM(require("os"));
|
|
10170
10700
|
var import_https2 = __toESM(require("https"));
|
|
10171
10701
|
init_core();
|
|
10172
10702
|
init_shields();
|
|
@@ -10202,7 +10732,7 @@ function fireTelemetryPing(agents) {
|
|
|
10202
10732
|
}
|
|
10203
10733
|
function registerInitCommand(program2) {
|
|
10204
10734
|
program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
|
|
10205
|
-
console.log(
|
|
10735
|
+
console.log(import_chalk12.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
10206
10736
|
let chosenMode = options.mode.toLowerCase();
|
|
10207
10737
|
if (!["standard", "strict", "audit"].includes(chosenMode)) {
|
|
10208
10738
|
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
@@ -10221,37 +10751,37 @@ function registerInitCommand(program2) {
|
|
|
10221
10751
|
const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
|
|
10222
10752
|
if (hasNewShields) writeActiveShields(merged);
|
|
10223
10753
|
} catch (err2) {
|
|
10224
|
-
console.log(
|
|
10754
|
+
console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
|
|
10225
10755
|
}
|
|
10226
10756
|
}
|
|
10227
10757
|
console.log("");
|
|
10228
10758
|
}
|
|
10229
|
-
const configPath =
|
|
10230
|
-
if (
|
|
10759
|
+
const configPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
|
|
10760
|
+
if (import_fs25.default.existsSync(configPath) && !options.force) {
|
|
10231
10761
|
try {
|
|
10232
|
-
const existing = JSON.parse(
|
|
10762
|
+
const existing = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
10233
10763
|
const settings = existing.settings ?? {};
|
|
10234
10764
|
if (settings.mode !== chosenMode) {
|
|
10235
10765
|
settings.mode = chosenMode;
|
|
10236
10766
|
existing.settings = settings;
|
|
10237
|
-
|
|
10238
|
-
console.log(
|
|
10767
|
+
import_fs25.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
10768
|
+
console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
10239
10769
|
} else {
|
|
10240
|
-
console.log(
|
|
10770
|
+
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
10241
10771
|
}
|
|
10242
10772
|
} catch {
|
|
10243
|
-
console.log(
|
|
10773
|
+
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
10244
10774
|
}
|
|
10245
10775
|
} else {
|
|
10246
10776
|
const configToSave = {
|
|
10247
10777
|
...DEFAULT_CONFIG,
|
|
10248
10778
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
10249
10779
|
};
|
|
10250
|
-
const dir =
|
|
10251
|
-
if (!
|
|
10252
|
-
|
|
10253
|
-
console.log(
|
|
10254
|
-
console.log(
|
|
10780
|
+
const dir = import_path27.default.dirname(configPath);
|
|
10781
|
+
if (!import_fs25.default.existsSync(dir)) import_fs25.default.mkdirSync(dir, { recursive: true });
|
|
10782
|
+
import_fs25.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
10783
|
+
console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
|
|
10784
|
+
console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
|
|
10255
10785
|
}
|
|
10256
10786
|
if (options.skipSetup) return;
|
|
10257
10787
|
console.log("");
|
|
@@ -10261,18 +10791,18 @@ function registerInitCommand(program2) {
|
|
|
10261
10791
|
);
|
|
10262
10792
|
if (found.length === 0) {
|
|
10263
10793
|
console.log(
|
|
10264
|
-
|
|
10794
|
+
import_chalk12.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
|
|
10265
10795
|
);
|
|
10266
|
-
console.log(
|
|
10796
|
+
console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
|
|
10267
10797
|
return;
|
|
10268
10798
|
}
|
|
10269
|
-
console.log(
|
|
10799
|
+
console.log(import_chalk12.default.bold("Detected agents:"));
|
|
10270
10800
|
for (const agent of found) {
|
|
10271
|
-
console.log(
|
|
10801
|
+
console.log(import_chalk12.default.green(` \u2713 ${agent}`));
|
|
10272
10802
|
}
|
|
10273
10803
|
console.log("");
|
|
10274
10804
|
for (const agent of found) {
|
|
10275
|
-
console.log(
|
|
10805
|
+
console.log(import_chalk12.default.bold(`Wiring ${agent}...`));
|
|
10276
10806
|
if (agent === "claude") await setupClaude();
|
|
10277
10807
|
else if (agent === "gemini") await setupGemini();
|
|
10278
10808
|
else if (agent === "cursor") await setupCursor();
|
|
@@ -10289,26 +10819,26 @@ function registerInitCommand(program2) {
|
|
|
10289
10819
|
console.log("");
|
|
10290
10820
|
}
|
|
10291
10821
|
const agentList = found.join(", ");
|
|
10292
|
-
console.log(
|
|
10822
|
+
console.log(import_chalk12.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
10293
10823
|
console.log("");
|
|
10294
|
-
console.log(
|
|
10295
|
-
console.log(
|
|
10824
|
+
console.log(import_chalk12.default.white(" Watch live: ") + import_chalk12.default.cyan("node9 tail"));
|
|
10825
|
+
console.log(import_chalk12.default.white(" Local UI: ") + import_chalk12.default.cyan("node9 daemon --openui"));
|
|
10296
10826
|
console.log("");
|
|
10297
|
-
console.log(
|
|
10827
|
+
console.log(import_chalk12.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10298
10828
|
console.log(
|
|
10299
|
-
|
|
10829
|
+
import_chalk12.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk12.default.cyan.bold("https://node9.ai")
|
|
10300
10830
|
);
|
|
10301
|
-
console.log(
|
|
10831
|
+
console.log(import_chalk12.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10302
10832
|
});
|
|
10303
10833
|
}
|
|
10304
10834
|
|
|
10305
10835
|
// src/cli/commands/undo.ts
|
|
10306
|
-
var
|
|
10307
|
-
var
|
|
10836
|
+
var import_path28 = __toESM(require("path"));
|
|
10837
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
10308
10838
|
|
|
10309
10839
|
// src/tui/undo-navigator.ts
|
|
10310
10840
|
var import_readline2 = __toESM(require("readline"));
|
|
10311
|
-
var
|
|
10841
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
10312
10842
|
var RESET = "\x1B[0m";
|
|
10313
10843
|
var BOLD = "\x1B[1m";
|
|
10314
10844
|
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
@@ -10326,15 +10856,15 @@ function renderDiff(raw) {
|
|
|
10326
10856
|
);
|
|
10327
10857
|
for (const line of lines) {
|
|
10328
10858
|
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
10329
|
-
process.stdout.write(
|
|
10859
|
+
process.stdout.write(import_chalk13.default.bold(line) + "\n");
|
|
10330
10860
|
} else if (line.startsWith("+")) {
|
|
10331
|
-
process.stdout.write(
|
|
10861
|
+
process.stdout.write(import_chalk13.default.green(line) + "\n");
|
|
10332
10862
|
} else if (line.startsWith("-")) {
|
|
10333
|
-
process.stdout.write(
|
|
10863
|
+
process.stdout.write(import_chalk13.default.red(line) + "\n");
|
|
10334
10864
|
} else if (line.startsWith("@@")) {
|
|
10335
|
-
process.stdout.write(
|
|
10865
|
+
process.stdout.write(import_chalk13.default.cyan(line) + "\n");
|
|
10336
10866
|
} else {
|
|
10337
|
-
process.stdout.write(
|
|
10867
|
+
process.stdout.write(import_chalk13.default.gray(line) + "\n");
|
|
10338
10868
|
}
|
|
10339
10869
|
}
|
|
10340
10870
|
}
|
|
@@ -10353,23 +10883,23 @@ function render(entries, idx) {
|
|
|
10353
10883
|
const step = idx + 1;
|
|
10354
10884
|
process.stdout.write(CLEAR_SCREEN);
|
|
10355
10885
|
process.stdout.write(
|
|
10356
|
-
|
|
10886
|
+
import_chalk13.default.magenta.bold(`\u23EA Node9 Undo`) + import_chalk13.default.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? import_chalk13.default.gray(
|
|
10357
10887
|
` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
|
|
10358
10888
|
) : "") + "\n\n"
|
|
10359
10889
|
);
|
|
10360
10890
|
process.stdout.write(
|
|
10361
|
-
` ${BOLD}Tool:${RESET} ${
|
|
10891
|
+
` ${BOLD}Tool:${RESET} ${import_chalk13.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk13.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
|
|
10362
10892
|
);
|
|
10363
|
-
process.stdout.write(` ${BOLD}When:${RESET} ${
|
|
10893
|
+
process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk13.default.gray(formatAge(entry.timestamp))}
|
|
10364
10894
|
`);
|
|
10365
|
-
process.stdout.write(` ${BOLD}Dir: ${RESET} ${
|
|
10895
|
+
process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk13.default.gray(entry.cwd)}
|
|
10366
10896
|
`);
|
|
10367
10897
|
if (entry.files && entry.files.length > 0) {
|
|
10368
|
-
process.stdout.write(` ${BOLD}Files:${RESET} ${
|
|
10898
|
+
process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk13.default.gray(entry.files.join(", "))}
|
|
10369
10899
|
`);
|
|
10370
10900
|
}
|
|
10371
10901
|
if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
|
|
10372
|
-
process.stdout.write(
|
|
10902
|
+
process.stdout.write(import_chalk13.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
|
|
10373
10903
|
}
|
|
10374
10904
|
process.stdout.write("\n");
|
|
10375
10905
|
const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
|
|
@@ -10377,12 +10907,12 @@ function render(entries, idx) {
|
|
|
10377
10907
|
renderDiff(diff);
|
|
10378
10908
|
} else {
|
|
10379
10909
|
process.stdout.write(
|
|
10380
|
-
|
|
10910
|
+
import_chalk13.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
|
|
10381
10911
|
);
|
|
10382
10912
|
}
|
|
10383
10913
|
process.stdout.write("\n");
|
|
10384
10914
|
process.stdout.write(
|
|
10385
|
-
|
|
10915
|
+
import_chalk13.default.gray(" ") + (idx < total - 1 ? import_chalk13.default.white("[\u2190] older") : import_chalk13.default.gray("[\u2190] older")) + import_chalk13.default.gray(" ") + (idx > 0 ? import_chalk13.default.white("[\u2192] newer") : import_chalk13.default.gray("[\u2192] newer")) + import_chalk13.default.gray(" ") + import_chalk13.default.green("[\u21B5] restore here") + import_chalk13.default.gray(" ") + import_chalk13.default.yellow("[s] session start") + import_chalk13.default.gray(" ") + import_chalk13.default.gray("[q] quit") + "\n"
|
|
10386
10916
|
);
|
|
10387
10917
|
}
|
|
10388
10918
|
async function runUndoNavigator(entries) {
|
|
@@ -10436,19 +10966,19 @@ async function runUndoNavigator(entries) {
|
|
|
10436
10966
|
cleanup();
|
|
10437
10967
|
process.stdout.write(CLEAR_SCREEN);
|
|
10438
10968
|
const entry = display[idx];
|
|
10439
|
-
process.stdout.write(
|
|
10969
|
+
process.stdout.write(import_chalk13.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
|
|
10440
10970
|
if (applyUndo(entry.hash, entry.cwd)) {
|
|
10441
|
-
process.stdout.write(
|
|
10971
|
+
process.stdout.write(import_chalk13.default.green("\u2705 Reverted successfully.\n\n"));
|
|
10442
10972
|
resolve({ restored: true });
|
|
10443
10973
|
} else {
|
|
10444
|
-
process.stdout.write(
|
|
10974
|
+
process.stdout.write(import_chalk13.default.red("\u274C Undo failed.\n\n"));
|
|
10445
10975
|
resolve({ restored: false });
|
|
10446
10976
|
}
|
|
10447
10977
|
} else if (name === "q" || key?.ctrl && name === "c") {
|
|
10448
10978
|
done = true;
|
|
10449
10979
|
cleanup();
|
|
10450
10980
|
process.stdout.write(CLEAR_SCREEN);
|
|
10451
|
-
process.stdout.write(
|
|
10981
|
+
process.stdout.write(import_chalk13.default.gray("\nCancelled.\n\n"));
|
|
10452
10982
|
resolve({ restored: false });
|
|
10453
10983
|
}
|
|
10454
10984
|
};
|
|
@@ -10462,7 +10992,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
10462
10992
|
let dir = startDir;
|
|
10463
10993
|
while (true) {
|
|
10464
10994
|
if (cwds.has(dir)) return dir;
|
|
10465
|
-
const parent =
|
|
10995
|
+
const parent = import_path28.default.dirname(dir);
|
|
10466
10996
|
if (parent === dir) return null;
|
|
10467
10997
|
dir = parent;
|
|
10468
10998
|
}
|
|
@@ -10484,39 +11014,39 @@ function registerUndoCommand(program2) {
|
|
|
10484
11014
|
if (history.length === 0) {
|
|
10485
11015
|
if (!options.all && allHistory.length > 0) {
|
|
10486
11016
|
console.log(
|
|
10487
|
-
|
|
11017
|
+
import_chalk14.default.yellow(
|
|
10488
11018
|
`
|
|
10489
11019
|
\u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
|
|
10490
|
-
Run ${
|
|
11020
|
+
Run ${import_chalk14.default.cyan("node9 undo --all")} to see snapshots from all projects.
|
|
10491
11021
|
`
|
|
10492
11022
|
)
|
|
10493
11023
|
);
|
|
10494
11024
|
} else {
|
|
10495
|
-
console.log(
|
|
11025
|
+
console.log(import_chalk14.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
|
|
10496
11026
|
}
|
|
10497
11027
|
return;
|
|
10498
11028
|
}
|
|
10499
11029
|
if (options.list) {
|
|
10500
|
-
console.log(
|
|
11030
|
+
console.log(import_chalk14.default.magenta.bold("\n\u23EA Snapshot History\n"));
|
|
10501
11031
|
console.log(
|
|
10502
|
-
|
|
11032
|
+
import_chalk14.default.gray(
|
|
10503
11033
|
` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
|
|
10504
11034
|
)
|
|
10505
11035
|
);
|
|
10506
|
-
console.log(
|
|
11036
|
+
console.log(import_chalk14.default.gray(" " + "\u2500".repeat(80)));
|
|
10507
11037
|
const display = [...history].reverse();
|
|
10508
11038
|
let prevTs = null;
|
|
10509
11039
|
for (let i = 0; i < display.length; i++) {
|
|
10510
11040
|
const e = display[i];
|
|
10511
11041
|
const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
|
|
10512
|
-
if (isGap) console.log(
|
|
11042
|
+
if (isGap) console.log(import_chalk14.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
|
|
10513
11043
|
const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
|
|
10514
11044
|
const tool = e.tool.slice(0, 8).padEnd(8);
|
|
10515
11045
|
const when = formatAge2(e.timestamp).padEnd(10);
|
|
10516
11046
|
const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
|
|
10517
11047
|
console.log(
|
|
10518
|
-
|
|
10519
|
-
` ${String(i + 1).padEnd(3)} ${label} ${
|
|
11048
|
+
import_chalk14.default.white(
|
|
11049
|
+
` ${String(i + 1).padEnd(3)} ${label} ${import_chalk14.default.cyan(tool)} ${import_chalk14.default.gray(when)} ${import_chalk14.default.gray(dir)}`
|
|
10520
11050
|
)
|
|
10521
11051
|
);
|
|
10522
11052
|
prevTs = e.timestamp;
|
|
@@ -10529,7 +11059,7 @@ function registerUndoCommand(program2) {
|
|
|
10529
11059
|
const idx = history.length - steps;
|
|
10530
11060
|
if (idx < 0) {
|
|
10531
11061
|
console.log(
|
|
10532
|
-
|
|
11062
|
+
import_chalk14.default.yellow(
|
|
10533
11063
|
`
|
|
10534
11064
|
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
10535
11065
|
`
|
|
@@ -10540,47 +11070,47 @@ function registerUndoCommand(program2) {
|
|
|
10540
11070
|
const snapshot = history[idx];
|
|
10541
11071
|
const ageStr = formatAge2(snapshot.timestamp);
|
|
10542
11072
|
console.log(
|
|
10543
|
-
|
|
11073
|
+
import_chalk14.default.magenta.bold(`
|
|
10544
11074
|
\u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
|
|
10545
11075
|
);
|
|
10546
11076
|
console.log(
|
|
10547
|
-
|
|
10548
|
-
` Tool: ${
|
|
11077
|
+
import_chalk14.default.white(
|
|
11078
|
+
` Tool: ${import_chalk14.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk14.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
10549
11079
|
)
|
|
10550
11080
|
);
|
|
10551
|
-
console.log(
|
|
10552
|
-
console.log(
|
|
11081
|
+
console.log(import_chalk14.default.white(` When: ${import_chalk14.default.gray(ageStr)}`));
|
|
11082
|
+
console.log(import_chalk14.default.white(` Dir: ${import_chalk14.default.gray(snapshot.cwd)}`));
|
|
10553
11083
|
if (steps > 1)
|
|
10554
11084
|
console.log(
|
|
10555
|
-
|
|
11085
|
+
import_chalk14.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
|
|
10556
11086
|
);
|
|
10557
11087
|
console.log("");
|
|
10558
11088
|
const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
|
|
10559
11089
|
if (diff) {
|
|
10560
11090
|
const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
|
|
10561
11091
|
for (const line of lines) {
|
|
10562
|
-
if (line.startsWith("+++") || line.startsWith("---")) console.log(
|
|
10563
|
-
else if (line.startsWith("+")) console.log(
|
|
10564
|
-
else if (line.startsWith("-")) console.log(
|
|
10565
|
-
else if (line.startsWith("@@")) console.log(
|
|
10566
|
-
else console.log(
|
|
11092
|
+
if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk14.default.bold(line));
|
|
11093
|
+
else if (line.startsWith("+")) console.log(import_chalk14.default.green(line));
|
|
11094
|
+
else if (line.startsWith("-")) console.log(import_chalk14.default.red(line));
|
|
11095
|
+
else if (line.startsWith("@@")) console.log(import_chalk14.default.cyan(line));
|
|
11096
|
+
else console.log(import_chalk14.default.gray(line));
|
|
10567
11097
|
}
|
|
10568
11098
|
console.log("");
|
|
10569
11099
|
} else {
|
|
10570
11100
|
console.log(
|
|
10571
|
-
|
|
11101
|
+
import_chalk14.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
|
|
10572
11102
|
);
|
|
10573
11103
|
}
|
|
10574
11104
|
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
10575
11105
|
const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
|
|
10576
11106
|
if (proceed) {
|
|
10577
11107
|
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
10578
|
-
console.log(
|
|
11108
|
+
console.log(import_chalk14.default.green("\n\u2705 Reverted successfully.\n"));
|
|
10579
11109
|
} else {
|
|
10580
|
-
console.error(
|
|
11110
|
+
console.error(import_chalk14.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
|
|
10581
11111
|
}
|
|
10582
11112
|
} else {
|
|
10583
|
-
console.log(
|
|
11113
|
+
console.log(import_chalk14.default.gray("\nCancelled.\n"));
|
|
10584
11114
|
}
|
|
10585
11115
|
return;
|
|
10586
11116
|
}
|
|
@@ -10589,7 +11119,7 @@ function registerUndoCommand(program2) {
|
|
|
10589
11119
|
}
|
|
10590
11120
|
|
|
10591
11121
|
// src/cli/commands/watch.ts
|
|
10592
|
-
var
|
|
11122
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
10593
11123
|
var import_child_process12 = require("child_process");
|
|
10594
11124
|
init_daemon();
|
|
10595
11125
|
function registerWatchCommand(program2) {
|
|
@@ -10606,7 +11136,7 @@ function registerWatchCommand(program2) {
|
|
|
10606
11136
|
throw new Error("not running");
|
|
10607
11137
|
}
|
|
10608
11138
|
} catch {
|
|
10609
|
-
console.error(
|
|
11139
|
+
console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
10610
11140
|
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10611
11141
|
detached: true,
|
|
10612
11142
|
stdio: "ignore",
|
|
@@ -10628,12 +11158,12 @@ function registerWatchCommand(program2) {
|
|
|
10628
11158
|
}
|
|
10629
11159
|
}
|
|
10630
11160
|
if (!ready) {
|
|
10631
|
-
console.error(
|
|
11161
|
+
console.error(import_chalk15.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
10632
11162
|
process.exit(1);
|
|
10633
11163
|
}
|
|
10634
11164
|
}
|
|
10635
11165
|
console.error(
|
|
10636
|
-
|
|
11166
|
+
import_chalk15.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk15.default.dim(` \u2192 localhost:${port}`) + import_chalk15.default.dim(
|
|
10637
11167
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
10638
11168
|
)
|
|
10639
11169
|
);
|
|
@@ -10642,7 +11172,7 @@ function registerWatchCommand(program2) {
|
|
|
10642
11172
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
10643
11173
|
});
|
|
10644
11174
|
if (result.error) {
|
|
10645
|
-
console.error(
|
|
11175
|
+
console.error(import_chalk15.default.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
10646
11176
|
process.exit(1);
|
|
10647
11177
|
}
|
|
10648
11178
|
process.exit(result.status ?? 0);
|
|
@@ -10651,19 +11181,19 @@ function registerWatchCommand(program2) {
|
|
|
10651
11181
|
|
|
10652
11182
|
// src/mcp-gateway/index.ts
|
|
10653
11183
|
var import_readline3 = __toESM(require("readline"));
|
|
10654
|
-
var
|
|
11184
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
10655
11185
|
var import_child_process13 = require("child_process");
|
|
10656
11186
|
var import_execa3 = require("execa");
|
|
10657
11187
|
init_orchestrator();
|
|
10658
11188
|
init_provenance();
|
|
10659
11189
|
|
|
10660
11190
|
// src/mcp-pin.ts
|
|
10661
|
-
var
|
|
10662
|
-
var
|
|
10663
|
-
var
|
|
10664
|
-
var
|
|
11191
|
+
var import_fs26 = __toESM(require("fs"));
|
|
11192
|
+
var import_path29 = __toESM(require("path"));
|
|
11193
|
+
var import_os22 = __toESM(require("os"));
|
|
11194
|
+
var import_crypto9 = __toESM(require("crypto"));
|
|
10665
11195
|
function getPinsFilePath() {
|
|
10666
|
-
return
|
|
11196
|
+
return import_path29.default.join(import_os22.default.homedir(), ".node9", "mcp-pins.json");
|
|
10667
11197
|
}
|
|
10668
11198
|
function hashToolDefinitions(tools) {
|
|
10669
11199
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -10672,15 +11202,15 @@ function hashToolDefinitions(tools) {
|
|
|
10672
11202
|
return nameA.localeCompare(nameB);
|
|
10673
11203
|
});
|
|
10674
11204
|
const canonical = JSON.stringify(sorted);
|
|
10675
|
-
return
|
|
11205
|
+
return import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
|
|
10676
11206
|
}
|
|
10677
11207
|
function getServerKey(upstreamCommand) {
|
|
10678
|
-
return
|
|
11208
|
+
return import_crypto9.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
|
|
10679
11209
|
}
|
|
10680
11210
|
function readMcpPinsSafe() {
|
|
10681
11211
|
const filePath = getPinsFilePath();
|
|
10682
11212
|
try {
|
|
10683
|
-
const raw =
|
|
11213
|
+
const raw = import_fs26.default.readFileSync(filePath, "utf-8");
|
|
10684
11214
|
if (!raw.trim()) {
|
|
10685
11215
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10686
11216
|
}
|
|
@@ -10704,10 +11234,10 @@ function readMcpPins() {
|
|
|
10704
11234
|
}
|
|
10705
11235
|
function writeMcpPins(data) {
|
|
10706
11236
|
const filePath = getPinsFilePath();
|
|
10707
|
-
|
|
10708
|
-
const tmp = `${filePath}.${
|
|
10709
|
-
|
|
10710
|
-
|
|
11237
|
+
import_fs26.default.mkdirSync(import_path29.default.dirname(filePath), { recursive: true });
|
|
11238
|
+
const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
|
|
11239
|
+
import_fs26.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
11240
|
+
import_fs26.default.renameSync(tmp, filePath);
|
|
10711
11241
|
}
|
|
10712
11242
|
function checkPin(serverKey, currentHash) {
|
|
10713
11243
|
const result = readMcpPinsSafe();
|
|
@@ -10799,13 +11329,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10799
11329
|
const prov = checkProvenance(executable);
|
|
10800
11330
|
if (prov.trustLevel === "suspect") {
|
|
10801
11331
|
console.error(
|
|
10802
|
-
|
|
11332
|
+
import_chalk16.default.red(
|
|
10803
11333
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
10804
11334
|
)
|
|
10805
11335
|
);
|
|
10806
|
-
console.error(
|
|
11336
|
+
console.error(import_chalk16.default.red(" Verify this binary is trusted before proceeding."));
|
|
10807
11337
|
}
|
|
10808
|
-
console.error(
|
|
11338
|
+
console.error(import_chalk16.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
10809
11339
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
10810
11340
|
"NODE_OPTIONS",
|
|
10811
11341
|
"NODE_PATH",
|
|
@@ -10908,10 +11438,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10908
11438
|
mcpServer
|
|
10909
11439
|
});
|
|
10910
11440
|
if (!result.approved) {
|
|
10911
|
-
console.error(
|
|
11441
|
+
console.error(import_chalk16.default.red(`
|
|
10912
11442
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
10913
|
-
console.error(
|
|
10914
|
-
console.error(
|
|
11443
|
+
console.error(import_chalk16.default.gray(` Tool: ${toolName}`));
|
|
11444
|
+
console.error(import_chalk16.default.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
10915
11445
|
`));
|
|
10916
11446
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
10917
11447
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -10990,7 +11520,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10990
11520
|
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
10991
11521
|
pinState = "validated";
|
|
10992
11522
|
console.error(
|
|
10993
|
-
|
|
11523
|
+
import_chalk16.default.green(
|
|
10994
11524
|
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
10995
11525
|
)
|
|
10996
11526
|
);
|
|
@@ -11003,11 +11533,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11003
11533
|
} else if (pinStatus === "corrupt") {
|
|
11004
11534
|
pinState = "quarantined";
|
|
11005
11535
|
console.error(
|
|
11006
|
-
|
|
11536
|
+
import_chalk16.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
11007
11537
|
);
|
|
11008
|
-
console.error(
|
|
11538
|
+
console.error(import_chalk16.default.red(" Tool calls are blocked until the pin file is repaired."));
|
|
11009
11539
|
console.error(
|
|
11010
|
-
|
|
11540
|
+
import_chalk16.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
11011
11541
|
`)
|
|
11012
11542
|
);
|
|
11013
11543
|
const errorResponse = {
|
|
@@ -11024,13 +11554,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11024
11554
|
} else {
|
|
11025
11555
|
pinState = "quarantined";
|
|
11026
11556
|
console.error(
|
|
11027
|
-
|
|
11557
|
+
import_chalk16.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
11028
11558
|
);
|
|
11029
11559
|
console.error(
|
|
11030
|
-
|
|
11560
|
+
import_chalk16.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
11031
11561
|
);
|
|
11032
|
-
console.error(
|
|
11033
|
-
console.error(
|
|
11562
|
+
console.error(import_chalk16.default.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
11563
|
+
console.error(import_chalk16.default.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
11034
11564
|
`));
|
|
11035
11565
|
const errorResponse = {
|
|
11036
11566
|
jsonrpc: "2.0",
|
|
@@ -11079,9 +11609,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
11079
11609
|
|
|
11080
11610
|
// src/mcp-server/index.ts
|
|
11081
11611
|
var import_readline4 = __toESM(require("readline"));
|
|
11082
|
-
var
|
|
11083
|
-
var
|
|
11084
|
-
var
|
|
11612
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11613
|
+
var import_os23 = __toESM(require("os"));
|
|
11614
|
+
var import_path30 = __toESM(require("path"));
|
|
11085
11615
|
init_core();
|
|
11086
11616
|
init_daemon();
|
|
11087
11617
|
init_shields();
|
|
@@ -11256,13 +11786,13 @@ function handleStatus() {
|
|
|
11256
11786
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
11257
11787
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
11258
11788
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
11259
|
-
const projectConfig =
|
|
11260
|
-
const globalConfig =
|
|
11789
|
+
const projectConfig = import_path30.default.join(process.cwd(), "node9.config.json");
|
|
11790
|
+
const globalConfig = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
|
|
11261
11791
|
lines.push(
|
|
11262
|
-
`Project config (node9.config.json): ${
|
|
11792
|
+
`Project config (node9.config.json): ${import_fs27.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
11263
11793
|
);
|
|
11264
11794
|
lines.push(
|
|
11265
|
-
`Global config (~/.node9/config.json): ${
|
|
11795
|
+
`Global config (~/.node9/config.json): ${import_fs27.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
11266
11796
|
);
|
|
11267
11797
|
return lines.join("\n");
|
|
11268
11798
|
}
|
|
@@ -11336,21 +11866,21 @@ function handleShieldDisable(args) {
|
|
|
11336
11866
|
writeActiveShields(active.filter((s) => s !== name));
|
|
11337
11867
|
return `Shield "${name}" disabled.`;
|
|
11338
11868
|
}
|
|
11339
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
11869
|
+
var GLOBAL_CONFIG_PATH2 = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
|
|
11340
11870
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
11341
11871
|
function readGlobalConfigRaw() {
|
|
11342
11872
|
try {
|
|
11343
|
-
if (
|
|
11344
|
-
return JSON.parse(
|
|
11873
|
+
if (import_fs27.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
11874
|
+
return JSON.parse(import_fs27.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
11345
11875
|
}
|
|
11346
11876
|
} catch {
|
|
11347
11877
|
}
|
|
11348
11878
|
return {};
|
|
11349
11879
|
}
|
|
11350
11880
|
function writeGlobalConfigRaw(data) {
|
|
11351
|
-
const dir =
|
|
11352
|
-
if (!
|
|
11353
|
-
|
|
11881
|
+
const dir = import_path30.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
11882
|
+
if (!import_fs27.default.existsSync(dir)) import_fs27.default.mkdirSync(dir, { recursive: true });
|
|
11883
|
+
import_fs27.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
11354
11884
|
}
|
|
11355
11885
|
function handleApproverList() {
|
|
11356
11886
|
const config = getConfig();
|
|
@@ -11393,9 +11923,9 @@ function handleApproverSet(args) {
|
|
|
11393
11923
|
}
|
|
11394
11924
|
function handleAuditGet(args) {
|
|
11395
11925
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
11396
|
-
const auditPath =
|
|
11397
|
-
if (!
|
|
11398
|
-
const lines =
|
|
11926
|
+
const auditPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "audit.log");
|
|
11927
|
+
if (!import_fs27.default.existsSync(auditPath)) return "No audit log found.";
|
|
11928
|
+
const lines = import_fs27.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
11399
11929
|
const recent = lines.slice(-limit);
|
|
11400
11930
|
const entries = recent.map((line) => {
|
|
11401
11931
|
try {
|
|
@@ -11582,7 +12112,7 @@ function registerMcpServerCommand(program2) {
|
|
|
11582
12112
|
}
|
|
11583
12113
|
|
|
11584
12114
|
// src/cli/commands/trust.ts
|
|
11585
|
-
var
|
|
12115
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
11586
12116
|
init_trusted_hosts();
|
|
11587
12117
|
function isValidHost(host) {
|
|
11588
12118
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
@@ -11593,51 +12123,51 @@ function registerTrustCommand(program2) {
|
|
|
11593
12123
|
const normalized = normalizeHost(host.trim());
|
|
11594
12124
|
if (!isValidHost(normalized)) {
|
|
11595
12125
|
console.error(
|
|
11596
|
-
|
|
12126
|
+
import_chalk17.default.red(`
|
|
11597
12127
|
\u274C Invalid host: "${host}"
|
|
11598
|
-
`) +
|
|
12128
|
+
`) + import_chalk17.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
11599
12129
|
);
|
|
11600
12130
|
process.exit(1);
|
|
11601
12131
|
}
|
|
11602
12132
|
addTrustedHost(normalized);
|
|
11603
|
-
console.log(
|
|
12133
|
+
console.log(import_chalk17.default.green(`
|
|
11604
12134
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
11605
12135
|
console.log(
|
|
11606
|
-
|
|
12136
|
+
import_chalk17.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
11607
12137
|
);
|
|
11608
12138
|
});
|
|
11609
12139
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
11610
12140
|
const normalized = normalizeHost(host.trim());
|
|
11611
12141
|
const removed = removeTrustedHost(normalized);
|
|
11612
12142
|
if (!removed) {
|
|
11613
|
-
console.error(
|
|
12143
|
+
console.error(import_chalk17.default.yellow(`
|
|
11614
12144
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
11615
12145
|
`));
|
|
11616
12146
|
process.exit(1);
|
|
11617
12147
|
}
|
|
11618
|
-
console.log(
|
|
12148
|
+
console.log(import_chalk17.default.green(`
|
|
11619
12149
|
\u2705 ${normalized} removed from trusted hosts.
|
|
11620
12150
|
`));
|
|
11621
12151
|
});
|
|
11622
12152
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
11623
12153
|
const hosts = readTrustedHosts();
|
|
11624
12154
|
if (hosts.length === 0) {
|
|
11625
|
-
console.log(
|
|
11626
|
-
console.log(` Add one: ${
|
|
12155
|
+
console.log(import_chalk17.default.gray("\n No trusted hosts configured.\n"));
|
|
12156
|
+
console.log(` Add one: ${import_chalk17.default.cyan("node9 trust add api.mycompany.com")}
|
|
11627
12157
|
`);
|
|
11628
12158
|
return;
|
|
11629
12159
|
}
|
|
11630
|
-
console.log(
|
|
12160
|
+
console.log(import_chalk17.default.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
11631
12161
|
for (const entry of hosts) {
|
|
11632
12162
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
11633
|
-
console.log(` ${
|
|
12163
|
+
console.log(` ${import_chalk17.default.cyan(entry.host.padEnd(40))} ${import_chalk17.default.gray(`added ${date}`)}`);
|
|
11634
12164
|
}
|
|
11635
12165
|
console.log("");
|
|
11636
12166
|
});
|
|
11637
12167
|
}
|
|
11638
12168
|
|
|
11639
12169
|
// src/cli/commands/mcp-pin.ts
|
|
11640
|
-
var
|
|
12170
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
11641
12171
|
function registerMcpPinCommand(program2) {
|
|
11642
12172
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
11643
12173
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -11645,31 +12175,31 @@ function registerMcpPinCommand(program2) {
|
|
|
11645
12175
|
const result = readMcpPinsSafe();
|
|
11646
12176
|
if (!result.ok) {
|
|
11647
12177
|
if (result.reason === "missing") {
|
|
11648
|
-
console.log(
|
|
12178
|
+
console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
|
|
11649
12179
|
console.log(
|
|
11650
|
-
|
|
12180
|
+
import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11651
12181
|
);
|
|
11652
12182
|
return;
|
|
11653
12183
|
}
|
|
11654
|
-
console.error(
|
|
12184
|
+
console.error(import_chalk18.default.red(`
|
|
11655
12185
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
11656
|
-
console.error(
|
|
12186
|
+
console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
11657
12187
|
process.exit(1);
|
|
11658
12188
|
}
|
|
11659
12189
|
const entries = Object.entries(result.pins.servers);
|
|
11660
12190
|
if (entries.length === 0) {
|
|
11661
|
-
console.log(
|
|
12191
|
+
console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
|
|
11662
12192
|
console.log(
|
|
11663
|
-
|
|
12193
|
+
import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11664
12194
|
);
|
|
11665
12195
|
return;
|
|
11666
12196
|
}
|
|
11667
|
-
console.log(
|
|
12197
|
+
console.log(import_chalk18.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
11668
12198
|
for (const [key, entry] of entries) {
|
|
11669
|
-
console.log(` ${
|
|
11670
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
11671
|
-
console.log(` Hash: ${
|
|
11672
|
-
console.log(` Pinned: ${
|
|
12199
|
+
console.log(` ${import_chalk18.default.cyan(key)} ${import_chalk18.default.gray(entry.label)}`);
|
|
12200
|
+
console.log(` Tools (${entry.toolCount}): ${import_chalk18.default.white(entry.toolNames.join(", "))}`);
|
|
12201
|
+
console.log(` Hash: ${import_chalk18.default.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
12202
|
+
console.log(` Pinned: ${import_chalk18.default.gray(entry.pinnedAt)}`);
|
|
11673
12203
|
console.log("");
|
|
11674
12204
|
}
|
|
11675
12205
|
});
|
|
@@ -11680,55 +12210,55 @@ function registerMcpPinCommand(program2) {
|
|
|
11680
12210
|
try {
|
|
11681
12211
|
pins = readMcpPins();
|
|
11682
12212
|
} catch {
|
|
11683
|
-
console.error(
|
|
11684
|
-
console.error(
|
|
12213
|
+
console.error(import_chalk18.default.red("\n\u274C Pin file is corrupt."));
|
|
12214
|
+
console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
11685
12215
|
process.exit(1);
|
|
11686
12216
|
}
|
|
11687
12217
|
if (!pins.servers[serverKey]) {
|
|
11688
|
-
console.error(
|
|
12218
|
+
console.error(import_chalk18.default.red(`
|
|
11689
12219
|
\u274C No pin found for server key "${serverKey}"
|
|
11690
12220
|
`));
|
|
11691
|
-
console.error(`Run ${
|
|
12221
|
+
console.error(`Run ${import_chalk18.default.cyan("node9 mcp pin list")} to see pinned servers.
|
|
11692
12222
|
`);
|
|
11693
12223
|
process.exit(1);
|
|
11694
12224
|
}
|
|
11695
12225
|
const label = pins.servers[serverKey].label;
|
|
11696
12226
|
removePin(serverKey);
|
|
11697
|
-
console.log(
|
|
11698
|
-
\u{1F513} Pin removed for ${
|
|
11699
|
-
console.log(
|
|
11700
|
-
console.log(
|
|
12227
|
+
console.log(import_chalk18.default.green(`
|
|
12228
|
+
\u{1F513} Pin removed for ${import_chalk18.default.cyan(serverKey)}`));
|
|
12229
|
+
console.log(import_chalk18.default.gray(` Server: ${label}`));
|
|
12230
|
+
console.log(import_chalk18.default.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
11701
12231
|
});
|
|
11702
12232
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
11703
12233
|
const result = readMcpPinsSafe();
|
|
11704
12234
|
if (!result.ok && result.reason === "missing") {
|
|
11705
|
-
console.log(
|
|
12235
|
+
console.log(import_chalk18.default.gray("\nNo pins to clear.\n"));
|
|
11706
12236
|
return;
|
|
11707
12237
|
}
|
|
11708
12238
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
11709
12239
|
clearAllPins();
|
|
11710
|
-
console.log(
|
|
12240
|
+
console.log(import_chalk18.default.green(`
|
|
11711
12241
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
11712
|
-
console.log(
|
|
12242
|
+
console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
|
|
11713
12243
|
});
|
|
11714
12244
|
}
|
|
11715
12245
|
|
|
11716
12246
|
// src/cli.ts
|
|
11717
12247
|
var { version } = JSON.parse(
|
|
11718
|
-
|
|
12248
|
+
import_fs30.default.readFileSync(import_path33.default.join(__dirname, "../package.json"), "utf-8")
|
|
11719
12249
|
);
|
|
11720
12250
|
var program = new import_commander.Command();
|
|
11721
12251
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
11722
12252
|
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) => {
|
|
11723
12253
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
11724
|
-
const credPath =
|
|
11725
|
-
if (!
|
|
11726
|
-
|
|
12254
|
+
const credPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "credentials.json");
|
|
12255
|
+
if (!import_fs30.default.existsSync(import_path33.default.dirname(credPath)))
|
|
12256
|
+
import_fs30.default.mkdirSync(import_path33.default.dirname(credPath), { recursive: true });
|
|
11727
12257
|
const profileName = options.profile || "default";
|
|
11728
12258
|
let existingCreds = {};
|
|
11729
12259
|
try {
|
|
11730
|
-
if (
|
|
11731
|
-
const raw = JSON.parse(
|
|
12260
|
+
if (import_fs30.default.existsSync(credPath)) {
|
|
12261
|
+
const raw = JSON.parse(import_fs30.default.readFileSync(credPath, "utf-8"));
|
|
11732
12262
|
if (raw.apiKey) {
|
|
11733
12263
|
existingCreds = {
|
|
11734
12264
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -11740,13 +12270,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11740
12270
|
} catch {
|
|
11741
12271
|
}
|
|
11742
12272
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
11743
|
-
|
|
12273
|
+
import_fs30.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
11744
12274
|
if (profileName === "default") {
|
|
11745
|
-
const configPath =
|
|
12275
|
+
const configPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
|
|
11746
12276
|
let config = {};
|
|
11747
12277
|
try {
|
|
11748
|
-
if (
|
|
11749
|
-
config = JSON.parse(
|
|
12278
|
+
if (import_fs30.default.existsSync(configPath))
|
|
12279
|
+
config = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
|
|
11750
12280
|
} catch {
|
|
11751
12281
|
}
|
|
11752
12282
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -11761,19 +12291,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11761
12291
|
approvers.cloud = false;
|
|
11762
12292
|
}
|
|
11763
12293
|
s.approvers = approvers;
|
|
11764
|
-
if (!
|
|
11765
|
-
|
|
11766
|
-
|
|
12294
|
+
if (!import_fs30.default.existsSync(import_path33.default.dirname(configPath)))
|
|
12295
|
+
import_fs30.default.mkdirSync(import_path33.default.dirname(configPath), { recursive: true });
|
|
12296
|
+
import_fs30.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
11767
12297
|
}
|
|
11768
12298
|
if (options.profile && profileName !== "default") {
|
|
11769
|
-
console.log(
|
|
11770
|
-
console.log(
|
|
12299
|
+
console.log(import_chalk20.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
12300
|
+
console.log(import_chalk20.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
11771
12301
|
} else if (options.local) {
|
|
11772
|
-
console.log(
|
|
11773
|
-
console.log(
|
|
12302
|
+
console.log(import_chalk20.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
12303
|
+
console.log(import_chalk20.default.gray(` All decisions stay on this machine.`));
|
|
11774
12304
|
} else {
|
|
11775
|
-
console.log(
|
|
11776
|
-
console.log(
|
|
12305
|
+
console.log(import_chalk20.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
12306
|
+
console.log(import_chalk20.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
11777
12307
|
}
|
|
11778
12308
|
});
|
|
11779
12309
|
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
@@ -11781,19 +12311,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
11781
12311
|
if (target === "claude") return await setupClaude();
|
|
11782
12312
|
if (target === "cursor") return await setupCursor();
|
|
11783
12313
|
if (target === "hud") return setupHud();
|
|
11784
|
-
console.error(
|
|
12314
|
+
console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11785
12315
|
process.exit(1);
|
|
11786
12316
|
});
|
|
11787
12317
|
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
11788
12318
|
if (!target) {
|
|
11789
|
-
console.log(
|
|
11790
|
-
console.log(" Usage: " +
|
|
12319
|
+
console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
12320
|
+
console.log(" Usage: " + import_chalk20.default.white("node9 setup <target>") + "\n");
|
|
11791
12321
|
console.log(" Targets:");
|
|
11792
|
-
console.log(" " +
|
|
11793
|
-
console.log(" " +
|
|
11794
|
-
console.log(" " +
|
|
12322
|
+
console.log(" " + import_chalk20.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
12323
|
+
console.log(" " + import_chalk20.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
12324
|
+
console.log(" " + import_chalk20.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
11795
12325
|
process.stdout.write(
|
|
11796
|
-
" " +
|
|
12326
|
+
" " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
11797
12327
|
);
|
|
11798
12328
|
console.log("");
|
|
11799
12329
|
return;
|
|
@@ -11803,7 +12333,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
11803
12333
|
if (t === "claude") return await setupClaude();
|
|
11804
12334
|
if (t === "cursor") return await setupCursor();
|
|
11805
12335
|
if (t === "hud") return setupHud();
|
|
11806
|
-
console.error(
|
|
12336
|
+
console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11807
12337
|
process.exit(1);
|
|
11808
12338
|
});
|
|
11809
12339
|
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
|
|
@@ -11814,31 +12344,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
11814
12344
|
else if (target === "hud") fn = teardownHud;
|
|
11815
12345
|
else {
|
|
11816
12346
|
console.error(
|
|
11817
|
-
|
|
12347
|
+
import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
|
|
11818
12348
|
);
|
|
11819
12349
|
process.exit(1);
|
|
11820
12350
|
}
|
|
11821
|
-
console.log(
|
|
12351
|
+
console.log(import_chalk20.default.cyan(`
|
|
11822
12352
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
11823
12353
|
`));
|
|
11824
12354
|
try {
|
|
11825
12355
|
fn();
|
|
11826
12356
|
} catch (err2) {
|
|
11827
|
-
console.error(
|
|
12357
|
+
console.error(import_chalk20.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11828
12358
|
process.exit(1);
|
|
11829
12359
|
}
|
|
11830
|
-
console.log(
|
|
12360
|
+
console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
|
|
11831
12361
|
});
|
|
11832
12362
|
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) => {
|
|
11833
|
-
console.log(
|
|
11834
|
-
console.log(
|
|
12363
|
+
console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
12364
|
+
console.log(import_chalk20.default.bold("Stopping daemon..."));
|
|
11835
12365
|
try {
|
|
11836
12366
|
stopDaemon();
|
|
11837
|
-
console.log(
|
|
12367
|
+
console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
|
|
11838
12368
|
} catch {
|
|
11839
|
-
console.log(
|
|
12369
|
+
console.log(import_chalk20.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
11840
12370
|
}
|
|
11841
|
-
console.log(
|
|
12371
|
+
console.log(import_chalk20.default.bold("\nRemoving hooks..."));
|
|
11842
12372
|
let teardownFailed = false;
|
|
11843
12373
|
for (const [label, fn] of [
|
|
11844
12374
|
["Claude", teardownClaude],
|
|
@@ -11850,45 +12380,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
11850
12380
|
} catch (err2) {
|
|
11851
12381
|
teardownFailed = true;
|
|
11852
12382
|
console.error(
|
|
11853
|
-
|
|
12383
|
+
import_chalk20.default.red(
|
|
11854
12384
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11855
12385
|
)
|
|
11856
12386
|
);
|
|
11857
12387
|
}
|
|
11858
12388
|
}
|
|
11859
12389
|
if (options.purge) {
|
|
11860
|
-
const node9Dir =
|
|
11861
|
-
if (
|
|
12390
|
+
const node9Dir = import_path33.default.join(import_os26.default.homedir(), ".node9");
|
|
12391
|
+
if (import_fs30.default.existsSync(node9Dir)) {
|
|
11862
12392
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
11863
12393
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
11864
12394
|
default: false
|
|
11865
12395
|
});
|
|
11866
12396
|
if (confirmed) {
|
|
11867
|
-
|
|
11868
|
-
if (
|
|
12397
|
+
import_fs30.default.rmSync(node9Dir, { recursive: true });
|
|
12398
|
+
if (import_fs30.default.existsSync(node9Dir)) {
|
|
11869
12399
|
console.error(
|
|
11870
|
-
|
|
12400
|
+
import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
11871
12401
|
);
|
|
11872
12402
|
} else {
|
|
11873
|
-
console.log(
|
|
12403
|
+
console.log(import_chalk20.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
11874
12404
|
}
|
|
11875
12405
|
} else {
|
|
11876
|
-
console.log(
|
|
12406
|
+
console.log(import_chalk20.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
11877
12407
|
}
|
|
11878
12408
|
} else {
|
|
11879
|
-
console.log(
|
|
12409
|
+
console.log(import_chalk20.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
11880
12410
|
}
|
|
11881
12411
|
} else {
|
|
11882
12412
|
console.log(
|
|
11883
|
-
|
|
12413
|
+
import_chalk20.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
11884
12414
|
);
|
|
11885
12415
|
}
|
|
11886
12416
|
if (teardownFailed) {
|
|
11887
|
-
console.error(
|
|
12417
|
+
console.error(import_chalk20.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
11888
12418
|
process.exit(1);
|
|
11889
12419
|
}
|
|
11890
|
-
console.log(
|
|
11891
|
-
console.log(
|
|
12420
|
+
console.log(import_chalk20.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
12421
|
+
console.log(import_chalk20.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
11892
12422
|
});
|
|
11893
12423
|
registerDoctorCommand(program, version);
|
|
11894
12424
|
program.command("explain").description(
|
|
@@ -11901,7 +12431,7 @@ program.command("explain").description(
|
|
|
11901
12431
|
try {
|
|
11902
12432
|
args = JSON.parse(trimmed);
|
|
11903
12433
|
} catch {
|
|
11904
|
-
console.error(
|
|
12434
|
+
console.error(import_chalk20.default.red(`
|
|
11905
12435
|
\u274C Invalid JSON: ${trimmed}
|
|
11906
12436
|
`));
|
|
11907
12437
|
process.exit(1);
|
|
@@ -11912,60 +12442,61 @@ program.command("explain").description(
|
|
|
11912
12442
|
}
|
|
11913
12443
|
const result = await explainPolicy(tool, args);
|
|
11914
12444
|
console.log("");
|
|
11915
|
-
console.log(
|
|
12445
|
+
console.log(import_chalk20.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
11916
12446
|
console.log("");
|
|
11917
|
-
console.log(` ${
|
|
12447
|
+
console.log(` ${import_chalk20.default.bold("Tool:")} ${import_chalk20.default.white(result.tool)}`);
|
|
11918
12448
|
if (argsRaw) {
|
|
11919
12449
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
11920
|
-
console.log(` ${
|
|
12450
|
+
console.log(` ${import_chalk20.default.bold("Input:")} ${import_chalk20.default.gray(preview)}`);
|
|
11921
12451
|
}
|
|
11922
12452
|
console.log("");
|
|
11923
|
-
console.log(
|
|
12453
|
+
console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
|
|
11924
12454
|
for (const tier of result.waterfall) {
|
|
11925
|
-
const
|
|
12455
|
+
const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
|
|
11926
12456
|
const label = tier.label.padEnd(16);
|
|
11927
12457
|
let statusStr;
|
|
11928
12458
|
if (tier.tier === 1) {
|
|
11929
|
-
statusStr =
|
|
12459
|
+
statusStr = import_chalk20.default.gray(tier.note ?? "");
|
|
11930
12460
|
} else if (tier.status === "active") {
|
|
11931
|
-
const loc = tier.path ?
|
|
11932
|
-
const note = tier.note ?
|
|
11933
|
-
statusStr =
|
|
12461
|
+
const loc = tier.path ? import_chalk20.default.gray(tier.path) : "";
|
|
12462
|
+
const note = tier.note ? import_chalk20.default.gray(`(${tier.note})`) : "";
|
|
12463
|
+
statusStr = import_chalk20.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
11934
12464
|
} else {
|
|
11935
|
-
statusStr =
|
|
12465
|
+
statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
11936
12466
|
}
|
|
11937
|
-
console.log(`${
|
|
12467
|
+
console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
|
|
11938
12468
|
}
|
|
11939
12469
|
console.log("");
|
|
11940
|
-
console.log(
|
|
12470
|
+
console.log(import_chalk20.default.bold("Policy Evaluation:"));
|
|
11941
12471
|
for (const step of result.steps) {
|
|
11942
12472
|
const isFinal = step.isFinal;
|
|
11943
12473
|
let icon;
|
|
11944
|
-
if (step.outcome === "allow") icon =
|
|
11945
|
-
else if (step.outcome === "review") icon =
|
|
11946
|
-
else if (step.outcome === "skip") icon =
|
|
11947
|
-
else icon =
|
|
12474
|
+
if (step.outcome === "allow") icon = import_chalk20.default.green(" \u2705");
|
|
12475
|
+
else if (step.outcome === "review") icon = import_chalk20.default.red(" \u{1F534}");
|
|
12476
|
+
else if (step.outcome === "skip") icon = import_chalk20.default.gray(" \u2500 ");
|
|
12477
|
+
else icon = import_chalk20.default.gray(" \u25CB ");
|
|
11948
12478
|
const name = step.name.padEnd(18);
|
|
11949
|
-
const nameStr = isFinal ?
|
|
11950
|
-
const detail = isFinal ?
|
|
11951
|
-
const arrow = isFinal ?
|
|
12479
|
+
const nameStr = isFinal ? import_chalk20.default.white.bold(name) : import_chalk20.default.white(name);
|
|
12480
|
+
const detail = isFinal ? import_chalk20.default.white(step.detail) : import_chalk20.default.gray(step.detail);
|
|
12481
|
+
const arrow = isFinal ? import_chalk20.default.yellow(" \u2190 STOP") : "";
|
|
11952
12482
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
11953
12483
|
}
|
|
11954
12484
|
console.log("");
|
|
11955
12485
|
if (result.decision === "allow") {
|
|
11956
|
-
console.log(
|
|
12486
|
+
console.log(import_chalk20.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk20.default.gray(" \u2014 no approval needed"));
|
|
11957
12487
|
} else {
|
|
11958
12488
|
console.log(
|
|
11959
|
-
|
|
12489
|
+
import_chalk20.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk20.default.gray(" \u2014 human approval required")
|
|
11960
12490
|
);
|
|
11961
12491
|
if (result.blockedByLabel) {
|
|
11962
|
-
console.log(
|
|
12492
|
+
console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
11963
12493
|
}
|
|
11964
12494
|
}
|
|
11965
12495
|
console.log("");
|
|
11966
12496
|
});
|
|
11967
12497
|
registerInitCommand(program);
|
|
11968
12498
|
registerAuditCommand(program);
|
|
12499
|
+
registerReportCommand(program);
|
|
11969
12500
|
registerStatusCommand(program);
|
|
11970
12501
|
registerDaemonCommand(program);
|
|
11971
12502
|
program.command("tail").description("Stream live agent activity to the terminal").option("--history", "Replay recent history then continue live", false).option("--clear", "Clear the history buffer and exit (does not stream)", false).action(async (options) => {
|
|
@@ -11973,7 +12504,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
11973
12504
|
try {
|
|
11974
12505
|
await startTail2(options);
|
|
11975
12506
|
} catch (err2) {
|
|
11976
|
-
console.error(
|
|
12507
|
+
console.error(import_chalk20.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11977
12508
|
process.exit(1);
|
|
11978
12509
|
}
|
|
11979
12510
|
});
|
|
@@ -12005,14 +12536,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
12005
12536
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
12006
12537
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
12007
12538
|
if (subcommand === "debug") {
|
|
12008
|
-
const flagFile =
|
|
12539
|
+
const flagFile = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug");
|
|
12009
12540
|
if (state === "on") {
|
|
12010
|
-
|
|
12011
|
-
|
|
12541
|
+
import_fs30.default.mkdirSync(import_path33.default.dirname(flagFile), { recursive: true });
|
|
12542
|
+
import_fs30.default.writeFileSync(flagFile, "");
|
|
12012
12543
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
12013
12544
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
12014
12545
|
} else if (state === "off") {
|
|
12015
|
-
if (
|
|
12546
|
+
if (import_fs30.default.existsSync(flagFile)) import_fs30.default.unlinkSync(flagFile);
|
|
12016
12547
|
console.log("HUD debug logging disabled.");
|
|
12017
12548
|
} else {
|
|
12018
12549
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -12027,7 +12558,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12027
12558
|
const ms = parseDuration(options.duration);
|
|
12028
12559
|
if (ms === null) {
|
|
12029
12560
|
console.error(
|
|
12030
|
-
|
|
12561
|
+
import_chalk20.default.red(`
|
|
12031
12562
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
12032
12563
|
`)
|
|
12033
12564
|
);
|
|
@@ -12035,20 +12566,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12035
12566
|
}
|
|
12036
12567
|
pauseNode9(ms, options.duration);
|
|
12037
12568
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
12038
|
-
console.log(
|
|
12569
|
+
console.log(import_chalk20.default.yellow(`
|
|
12039
12570
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
12040
|
-
console.log(
|
|
12041
|
-
console.log(
|
|
12571
|
+
console.log(import_chalk20.default.gray(` All tool calls will be allowed without review.`));
|
|
12572
|
+
console.log(import_chalk20.default.gray(` Run "node9 resume" to re-enable early.
|
|
12042
12573
|
`));
|
|
12043
12574
|
});
|
|
12044
12575
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
12045
12576
|
const { paused } = checkPause();
|
|
12046
12577
|
if (!paused) {
|
|
12047
|
-
console.log(
|
|
12578
|
+
console.log(import_chalk20.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
12048
12579
|
return;
|
|
12049
12580
|
}
|
|
12050
12581
|
resumeNode9();
|
|
12051
|
-
console.log(
|
|
12582
|
+
console.log(import_chalk20.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
12052
12583
|
});
|
|
12053
12584
|
var HOOK_BASED_AGENTS = {
|
|
12054
12585
|
claude: "claude",
|
|
@@ -12061,15 +12592,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12061
12592
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
12062
12593
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
12063
12594
|
console.error(
|
|
12064
|
-
|
|
12595
|
+
import_chalk20.default.yellow(`
|
|
12065
12596
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
12066
12597
|
);
|
|
12067
|
-
console.error(
|
|
12598
|
+
console.error(import_chalk20.default.white(`
|
|
12068
12599
|
"${target}" uses its own hook system. Use:`));
|
|
12069
12600
|
console.error(
|
|
12070
|
-
|
|
12601
|
+
import_chalk20.default.green(` node9 addto ${target} `) + import_chalk20.default.gray("# one-time setup")
|
|
12071
12602
|
);
|
|
12072
|
-
console.error(
|
|
12603
|
+
console.error(import_chalk20.default.green(` ${target} `) + import_chalk20.default.gray("# run normally"));
|
|
12073
12604
|
process.exit(1);
|
|
12074
12605
|
}
|
|
12075
12606
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -12086,7 +12617,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12086
12617
|
}
|
|
12087
12618
|
);
|
|
12088
12619
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
12089
|
-
console.error(
|
|
12620
|
+
console.error(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
12090
12621
|
const daemonReady = await autoStartDaemonAndWait();
|
|
12091
12622
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
12092
12623
|
}
|
|
@@ -12099,12 +12630,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12099
12630
|
}
|
|
12100
12631
|
if (!result.approved) {
|
|
12101
12632
|
console.error(
|
|
12102
|
-
|
|
12633
|
+
import_chalk20.default.red(`
|
|
12103
12634
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
12104
12635
|
);
|
|
12105
12636
|
process.exit(1);
|
|
12106
12637
|
}
|
|
12107
|
-
console.error(
|
|
12638
|
+
console.error(import_chalk20.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
12108
12639
|
await runProxy(fullCommand);
|
|
12109
12640
|
} else {
|
|
12110
12641
|
program.help();
|
|
@@ -12119,9 +12650,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
12119
12650
|
const isCheckHook = process.argv[2] === "check";
|
|
12120
12651
|
if (isCheckHook) {
|
|
12121
12652
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
12122
|
-
const logPath =
|
|
12653
|
+
const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hook-debug.log");
|
|
12123
12654
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
12124
|
-
|
|
12655
|
+
import_fs30.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
12125
12656
|
`);
|
|
12126
12657
|
}
|
|
12127
12658
|
process.exit(0);
|