@node9/proxy 1.9.3 → 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 +32 -3
- package/dist/cli.js +1123 -610
- package/dist/cli.mjs +1105 -592
- package/dist/index.js +97 -10
- package/dist/index.mjs +95 -8
- 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,
|
|
@@ -261,6 +269,11 @@ var init_config_schema = __esm({
|
|
|
261
269
|
dlp: import_zod.z.object({
|
|
262
270
|
enabled: import_zod.z.boolean().optional(),
|
|
263
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()
|
|
264
277
|
}).optional()
|
|
265
278
|
}).optional(),
|
|
266
279
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -555,7 +568,8 @@ function getConfig(cwd) {
|
|
|
555
568
|
onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
|
|
556
569
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
557
570
|
},
|
|
558
|
-
dlp: { ...DEFAULT_CONFIG.policy.dlp }
|
|
571
|
+
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
572
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
559
573
|
};
|
|
560
574
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
561
575
|
const applyLayer = (source) => {
|
|
@@ -594,6 +608,13 @@ function getConfig(cwd) {
|
|
|
594
608
|
if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
|
|
595
609
|
if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
|
|
596
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
|
+
}
|
|
597
618
|
const envs = source.environments || {};
|
|
598
619
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
599
620
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -900,7 +921,8 @@ var init_config = __esm({
|
|
|
900
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."
|
|
901
922
|
}
|
|
902
923
|
],
|
|
903
|
-
dlp: { enabled: true, scanIgnoredTools: true }
|
|
924
|
+
dlp: { enabled: true, scanIgnoredTools: true },
|
|
925
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
904
926
|
},
|
|
905
927
|
environments: {}
|
|
906
928
|
};
|
|
@@ -1704,9 +1726,9 @@ function matchesPattern(text, patterns) {
|
|
|
1704
1726
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1705
1727
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1706
1728
|
}
|
|
1707
|
-
function getNestedValue(obj,
|
|
1729
|
+
function getNestedValue(obj, path34) {
|
|
1708
1730
|
if (!obj || typeof obj !== "object") return null;
|
|
1709
|
-
return
|
|
1731
|
+
return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1710
1732
|
}
|
|
1711
1733
|
function shouldSnapshot(toolName, args, config) {
|
|
1712
1734
|
if (!config.settings.enableUndo) return false;
|
|
@@ -3079,6 +3101,58 @@ var init_cloud = __esm({
|
|
|
3079
3101
|
}
|
|
3080
3102
|
});
|
|
3081
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
|
+
|
|
3082
3156
|
// src/auth/orchestrator.ts
|
|
3083
3157
|
function isWriteTool(toolName) {
|
|
3084
3158
|
const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
|
|
@@ -3119,7 +3193,7 @@ function notifyActivity(data) {
|
|
|
3119
3193
|
}
|
|
3120
3194
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
3121
3195
|
if (!options?.calledFromDaemon) {
|
|
3122
|
-
const actId = (0,
|
|
3196
|
+
const actId = (0, import_crypto4.randomUUID)();
|
|
3123
3197
|
const actTs = Date.now();
|
|
3124
3198
|
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
3125
3199
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
@@ -3166,6 +3240,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3166
3240
|
let explainableLabel = "Local Config";
|
|
3167
3241
|
let policyMatchedField;
|
|
3168
3242
|
let policyMatchedWord;
|
|
3243
|
+
let policyRuleDescription;
|
|
3169
3244
|
let riskMetadata;
|
|
3170
3245
|
let statefulRecoveryCommand;
|
|
3171
3246
|
let localSmartRuleMatched = false;
|
|
@@ -3259,6 +3334,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3259
3334
|
return { approved: true, checkedBy: "audit" };
|
|
3260
3335
|
}
|
|
3261
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
|
+
}
|
|
3262
3352
|
if (getActiveTrustSession(toolName)) {
|
|
3263
3353
|
if (approvers.cloud && creds?.apiKey)
|
|
3264
3354
|
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
@@ -3314,6 +3404,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3314
3404
|
policyMatchedField = policyResult.matchedField;
|
|
3315
3405
|
policyMatchedWord = policyResult.matchedWord;
|
|
3316
3406
|
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
3407
|
+
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
3408
|
+
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
3317
3409
|
riskMetadata = computeRiskMetadata(
|
|
3318
3410
|
args,
|
|
3319
3411
|
policyResult.tier ?? 6,
|
|
@@ -3577,13 +3669,14 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3577
3669
|
hashAuditArgs
|
|
3578
3670
|
);
|
|
3579
3671
|
}
|
|
3580
|
-
|
|
3672
|
+
const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
|
|
3673
|
+
return enrichedResult;
|
|
3581
3674
|
}
|
|
3582
|
-
var
|
|
3675
|
+
var import_crypto4, WRITE_TOOLS;
|
|
3583
3676
|
var init_orchestrator = __esm({
|
|
3584
3677
|
"src/auth/orchestrator.ts"() {
|
|
3585
3678
|
"use strict";
|
|
3586
|
-
|
|
3679
|
+
import_crypto4 = require("crypto");
|
|
3587
3680
|
init_native();
|
|
3588
3681
|
init_context_sniper();
|
|
3589
3682
|
init_dlp();
|
|
@@ -3593,6 +3686,7 @@ var init_orchestrator = __esm({
|
|
|
3593
3686
|
init_state();
|
|
3594
3687
|
init_daemon();
|
|
3595
3688
|
init_cloud();
|
|
3689
|
+
init_loop_detector();
|
|
3596
3690
|
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
3597
3691
|
"write",
|
|
3598
3692
|
"write_file",
|
|
@@ -5266,11 +5360,11 @@ function commonPathPrefix(paths) {
|
|
|
5266
5360
|
const prefix = common.join("/").replace(/\/?$/, "/");
|
|
5267
5361
|
return prefix.length > 1 ? prefix : null;
|
|
5268
5362
|
}
|
|
5269
|
-
var
|
|
5363
|
+
var import_crypto5, SuggestionTracker;
|
|
5270
5364
|
var init_suggestion_tracker = __esm({
|
|
5271
5365
|
"src/daemon/suggestion-tracker.ts"() {
|
|
5272
5366
|
"use strict";
|
|
5273
|
-
|
|
5367
|
+
import_crypto5 = require("crypto");
|
|
5274
5368
|
SuggestionTracker = class {
|
|
5275
5369
|
events = /* @__PURE__ */ new Map();
|
|
5276
5370
|
threshold;
|
|
@@ -5316,7 +5410,7 @@ var init_suggestion_tracker = __esm({
|
|
|
5316
5410
|
}
|
|
5317
5411
|
} : { type: "ignoredTool", toolName };
|
|
5318
5412
|
return {
|
|
5319
|
-
id: (0,
|
|
5413
|
+
id: (0, import_crypto5.randomUUID)(),
|
|
5320
5414
|
toolName,
|
|
5321
5415
|
allowCount: events.length,
|
|
5322
5416
|
suggestedRule,
|
|
@@ -5330,12 +5424,12 @@ var init_suggestion_tracker = __esm({
|
|
|
5330
5424
|
});
|
|
5331
5425
|
|
|
5332
5426
|
// src/daemon/taint-store.ts
|
|
5333
|
-
var
|
|
5427
|
+
var import_fs13, import_path16, DEFAULT_TTL_MS, TaintStore;
|
|
5334
5428
|
var init_taint_store = __esm({
|
|
5335
5429
|
"src/daemon/taint-store.ts"() {
|
|
5336
5430
|
"use strict";
|
|
5337
|
-
|
|
5338
|
-
|
|
5431
|
+
import_fs13 = __toESM(require("fs"));
|
|
5432
|
+
import_path16 = __toESM(require("path"));
|
|
5339
5433
|
DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
5340
5434
|
TaintStore = class {
|
|
5341
5435
|
records = /* @__PURE__ */ new Map();
|
|
@@ -5400,9 +5494,9 @@ var init_taint_store = __esm({
|
|
|
5400
5494
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
5401
5495
|
_resolve(filePath) {
|
|
5402
5496
|
try {
|
|
5403
|
-
return
|
|
5497
|
+
return import_fs13.default.realpathSync.native(import_path16.default.resolve(filePath));
|
|
5404
5498
|
} catch {
|
|
5405
|
-
return
|
|
5499
|
+
return import_path16.default.resolve(filePath);
|
|
5406
5500
|
}
|
|
5407
5501
|
}
|
|
5408
5502
|
};
|
|
@@ -5520,8 +5614,8 @@ var init_session_history = __esm({
|
|
|
5520
5614
|
// src/daemon/state.ts
|
|
5521
5615
|
function loadInsightCounts() {
|
|
5522
5616
|
try {
|
|
5523
|
-
if (!
|
|
5524
|
-
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"));
|
|
5525
5619
|
for (const [tool, count] of Object.entries(data)) {
|
|
5526
5620
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
5527
5621
|
}
|
|
@@ -5560,23 +5654,23 @@ function markRejectionHandlerRegistered() {
|
|
|
5560
5654
|
daemonRejectionHandlerRegistered = true;
|
|
5561
5655
|
}
|
|
5562
5656
|
function atomicWriteSync2(filePath, data, options) {
|
|
5563
|
-
const dir =
|
|
5564
|
-
if (!
|
|
5565
|
-
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`;
|
|
5566
5660
|
try {
|
|
5567
|
-
|
|
5661
|
+
import_fs14.default.writeFileSync(tmpPath, data, options);
|
|
5568
5662
|
} catch (err2) {
|
|
5569
5663
|
try {
|
|
5570
|
-
|
|
5664
|
+
import_fs14.default.unlinkSync(tmpPath);
|
|
5571
5665
|
} catch {
|
|
5572
5666
|
}
|
|
5573
5667
|
throw err2;
|
|
5574
5668
|
}
|
|
5575
5669
|
try {
|
|
5576
|
-
|
|
5670
|
+
import_fs14.default.renameSync(tmpPath, filePath);
|
|
5577
5671
|
} catch (err2) {
|
|
5578
5672
|
try {
|
|
5579
|
-
|
|
5673
|
+
import_fs14.default.unlinkSync(tmpPath);
|
|
5580
5674
|
} catch {
|
|
5581
5675
|
}
|
|
5582
5676
|
throw err2;
|
|
@@ -5600,16 +5694,16 @@ function appendAuditLog(data) {
|
|
|
5600
5694
|
decision: data.decision,
|
|
5601
5695
|
source: "daemon"
|
|
5602
5696
|
};
|
|
5603
|
-
const dir =
|
|
5604
|
-
if (!
|
|
5605
|
-
|
|
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");
|
|
5606
5700
|
} catch {
|
|
5607
5701
|
}
|
|
5608
5702
|
}
|
|
5609
5703
|
function getAuditHistory(limit = 20) {
|
|
5610
5704
|
try {
|
|
5611
|
-
if (!
|
|
5612
|
-
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");
|
|
5613
5707
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
5614
5708
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
5615
5709
|
} catch {
|
|
@@ -5618,19 +5712,19 @@ function getAuditHistory(limit = 20) {
|
|
|
5618
5712
|
}
|
|
5619
5713
|
function getOrgName() {
|
|
5620
5714
|
try {
|
|
5621
|
-
if (
|
|
5715
|
+
if (import_fs14.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
5622
5716
|
} catch {
|
|
5623
5717
|
}
|
|
5624
5718
|
return null;
|
|
5625
5719
|
}
|
|
5626
5720
|
function hasStoredSlackKey() {
|
|
5627
|
-
return
|
|
5721
|
+
return import_fs14.default.existsSync(CREDENTIALS_FILE);
|
|
5628
5722
|
}
|
|
5629
5723
|
function writeGlobalSetting(key, value) {
|
|
5630
5724
|
let config = {};
|
|
5631
5725
|
try {
|
|
5632
|
-
if (
|
|
5633
|
-
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"));
|
|
5634
5728
|
}
|
|
5635
5729
|
} catch {
|
|
5636
5730
|
}
|
|
@@ -5642,8 +5736,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5642
5736
|
try {
|
|
5643
5737
|
let trust = { entries: [] };
|
|
5644
5738
|
try {
|
|
5645
|
-
if (
|
|
5646
|
-
trust = JSON.parse(
|
|
5739
|
+
if (import_fs14.default.existsSync(TRUST_FILE2))
|
|
5740
|
+
trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
5647
5741
|
} catch {
|
|
5648
5742
|
}
|
|
5649
5743
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -5654,8 +5748,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5654
5748
|
}
|
|
5655
5749
|
function readPersistentDecisions() {
|
|
5656
5750
|
try {
|
|
5657
|
-
if (
|
|
5658
|
-
return JSON.parse(
|
|
5751
|
+
if (import_fs14.default.existsSync(DECISIONS_FILE)) {
|
|
5752
|
+
return JSON.parse(import_fs14.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
5659
5753
|
}
|
|
5660
5754
|
} catch {
|
|
5661
5755
|
}
|
|
@@ -5692,7 +5786,7 @@ function estimateToolCost(tool, args) {
|
|
|
5692
5786
|
const filePath = a.file_path ?? a.path;
|
|
5693
5787
|
if (filePath) {
|
|
5694
5788
|
try {
|
|
5695
|
-
const bytes =
|
|
5789
|
+
const bytes = import_fs14.default.statSync(filePath).size;
|
|
5696
5790
|
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
5697
5791
|
} catch {
|
|
5698
5792
|
}
|
|
@@ -5750,7 +5844,7 @@ function abandonPending() {
|
|
|
5750
5844
|
});
|
|
5751
5845
|
if (autoStarted) {
|
|
5752
5846
|
try {
|
|
5753
|
-
|
|
5847
|
+
import_fs14.default.unlinkSync(DAEMON_PID_FILE);
|
|
5754
5848
|
} catch {
|
|
5755
5849
|
}
|
|
5756
5850
|
setTimeout(() => {
|
|
@@ -5761,7 +5855,7 @@ function abandonPending() {
|
|
|
5761
5855
|
}
|
|
5762
5856
|
function startActivitySocket() {
|
|
5763
5857
|
try {
|
|
5764
|
-
|
|
5858
|
+
import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5765
5859
|
} catch {
|
|
5766
5860
|
}
|
|
5767
5861
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -5842,34 +5936,34 @@ function startActivitySocket() {
|
|
|
5842
5936
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
5843
5937
|
process.on("exit", () => {
|
|
5844
5938
|
try {
|
|
5845
|
-
|
|
5939
|
+
import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5846
5940
|
} catch {
|
|
5847
5941
|
}
|
|
5848
5942
|
});
|
|
5849
5943
|
}
|
|
5850
|
-
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;
|
|
5851
5945
|
var init_state2 = __esm({
|
|
5852
5946
|
"src/daemon/state.ts"() {
|
|
5853
5947
|
"use strict";
|
|
5854
5948
|
import_net2 = __toESM(require("net"));
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5949
|
+
import_fs14 = __toESM(require("fs"));
|
|
5950
|
+
import_path17 = __toESM(require("path"));
|
|
5951
|
+
import_os12 = __toESM(require("os"));
|
|
5858
5952
|
import_child_process3 = require("child_process");
|
|
5859
|
-
|
|
5953
|
+
import_crypto6 = require("crypto");
|
|
5860
5954
|
init_daemon();
|
|
5861
5955
|
init_suggestion_tracker();
|
|
5862
5956
|
init_taint_store();
|
|
5863
5957
|
init_session_counters();
|
|
5864
5958
|
init_session_history();
|
|
5865
|
-
homeDir =
|
|
5866
|
-
DAEMON_PID_FILE =
|
|
5867
|
-
DECISIONS_FILE =
|
|
5868
|
-
AUDIT_LOG_FILE =
|
|
5869
|
-
TRUST_FILE2 =
|
|
5870
|
-
GLOBAL_CONFIG_FILE =
|
|
5871
|
-
CREDENTIALS_FILE =
|
|
5872
|
-
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");
|
|
5873
5967
|
pending = /* @__PURE__ */ new Map();
|
|
5874
5968
|
sseClients = /* @__PURE__ */ new Set();
|
|
5875
5969
|
suggestionTracker = new SuggestionTracker(3);
|
|
@@ -5887,7 +5981,7 @@ var init_state2 = __esm({
|
|
|
5887
5981
|
"2h": 2 * 60 * 6e4
|
|
5888
5982
|
};
|
|
5889
5983
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
5890
|
-
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");
|
|
5891
5985
|
ACTIVITY_RING_SIZE = 100;
|
|
5892
5986
|
activityRing = [];
|
|
5893
5987
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -5912,8 +6006,8 @@ var init_state2 = __esm({
|
|
|
5912
6006
|
function patchConfig(configPath, patch) {
|
|
5913
6007
|
let config = {};
|
|
5914
6008
|
try {
|
|
5915
|
-
if (
|
|
5916
|
-
config = JSON.parse(
|
|
6009
|
+
if (import_fs15.default.existsSync(configPath)) {
|
|
6010
|
+
config = JSON.parse(import_fs15.default.readFileSync(configPath, "utf8"));
|
|
5917
6011
|
}
|
|
5918
6012
|
} catch {
|
|
5919
6013
|
throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
|
|
@@ -5932,44 +6026,44 @@ function patchConfig(configPath, patch) {
|
|
|
5932
6026
|
ignored.push(patch.toolName);
|
|
5933
6027
|
}
|
|
5934
6028
|
}
|
|
5935
|
-
const dir =
|
|
5936
|
-
|
|
6029
|
+
const dir = import_path18.default.dirname(configPath);
|
|
6030
|
+
import_fs15.default.mkdirSync(dir, { recursive: true });
|
|
5937
6031
|
const tmp = configPath + ".node9-tmp";
|
|
5938
6032
|
try {
|
|
5939
|
-
|
|
6033
|
+
import_fs15.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5940
6034
|
} catch (err2) {
|
|
5941
6035
|
try {
|
|
5942
|
-
|
|
6036
|
+
import_fs15.default.unlinkSync(tmp);
|
|
5943
6037
|
} catch {
|
|
5944
6038
|
}
|
|
5945
6039
|
throw err2;
|
|
5946
6040
|
}
|
|
5947
6041
|
try {
|
|
5948
|
-
|
|
6042
|
+
import_fs15.default.renameSync(tmp, configPath);
|
|
5949
6043
|
} catch (err2) {
|
|
5950
6044
|
try {
|
|
5951
|
-
|
|
6045
|
+
import_fs15.default.unlinkSync(tmp);
|
|
5952
6046
|
} catch {
|
|
5953
6047
|
}
|
|
5954
6048
|
throw err2;
|
|
5955
6049
|
}
|
|
5956
6050
|
}
|
|
5957
|
-
var
|
|
6051
|
+
var import_fs15, import_path18, import_os13, GLOBAL_CONFIG_PATH;
|
|
5958
6052
|
var init_patch = __esm({
|
|
5959
6053
|
"src/config/patch.ts"() {
|
|
5960
6054
|
"use strict";
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
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");
|
|
5965
6059
|
}
|
|
5966
6060
|
});
|
|
5967
6061
|
|
|
5968
6062
|
// src/daemon/server.ts
|
|
5969
6063
|
function startDaemon() {
|
|
5970
6064
|
loadInsightCounts();
|
|
5971
|
-
const csrfToken = (0,
|
|
5972
|
-
const internalToken = (0,
|
|
6065
|
+
const csrfToken = (0, import_crypto7.randomUUID)();
|
|
6066
|
+
const internalToken = (0, import_crypto7.randomUUID)();
|
|
5973
6067
|
const UI_HTML = UI_HTML_TEMPLATE.replace("{{CSRF_TOKEN}}", csrfToken);
|
|
5974
6068
|
const validToken = (req) => req.headers["x-node9-token"] === csrfToken;
|
|
5975
6069
|
const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
|
|
@@ -5982,7 +6076,7 @@ function startDaemon() {
|
|
|
5982
6076
|
idleTimer = setTimeout(() => {
|
|
5983
6077
|
if (autoStarted) {
|
|
5984
6078
|
try {
|
|
5985
|
-
|
|
6079
|
+
import_fs16.default.unlinkSync(DAEMON_PID_FILE);
|
|
5986
6080
|
} catch {
|
|
5987
6081
|
}
|
|
5988
6082
|
}
|
|
@@ -6103,7 +6197,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6103
6197
|
cwd,
|
|
6104
6198
|
localSmartRuleMatched = false
|
|
6105
6199
|
} = JSON.parse(body);
|
|
6106
|
-
const id = fromCLI && typeof activityId === "string" && activityId || (0,
|
|
6200
|
+
const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto7.randomUUID)();
|
|
6107
6201
|
const entry = {
|
|
6108
6202
|
id,
|
|
6109
6203
|
toolName,
|
|
@@ -6145,7 +6239,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6145
6239
|
status: "pending"
|
|
6146
6240
|
});
|
|
6147
6241
|
}
|
|
6148
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6242
|
+
const projectCwd = typeof cwd === "string" && import_path19.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6149
6243
|
const projectConfig = getConfig(projectCwd);
|
|
6150
6244
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6151
6245
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6535,8 +6629,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6535
6629
|
const body = await readBody(req);
|
|
6536
6630
|
const data = body ? JSON.parse(body) : {};
|
|
6537
6631
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6538
|
-
const node9Dir =
|
|
6539
|
-
if (!
|
|
6632
|
+
const node9Dir = import_path19.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6633
|
+
if (!import_path19.default.resolve(configPath).startsWith(node9Dir + import_path19.default.sep)) {
|
|
6540
6634
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6541
6635
|
return res.end(
|
|
6542
6636
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6647,14 +6741,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6647
6741
|
server.on("error", (e) => {
|
|
6648
6742
|
if (e.code === "EADDRINUSE") {
|
|
6649
6743
|
try {
|
|
6650
|
-
if (
|
|
6651
|
-
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"));
|
|
6652
6746
|
process.kill(pid, 0);
|
|
6653
6747
|
return process.exit(0);
|
|
6654
6748
|
}
|
|
6655
6749
|
} catch {
|
|
6656
6750
|
try {
|
|
6657
|
-
|
|
6751
|
+
import_fs16.default.unlinkSync(DAEMON_PID_FILE);
|
|
6658
6752
|
} catch {
|
|
6659
6753
|
}
|
|
6660
6754
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -6713,14 +6807,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6713
6807
|
}
|
|
6714
6808
|
startActivitySocket();
|
|
6715
6809
|
}
|
|
6716
|
-
var import_http,
|
|
6810
|
+
var import_http, import_fs16, import_path19, import_crypto7, import_child_process4, import_chalk2;
|
|
6717
6811
|
var init_server = __esm({
|
|
6718
6812
|
"src/daemon/server.ts"() {
|
|
6719
6813
|
"use strict";
|
|
6720
6814
|
import_http = __toESM(require("http"));
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6815
|
+
import_fs16 = __toESM(require("fs"));
|
|
6816
|
+
import_path19 = __toESM(require("path"));
|
|
6817
|
+
import_crypto7 = require("crypto");
|
|
6724
6818
|
import_child_process4 = require("child_process");
|
|
6725
6819
|
import_chalk2 = __toESM(require("chalk"));
|
|
6726
6820
|
init_core();
|
|
@@ -6734,24 +6828,24 @@ var init_server = __esm({
|
|
|
6734
6828
|
|
|
6735
6829
|
// src/daemon/index.ts
|
|
6736
6830
|
function stopDaemon() {
|
|
6737
|
-
if (!
|
|
6831
|
+
if (!import_fs17.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
6738
6832
|
try {
|
|
6739
|
-
const { pid } = JSON.parse(
|
|
6833
|
+
const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6740
6834
|
process.kill(pid, "SIGTERM");
|
|
6741
6835
|
console.log(import_chalk3.default.green("\u2705 Stopped."));
|
|
6742
6836
|
} catch {
|
|
6743
6837
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
6744
6838
|
} finally {
|
|
6745
6839
|
try {
|
|
6746
|
-
|
|
6840
|
+
import_fs17.default.unlinkSync(DAEMON_PID_FILE);
|
|
6747
6841
|
} catch {
|
|
6748
6842
|
}
|
|
6749
6843
|
}
|
|
6750
6844
|
}
|
|
6751
6845
|
function daemonStatus() {
|
|
6752
|
-
if (
|
|
6846
|
+
if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
|
|
6753
6847
|
try {
|
|
6754
|
-
const { pid } = JSON.parse(
|
|
6848
|
+
const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6755
6849
|
process.kill(pid, 0);
|
|
6756
6850
|
console.log(import_chalk3.default.green("Node9 daemon: running"));
|
|
6757
6851
|
return;
|
|
@@ -6770,11 +6864,11 @@ function daemonStatus() {
|
|
|
6770
6864
|
console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
|
|
6771
6865
|
}
|
|
6772
6866
|
}
|
|
6773
|
-
var
|
|
6867
|
+
var import_fs17, import_chalk3, import_child_process5;
|
|
6774
6868
|
var init_daemon2 = __esm({
|
|
6775
6869
|
"src/daemon/index.ts"() {
|
|
6776
6870
|
"use strict";
|
|
6777
|
-
|
|
6871
|
+
import_fs17 = __toESM(require("fs"));
|
|
6778
6872
|
import_chalk3 = __toESM(require("chalk"));
|
|
6779
6873
|
import_child_process5 = require("child_process");
|
|
6780
6874
|
init_server();
|
|
@@ -6795,44 +6889,64 @@ function getIcon(tool) {
|
|
|
6795
6889
|
}
|
|
6796
6890
|
return "\u{1F6E0}\uFE0F";
|
|
6797
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
|
+
}
|
|
6798
6901
|
function formatBase(activity) {
|
|
6799
6902
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
6800
6903
|
const icon = getIcon(activity.tool);
|
|
6801
6904
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6802
|
-
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(), "~");
|
|
6803
6906
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6804
|
-
return `${
|
|
6907
|
+
return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
|
|
6805
6908
|
}
|
|
6806
6909
|
function renderResult(activity, result) {
|
|
6807
6910
|
const base = formatBase(activity);
|
|
6808
6911
|
let status;
|
|
6809
6912
|
if (result.status === "allow") {
|
|
6810
|
-
status =
|
|
6913
|
+
status = import_chalk19.default.green("\u2713 ALLOW");
|
|
6811
6914
|
} else if (result.status === "dlp") {
|
|
6812
|
-
status =
|
|
6915
|
+
status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
6813
6916
|
} else {
|
|
6814
|
-
status =
|
|
6917
|
+
status = import_chalk19.default.red("\u2717 BLOCK");
|
|
6815
6918
|
}
|
|
6816
6919
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
6817
|
-
const costSuffix = cost == null ? "" :
|
|
6920
|
+
const costSuffix = cost == null ? "" : import_chalk19.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
6818
6921
|
if (process.stdout.isTTY) {
|
|
6819
|
-
|
|
6820
|
-
|
|
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;
|
|
6821
6932
|
}
|
|
6822
6933
|
console.log(`${base} ${status}${costSuffix}`);
|
|
6823
6934
|
}
|
|
6824
6935
|
function renderPending(activity) {
|
|
6825
6936
|
if (!process.stdout.isTTY) return;
|
|
6826
|
-
|
|
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`);
|
|
6827
6941
|
}
|
|
6828
6942
|
async function ensureDaemon() {
|
|
6829
6943
|
let pidPort = null;
|
|
6830
|
-
if (
|
|
6944
|
+
if (import_fs28.default.existsSync(PID_FILE)) {
|
|
6831
6945
|
try {
|
|
6832
|
-
const { port } = JSON.parse(
|
|
6946
|
+
const { port } = JSON.parse(import_fs28.default.readFileSync(PID_FILE, "utf-8"));
|
|
6833
6947
|
pidPort = port;
|
|
6834
6948
|
} catch {
|
|
6835
|
-
console.error(
|
|
6949
|
+
console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
6836
6950
|
}
|
|
6837
6951
|
}
|
|
6838
6952
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -6843,7 +6957,7 @@ async function ensureDaemon() {
|
|
|
6843
6957
|
if (res.ok) return checkPort;
|
|
6844
6958
|
} catch {
|
|
6845
6959
|
}
|
|
6846
|
-
console.log(
|
|
6960
|
+
console.log(import_chalk19.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6847
6961
|
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6848
6962
|
detached: true,
|
|
6849
6963
|
stdio: "ignore",
|
|
@@ -6860,7 +6974,7 @@ async function ensureDaemon() {
|
|
|
6860
6974
|
} catch {
|
|
6861
6975
|
}
|
|
6862
6976
|
}
|
|
6863
|
-
console.error(
|
|
6977
|
+
console.error(import_chalk19.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
6864
6978
|
process.exit(1);
|
|
6865
6979
|
}
|
|
6866
6980
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -6949,9 +7063,9 @@ function buildRecoveryCardLines(req) {
|
|
|
6949
7063
|
];
|
|
6950
7064
|
}
|
|
6951
7065
|
function readApproversFromDisk() {
|
|
6952
|
-
const configPath =
|
|
7066
|
+
const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
6953
7067
|
try {
|
|
6954
|
-
const raw = JSON.parse(
|
|
7068
|
+
const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
|
|
6955
7069
|
const settings = raw.settings ?? {};
|
|
6956
7070
|
return settings.approvers ?? {};
|
|
6957
7071
|
} catch {
|
|
@@ -6962,20 +7076,20 @@ function approverStatusLine() {
|
|
|
6962
7076
|
const a = readApproversFromDisk();
|
|
6963
7077
|
const fmt = (label, key) => {
|
|
6964
7078
|
const on = a[key] !== false;
|
|
6965
|
-
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")}`;
|
|
6966
7080
|
};
|
|
6967
7081
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
6968
7082
|
}
|
|
6969
7083
|
function toggleApprover(channel) {
|
|
6970
|
-
const configPath =
|
|
7084
|
+
const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
6971
7085
|
try {
|
|
6972
|
-
const raw = JSON.parse(
|
|
7086
|
+
const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
|
|
6973
7087
|
const settings = raw.settings ?? {};
|
|
6974
7088
|
const approvers = settings.approvers ?? {};
|
|
6975
7089
|
approvers[channel] = approvers[channel] === false;
|
|
6976
7090
|
settings.approvers = approvers;
|
|
6977
7091
|
raw.settings = settings;
|
|
6978
|
-
|
|
7092
|
+
import_fs28.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
6979
7093
|
} catch (err2) {
|
|
6980
7094
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
6981
7095
|
`);
|
|
@@ -7007,7 +7121,7 @@ async function startTail(options = {}) {
|
|
|
7007
7121
|
req2.end();
|
|
7008
7122
|
});
|
|
7009
7123
|
if (result.ok) {
|
|
7010
|
-
console.log(
|
|
7124
|
+
console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
7011
7125
|
} else if (result.code === "ECONNREFUSED") {
|
|
7012
7126
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7013
7127
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7051,7 +7165,7 @@ async function startTail(options = {}) {
|
|
|
7051
7165
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7052
7166
|
if (channel) {
|
|
7053
7167
|
toggleApprover(channel);
|
|
7054
|
-
console.log(
|
|
7168
|
+
console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7055
7169
|
}
|
|
7056
7170
|
};
|
|
7057
7171
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7117,7 +7231,7 @@ async function startTail(options = {}) {
|
|
|
7117
7231
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7118
7232
|
)
|
|
7119
7233
|
);
|
|
7120
|
-
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");
|
|
7121
7235
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7122
7236
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7123
7237
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7145,8 +7259,8 @@ async function startTail(options = {}) {
|
|
|
7145
7259
|
}
|
|
7146
7260
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7147
7261
|
try {
|
|
7148
|
-
|
|
7149
|
-
|
|
7262
|
+
import_fs28.default.appendFileSync(
|
|
7263
|
+
import_path31.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log"),
|
|
7150
7264
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7151
7265
|
`
|
|
7152
7266
|
);
|
|
@@ -7168,7 +7282,7 @@ async function startTail(options = {}) {
|
|
|
7168
7282
|
);
|
|
7169
7283
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7170
7284
|
if (externalDecision) {
|
|
7171
|
-
const source = externalDecision === "allow" ?
|
|
7285
|
+
const source = externalDecision === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : import_chalk19.default.red("\u2717 DENIED");
|
|
7172
7286
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7173
7287
|
}
|
|
7174
7288
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -7227,16 +7341,16 @@ async function startTail(options = {}) {
|
|
|
7227
7341
|
}
|
|
7228
7342
|
} catch {
|
|
7229
7343
|
}
|
|
7230
|
-
console.log(
|
|
7231
|
-
\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}`));
|
|
7232
7346
|
if (canApprove) {
|
|
7233
|
-
console.log(
|
|
7234
|
-
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`));
|
|
7235
7349
|
}
|
|
7236
7350
|
if (options.history) {
|
|
7237
|
-
console.log(
|
|
7351
|
+
console.log(import_chalk19.default.dim("Showing history + live events.\n"));
|
|
7238
7352
|
} else {
|
|
7239
|
-
console.log(
|
|
7353
|
+
console.log(import_chalk19.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
7240
7354
|
}
|
|
7241
7355
|
process.on("SIGINT", () => {
|
|
7242
7356
|
exitIdleMode();
|
|
@@ -7246,13 +7360,13 @@ async function startTail(options = {}) {
|
|
|
7246
7360
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7247
7361
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7248
7362
|
}
|
|
7249
|
-
console.log(
|
|
7363
|
+
console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7250
7364
|
process.exit(0);
|
|
7251
7365
|
});
|
|
7252
7366
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
7253
7367
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
7254
7368
|
if (res.statusCode !== 200) {
|
|
7255
|
-
console.error(
|
|
7369
|
+
console.error(import_chalk19.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7256
7370
|
process.exit(1);
|
|
7257
7371
|
}
|
|
7258
7372
|
if (canApprove) enterIdleMode();
|
|
@@ -7283,7 +7397,7 @@ async function startTail(options = {}) {
|
|
|
7283
7397
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7284
7398
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7285
7399
|
}
|
|
7286
|
-
console.log(
|
|
7400
|
+
console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
|
|
7287
7401
|
process.exit(1);
|
|
7288
7402
|
});
|
|
7289
7403
|
});
|
|
@@ -7375,9 +7489,9 @@ async function startTail(options = {}) {
|
|
|
7375
7489
|
const hash = data.hash ?? "";
|
|
7376
7490
|
const summary = data.argsSummary ?? data.tool;
|
|
7377
7491
|
const fileCount = data.fileCount ?? 0;
|
|
7378
|
-
const files = fileCount > 0 ?
|
|
7492
|
+
const files = fileCount > 0 ? import_chalk19.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
7379
7493
|
process.stdout.write(
|
|
7380
|
-
`${
|
|
7494
|
+
`${import_chalk19.default.dim(time)} ${import_chalk19.default.cyan("\u{1F4F8} snapshot")} ${import_chalk19.default.dim(hash)} ${summary}${files}
|
|
7381
7495
|
`
|
|
7382
7496
|
);
|
|
7383
7497
|
return;
|
|
@@ -7394,26 +7508,26 @@ async function startTail(options = {}) {
|
|
|
7394
7508
|
}
|
|
7395
7509
|
req.on("error", (err2) => {
|
|
7396
7510
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
7397
|
-
console.error(
|
|
7511
|
+
console.error(import_chalk19.default.red(`
|
|
7398
7512
|
\u274C ${msg}`));
|
|
7399
7513
|
process.exit(1);
|
|
7400
7514
|
});
|
|
7401
7515
|
}
|
|
7402
|
-
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;
|
|
7403
7517
|
var init_tail = __esm({
|
|
7404
7518
|
"src/tui/tail.ts"() {
|
|
7405
7519
|
"use strict";
|
|
7406
7520
|
import_http2 = __toESM(require("http"));
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7521
|
+
import_chalk19 = __toESM(require("chalk"));
|
|
7522
|
+
import_fs28 = __toESM(require("fs"));
|
|
7523
|
+
import_os24 = __toESM(require("os"));
|
|
7524
|
+
import_path31 = __toESM(require("path"));
|
|
7411
7525
|
import_readline5 = __toESM(require("readline"));
|
|
7412
7526
|
import_child_process14 = require("child_process");
|
|
7413
7527
|
init_daemon2();
|
|
7414
7528
|
init_daemon();
|
|
7415
7529
|
init_core();
|
|
7416
|
-
PID_FILE =
|
|
7530
|
+
PID_FILE = import_path31.default.join(import_os24.default.homedir(), ".node9", "daemon.pid");
|
|
7417
7531
|
ICONS = {
|
|
7418
7532
|
bash: "\u{1F4BB}",
|
|
7419
7533
|
shell: "\u{1F4BB}",
|
|
@@ -7441,6 +7555,8 @@ var init_tail = __esm({
|
|
|
7441
7555
|
HIDE_CURSOR = "\x1B[?25l";
|
|
7442
7556
|
SHOW_CURSOR = "\x1B[?25h";
|
|
7443
7557
|
ERASE_DOWN = "\x1B[J";
|
|
7558
|
+
pendingShownForId = null;
|
|
7559
|
+
pendingWrappedLines = 0;
|
|
7444
7560
|
DIVIDER = "\u2500".repeat(60);
|
|
7445
7561
|
}
|
|
7446
7562
|
});
|
|
@@ -7509,10 +7625,10 @@ function bold(s) {
|
|
|
7509
7625
|
function color(c, s) {
|
|
7510
7626
|
return `${c}${s}${RESET3}`;
|
|
7511
7627
|
}
|
|
7512
|
-
function progressBar(
|
|
7513
|
-
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);
|
|
7514
7630
|
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
7515
|
-
const c =
|
|
7631
|
+
const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
|
|
7516
7632
|
return `${c}${bar}${RESET3}`;
|
|
7517
7633
|
}
|
|
7518
7634
|
function formatTimeLeft(resetsAt) {
|
|
@@ -7526,9 +7642,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7526
7642
|
return ` (${m}m left)`;
|
|
7527
7643
|
}
|
|
7528
7644
|
function safeReadJson(filePath) {
|
|
7529
|
-
if (!
|
|
7645
|
+
if (!import_fs29.default.existsSync(filePath)) return null;
|
|
7530
7646
|
try {
|
|
7531
|
-
return JSON.parse(
|
|
7647
|
+
return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
|
|
7532
7648
|
} catch {
|
|
7533
7649
|
return null;
|
|
7534
7650
|
}
|
|
@@ -7549,12 +7665,12 @@ function countHooksInFile(filePath) {
|
|
|
7549
7665
|
return Object.keys(cfg.hooks).length;
|
|
7550
7666
|
}
|
|
7551
7667
|
function countRulesInDir(rulesDir) {
|
|
7552
|
-
if (!
|
|
7668
|
+
if (!import_fs29.default.existsSync(rulesDir)) return 0;
|
|
7553
7669
|
let count = 0;
|
|
7554
7670
|
try {
|
|
7555
|
-
for (const entry of
|
|
7671
|
+
for (const entry of import_fs29.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7556
7672
|
if (entry.isDirectory()) {
|
|
7557
|
-
count += countRulesInDir(
|
|
7673
|
+
count += countRulesInDir(import_path32.default.join(rulesDir, entry.name));
|
|
7558
7674
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7559
7675
|
count++;
|
|
7560
7676
|
}
|
|
@@ -7565,46 +7681,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7565
7681
|
}
|
|
7566
7682
|
function isSamePath(a, b) {
|
|
7567
7683
|
try {
|
|
7568
|
-
return
|
|
7684
|
+
return import_path32.default.resolve(a) === import_path32.default.resolve(b);
|
|
7569
7685
|
} catch {
|
|
7570
7686
|
return false;
|
|
7571
7687
|
}
|
|
7572
7688
|
}
|
|
7573
7689
|
function countConfigs(cwd) {
|
|
7574
|
-
const homeDir2 =
|
|
7575
|
-
const claudeDir =
|
|
7690
|
+
const homeDir2 = import_os25.default.homedir();
|
|
7691
|
+
const claudeDir = import_path32.default.join(homeDir2, ".claude");
|
|
7576
7692
|
let claudeMdCount = 0;
|
|
7577
7693
|
let rulesCount = 0;
|
|
7578
7694
|
let hooksCount = 0;
|
|
7579
7695
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7580
7696
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7581
|
-
if (
|
|
7582
|
-
rulesCount += countRulesInDir(
|
|
7583
|
-
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");
|
|
7584
7700
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7585
7701
|
hooksCount += countHooksInFile(userSettings);
|
|
7586
|
-
const userClaudeJson =
|
|
7702
|
+
const userClaudeJson = import_path32.default.join(homeDir2, ".claude.json");
|
|
7587
7703
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7588
7704
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7589
7705
|
userMcpServers.delete(name);
|
|
7590
7706
|
}
|
|
7591
7707
|
if (cwd) {
|
|
7592
|
-
if (
|
|
7593
|
-
if (
|
|
7594
|
-
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");
|
|
7595
7711
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7596
7712
|
if (!overlapsUserScope) {
|
|
7597
|
-
if (
|
|
7598
|
-
rulesCount += countRulesInDir(
|
|
7599
|
-
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");
|
|
7600
7716
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7601
7717
|
hooksCount += countHooksInFile(projSettings);
|
|
7602
7718
|
}
|
|
7603
|
-
if (
|
|
7604
|
-
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");
|
|
7605
7721
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7606
7722
|
hooksCount += countHooksInFile(localSettings);
|
|
7607
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7723
|
+
const mcpJsonServers = getMcpServerNames(import_path32.default.join(cwd, ".mcp.json"));
|
|
7608
7724
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7609
7725
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7610
7726
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7637,12 +7753,12 @@ function readActiveShieldsHud() {
|
|
|
7637
7753
|
return shieldsCache.value;
|
|
7638
7754
|
}
|
|
7639
7755
|
try {
|
|
7640
|
-
const shieldsPath =
|
|
7641
|
-
if (!
|
|
7756
|
+
const shieldsPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "shields.json");
|
|
7757
|
+
if (!import_fs29.default.existsSync(shieldsPath)) {
|
|
7642
7758
|
shieldsCache = { value: [], ts: now };
|
|
7643
7759
|
return [];
|
|
7644
7760
|
}
|
|
7645
|
-
const parsed = JSON.parse(
|
|
7761
|
+
const parsed = JSON.parse(import_fs29.default.readFileSync(shieldsPath, "utf-8"));
|
|
7646
7762
|
if (!Array.isArray(parsed.active)) {
|
|
7647
7763
|
shieldsCache = { value: [], ts: now };
|
|
7648
7764
|
return [];
|
|
@@ -7728,15 +7844,15 @@ function renderContextLine(stdin) {
|
|
|
7728
7844
|
}
|
|
7729
7845
|
const rl = stdin.rate_limits;
|
|
7730
7846
|
if (rl?.five_hour?.used_percentage !== void 0) {
|
|
7731
|
-
const
|
|
7732
|
-
const bar = progressBar(
|
|
7847
|
+
const pct2 = Math.round(rl.five_hour.used_percentage);
|
|
7848
|
+
const bar = progressBar(pct2, 60, 80);
|
|
7733
7849
|
const left = formatTimeLeft(rl.five_hour.resets_at);
|
|
7734
|
-
parts.push(`${dim("\u2502")} 5h ${bar} ${
|
|
7850
|
+
parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
|
|
7735
7851
|
}
|
|
7736
7852
|
if (rl?.seven_day?.used_percentage !== void 0) {
|
|
7737
|
-
const
|
|
7738
|
-
const bar = progressBar(
|
|
7739
|
-
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}%`);
|
|
7740
7856
|
}
|
|
7741
7857
|
if (parts.length === 0) return null;
|
|
7742
7858
|
return parts.join(" ");
|
|
@@ -7744,17 +7860,17 @@ function renderContextLine(stdin) {
|
|
|
7744
7860
|
async function main() {
|
|
7745
7861
|
try {
|
|
7746
7862
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7747
|
-
if (
|
|
7863
|
+
if (import_fs29.default.existsSync(import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug"))) {
|
|
7748
7864
|
try {
|
|
7749
|
-
const logPath =
|
|
7865
|
+
const logPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug.log");
|
|
7750
7866
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7751
7867
|
let size = 0;
|
|
7752
7868
|
try {
|
|
7753
|
-
size =
|
|
7869
|
+
size = import_fs29.default.statSync(logPath).size;
|
|
7754
7870
|
} catch {
|
|
7755
7871
|
}
|
|
7756
7872
|
if (size < MAX_LOG_SIZE) {
|
|
7757
|
-
|
|
7873
|
+
import_fs29.default.appendFileSync(
|
|
7758
7874
|
logPath,
|
|
7759
7875
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7760
7876
|
);
|
|
@@ -7775,11 +7891,11 @@ async function main() {
|
|
|
7775
7891
|
try {
|
|
7776
7892
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7777
7893
|
for (const configPath of [
|
|
7778
|
-
|
|
7779
|
-
|
|
7894
|
+
import_path32.default.join(cwd, "node9.config.json"),
|
|
7895
|
+
import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json")
|
|
7780
7896
|
]) {
|
|
7781
|
-
if (!
|
|
7782
|
-
const cfg = JSON.parse(
|
|
7897
|
+
if (!import_fs29.default.existsSync(configPath)) continue;
|
|
7898
|
+
const cfg = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
|
|
7783
7899
|
const hud = cfg.settings?.hud;
|
|
7784
7900
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7785
7901
|
}
|
|
@@ -7797,13 +7913,13 @@ async function main() {
|
|
|
7797
7913
|
renderOffline();
|
|
7798
7914
|
}
|
|
7799
7915
|
}
|
|
7800
|
-
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;
|
|
7801
7917
|
var init_hud = __esm({
|
|
7802
7918
|
"src/cli/hud.ts"() {
|
|
7803
7919
|
"use strict";
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7920
|
+
import_fs29 = __toESM(require("fs"));
|
|
7921
|
+
import_path32 = __toESM(require("path"));
|
|
7922
|
+
import_os25 = __toESM(require("os"));
|
|
7807
7923
|
import_http3 = __toESM(require("http"));
|
|
7808
7924
|
init_daemon();
|
|
7809
7925
|
RESET3 = "\x1B[0m";
|
|
@@ -7829,9 +7945,9 @@ var import_commander = require("commander");
|
|
|
7829
7945
|
init_core();
|
|
7830
7946
|
|
|
7831
7947
|
// src/setup.ts
|
|
7832
|
-
var
|
|
7833
|
-
var
|
|
7834
|
-
var
|
|
7948
|
+
var import_fs12 = __toESM(require("fs"));
|
|
7949
|
+
var import_path15 = __toESM(require("path"));
|
|
7950
|
+
var import_os11 = __toESM(require("os"));
|
|
7835
7951
|
var import_chalk = __toESM(require("chalk"));
|
|
7836
7952
|
var import_prompts = require("@inquirer/prompts");
|
|
7837
7953
|
var import_smol_toml = require("smol-toml");
|
|
@@ -7859,26 +7975,26 @@ function fullPathCommand(subcommand) {
|
|
|
7859
7975
|
}
|
|
7860
7976
|
function readJson(filePath) {
|
|
7861
7977
|
try {
|
|
7862
|
-
if (
|
|
7863
|
-
return JSON.parse(
|
|
7978
|
+
if (import_fs12.default.existsSync(filePath)) {
|
|
7979
|
+
return JSON.parse(import_fs12.default.readFileSync(filePath, "utf-8"));
|
|
7864
7980
|
}
|
|
7865
7981
|
} catch {
|
|
7866
7982
|
}
|
|
7867
7983
|
return null;
|
|
7868
7984
|
}
|
|
7869
7985
|
function writeJson(filePath, data) {
|
|
7870
|
-
const dir =
|
|
7871
|
-
if (!
|
|
7872
|
-
|
|
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");
|
|
7873
7989
|
}
|
|
7874
7990
|
function isNode9Hook(cmd) {
|
|
7875
7991
|
if (!cmd) return false;
|
|
7876
7992
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
7877
7993
|
}
|
|
7878
7994
|
function teardownClaude() {
|
|
7879
|
-
const homeDir2 =
|
|
7880
|
-
const hooksPath =
|
|
7881
|
-
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");
|
|
7882
7998
|
let changed = false;
|
|
7883
7999
|
const settings = readJson(hooksPath);
|
|
7884
8000
|
if (settings?.hooks) {
|
|
@@ -7926,8 +8042,8 @@ function teardownClaude() {
|
|
|
7926
8042
|
}
|
|
7927
8043
|
}
|
|
7928
8044
|
function teardownGemini() {
|
|
7929
|
-
const homeDir2 =
|
|
7930
|
-
const settingsPath =
|
|
8045
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8046
|
+
const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
|
|
7931
8047
|
const settings = readJson(settingsPath);
|
|
7932
8048
|
if (!settings) {
|
|
7933
8049
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -7970,8 +8086,8 @@ function teardownGemini() {
|
|
|
7970
8086
|
}
|
|
7971
8087
|
}
|
|
7972
8088
|
function teardownCursor() {
|
|
7973
|
-
const homeDir2 =
|
|
7974
|
-
const mcpPath =
|
|
8089
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8090
|
+
const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7975
8091
|
const mcpConfig = readJson(mcpPath);
|
|
7976
8092
|
if (!mcpConfig?.mcpServers) {
|
|
7977
8093
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -8002,9 +8118,9 @@ function teardownCursor() {
|
|
|
8002
8118
|
}
|
|
8003
8119
|
}
|
|
8004
8120
|
async function setupClaude() {
|
|
8005
|
-
const homeDir2 =
|
|
8006
|
-
const mcpPath =
|
|
8007
|
-
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");
|
|
8008
8124
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
8009
8125
|
const settings = readJson(hooksPath) ?? {};
|
|
8010
8126
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -8101,8 +8217,8 @@ async function setupClaude() {
|
|
|
8101
8217
|
}
|
|
8102
8218
|
}
|
|
8103
8219
|
async function setupGemini() {
|
|
8104
|
-
const homeDir2 =
|
|
8105
|
-
const settingsPath =
|
|
8220
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8221
|
+
const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
|
|
8106
8222
|
const settings = readJson(settingsPath) ?? {};
|
|
8107
8223
|
const servers = settings.mcpServers ?? {};
|
|
8108
8224
|
let hooksChanged = false;
|
|
@@ -8197,10 +8313,10 @@ async function setupGemini() {
|
|
|
8197
8313
|
printDaemonTip();
|
|
8198
8314
|
}
|
|
8199
8315
|
}
|
|
8200
|
-
function detectAgents(homeDir2 =
|
|
8316
|
+
function detectAgents(homeDir2 = import_os11.default.homedir()) {
|
|
8201
8317
|
const exists = (p) => {
|
|
8202
8318
|
try {
|
|
8203
|
-
return
|
|
8319
|
+
return import_fs12.default.existsSync(p);
|
|
8204
8320
|
} catch (err2) {
|
|
8205
8321
|
const code = err2.code;
|
|
8206
8322
|
if (code !== "ENOENT") {
|
|
@@ -8211,15 +8327,15 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
|
|
|
8211
8327
|
}
|
|
8212
8328
|
};
|
|
8213
8329
|
return {
|
|
8214
|
-
claude: exists(
|
|
8215
|
-
gemini: exists(
|
|
8216
|
-
cursor: exists(
|
|
8217
|
-
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"))
|
|
8218
8334
|
};
|
|
8219
8335
|
}
|
|
8220
8336
|
async function setupCursor() {
|
|
8221
|
-
const homeDir2 =
|
|
8222
|
-
const mcpPath =
|
|
8337
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8338
|
+
const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
|
|
8223
8339
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
8224
8340
|
const servers = mcpConfig.mcpServers ?? {};
|
|
8225
8341
|
let anythingChanged = false;
|
|
@@ -8285,21 +8401,21 @@ async function setupCursor() {
|
|
|
8285
8401
|
}
|
|
8286
8402
|
function readToml(filePath) {
|
|
8287
8403
|
try {
|
|
8288
|
-
if (
|
|
8289
|
-
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"));
|
|
8290
8406
|
}
|
|
8291
8407
|
} catch {
|
|
8292
8408
|
}
|
|
8293
8409
|
return null;
|
|
8294
8410
|
}
|
|
8295
8411
|
function writeToml(filePath, data) {
|
|
8296
|
-
const dir =
|
|
8297
|
-
if (!
|
|
8298
|
-
|
|
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));
|
|
8299
8415
|
}
|
|
8300
8416
|
async function setupCodex() {
|
|
8301
|
-
const homeDir2 =
|
|
8302
|
-
const configPath =
|
|
8417
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8418
|
+
const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
|
|
8303
8419
|
const config = readToml(configPath) ?? {};
|
|
8304
8420
|
const servers = config.mcp_servers ?? {};
|
|
8305
8421
|
let anythingChanged = false;
|
|
@@ -8364,8 +8480,8 @@ async function setupCodex() {
|
|
|
8364
8480
|
}
|
|
8365
8481
|
}
|
|
8366
8482
|
function setupHud() {
|
|
8367
|
-
const homeDir2 =
|
|
8368
|
-
const hooksPath =
|
|
8483
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8484
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8369
8485
|
const settings = readJson(hooksPath) ?? {};
|
|
8370
8486
|
const hudCommand = fullPathCommand("hud");
|
|
8371
8487
|
const statusLineObj = { type: "command", command: hudCommand };
|
|
@@ -8391,8 +8507,8 @@ function setupHud() {
|
|
|
8391
8507
|
console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
|
|
8392
8508
|
}
|
|
8393
8509
|
function teardownHud() {
|
|
8394
|
-
const homeDir2 =
|
|
8395
|
-
const hooksPath =
|
|
8510
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8511
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8396
8512
|
const settings = readJson(hooksPath);
|
|
8397
8513
|
if (!settings) {
|
|
8398
8514
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
|
|
@@ -8412,10 +8528,10 @@ function teardownHud() {
|
|
|
8412
8528
|
|
|
8413
8529
|
// src/cli.ts
|
|
8414
8530
|
init_daemon2();
|
|
8415
|
-
var
|
|
8416
|
-
var
|
|
8417
|
-
var
|
|
8418
|
-
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"));
|
|
8419
8535
|
var import_prompts2 = require("@inquirer/prompts");
|
|
8420
8536
|
|
|
8421
8537
|
// src/utils/duration.ts
|
|
@@ -8640,10 +8756,10 @@ async function autoStartDaemonAndWait() {
|
|
|
8640
8756
|
|
|
8641
8757
|
// src/cli/commands/check.ts
|
|
8642
8758
|
var import_chalk5 = __toESM(require("chalk"));
|
|
8643
|
-
var
|
|
8759
|
+
var import_fs19 = __toESM(require("fs"));
|
|
8644
8760
|
var import_child_process9 = require("child_process");
|
|
8645
|
-
var
|
|
8646
|
-
var
|
|
8761
|
+
var import_path21 = __toESM(require("path"));
|
|
8762
|
+
var import_os15 = __toESM(require("os"));
|
|
8647
8763
|
init_orchestrator();
|
|
8648
8764
|
init_daemon();
|
|
8649
8765
|
init_config();
|
|
@@ -8651,12 +8767,12 @@ init_policy();
|
|
|
8651
8767
|
|
|
8652
8768
|
// src/undo.ts
|
|
8653
8769
|
var import_child_process8 = require("child_process");
|
|
8654
|
-
var
|
|
8655
|
-
var
|
|
8770
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
8771
|
+
var import_fs18 = __toESM(require("fs"));
|
|
8656
8772
|
var import_net3 = __toESM(require("net"));
|
|
8657
|
-
var
|
|
8658
|
-
var
|
|
8659
|
-
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");
|
|
8660
8776
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8661
8777
|
try {
|
|
8662
8778
|
const payload = JSON.stringify({
|
|
@@ -8676,22 +8792,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
8676
8792
|
} catch {
|
|
8677
8793
|
}
|
|
8678
8794
|
}
|
|
8679
|
-
var SNAPSHOT_STACK_PATH =
|
|
8680
|
-
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");
|
|
8681
8797
|
var MAX_SNAPSHOTS = 10;
|
|
8682
8798
|
var GIT_TIMEOUT = 15e3;
|
|
8683
8799
|
function readStack() {
|
|
8684
8800
|
try {
|
|
8685
|
-
if (
|
|
8686
|
-
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"));
|
|
8687
8803
|
} catch {
|
|
8688
8804
|
}
|
|
8689
8805
|
return [];
|
|
8690
8806
|
}
|
|
8691
8807
|
function writeStack(stack) {
|
|
8692
|
-
const dir =
|
|
8693
|
-
if (!
|
|
8694
|
-
|
|
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));
|
|
8695
8811
|
}
|
|
8696
8812
|
function extractFilePath(args) {
|
|
8697
8813
|
if (!args || typeof args !== "object") return null;
|
|
@@ -8711,12 +8827,12 @@ function buildArgsSummary(tool, args) {
|
|
|
8711
8827
|
return "";
|
|
8712
8828
|
}
|
|
8713
8829
|
function findProjectRoot(filePath) {
|
|
8714
|
-
let dir =
|
|
8830
|
+
let dir = import_path20.default.dirname(filePath);
|
|
8715
8831
|
while (true) {
|
|
8716
|
-
if (
|
|
8832
|
+
if (import_fs18.default.existsSync(import_path20.default.join(dir, ".git")) || import_fs18.default.existsSync(import_path20.default.join(dir, "package.json"))) {
|
|
8717
8833
|
return dir;
|
|
8718
8834
|
}
|
|
8719
|
-
const parent =
|
|
8835
|
+
const parent = import_path20.default.dirname(dir);
|
|
8720
8836
|
if (parent === dir) return process.cwd();
|
|
8721
8837
|
dir = parent;
|
|
8722
8838
|
}
|
|
@@ -8724,7 +8840,7 @@ function findProjectRoot(filePath) {
|
|
|
8724
8840
|
function normalizeCwdForHash(cwd) {
|
|
8725
8841
|
let normalized;
|
|
8726
8842
|
try {
|
|
8727
|
-
normalized =
|
|
8843
|
+
normalized = import_fs18.default.realpathSync(cwd);
|
|
8728
8844
|
} catch {
|
|
8729
8845
|
normalized = cwd;
|
|
8730
8846
|
}
|
|
@@ -8733,17 +8849,17 @@ function normalizeCwdForHash(cwd) {
|
|
|
8733
8849
|
return normalized;
|
|
8734
8850
|
}
|
|
8735
8851
|
function getShadowRepoDir(cwd) {
|
|
8736
|
-
const hash =
|
|
8737
|
-
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);
|
|
8738
8854
|
}
|
|
8739
8855
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
8740
8856
|
try {
|
|
8741
8857
|
const cutoff = Date.now() - 6e4;
|
|
8742
|
-
for (const f of
|
|
8858
|
+
for (const f of import_fs18.default.readdirSync(shadowDir)) {
|
|
8743
8859
|
if (f.startsWith("index_")) {
|
|
8744
|
-
const fp =
|
|
8860
|
+
const fp = import_path20.default.join(shadowDir, f);
|
|
8745
8861
|
try {
|
|
8746
|
-
if (
|
|
8862
|
+
if (import_fs18.default.statSync(fp).mtimeMs < cutoff) import_fs18.default.unlinkSync(fp);
|
|
8747
8863
|
} catch {
|
|
8748
8864
|
}
|
|
8749
8865
|
}
|
|
@@ -8755,7 +8871,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
8755
8871
|
const hardcoded = [".git", ".node9"];
|
|
8756
8872
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
8757
8873
|
try {
|
|
8758
|
-
|
|
8874
|
+
import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
8759
8875
|
} catch {
|
|
8760
8876
|
}
|
|
8761
8877
|
}
|
|
@@ -8768,25 +8884,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8768
8884
|
timeout: 3e3
|
|
8769
8885
|
});
|
|
8770
8886
|
if (check.status === 0) {
|
|
8771
|
-
const ptPath =
|
|
8887
|
+
const ptPath = import_path20.default.join(shadowDir, "project-path.txt");
|
|
8772
8888
|
try {
|
|
8773
|
-
const stored =
|
|
8889
|
+
const stored = import_fs18.default.readFileSync(ptPath, "utf8").trim();
|
|
8774
8890
|
if (stored === normalizedCwd) return true;
|
|
8775
8891
|
if (process.env.NODE9_DEBUG === "1")
|
|
8776
8892
|
console.error(
|
|
8777
8893
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
8778
8894
|
);
|
|
8779
|
-
|
|
8895
|
+
import_fs18.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
8780
8896
|
} catch {
|
|
8781
8897
|
try {
|
|
8782
|
-
|
|
8898
|
+
import_fs18.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
8783
8899
|
} catch {
|
|
8784
8900
|
}
|
|
8785
8901
|
return true;
|
|
8786
8902
|
}
|
|
8787
8903
|
}
|
|
8788
8904
|
try {
|
|
8789
|
-
|
|
8905
|
+
import_fs18.default.mkdirSync(shadowDir, { recursive: true });
|
|
8790
8906
|
} catch {
|
|
8791
8907
|
}
|
|
8792
8908
|
const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -8795,7 +8911,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8795
8911
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
8796
8912
|
return false;
|
|
8797
8913
|
}
|
|
8798
|
-
const configFile =
|
|
8914
|
+
const configFile = import_path20.default.join(shadowDir, "config");
|
|
8799
8915
|
(0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
8800
8916
|
timeout: 3e3
|
|
8801
8917
|
});
|
|
@@ -8803,7 +8919,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8803
8919
|
timeout: 3e3
|
|
8804
8920
|
});
|
|
8805
8921
|
try {
|
|
8806
|
-
|
|
8922
|
+
import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
8807
8923
|
} catch {
|
|
8808
8924
|
}
|
|
8809
8925
|
return true;
|
|
@@ -8823,12 +8939,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8823
8939
|
let indexFile = null;
|
|
8824
8940
|
try {
|
|
8825
8941
|
const rawFilePath = extractFilePath(args);
|
|
8826
|
-
const absFilePath = rawFilePath &&
|
|
8942
|
+
const absFilePath = rawFilePath && import_path20.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
8827
8943
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
8828
8944
|
const shadowDir = getShadowRepoDir(cwd);
|
|
8829
8945
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
8830
8946
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
8831
|
-
indexFile =
|
|
8947
|
+
indexFile = import_path20.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
8832
8948
|
const shadowEnv = {
|
|
8833
8949
|
...process.env,
|
|
8834
8950
|
GIT_DIR: shadowDir,
|
|
@@ -8900,7 +9016,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8900
9016
|
writeStack(stack);
|
|
8901
9017
|
const entry = stack[stack.length - 1];
|
|
8902
9018
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
8903
|
-
|
|
9019
|
+
import_fs18.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
8904
9020
|
if (shouldGc) {
|
|
8905
9021
|
(0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
8906
9022
|
}
|
|
@@ -8911,7 +9027,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8911
9027
|
} finally {
|
|
8912
9028
|
if (indexFile) {
|
|
8913
9029
|
try {
|
|
8914
|
-
|
|
9030
|
+
import_fs18.default.unlinkSync(indexFile);
|
|
8915
9031
|
} catch {
|
|
8916
9032
|
}
|
|
8917
9033
|
}
|
|
@@ -8987,9 +9103,9 @@ function applyUndo(hash, cwd) {
|
|
|
8987
9103
|
timeout: GIT_TIMEOUT
|
|
8988
9104
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8989
9105
|
for (const file of [...tracked, ...untracked]) {
|
|
8990
|
-
const fullPath =
|
|
8991
|
-
if (!snapshotFiles.has(file) &&
|
|
8992
|
-
|
|
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);
|
|
8993
9109
|
}
|
|
8994
9110
|
}
|
|
8995
9111
|
return true;
|
|
@@ -9013,9 +9129,9 @@ function registerCheckCommand(program2) {
|
|
|
9013
9129
|
} catch (err2) {
|
|
9014
9130
|
const tempConfig = getConfig();
|
|
9015
9131
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
9016
|
-
const logPath =
|
|
9132
|
+
const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
9017
9133
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9018
|
-
|
|
9134
|
+
import_fs19.default.appendFileSync(
|
|
9019
9135
|
logPath,
|
|
9020
9136
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
9021
9137
|
RAW: ${raw}
|
|
@@ -9028,10 +9144,10 @@ RAW: ${raw}
|
|
|
9028
9144
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9029
9145
|
try {
|
|
9030
9146
|
const scriptPath = process.argv[1];
|
|
9031
|
-
if (typeof scriptPath !== "string" || !
|
|
9147
|
+
if (typeof scriptPath !== "string" || !import_path21.default.isAbsolute(scriptPath))
|
|
9032
9148
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
9033
|
-
const resolvedScript =
|
|
9034
|
-
const expectedCli =
|
|
9149
|
+
const resolvedScript = import_fs19.default.realpathSync(scriptPath);
|
|
9150
|
+
const expectedCli = import_fs19.default.realpathSync(import_path21.default.resolve(__dirname, "../../cli.js"));
|
|
9035
9151
|
if (resolvedScript !== expectedCli)
|
|
9036
9152
|
throw new Error(
|
|
9037
9153
|
"node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
|
|
@@ -9057,10 +9173,10 @@ RAW: ${raw}
|
|
|
9057
9173
|
}
|
|
9058
9174
|
}
|
|
9059
9175
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9060
|
-
const logPath =
|
|
9061
|
-
if (!
|
|
9062
|
-
|
|
9063
|
-
|
|
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}
|
|
9064
9180
|
`);
|
|
9065
9181
|
}
|
|
9066
9182
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -9073,8 +9189,8 @@ RAW: ${raw}
|
|
|
9073
9189
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
9074
9190
|
let ttyFd = null;
|
|
9075
9191
|
try {
|
|
9076
|
-
ttyFd =
|
|
9077
|
-
const writeTty = (line) =>
|
|
9192
|
+
ttyFd = import_fs19.default.openSync("/dev/tty", "w");
|
|
9193
|
+
const writeTty = (line) => import_fs19.default.writeSync(ttyFd, line + "\n");
|
|
9078
9194
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
9079
9195
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
9080
9196
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -9093,7 +9209,7 @@ RAW: ${raw}
|
|
|
9093
9209
|
} finally {
|
|
9094
9210
|
if (ttyFd !== null)
|
|
9095
9211
|
try {
|
|
9096
|
-
|
|
9212
|
+
import_fs19.default.closeSync(ttyFd);
|
|
9097
9213
|
} catch {
|
|
9098
9214
|
}
|
|
9099
9215
|
}
|
|
@@ -9125,7 +9241,7 @@ RAW: ${raw}
|
|
|
9125
9241
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
9126
9242
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
9127
9243
|
}
|
|
9128
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
9244
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9129
9245
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
9130
9246
|
cwd: safeCwdForAuth
|
|
9131
9247
|
});
|
|
@@ -9137,12 +9253,12 @@ RAW: ${raw}
|
|
|
9137
9253
|
}
|
|
9138
9254
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
9139
9255
|
try {
|
|
9140
|
-
const tty =
|
|
9141
|
-
|
|
9256
|
+
const tty = import_fs19.default.openSync("/dev/tty", "w");
|
|
9257
|
+
import_fs19.default.writeSync(
|
|
9142
9258
|
tty,
|
|
9143
9259
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
9144
9260
|
);
|
|
9145
|
-
|
|
9261
|
+
import_fs19.default.closeSync(tty);
|
|
9146
9262
|
} catch {
|
|
9147
9263
|
}
|
|
9148
9264
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -9169,9 +9285,9 @@ RAW: ${raw}
|
|
|
9169
9285
|
});
|
|
9170
9286
|
} catch (err2) {
|
|
9171
9287
|
if (process.env.NODE9_DEBUG === "1") {
|
|
9172
|
-
const logPath =
|
|
9288
|
+
const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
9173
9289
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9174
|
-
|
|
9290
|
+
import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
9175
9291
|
`);
|
|
9176
9292
|
}
|
|
9177
9293
|
process.exit(0);
|
|
@@ -9205,9 +9321,9 @@ RAW: ${raw}
|
|
|
9205
9321
|
}
|
|
9206
9322
|
|
|
9207
9323
|
// src/cli/commands/log.ts
|
|
9208
|
-
var
|
|
9209
|
-
var
|
|
9210
|
-
var
|
|
9324
|
+
var import_fs20 = __toESM(require("fs"));
|
|
9325
|
+
var import_path22 = __toESM(require("path"));
|
|
9326
|
+
var import_os16 = __toESM(require("os"));
|
|
9211
9327
|
init_audit();
|
|
9212
9328
|
init_config();
|
|
9213
9329
|
init_policy();
|
|
@@ -9251,9 +9367,9 @@ function containsShellMetachar(token) {
|
|
|
9251
9367
|
}
|
|
9252
9368
|
|
|
9253
9369
|
// src/cli/commands/log.ts
|
|
9254
|
-
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;
|
|
9255
9371
|
function detectTestResult(command, output) {
|
|
9256
|
-
if (!
|
|
9372
|
+
if (!TEST_COMMAND_RE2.test(command)) return null;
|
|
9257
9373
|
const out = output.toLowerCase();
|
|
9258
9374
|
if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
|
|
9259
9375
|
out
|
|
@@ -9283,10 +9399,10 @@ function registerLogCommand(program2) {
|
|
|
9283
9399
|
decision: "allowed",
|
|
9284
9400
|
source: "post-hook"
|
|
9285
9401
|
};
|
|
9286
|
-
const logPath =
|
|
9287
|
-
if (!
|
|
9288
|
-
|
|
9289
|
-
|
|
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");
|
|
9290
9406
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
9291
9407
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
9292
9408
|
if (command) {
|
|
@@ -9302,16 +9418,24 @@ function registerLogCommand(program2) {
|
|
|
9302
9418
|
if (bashCommand && output) {
|
|
9303
9419
|
const testResult = detectTestResult(bashCommand, output);
|
|
9304
9420
|
if (testResult) {
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
ts: Date.now(),
|
|
9421
|
+
appendToLog(LOCAL_AUDIT_LOG, {
|
|
9422
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9308
9423
|
tool,
|
|
9309
|
-
|
|
9424
|
+
testResult,
|
|
9425
|
+
source: "test-result"
|
|
9310
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
|
+
}
|
|
9311
9435
|
}
|
|
9312
9436
|
}
|
|
9313
9437
|
}
|
|
9314
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
9438
|
+
const safeCwd = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9315
9439
|
const config = getConfig(safeCwd);
|
|
9316
9440
|
if (shouldSnapshot(tool, {}, config)) {
|
|
9317
9441
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -9320,9 +9444,9 @@ function registerLogCommand(program2) {
|
|
|
9320
9444
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
9321
9445
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
9322
9446
|
`);
|
|
9323
|
-
const debugPath =
|
|
9447
|
+
const debugPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
|
|
9324
9448
|
try {
|
|
9325
|
-
|
|
9449
|
+
import_fs20.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
9326
9450
|
`);
|
|
9327
9451
|
} catch {
|
|
9328
9452
|
}
|
|
@@ -9722,14 +9846,14 @@ function registerConfigShowCommand(program2) {
|
|
|
9722
9846
|
|
|
9723
9847
|
// src/cli/commands/doctor.ts
|
|
9724
9848
|
var import_chalk7 = __toESM(require("chalk"));
|
|
9725
|
-
var
|
|
9726
|
-
var
|
|
9727
|
-
var
|
|
9849
|
+
var import_fs21 = __toESM(require("fs"));
|
|
9850
|
+
var import_path23 = __toESM(require("path"));
|
|
9851
|
+
var import_os17 = __toESM(require("os"));
|
|
9728
9852
|
var import_child_process10 = require("child_process");
|
|
9729
9853
|
init_daemon();
|
|
9730
9854
|
function registerDoctorCommand(program2, version2) {
|
|
9731
9855
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
9732
|
-
const homeDir2 =
|
|
9856
|
+
const homeDir2 = import_os17.default.homedir();
|
|
9733
9857
|
let failures = 0;
|
|
9734
9858
|
function pass(msg) {
|
|
9735
9859
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -9778,10 +9902,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9778
9902
|
);
|
|
9779
9903
|
}
|
|
9780
9904
|
section("Configuration");
|
|
9781
|
-
const globalConfigPath =
|
|
9782
|
-
if (
|
|
9905
|
+
const globalConfigPath = import_path23.default.join(homeDir2, ".node9", "config.json");
|
|
9906
|
+
if (import_fs21.default.existsSync(globalConfigPath)) {
|
|
9783
9907
|
try {
|
|
9784
|
-
JSON.parse(
|
|
9908
|
+
JSON.parse(import_fs21.default.readFileSync(globalConfigPath, "utf-8"));
|
|
9785
9909
|
pass("~/.node9/config.json found and valid");
|
|
9786
9910
|
} catch {
|
|
9787
9911
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -9789,10 +9913,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9789
9913
|
} else {
|
|
9790
9914
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
9791
9915
|
}
|
|
9792
|
-
const projectConfigPath =
|
|
9793
|
-
if (
|
|
9916
|
+
const projectConfigPath = import_path23.default.join(process.cwd(), "node9.config.json");
|
|
9917
|
+
if (import_fs21.default.existsSync(projectConfigPath)) {
|
|
9794
9918
|
try {
|
|
9795
|
-
JSON.parse(
|
|
9919
|
+
JSON.parse(import_fs21.default.readFileSync(projectConfigPath, "utf-8"));
|
|
9796
9920
|
pass("node9.config.json found and valid (project)");
|
|
9797
9921
|
} catch {
|
|
9798
9922
|
fail(
|
|
@@ -9801,8 +9925,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9801
9925
|
);
|
|
9802
9926
|
}
|
|
9803
9927
|
}
|
|
9804
|
-
const credsPath =
|
|
9805
|
-
if (
|
|
9928
|
+
const credsPath = import_path23.default.join(homeDir2, ".node9", "credentials.json");
|
|
9929
|
+
if (import_fs21.default.existsSync(credsPath)) {
|
|
9806
9930
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
9807
9931
|
} else {
|
|
9808
9932
|
warn(
|
|
@@ -9811,10 +9935,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9811
9935
|
);
|
|
9812
9936
|
}
|
|
9813
9937
|
section("Agent Hooks");
|
|
9814
|
-
const claudeSettingsPath =
|
|
9815
|
-
if (
|
|
9938
|
+
const claudeSettingsPath = import_path23.default.join(homeDir2, ".claude", "settings.json");
|
|
9939
|
+
if (import_fs21.default.existsSync(claudeSettingsPath)) {
|
|
9816
9940
|
try {
|
|
9817
|
-
const cs = JSON.parse(
|
|
9941
|
+
const cs = JSON.parse(import_fs21.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
9818
9942
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
9819
9943
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9820
9944
|
);
|
|
@@ -9830,10 +9954,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9830
9954
|
} else {
|
|
9831
9955
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
9832
9956
|
}
|
|
9833
|
-
const geminiSettingsPath =
|
|
9834
|
-
if (
|
|
9957
|
+
const geminiSettingsPath = import_path23.default.join(homeDir2, ".gemini", "settings.json");
|
|
9958
|
+
if (import_fs21.default.existsSync(geminiSettingsPath)) {
|
|
9835
9959
|
try {
|
|
9836
|
-
const gs = JSON.parse(
|
|
9960
|
+
const gs = JSON.parse(import_fs21.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
9837
9961
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
9838
9962
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9839
9963
|
);
|
|
@@ -9849,10 +9973,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9849
9973
|
} else {
|
|
9850
9974
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
9851
9975
|
}
|
|
9852
|
-
const cursorHooksPath =
|
|
9853
|
-
if (
|
|
9976
|
+
const cursorHooksPath = import_path23.default.join(homeDir2, ".cursor", "hooks.json");
|
|
9977
|
+
if (import_fs21.default.existsSync(cursorHooksPath)) {
|
|
9854
9978
|
try {
|
|
9855
|
-
const cur = JSON.parse(
|
|
9979
|
+
const cur = JSON.parse(import_fs21.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
9856
9980
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
9857
9981
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
9858
9982
|
);
|
|
@@ -9890,9 +10014,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9890
10014
|
|
|
9891
10015
|
// src/cli/commands/audit.ts
|
|
9892
10016
|
var import_chalk8 = __toESM(require("chalk"));
|
|
9893
|
-
var
|
|
9894
|
-
var
|
|
9895
|
-
var
|
|
10017
|
+
var import_fs22 = __toESM(require("fs"));
|
|
10018
|
+
var import_path24 = __toESM(require("path"));
|
|
10019
|
+
var import_os18 = __toESM(require("os"));
|
|
9896
10020
|
function formatRelativeTime(timestamp) {
|
|
9897
10021
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
9898
10022
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -9905,14 +10029,14 @@ function formatRelativeTime(timestamp) {
|
|
|
9905
10029
|
}
|
|
9906
10030
|
function registerAuditCommand(program2) {
|
|
9907
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) => {
|
|
9908
|
-
const logPath =
|
|
9909
|
-
if (!
|
|
10032
|
+
const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "audit.log");
|
|
10033
|
+
if (!import_fs22.default.existsSync(logPath)) {
|
|
9910
10034
|
console.log(
|
|
9911
10035
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
9912
10036
|
);
|
|
9913
10037
|
return;
|
|
9914
10038
|
}
|
|
9915
|
-
const raw =
|
|
10039
|
+
const raw = import_fs22.default.readFileSync(logPath, "utf-8");
|
|
9916
10040
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
9917
10041
|
let entries = lines.flatMap((line) => {
|
|
9918
10042
|
try {
|
|
@@ -9964,8 +10088,396 @@ function registerAuditCommand(program2) {
|
|
|
9964
10088
|
});
|
|
9965
10089
|
}
|
|
9966
10090
|
|
|
9967
|
-
// src/cli/commands/
|
|
10091
|
+
// src/cli/commands/report.ts
|
|
9968
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"));
|
|
9969
10481
|
var import_child_process11 = require("child_process");
|
|
9970
10482
|
init_daemon2();
|
|
9971
10483
|
init_daemon();
|
|
@@ -9980,7 +10492,7 @@ function registerDaemonCommand(program2) {
|
|
|
9980
10492
|
if (cmd === "status") return daemonStatus();
|
|
9981
10493
|
if (cmd !== "start" && action !== void 0) {
|
|
9982
10494
|
console.error(
|
|
9983
|
-
|
|
10495
|
+
import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
|
|
9984
10496
|
);
|
|
9985
10497
|
process.exit(1);
|
|
9986
10498
|
}
|
|
@@ -9988,7 +10500,7 @@ function registerDaemonCommand(program2) {
|
|
|
9988
10500
|
process.env.NODE9_WATCH_MODE = "1";
|
|
9989
10501
|
setTimeout(() => {
|
|
9990
10502
|
openBrowserLocal();
|
|
9991
|
-
console.log(
|
|
10503
|
+
console.log(import_chalk10.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
9992
10504
|
}, 600);
|
|
9993
10505
|
startDaemon();
|
|
9994
10506
|
return;
|
|
@@ -9996,7 +10508,7 @@ function registerDaemonCommand(program2) {
|
|
|
9996
10508
|
if (options.openui) {
|
|
9997
10509
|
if (isDaemonRunning()) {
|
|
9998
10510
|
openBrowserLocal();
|
|
9999
|
-
console.log(
|
|
10511
|
+
console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
10000
10512
|
process.exit(0);
|
|
10001
10513
|
}
|
|
10002
10514
|
const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
@@ -10009,7 +10521,7 @@ function registerDaemonCommand(program2) {
|
|
|
10009
10521
|
if (isDaemonRunning()) break;
|
|
10010
10522
|
}
|
|
10011
10523
|
openBrowserLocal();
|
|
10012
|
-
console.log(
|
|
10524
|
+
console.log(import_chalk10.default.green(`
|
|
10013
10525
|
\u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
|
|
10014
10526
|
process.exit(0);
|
|
10015
10527
|
}
|
|
@@ -10019,7 +10531,7 @@ function registerDaemonCommand(program2) {
|
|
|
10019
10531
|
stdio: "ignore"
|
|
10020
10532
|
});
|
|
10021
10533
|
child.unref();
|
|
10022
|
-
console.log(
|
|
10534
|
+
console.log(import_chalk10.default.green(`
|
|
10023
10535
|
\u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
|
|
10024
10536
|
process.exit(0);
|
|
10025
10537
|
}
|
|
@@ -10029,15 +10541,15 @@ function registerDaemonCommand(program2) {
|
|
|
10029
10541
|
}
|
|
10030
10542
|
|
|
10031
10543
|
// src/cli/commands/status.ts
|
|
10032
|
-
var
|
|
10033
|
-
var
|
|
10034
|
-
var
|
|
10035
|
-
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"));
|
|
10036
10548
|
init_core();
|
|
10037
10549
|
init_daemon();
|
|
10038
10550
|
function readJson2(filePath) {
|
|
10039
10551
|
try {
|
|
10040
|
-
if (
|
|
10552
|
+
if (import_fs24.default.existsSync(filePath)) return JSON.parse(import_fs24.default.readFileSync(filePath, "utf-8"));
|
|
10041
10553
|
} catch {
|
|
10042
10554
|
}
|
|
10043
10555
|
return null;
|
|
@@ -10051,21 +10563,21 @@ function wrappedMcpServers(servers) {
|
|
|
10051
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(" ")}`);
|
|
10052
10564
|
}
|
|
10053
10565
|
function printAgentSection(label, hookPairs, wrapped) {
|
|
10054
|
-
console.log(
|
|
10566
|
+
console.log(import_chalk11.default.bold(` ${label}`));
|
|
10055
10567
|
for (const { name, present } of hookPairs) {
|
|
10056
10568
|
if (present) {
|
|
10057
|
-
console.log(
|
|
10569
|
+
console.log(import_chalk11.default.green(` \u2713 ${name}`));
|
|
10058
10570
|
} else {
|
|
10059
|
-
console.log(
|
|
10571
|
+
console.log(import_chalk11.default.red(` \u2717 ${name}`) + import_chalk11.default.gray(" (not wired)"));
|
|
10060
10572
|
}
|
|
10061
10573
|
}
|
|
10062
10574
|
if (wrapped.length > 0) {
|
|
10063
|
-
console.log(
|
|
10575
|
+
console.log(import_chalk11.default.cyan(` MCP proxied:`));
|
|
10064
10576
|
for (const entry of wrapped) {
|
|
10065
|
-
console.log(
|
|
10577
|
+
console.log(import_chalk11.default.gray(` \u2022 ${entry}`));
|
|
10066
10578
|
}
|
|
10067
10579
|
} else {
|
|
10068
|
-
console.log(
|
|
10580
|
+
console.log(import_chalk11.default.gray(` MCP proxied: none`));
|
|
10069
10581
|
}
|
|
10070
10582
|
}
|
|
10071
10583
|
function registerStatusCommand(program2) {
|
|
@@ -10076,58 +10588,58 @@ function registerStatusCommand(program2) {
|
|
|
10076
10588
|
const settings = mergedConfig.settings;
|
|
10077
10589
|
console.log("");
|
|
10078
10590
|
if (creds && settings.approvers.cloud) {
|
|
10079
|
-
console.log(
|
|
10591
|
+
console.log(import_chalk11.default.green(" \u25CF Agent mode") + import_chalk11.default.gray(" \u2014 cloud team policy enforced"));
|
|
10080
10592
|
} else if (creds && !settings.approvers.cloud) {
|
|
10081
10593
|
console.log(
|
|
10082
|
-
|
|
10594
|
+
import_chalk11.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 all decisions stay on this machine")
|
|
10083
10595
|
);
|
|
10084
10596
|
} else {
|
|
10085
10597
|
console.log(
|
|
10086
|
-
|
|
10598
|
+
import_chalk11.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 no API key (Local rules only)")
|
|
10087
10599
|
);
|
|
10088
10600
|
}
|
|
10089
10601
|
console.log("");
|
|
10090
10602
|
if (daemonRunning) {
|
|
10091
10603
|
console.log(
|
|
10092
|
-
|
|
10604
|
+
import_chalk11.default.green(" \u25CF Daemon running") + import_chalk11.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
|
|
10093
10605
|
);
|
|
10094
10606
|
} else {
|
|
10095
|
-
console.log(
|
|
10607
|
+
console.log(import_chalk11.default.gray(" \u25CB Daemon stopped"));
|
|
10096
10608
|
}
|
|
10097
10609
|
if (settings.enableUndo) {
|
|
10098
10610
|
console.log(
|
|
10099
|
-
|
|
10611
|
+
import_chalk11.default.magenta(" \u25CF Undo Engine") + import_chalk11.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
|
|
10100
10612
|
);
|
|
10101
10613
|
}
|
|
10102
10614
|
console.log("");
|
|
10103
|
-
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");
|
|
10104
10616
|
console.log(` Mode: ${modeLabel}`);
|
|
10105
|
-
const projectConfig =
|
|
10106
|
-
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");
|
|
10107
10619
|
console.log(
|
|
10108
|
-
` Local: ${
|
|
10620
|
+
` Local: ${import_fs24.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10109
10621
|
);
|
|
10110
10622
|
console.log(
|
|
10111
|
-
` Global: ${
|
|
10623
|
+
` Global: ${import_fs24.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10112
10624
|
);
|
|
10113
10625
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
10114
10626
|
console.log(
|
|
10115
|
-
` Sandbox: ${
|
|
10627
|
+
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
10116
10628
|
);
|
|
10117
10629
|
}
|
|
10118
|
-
const homeDir2 =
|
|
10630
|
+
const homeDir2 = import_os20.default.homedir();
|
|
10119
10631
|
const claudeSettings = readJson2(
|
|
10120
|
-
|
|
10632
|
+
import_path26.default.join(homeDir2, ".claude", "settings.json")
|
|
10121
10633
|
);
|
|
10122
|
-
const claudeConfig = readJson2(
|
|
10634
|
+
const claudeConfig = readJson2(import_path26.default.join(homeDir2, ".claude.json"));
|
|
10123
10635
|
const geminiSettings = readJson2(
|
|
10124
|
-
|
|
10636
|
+
import_path26.default.join(homeDir2, ".gemini", "settings.json")
|
|
10125
10637
|
);
|
|
10126
|
-
const cursorConfig = readJson2(
|
|
10638
|
+
const cursorConfig = readJson2(import_path26.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
10127
10639
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
10128
10640
|
if (agentFound) {
|
|
10129
10641
|
console.log("");
|
|
10130
|
-
console.log(
|
|
10642
|
+
console.log(import_chalk11.default.bold(" Agent Wiring:"));
|
|
10131
10643
|
console.log("");
|
|
10132
10644
|
if (claudeSettings || claudeConfig) {
|
|
10133
10645
|
const preHook = claudeSettings?.hooks?.PreToolUse?.some(
|
|
@@ -10173,7 +10685,7 @@ function registerStatusCommand(program2) {
|
|
|
10173
10685
|
const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
|
|
10174
10686
|
console.log("");
|
|
10175
10687
|
console.log(
|
|
10176
|
-
|
|
10688
|
+
import_chalk11.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk11.default.gray(" \u2014 all tool calls allowed")
|
|
10177
10689
|
);
|
|
10178
10690
|
}
|
|
10179
10691
|
console.log("");
|
|
@@ -10181,10 +10693,10 @@ function registerStatusCommand(program2) {
|
|
|
10181
10693
|
}
|
|
10182
10694
|
|
|
10183
10695
|
// src/cli/commands/init.ts
|
|
10184
|
-
var
|
|
10185
|
-
var
|
|
10186
|
-
var
|
|
10187
|
-
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"));
|
|
10188
10700
|
var import_https2 = __toESM(require("https"));
|
|
10189
10701
|
init_core();
|
|
10190
10702
|
init_shields();
|
|
@@ -10220,7 +10732,7 @@ function fireTelemetryPing(agents) {
|
|
|
10220
10732
|
}
|
|
10221
10733
|
function registerInitCommand(program2) {
|
|
10222
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) => {
|
|
10223
|
-
console.log(
|
|
10735
|
+
console.log(import_chalk12.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
10224
10736
|
let chosenMode = options.mode.toLowerCase();
|
|
10225
10737
|
if (!["standard", "strict", "audit"].includes(chosenMode)) {
|
|
10226
10738
|
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
@@ -10239,37 +10751,37 @@ function registerInitCommand(program2) {
|
|
|
10239
10751
|
const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
|
|
10240
10752
|
if (hasNewShields) writeActiveShields(merged);
|
|
10241
10753
|
} catch (err2) {
|
|
10242
|
-
console.log(
|
|
10754
|
+
console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
|
|
10243
10755
|
}
|
|
10244
10756
|
}
|
|
10245
10757
|
console.log("");
|
|
10246
10758
|
}
|
|
10247
|
-
const configPath =
|
|
10248
|
-
if (
|
|
10759
|
+
const configPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
|
|
10760
|
+
if (import_fs25.default.existsSync(configPath) && !options.force) {
|
|
10249
10761
|
try {
|
|
10250
|
-
const existing = JSON.parse(
|
|
10762
|
+
const existing = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
10251
10763
|
const settings = existing.settings ?? {};
|
|
10252
10764
|
if (settings.mode !== chosenMode) {
|
|
10253
10765
|
settings.mode = chosenMode;
|
|
10254
10766
|
existing.settings = settings;
|
|
10255
|
-
|
|
10256
|
-
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}`));
|
|
10257
10769
|
} else {
|
|
10258
|
-
console.log(
|
|
10770
|
+
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
10259
10771
|
}
|
|
10260
10772
|
} catch {
|
|
10261
|
-
console.log(
|
|
10773
|
+
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
10262
10774
|
}
|
|
10263
10775
|
} else {
|
|
10264
10776
|
const configToSave = {
|
|
10265
10777
|
...DEFAULT_CONFIG,
|
|
10266
10778
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
10267
10779
|
};
|
|
10268
|
-
const dir =
|
|
10269
|
-
if (!
|
|
10270
|
-
|
|
10271
|
-
console.log(
|
|
10272
|
-
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}`));
|
|
10273
10785
|
}
|
|
10274
10786
|
if (options.skipSetup) return;
|
|
10275
10787
|
console.log("");
|
|
@@ -10279,18 +10791,18 @@ function registerInitCommand(program2) {
|
|
|
10279
10791
|
);
|
|
10280
10792
|
if (found.length === 0) {
|
|
10281
10793
|
console.log(
|
|
10282
|
-
|
|
10794
|
+
import_chalk12.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
|
|
10283
10795
|
);
|
|
10284
|
-
console.log(
|
|
10796
|
+
console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
|
|
10285
10797
|
return;
|
|
10286
10798
|
}
|
|
10287
|
-
console.log(
|
|
10799
|
+
console.log(import_chalk12.default.bold("Detected agents:"));
|
|
10288
10800
|
for (const agent of found) {
|
|
10289
|
-
console.log(
|
|
10801
|
+
console.log(import_chalk12.default.green(` \u2713 ${agent}`));
|
|
10290
10802
|
}
|
|
10291
10803
|
console.log("");
|
|
10292
10804
|
for (const agent of found) {
|
|
10293
|
-
console.log(
|
|
10805
|
+
console.log(import_chalk12.default.bold(`Wiring ${agent}...`));
|
|
10294
10806
|
if (agent === "claude") await setupClaude();
|
|
10295
10807
|
else if (agent === "gemini") await setupGemini();
|
|
10296
10808
|
else if (agent === "cursor") await setupCursor();
|
|
@@ -10307,26 +10819,26 @@ function registerInitCommand(program2) {
|
|
|
10307
10819
|
console.log("");
|
|
10308
10820
|
}
|
|
10309
10821
|
const agentList = found.join(", ");
|
|
10310
|
-
console.log(
|
|
10822
|
+
console.log(import_chalk12.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
10311
10823
|
console.log("");
|
|
10312
|
-
console.log(
|
|
10313
|
-
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"));
|
|
10314
10826
|
console.log("");
|
|
10315
|
-
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"));
|
|
10316
10828
|
console.log(
|
|
10317
|
-
|
|
10829
|
+
import_chalk12.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk12.default.cyan.bold("https://node9.ai")
|
|
10318
10830
|
);
|
|
10319
|
-
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"));
|
|
10320
10832
|
});
|
|
10321
10833
|
}
|
|
10322
10834
|
|
|
10323
10835
|
// src/cli/commands/undo.ts
|
|
10324
|
-
var
|
|
10325
|
-
var
|
|
10836
|
+
var import_path28 = __toESM(require("path"));
|
|
10837
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
10326
10838
|
|
|
10327
10839
|
// src/tui/undo-navigator.ts
|
|
10328
10840
|
var import_readline2 = __toESM(require("readline"));
|
|
10329
|
-
var
|
|
10841
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
10330
10842
|
var RESET = "\x1B[0m";
|
|
10331
10843
|
var BOLD = "\x1B[1m";
|
|
10332
10844
|
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
@@ -10344,15 +10856,15 @@ function renderDiff(raw) {
|
|
|
10344
10856
|
);
|
|
10345
10857
|
for (const line of lines) {
|
|
10346
10858
|
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
10347
|
-
process.stdout.write(
|
|
10859
|
+
process.stdout.write(import_chalk13.default.bold(line) + "\n");
|
|
10348
10860
|
} else if (line.startsWith("+")) {
|
|
10349
|
-
process.stdout.write(
|
|
10861
|
+
process.stdout.write(import_chalk13.default.green(line) + "\n");
|
|
10350
10862
|
} else if (line.startsWith("-")) {
|
|
10351
|
-
process.stdout.write(
|
|
10863
|
+
process.stdout.write(import_chalk13.default.red(line) + "\n");
|
|
10352
10864
|
} else if (line.startsWith("@@")) {
|
|
10353
|
-
process.stdout.write(
|
|
10865
|
+
process.stdout.write(import_chalk13.default.cyan(line) + "\n");
|
|
10354
10866
|
} else {
|
|
10355
|
-
process.stdout.write(
|
|
10867
|
+
process.stdout.write(import_chalk13.default.gray(line) + "\n");
|
|
10356
10868
|
}
|
|
10357
10869
|
}
|
|
10358
10870
|
}
|
|
@@ -10371,23 +10883,23 @@ function render(entries, idx) {
|
|
|
10371
10883
|
const step = idx + 1;
|
|
10372
10884
|
process.stdout.write(CLEAR_SCREEN);
|
|
10373
10885
|
process.stdout.write(
|
|
10374
|
-
|
|
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(
|
|
10375
10887
|
` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
|
|
10376
10888
|
) : "") + "\n\n"
|
|
10377
10889
|
);
|
|
10378
10890
|
process.stdout.write(
|
|
10379
|
-
` ${BOLD}Tool:${RESET} ${
|
|
10891
|
+
` ${BOLD}Tool:${RESET} ${import_chalk13.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk13.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
|
|
10380
10892
|
);
|
|
10381
|
-
process.stdout.write(` ${BOLD}When:${RESET} ${
|
|
10893
|
+
process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk13.default.gray(formatAge(entry.timestamp))}
|
|
10382
10894
|
`);
|
|
10383
|
-
process.stdout.write(` ${BOLD}Dir: ${RESET} ${
|
|
10895
|
+
process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk13.default.gray(entry.cwd)}
|
|
10384
10896
|
`);
|
|
10385
10897
|
if (entry.files && entry.files.length > 0) {
|
|
10386
|
-
process.stdout.write(` ${BOLD}Files:${RESET} ${
|
|
10898
|
+
process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk13.default.gray(entry.files.join(", "))}
|
|
10387
10899
|
`);
|
|
10388
10900
|
}
|
|
10389
10901
|
if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
|
|
10390
|
-
process.stdout.write(
|
|
10902
|
+
process.stdout.write(import_chalk13.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
|
|
10391
10903
|
}
|
|
10392
10904
|
process.stdout.write("\n");
|
|
10393
10905
|
const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
|
|
@@ -10395,12 +10907,12 @@ function render(entries, idx) {
|
|
|
10395
10907
|
renderDiff(diff);
|
|
10396
10908
|
} else {
|
|
10397
10909
|
process.stdout.write(
|
|
10398
|
-
|
|
10910
|
+
import_chalk13.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
|
|
10399
10911
|
);
|
|
10400
10912
|
}
|
|
10401
10913
|
process.stdout.write("\n");
|
|
10402
10914
|
process.stdout.write(
|
|
10403
|
-
|
|
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"
|
|
10404
10916
|
);
|
|
10405
10917
|
}
|
|
10406
10918
|
async function runUndoNavigator(entries) {
|
|
@@ -10454,19 +10966,19 @@ async function runUndoNavigator(entries) {
|
|
|
10454
10966
|
cleanup();
|
|
10455
10967
|
process.stdout.write(CLEAR_SCREEN);
|
|
10456
10968
|
const entry = display[idx];
|
|
10457
|
-
process.stdout.write(
|
|
10969
|
+
process.stdout.write(import_chalk13.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
|
|
10458
10970
|
if (applyUndo(entry.hash, entry.cwd)) {
|
|
10459
|
-
process.stdout.write(
|
|
10971
|
+
process.stdout.write(import_chalk13.default.green("\u2705 Reverted successfully.\n\n"));
|
|
10460
10972
|
resolve({ restored: true });
|
|
10461
10973
|
} else {
|
|
10462
|
-
process.stdout.write(
|
|
10974
|
+
process.stdout.write(import_chalk13.default.red("\u274C Undo failed.\n\n"));
|
|
10463
10975
|
resolve({ restored: false });
|
|
10464
10976
|
}
|
|
10465
10977
|
} else if (name === "q" || key?.ctrl && name === "c") {
|
|
10466
10978
|
done = true;
|
|
10467
10979
|
cleanup();
|
|
10468
10980
|
process.stdout.write(CLEAR_SCREEN);
|
|
10469
|
-
process.stdout.write(
|
|
10981
|
+
process.stdout.write(import_chalk13.default.gray("\nCancelled.\n\n"));
|
|
10470
10982
|
resolve({ restored: false });
|
|
10471
10983
|
}
|
|
10472
10984
|
};
|
|
@@ -10480,7 +10992,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
10480
10992
|
let dir = startDir;
|
|
10481
10993
|
while (true) {
|
|
10482
10994
|
if (cwds.has(dir)) return dir;
|
|
10483
|
-
const parent =
|
|
10995
|
+
const parent = import_path28.default.dirname(dir);
|
|
10484
10996
|
if (parent === dir) return null;
|
|
10485
10997
|
dir = parent;
|
|
10486
10998
|
}
|
|
@@ -10502,39 +11014,39 @@ function registerUndoCommand(program2) {
|
|
|
10502
11014
|
if (history.length === 0) {
|
|
10503
11015
|
if (!options.all && allHistory.length > 0) {
|
|
10504
11016
|
console.log(
|
|
10505
|
-
|
|
11017
|
+
import_chalk14.default.yellow(
|
|
10506
11018
|
`
|
|
10507
11019
|
\u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
|
|
10508
|
-
Run ${
|
|
11020
|
+
Run ${import_chalk14.default.cyan("node9 undo --all")} to see snapshots from all projects.
|
|
10509
11021
|
`
|
|
10510
11022
|
)
|
|
10511
11023
|
);
|
|
10512
11024
|
} else {
|
|
10513
|
-
console.log(
|
|
11025
|
+
console.log(import_chalk14.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
|
|
10514
11026
|
}
|
|
10515
11027
|
return;
|
|
10516
11028
|
}
|
|
10517
11029
|
if (options.list) {
|
|
10518
|
-
console.log(
|
|
11030
|
+
console.log(import_chalk14.default.magenta.bold("\n\u23EA Snapshot History\n"));
|
|
10519
11031
|
console.log(
|
|
10520
|
-
|
|
11032
|
+
import_chalk14.default.gray(
|
|
10521
11033
|
` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
|
|
10522
11034
|
)
|
|
10523
11035
|
);
|
|
10524
|
-
console.log(
|
|
11036
|
+
console.log(import_chalk14.default.gray(" " + "\u2500".repeat(80)));
|
|
10525
11037
|
const display = [...history].reverse();
|
|
10526
11038
|
let prevTs = null;
|
|
10527
11039
|
for (let i = 0; i < display.length; i++) {
|
|
10528
11040
|
const e = display[i];
|
|
10529
11041
|
const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
|
|
10530
|
-
if (isGap) console.log(
|
|
11042
|
+
if (isGap) console.log(import_chalk14.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
|
|
10531
11043
|
const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
|
|
10532
11044
|
const tool = e.tool.slice(0, 8).padEnd(8);
|
|
10533
11045
|
const when = formatAge2(e.timestamp).padEnd(10);
|
|
10534
11046
|
const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
|
|
10535
11047
|
console.log(
|
|
10536
|
-
|
|
10537
|
-
` ${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)}`
|
|
10538
11050
|
)
|
|
10539
11051
|
);
|
|
10540
11052
|
prevTs = e.timestamp;
|
|
@@ -10547,7 +11059,7 @@ function registerUndoCommand(program2) {
|
|
|
10547
11059
|
const idx = history.length - steps;
|
|
10548
11060
|
if (idx < 0) {
|
|
10549
11061
|
console.log(
|
|
10550
|
-
|
|
11062
|
+
import_chalk14.default.yellow(
|
|
10551
11063
|
`
|
|
10552
11064
|
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
10553
11065
|
`
|
|
@@ -10558,47 +11070,47 @@ function registerUndoCommand(program2) {
|
|
|
10558
11070
|
const snapshot = history[idx];
|
|
10559
11071
|
const ageStr = formatAge2(snapshot.timestamp);
|
|
10560
11072
|
console.log(
|
|
10561
|
-
|
|
11073
|
+
import_chalk14.default.magenta.bold(`
|
|
10562
11074
|
\u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
|
|
10563
11075
|
);
|
|
10564
11076
|
console.log(
|
|
10565
|
-
|
|
10566
|
-
` Tool: ${
|
|
11077
|
+
import_chalk14.default.white(
|
|
11078
|
+
` Tool: ${import_chalk14.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk14.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
10567
11079
|
)
|
|
10568
11080
|
);
|
|
10569
|
-
console.log(
|
|
10570
|
-
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)}`));
|
|
10571
11083
|
if (steps > 1)
|
|
10572
11084
|
console.log(
|
|
10573
|
-
|
|
11085
|
+
import_chalk14.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
|
|
10574
11086
|
);
|
|
10575
11087
|
console.log("");
|
|
10576
11088
|
const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
|
|
10577
11089
|
if (diff) {
|
|
10578
11090
|
const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
|
|
10579
11091
|
for (const line of lines) {
|
|
10580
|
-
if (line.startsWith("+++") || line.startsWith("---")) console.log(
|
|
10581
|
-
else if (line.startsWith("+")) console.log(
|
|
10582
|
-
else if (line.startsWith("-")) console.log(
|
|
10583
|
-
else if (line.startsWith("@@")) console.log(
|
|
10584
|
-
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));
|
|
10585
11097
|
}
|
|
10586
11098
|
console.log("");
|
|
10587
11099
|
} else {
|
|
10588
11100
|
console.log(
|
|
10589
|
-
|
|
11101
|
+
import_chalk14.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
|
|
10590
11102
|
);
|
|
10591
11103
|
}
|
|
10592
11104
|
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
10593
11105
|
const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
|
|
10594
11106
|
if (proceed) {
|
|
10595
11107
|
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
10596
|
-
console.log(
|
|
11108
|
+
console.log(import_chalk14.default.green("\n\u2705 Reverted successfully.\n"));
|
|
10597
11109
|
} else {
|
|
10598
|
-
console.error(
|
|
11110
|
+
console.error(import_chalk14.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
|
|
10599
11111
|
}
|
|
10600
11112
|
} else {
|
|
10601
|
-
console.log(
|
|
11113
|
+
console.log(import_chalk14.default.gray("\nCancelled.\n"));
|
|
10602
11114
|
}
|
|
10603
11115
|
return;
|
|
10604
11116
|
}
|
|
@@ -10607,7 +11119,7 @@ function registerUndoCommand(program2) {
|
|
|
10607
11119
|
}
|
|
10608
11120
|
|
|
10609
11121
|
// src/cli/commands/watch.ts
|
|
10610
|
-
var
|
|
11122
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
10611
11123
|
var import_child_process12 = require("child_process");
|
|
10612
11124
|
init_daemon();
|
|
10613
11125
|
function registerWatchCommand(program2) {
|
|
@@ -10624,7 +11136,7 @@ function registerWatchCommand(program2) {
|
|
|
10624
11136
|
throw new Error("not running");
|
|
10625
11137
|
}
|
|
10626
11138
|
} catch {
|
|
10627
|
-
console.error(
|
|
11139
|
+
console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
10628
11140
|
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10629
11141
|
detached: true,
|
|
10630
11142
|
stdio: "ignore",
|
|
@@ -10646,12 +11158,12 @@ function registerWatchCommand(program2) {
|
|
|
10646
11158
|
}
|
|
10647
11159
|
}
|
|
10648
11160
|
if (!ready) {
|
|
10649
|
-
console.error(
|
|
11161
|
+
console.error(import_chalk15.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
10650
11162
|
process.exit(1);
|
|
10651
11163
|
}
|
|
10652
11164
|
}
|
|
10653
11165
|
console.error(
|
|
10654
|
-
|
|
11166
|
+
import_chalk15.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk15.default.dim(` \u2192 localhost:${port}`) + import_chalk15.default.dim(
|
|
10655
11167
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
10656
11168
|
)
|
|
10657
11169
|
);
|
|
@@ -10660,7 +11172,7 @@ function registerWatchCommand(program2) {
|
|
|
10660
11172
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
10661
11173
|
});
|
|
10662
11174
|
if (result.error) {
|
|
10663
|
-
console.error(
|
|
11175
|
+
console.error(import_chalk15.default.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
10664
11176
|
process.exit(1);
|
|
10665
11177
|
}
|
|
10666
11178
|
process.exit(result.status ?? 0);
|
|
@@ -10669,19 +11181,19 @@ function registerWatchCommand(program2) {
|
|
|
10669
11181
|
|
|
10670
11182
|
// src/mcp-gateway/index.ts
|
|
10671
11183
|
var import_readline3 = __toESM(require("readline"));
|
|
10672
|
-
var
|
|
11184
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
10673
11185
|
var import_child_process13 = require("child_process");
|
|
10674
11186
|
var import_execa3 = require("execa");
|
|
10675
11187
|
init_orchestrator();
|
|
10676
11188
|
init_provenance();
|
|
10677
11189
|
|
|
10678
11190
|
// src/mcp-pin.ts
|
|
10679
|
-
var
|
|
10680
|
-
var
|
|
10681
|
-
var
|
|
10682
|
-
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"));
|
|
10683
11195
|
function getPinsFilePath() {
|
|
10684
|
-
return
|
|
11196
|
+
return import_path29.default.join(import_os22.default.homedir(), ".node9", "mcp-pins.json");
|
|
10685
11197
|
}
|
|
10686
11198
|
function hashToolDefinitions(tools) {
|
|
10687
11199
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -10690,15 +11202,15 @@ function hashToolDefinitions(tools) {
|
|
|
10690
11202
|
return nameA.localeCompare(nameB);
|
|
10691
11203
|
});
|
|
10692
11204
|
const canonical = JSON.stringify(sorted);
|
|
10693
|
-
return
|
|
11205
|
+
return import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
|
|
10694
11206
|
}
|
|
10695
11207
|
function getServerKey(upstreamCommand) {
|
|
10696
|
-
return
|
|
11208
|
+
return import_crypto9.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
|
|
10697
11209
|
}
|
|
10698
11210
|
function readMcpPinsSafe() {
|
|
10699
11211
|
const filePath = getPinsFilePath();
|
|
10700
11212
|
try {
|
|
10701
|
-
const raw =
|
|
11213
|
+
const raw = import_fs26.default.readFileSync(filePath, "utf-8");
|
|
10702
11214
|
if (!raw.trim()) {
|
|
10703
11215
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10704
11216
|
}
|
|
@@ -10722,10 +11234,10 @@ function readMcpPins() {
|
|
|
10722
11234
|
}
|
|
10723
11235
|
function writeMcpPins(data) {
|
|
10724
11236
|
const filePath = getPinsFilePath();
|
|
10725
|
-
|
|
10726
|
-
const tmp = `${filePath}.${
|
|
10727
|
-
|
|
10728
|
-
|
|
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);
|
|
10729
11241
|
}
|
|
10730
11242
|
function checkPin(serverKey, currentHash) {
|
|
10731
11243
|
const result = readMcpPinsSafe();
|
|
@@ -10817,13 +11329,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10817
11329
|
const prov = checkProvenance(executable);
|
|
10818
11330
|
if (prov.trustLevel === "suspect") {
|
|
10819
11331
|
console.error(
|
|
10820
|
-
|
|
11332
|
+
import_chalk16.default.red(
|
|
10821
11333
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
10822
11334
|
)
|
|
10823
11335
|
);
|
|
10824
|
-
console.error(
|
|
11336
|
+
console.error(import_chalk16.default.red(" Verify this binary is trusted before proceeding."));
|
|
10825
11337
|
}
|
|
10826
|
-
console.error(
|
|
11338
|
+
console.error(import_chalk16.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
10827
11339
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
10828
11340
|
"NODE_OPTIONS",
|
|
10829
11341
|
"NODE_PATH",
|
|
@@ -10926,10 +11438,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10926
11438
|
mcpServer
|
|
10927
11439
|
});
|
|
10928
11440
|
if (!result.approved) {
|
|
10929
|
-
console.error(
|
|
11441
|
+
console.error(import_chalk16.default.red(`
|
|
10930
11442
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
10931
|
-
console.error(
|
|
10932
|
-
console.error(
|
|
11443
|
+
console.error(import_chalk16.default.gray(` Tool: ${toolName}`));
|
|
11444
|
+
console.error(import_chalk16.default.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
10933
11445
|
`));
|
|
10934
11446
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
10935
11447
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -11008,7 +11520,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11008
11520
|
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
11009
11521
|
pinState = "validated";
|
|
11010
11522
|
console.error(
|
|
11011
|
-
|
|
11523
|
+
import_chalk16.default.green(
|
|
11012
11524
|
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
11013
11525
|
)
|
|
11014
11526
|
);
|
|
@@ -11021,11 +11533,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11021
11533
|
} else if (pinStatus === "corrupt") {
|
|
11022
11534
|
pinState = "quarantined";
|
|
11023
11535
|
console.error(
|
|
11024
|
-
|
|
11536
|
+
import_chalk16.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
11025
11537
|
);
|
|
11026
|
-
console.error(
|
|
11538
|
+
console.error(import_chalk16.default.red(" Tool calls are blocked until the pin file is repaired."));
|
|
11027
11539
|
console.error(
|
|
11028
|
-
|
|
11540
|
+
import_chalk16.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
11029
11541
|
`)
|
|
11030
11542
|
);
|
|
11031
11543
|
const errorResponse = {
|
|
@@ -11042,13 +11554,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11042
11554
|
} else {
|
|
11043
11555
|
pinState = "quarantined";
|
|
11044
11556
|
console.error(
|
|
11045
|
-
|
|
11557
|
+
import_chalk16.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
11046
11558
|
);
|
|
11047
11559
|
console.error(
|
|
11048
|
-
|
|
11560
|
+
import_chalk16.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
11049
11561
|
);
|
|
11050
|
-
console.error(
|
|
11051
|
-
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}
|
|
11052
11564
|
`));
|
|
11053
11565
|
const errorResponse = {
|
|
11054
11566
|
jsonrpc: "2.0",
|
|
@@ -11097,9 +11609,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
11097
11609
|
|
|
11098
11610
|
// src/mcp-server/index.ts
|
|
11099
11611
|
var import_readline4 = __toESM(require("readline"));
|
|
11100
|
-
var
|
|
11101
|
-
var
|
|
11102
|
-
var
|
|
11612
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11613
|
+
var import_os23 = __toESM(require("os"));
|
|
11614
|
+
var import_path30 = __toESM(require("path"));
|
|
11103
11615
|
init_core();
|
|
11104
11616
|
init_daemon();
|
|
11105
11617
|
init_shields();
|
|
@@ -11274,13 +11786,13 @@ function handleStatus() {
|
|
|
11274
11786
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
11275
11787
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
11276
11788
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
11277
|
-
const projectConfig =
|
|
11278
|
-
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");
|
|
11279
11791
|
lines.push(
|
|
11280
|
-
`Project config (node9.config.json): ${
|
|
11792
|
+
`Project config (node9.config.json): ${import_fs27.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
11281
11793
|
);
|
|
11282
11794
|
lines.push(
|
|
11283
|
-
`Global config (~/.node9/config.json): ${
|
|
11795
|
+
`Global config (~/.node9/config.json): ${import_fs27.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
11284
11796
|
);
|
|
11285
11797
|
return lines.join("\n");
|
|
11286
11798
|
}
|
|
@@ -11354,21 +11866,21 @@ function handleShieldDisable(args) {
|
|
|
11354
11866
|
writeActiveShields(active.filter((s) => s !== name));
|
|
11355
11867
|
return `Shield "${name}" disabled.`;
|
|
11356
11868
|
}
|
|
11357
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
11869
|
+
var GLOBAL_CONFIG_PATH2 = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
|
|
11358
11870
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
11359
11871
|
function readGlobalConfigRaw() {
|
|
11360
11872
|
try {
|
|
11361
|
-
if (
|
|
11362
|
-
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"));
|
|
11363
11875
|
}
|
|
11364
11876
|
} catch {
|
|
11365
11877
|
}
|
|
11366
11878
|
return {};
|
|
11367
11879
|
}
|
|
11368
11880
|
function writeGlobalConfigRaw(data) {
|
|
11369
|
-
const dir =
|
|
11370
|
-
if (!
|
|
11371
|
-
|
|
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");
|
|
11372
11884
|
}
|
|
11373
11885
|
function handleApproverList() {
|
|
11374
11886
|
const config = getConfig();
|
|
@@ -11411,9 +11923,9 @@ function handleApproverSet(args) {
|
|
|
11411
11923
|
}
|
|
11412
11924
|
function handleAuditGet(args) {
|
|
11413
11925
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
11414
|
-
const auditPath =
|
|
11415
|
-
if (!
|
|
11416
|
-
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);
|
|
11417
11929
|
const recent = lines.slice(-limit);
|
|
11418
11930
|
const entries = recent.map((line) => {
|
|
11419
11931
|
try {
|
|
@@ -11600,7 +12112,7 @@ function registerMcpServerCommand(program2) {
|
|
|
11600
12112
|
}
|
|
11601
12113
|
|
|
11602
12114
|
// src/cli/commands/trust.ts
|
|
11603
|
-
var
|
|
12115
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
11604
12116
|
init_trusted_hosts();
|
|
11605
12117
|
function isValidHost(host) {
|
|
11606
12118
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
@@ -11611,51 +12123,51 @@ function registerTrustCommand(program2) {
|
|
|
11611
12123
|
const normalized = normalizeHost(host.trim());
|
|
11612
12124
|
if (!isValidHost(normalized)) {
|
|
11613
12125
|
console.error(
|
|
11614
|
-
|
|
12126
|
+
import_chalk17.default.red(`
|
|
11615
12127
|
\u274C Invalid host: "${host}"
|
|
11616
|
-
`) +
|
|
12128
|
+
`) + import_chalk17.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
11617
12129
|
);
|
|
11618
12130
|
process.exit(1);
|
|
11619
12131
|
}
|
|
11620
12132
|
addTrustedHost(normalized);
|
|
11621
|
-
console.log(
|
|
12133
|
+
console.log(import_chalk17.default.green(`
|
|
11622
12134
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
11623
12135
|
console.log(
|
|
11624
|
-
|
|
12136
|
+
import_chalk17.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
11625
12137
|
);
|
|
11626
12138
|
});
|
|
11627
12139
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
11628
12140
|
const normalized = normalizeHost(host.trim());
|
|
11629
12141
|
const removed = removeTrustedHost(normalized);
|
|
11630
12142
|
if (!removed) {
|
|
11631
|
-
console.error(
|
|
12143
|
+
console.error(import_chalk17.default.yellow(`
|
|
11632
12144
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
11633
12145
|
`));
|
|
11634
12146
|
process.exit(1);
|
|
11635
12147
|
}
|
|
11636
|
-
console.log(
|
|
12148
|
+
console.log(import_chalk17.default.green(`
|
|
11637
12149
|
\u2705 ${normalized} removed from trusted hosts.
|
|
11638
12150
|
`));
|
|
11639
12151
|
});
|
|
11640
12152
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
11641
12153
|
const hosts = readTrustedHosts();
|
|
11642
12154
|
if (hosts.length === 0) {
|
|
11643
|
-
console.log(
|
|
11644
|
-
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")}
|
|
11645
12157
|
`);
|
|
11646
12158
|
return;
|
|
11647
12159
|
}
|
|
11648
|
-
console.log(
|
|
12160
|
+
console.log(import_chalk17.default.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
11649
12161
|
for (const entry of hosts) {
|
|
11650
12162
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
11651
|
-
console.log(` ${
|
|
12163
|
+
console.log(` ${import_chalk17.default.cyan(entry.host.padEnd(40))} ${import_chalk17.default.gray(`added ${date}`)}`);
|
|
11652
12164
|
}
|
|
11653
12165
|
console.log("");
|
|
11654
12166
|
});
|
|
11655
12167
|
}
|
|
11656
12168
|
|
|
11657
12169
|
// src/cli/commands/mcp-pin.ts
|
|
11658
|
-
var
|
|
12170
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
11659
12171
|
function registerMcpPinCommand(program2) {
|
|
11660
12172
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
11661
12173
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -11663,31 +12175,31 @@ function registerMcpPinCommand(program2) {
|
|
|
11663
12175
|
const result = readMcpPinsSafe();
|
|
11664
12176
|
if (!result.ok) {
|
|
11665
12177
|
if (result.reason === "missing") {
|
|
11666
|
-
console.log(
|
|
12178
|
+
console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
|
|
11667
12179
|
console.log(
|
|
11668
|
-
|
|
12180
|
+
import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11669
12181
|
);
|
|
11670
12182
|
return;
|
|
11671
12183
|
}
|
|
11672
|
-
console.error(
|
|
12184
|
+
console.error(import_chalk18.default.red(`
|
|
11673
12185
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
11674
|
-
console.error(
|
|
12186
|
+
console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
11675
12187
|
process.exit(1);
|
|
11676
12188
|
}
|
|
11677
12189
|
const entries = Object.entries(result.pins.servers);
|
|
11678
12190
|
if (entries.length === 0) {
|
|
11679
|
-
console.log(
|
|
12191
|
+
console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
|
|
11680
12192
|
console.log(
|
|
11681
|
-
|
|
12193
|
+
import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11682
12194
|
);
|
|
11683
12195
|
return;
|
|
11684
12196
|
}
|
|
11685
|
-
console.log(
|
|
12197
|
+
console.log(import_chalk18.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
11686
12198
|
for (const [key, entry] of entries) {
|
|
11687
|
-
console.log(` ${
|
|
11688
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
11689
|
-
console.log(` Hash: ${
|
|
11690
|
-
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)}`);
|
|
11691
12203
|
console.log("");
|
|
11692
12204
|
}
|
|
11693
12205
|
});
|
|
@@ -11698,55 +12210,55 @@ function registerMcpPinCommand(program2) {
|
|
|
11698
12210
|
try {
|
|
11699
12211
|
pins = readMcpPins();
|
|
11700
12212
|
} catch {
|
|
11701
|
-
console.error(
|
|
11702
|
-
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"));
|
|
11703
12215
|
process.exit(1);
|
|
11704
12216
|
}
|
|
11705
12217
|
if (!pins.servers[serverKey]) {
|
|
11706
|
-
console.error(
|
|
12218
|
+
console.error(import_chalk18.default.red(`
|
|
11707
12219
|
\u274C No pin found for server key "${serverKey}"
|
|
11708
12220
|
`));
|
|
11709
|
-
console.error(`Run ${
|
|
12221
|
+
console.error(`Run ${import_chalk18.default.cyan("node9 mcp pin list")} to see pinned servers.
|
|
11710
12222
|
`);
|
|
11711
12223
|
process.exit(1);
|
|
11712
12224
|
}
|
|
11713
12225
|
const label = pins.servers[serverKey].label;
|
|
11714
12226
|
removePin(serverKey);
|
|
11715
|
-
console.log(
|
|
11716
|
-
\u{1F513} Pin removed for ${
|
|
11717
|
-
console.log(
|
|
11718
|
-
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"));
|
|
11719
12231
|
});
|
|
11720
12232
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
11721
12233
|
const result = readMcpPinsSafe();
|
|
11722
12234
|
if (!result.ok && result.reason === "missing") {
|
|
11723
|
-
console.log(
|
|
12235
|
+
console.log(import_chalk18.default.gray("\nNo pins to clear.\n"));
|
|
11724
12236
|
return;
|
|
11725
12237
|
}
|
|
11726
12238
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
11727
12239
|
clearAllPins();
|
|
11728
|
-
console.log(
|
|
12240
|
+
console.log(import_chalk18.default.green(`
|
|
11729
12241
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
11730
|
-
console.log(
|
|
12242
|
+
console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
|
|
11731
12243
|
});
|
|
11732
12244
|
}
|
|
11733
12245
|
|
|
11734
12246
|
// src/cli.ts
|
|
11735
12247
|
var { version } = JSON.parse(
|
|
11736
|
-
|
|
12248
|
+
import_fs30.default.readFileSync(import_path33.default.join(__dirname, "../package.json"), "utf-8")
|
|
11737
12249
|
);
|
|
11738
12250
|
var program = new import_commander.Command();
|
|
11739
12251
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
11740
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) => {
|
|
11741
12253
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
11742
|
-
const credPath =
|
|
11743
|
-
if (!
|
|
11744
|
-
|
|
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 });
|
|
11745
12257
|
const profileName = options.profile || "default";
|
|
11746
12258
|
let existingCreds = {};
|
|
11747
12259
|
try {
|
|
11748
|
-
if (
|
|
11749
|
-
const raw = JSON.parse(
|
|
12260
|
+
if (import_fs30.default.existsSync(credPath)) {
|
|
12261
|
+
const raw = JSON.parse(import_fs30.default.readFileSync(credPath, "utf-8"));
|
|
11750
12262
|
if (raw.apiKey) {
|
|
11751
12263
|
existingCreds = {
|
|
11752
12264
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -11758,13 +12270,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11758
12270
|
} catch {
|
|
11759
12271
|
}
|
|
11760
12272
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
11761
|
-
|
|
12273
|
+
import_fs30.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
11762
12274
|
if (profileName === "default") {
|
|
11763
|
-
const configPath =
|
|
12275
|
+
const configPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
|
|
11764
12276
|
let config = {};
|
|
11765
12277
|
try {
|
|
11766
|
-
if (
|
|
11767
|
-
config = JSON.parse(
|
|
12278
|
+
if (import_fs30.default.existsSync(configPath))
|
|
12279
|
+
config = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
|
|
11768
12280
|
} catch {
|
|
11769
12281
|
}
|
|
11770
12282
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -11779,19 +12291,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11779
12291
|
approvers.cloud = false;
|
|
11780
12292
|
}
|
|
11781
12293
|
s.approvers = approvers;
|
|
11782
|
-
if (!
|
|
11783
|
-
|
|
11784
|
-
|
|
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 });
|
|
11785
12297
|
}
|
|
11786
12298
|
if (options.profile && profileName !== "default") {
|
|
11787
|
-
console.log(
|
|
11788
|
-
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`));
|
|
11789
12301
|
} else if (options.local) {
|
|
11790
|
-
console.log(
|
|
11791
|
-
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.`));
|
|
11792
12304
|
} else {
|
|
11793
|
-
console.log(
|
|
11794
|
-
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.`));
|
|
11795
12307
|
}
|
|
11796
12308
|
});
|
|
11797
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) => {
|
|
@@ -11799,19 +12311,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
11799
12311
|
if (target === "claude") return await setupClaude();
|
|
11800
12312
|
if (target === "cursor") return await setupCursor();
|
|
11801
12313
|
if (target === "hud") return setupHud();
|
|
11802
|
-
console.error(
|
|
12314
|
+
console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11803
12315
|
process.exit(1);
|
|
11804
12316
|
});
|
|
11805
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) => {
|
|
11806
12318
|
if (!target) {
|
|
11807
|
-
console.log(
|
|
11808
|
-
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");
|
|
11809
12321
|
console.log(" Targets:");
|
|
11810
|
-
console.log(" " +
|
|
11811
|
-
console.log(" " +
|
|
11812
|
-
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)");
|
|
11813
12325
|
process.stdout.write(
|
|
11814
|
-
" " +
|
|
12326
|
+
" " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
11815
12327
|
);
|
|
11816
12328
|
console.log("");
|
|
11817
12329
|
return;
|
|
@@ -11821,7 +12333,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
11821
12333
|
if (t === "claude") return await setupClaude();
|
|
11822
12334
|
if (t === "cursor") return await setupCursor();
|
|
11823
12335
|
if (t === "hud") return setupHud();
|
|
11824
|
-
console.error(
|
|
12336
|
+
console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11825
12337
|
process.exit(1);
|
|
11826
12338
|
});
|
|
11827
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) => {
|
|
@@ -11832,31 +12344,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
11832
12344
|
else if (target === "hud") fn = teardownHud;
|
|
11833
12345
|
else {
|
|
11834
12346
|
console.error(
|
|
11835
|
-
|
|
12347
|
+
import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
|
|
11836
12348
|
);
|
|
11837
12349
|
process.exit(1);
|
|
11838
12350
|
}
|
|
11839
|
-
console.log(
|
|
12351
|
+
console.log(import_chalk20.default.cyan(`
|
|
11840
12352
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
11841
12353
|
`));
|
|
11842
12354
|
try {
|
|
11843
12355
|
fn();
|
|
11844
12356
|
} catch (err2) {
|
|
11845
|
-
console.error(
|
|
12357
|
+
console.error(import_chalk20.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11846
12358
|
process.exit(1);
|
|
11847
12359
|
}
|
|
11848
|
-
console.log(
|
|
12360
|
+
console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
|
|
11849
12361
|
});
|
|
11850
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) => {
|
|
11851
|
-
console.log(
|
|
11852
|
-
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..."));
|
|
11853
12365
|
try {
|
|
11854
12366
|
stopDaemon();
|
|
11855
|
-
console.log(
|
|
12367
|
+
console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
|
|
11856
12368
|
} catch {
|
|
11857
|
-
console.log(
|
|
12369
|
+
console.log(import_chalk20.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
11858
12370
|
}
|
|
11859
|
-
console.log(
|
|
12371
|
+
console.log(import_chalk20.default.bold("\nRemoving hooks..."));
|
|
11860
12372
|
let teardownFailed = false;
|
|
11861
12373
|
for (const [label, fn] of [
|
|
11862
12374
|
["Claude", teardownClaude],
|
|
@@ -11868,45 +12380,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
11868
12380
|
} catch (err2) {
|
|
11869
12381
|
teardownFailed = true;
|
|
11870
12382
|
console.error(
|
|
11871
|
-
|
|
12383
|
+
import_chalk20.default.red(
|
|
11872
12384
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11873
12385
|
)
|
|
11874
12386
|
);
|
|
11875
12387
|
}
|
|
11876
12388
|
}
|
|
11877
12389
|
if (options.purge) {
|
|
11878
|
-
const node9Dir =
|
|
11879
|
-
if (
|
|
12390
|
+
const node9Dir = import_path33.default.join(import_os26.default.homedir(), ".node9");
|
|
12391
|
+
if (import_fs30.default.existsSync(node9Dir)) {
|
|
11880
12392
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
11881
12393
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
11882
12394
|
default: false
|
|
11883
12395
|
});
|
|
11884
12396
|
if (confirmed) {
|
|
11885
|
-
|
|
11886
|
-
if (
|
|
12397
|
+
import_fs30.default.rmSync(node9Dir, { recursive: true });
|
|
12398
|
+
if (import_fs30.default.existsSync(node9Dir)) {
|
|
11887
12399
|
console.error(
|
|
11888
|
-
|
|
12400
|
+
import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
11889
12401
|
);
|
|
11890
12402
|
} else {
|
|
11891
|
-
console.log(
|
|
12403
|
+
console.log(import_chalk20.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
11892
12404
|
}
|
|
11893
12405
|
} else {
|
|
11894
|
-
console.log(
|
|
12406
|
+
console.log(import_chalk20.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
11895
12407
|
}
|
|
11896
12408
|
} else {
|
|
11897
|
-
console.log(
|
|
12409
|
+
console.log(import_chalk20.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
11898
12410
|
}
|
|
11899
12411
|
} else {
|
|
11900
12412
|
console.log(
|
|
11901
|
-
|
|
12413
|
+
import_chalk20.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
11902
12414
|
);
|
|
11903
12415
|
}
|
|
11904
12416
|
if (teardownFailed) {
|
|
11905
|
-
console.error(
|
|
12417
|
+
console.error(import_chalk20.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
11906
12418
|
process.exit(1);
|
|
11907
12419
|
}
|
|
11908
|
-
console.log(
|
|
11909
|
-
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"));
|
|
11910
12422
|
});
|
|
11911
12423
|
registerDoctorCommand(program, version);
|
|
11912
12424
|
program.command("explain").description(
|
|
@@ -11919,7 +12431,7 @@ program.command("explain").description(
|
|
|
11919
12431
|
try {
|
|
11920
12432
|
args = JSON.parse(trimmed);
|
|
11921
12433
|
} catch {
|
|
11922
|
-
console.error(
|
|
12434
|
+
console.error(import_chalk20.default.red(`
|
|
11923
12435
|
\u274C Invalid JSON: ${trimmed}
|
|
11924
12436
|
`));
|
|
11925
12437
|
process.exit(1);
|
|
@@ -11930,60 +12442,61 @@ program.command("explain").description(
|
|
|
11930
12442
|
}
|
|
11931
12443
|
const result = await explainPolicy(tool, args);
|
|
11932
12444
|
console.log("");
|
|
11933
|
-
console.log(
|
|
12445
|
+
console.log(import_chalk20.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
11934
12446
|
console.log("");
|
|
11935
|
-
console.log(` ${
|
|
12447
|
+
console.log(` ${import_chalk20.default.bold("Tool:")} ${import_chalk20.default.white(result.tool)}`);
|
|
11936
12448
|
if (argsRaw) {
|
|
11937
12449
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
11938
|
-
console.log(` ${
|
|
12450
|
+
console.log(` ${import_chalk20.default.bold("Input:")} ${import_chalk20.default.gray(preview)}`);
|
|
11939
12451
|
}
|
|
11940
12452
|
console.log("");
|
|
11941
|
-
console.log(
|
|
12453
|
+
console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
|
|
11942
12454
|
for (const tier of result.waterfall) {
|
|
11943
|
-
const
|
|
12455
|
+
const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
|
|
11944
12456
|
const label = tier.label.padEnd(16);
|
|
11945
12457
|
let statusStr;
|
|
11946
12458
|
if (tier.tier === 1) {
|
|
11947
|
-
statusStr =
|
|
12459
|
+
statusStr = import_chalk20.default.gray(tier.note ?? "");
|
|
11948
12460
|
} else if (tier.status === "active") {
|
|
11949
|
-
const loc = tier.path ?
|
|
11950
|
-
const note = tier.note ?
|
|
11951
|
-
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 : "");
|
|
11952
12464
|
} else {
|
|
11953
|
-
statusStr =
|
|
12465
|
+
statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
11954
12466
|
}
|
|
11955
|
-
console.log(`${
|
|
12467
|
+
console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
|
|
11956
12468
|
}
|
|
11957
12469
|
console.log("");
|
|
11958
|
-
console.log(
|
|
12470
|
+
console.log(import_chalk20.default.bold("Policy Evaluation:"));
|
|
11959
12471
|
for (const step of result.steps) {
|
|
11960
12472
|
const isFinal = step.isFinal;
|
|
11961
12473
|
let icon;
|
|
11962
|
-
if (step.outcome === "allow") icon =
|
|
11963
|
-
else if (step.outcome === "review") icon =
|
|
11964
|
-
else if (step.outcome === "skip") icon =
|
|
11965
|
-
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 ");
|
|
11966
12478
|
const name = step.name.padEnd(18);
|
|
11967
|
-
const nameStr = isFinal ?
|
|
11968
|
-
const detail = isFinal ?
|
|
11969
|
-
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") : "";
|
|
11970
12482
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
11971
12483
|
}
|
|
11972
12484
|
console.log("");
|
|
11973
12485
|
if (result.decision === "allow") {
|
|
11974
|
-
console.log(
|
|
12486
|
+
console.log(import_chalk20.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk20.default.gray(" \u2014 no approval needed"));
|
|
11975
12487
|
} else {
|
|
11976
12488
|
console.log(
|
|
11977
|
-
|
|
12489
|
+
import_chalk20.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk20.default.gray(" \u2014 human approval required")
|
|
11978
12490
|
);
|
|
11979
12491
|
if (result.blockedByLabel) {
|
|
11980
|
-
console.log(
|
|
12492
|
+
console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
11981
12493
|
}
|
|
11982
12494
|
}
|
|
11983
12495
|
console.log("");
|
|
11984
12496
|
});
|
|
11985
12497
|
registerInitCommand(program);
|
|
11986
12498
|
registerAuditCommand(program);
|
|
12499
|
+
registerReportCommand(program);
|
|
11987
12500
|
registerStatusCommand(program);
|
|
11988
12501
|
registerDaemonCommand(program);
|
|
11989
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) => {
|
|
@@ -11991,7 +12504,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
11991
12504
|
try {
|
|
11992
12505
|
await startTail2(options);
|
|
11993
12506
|
} catch (err2) {
|
|
11994
|
-
console.error(
|
|
12507
|
+
console.error(import_chalk20.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11995
12508
|
process.exit(1);
|
|
11996
12509
|
}
|
|
11997
12510
|
});
|
|
@@ -12023,14 +12536,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
12023
12536
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
12024
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) => {
|
|
12025
12538
|
if (subcommand === "debug") {
|
|
12026
|
-
const flagFile =
|
|
12539
|
+
const flagFile = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug");
|
|
12027
12540
|
if (state === "on") {
|
|
12028
|
-
|
|
12029
|
-
|
|
12541
|
+
import_fs30.default.mkdirSync(import_path33.default.dirname(flagFile), { recursive: true });
|
|
12542
|
+
import_fs30.default.writeFileSync(flagFile, "");
|
|
12030
12543
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
12031
12544
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
12032
12545
|
} else if (state === "off") {
|
|
12033
|
-
if (
|
|
12546
|
+
if (import_fs30.default.existsSync(flagFile)) import_fs30.default.unlinkSync(flagFile);
|
|
12034
12547
|
console.log("HUD debug logging disabled.");
|
|
12035
12548
|
} else {
|
|
12036
12549
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -12045,7 +12558,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12045
12558
|
const ms = parseDuration(options.duration);
|
|
12046
12559
|
if (ms === null) {
|
|
12047
12560
|
console.error(
|
|
12048
|
-
|
|
12561
|
+
import_chalk20.default.red(`
|
|
12049
12562
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
12050
12563
|
`)
|
|
12051
12564
|
);
|
|
@@ -12053,20 +12566,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12053
12566
|
}
|
|
12054
12567
|
pauseNode9(ms, options.duration);
|
|
12055
12568
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
12056
|
-
console.log(
|
|
12569
|
+
console.log(import_chalk20.default.yellow(`
|
|
12057
12570
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
12058
|
-
console.log(
|
|
12059
|
-
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.
|
|
12060
12573
|
`));
|
|
12061
12574
|
});
|
|
12062
12575
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
12063
12576
|
const { paused } = checkPause();
|
|
12064
12577
|
if (!paused) {
|
|
12065
|
-
console.log(
|
|
12578
|
+
console.log(import_chalk20.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
12066
12579
|
return;
|
|
12067
12580
|
}
|
|
12068
12581
|
resumeNode9();
|
|
12069
|
-
console.log(
|
|
12582
|
+
console.log(import_chalk20.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
12070
12583
|
});
|
|
12071
12584
|
var HOOK_BASED_AGENTS = {
|
|
12072
12585
|
claude: "claude",
|
|
@@ -12079,15 +12592,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12079
12592
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
12080
12593
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
12081
12594
|
console.error(
|
|
12082
|
-
|
|
12595
|
+
import_chalk20.default.yellow(`
|
|
12083
12596
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
12084
12597
|
);
|
|
12085
|
-
console.error(
|
|
12598
|
+
console.error(import_chalk20.default.white(`
|
|
12086
12599
|
"${target}" uses its own hook system. Use:`));
|
|
12087
12600
|
console.error(
|
|
12088
|
-
|
|
12601
|
+
import_chalk20.default.green(` node9 addto ${target} `) + import_chalk20.default.gray("# one-time setup")
|
|
12089
12602
|
);
|
|
12090
|
-
console.error(
|
|
12603
|
+
console.error(import_chalk20.default.green(` ${target} `) + import_chalk20.default.gray("# run normally"));
|
|
12091
12604
|
process.exit(1);
|
|
12092
12605
|
}
|
|
12093
12606
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -12104,7 +12617,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12104
12617
|
}
|
|
12105
12618
|
);
|
|
12106
12619
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
12107
|
-
console.error(
|
|
12620
|
+
console.error(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
12108
12621
|
const daemonReady = await autoStartDaemonAndWait();
|
|
12109
12622
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
12110
12623
|
}
|
|
@@ -12117,12 +12630,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12117
12630
|
}
|
|
12118
12631
|
if (!result.approved) {
|
|
12119
12632
|
console.error(
|
|
12120
|
-
|
|
12633
|
+
import_chalk20.default.red(`
|
|
12121
12634
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
12122
12635
|
);
|
|
12123
12636
|
process.exit(1);
|
|
12124
12637
|
}
|
|
12125
|
-
console.error(
|
|
12638
|
+
console.error(import_chalk20.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
12126
12639
|
await runProxy(fullCommand);
|
|
12127
12640
|
} else {
|
|
12128
12641
|
program.help();
|
|
@@ -12137,9 +12650,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
12137
12650
|
const isCheckHook = process.argv[2] === "check";
|
|
12138
12651
|
if (isCheckHook) {
|
|
12139
12652
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
12140
|
-
const logPath =
|
|
12653
|
+
const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hook-debug.log");
|
|
12141
12654
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
12142
|
-
|
|
12655
|
+
import_fs30.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
12143
12656
|
`);
|
|
12144
12657
|
}
|
|
12145
12658
|
process.exit(0);
|