@node9/proxy 1.63.0 → 1.64.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/dist/cli.js +908 -336
- package/dist/cli.mjs +906 -334
- package/dist/dashboard.mjs +82 -25
- package/dist/index.js +144 -39
- package/dist/index.mjs +144 -39
- package/dist/scan-ink.mjs +6 -5
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -347,6 +347,7 @@ var init_config_schema = __esm({
|
|
|
347
347
|
// nudge-only (default), and the scan cadence in minutes.
|
|
348
348
|
mcpAutoWrap: z.boolean().optional(),
|
|
349
349
|
mcpReconcileIntervalMinutes: z.number().positive().optional(),
|
|
350
|
+
mcpStaleAfterDays: z.number().min(0).optional(),
|
|
350
351
|
cloudSyncIntervalHours: z.number().positive().optional(),
|
|
351
352
|
// Seconds-granular override for the cloud policy sync cadence. Wins over
|
|
352
353
|
// cloudSyncIntervalHours when set. Lets you opt into fast apply (e.g. 20)
|
|
@@ -3981,6 +3982,7 @@ var init_dist = __esm({
|
|
|
3981
3982
|
name: "redis",
|
|
3982
3983
|
description: "Protects Redis instances from destructive AI operations",
|
|
3983
3984
|
aliases: [],
|
|
3985
|
+
_scopeNote: "Every rule requires Redis to actually be involved: a client in the command (redis-cli / valkey-cli, in either order \u2014 `redis-cli FLUSHALL` and `echo FLUSHALL | redis-cli` are both real), OR the command field holding the bare keyword, which is what the MCP redis_command tool sends ({ command: 'FLUSHALL', args: [] }). Without the client requirement these rules matched the WORD anywhere \u2014 including prose in a heredoc writing a file \u2014 which produced two observed false positives: a test fixture describing the shield, and a commit message naming the rule. Every other builtin shield already scopes this way (aws s3, docker system prune, kubectl delete, gh repo delete). See redis-scope.spec.ts.",
|
|
3984
3986
|
smartRules: [
|
|
3985
3987
|
{
|
|
3986
3988
|
name: "shield:redis:block-flushall",
|
|
@@ -3989,7 +3991,7 @@ var init_dist = __esm({
|
|
|
3989
3991
|
{
|
|
3990
3992
|
field: "command",
|
|
3991
3993
|
op: "matches",
|
|
3992
|
-
value: "\\bFLUSHALL\\b",
|
|
3994
|
+
value: "(redis|valkey)-cli.*\\bFLUSHALL\\b|\\bFLUSHALL\\b.*(redis|valkey)-cli|^ ?FLUSHALL\\b|\\.flushall\\s*\\(",
|
|
3993
3995
|
flags: "i"
|
|
3994
3996
|
}
|
|
3995
3997
|
],
|
|
@@ -4003,7 +4005,7 @@ var init_dist = __esm({
|
|
|
4003
4005
|
{
|
|
4004
4006
|
field: "command",
|
|
4005
4007
|
op: "matches",
|
|
4006
|
-
value: "\\bFLUSHDB\\b",
|
|
4008
|
+
value: "(redis|valkey)-cli.*\\bFLUSHDB\\b|\\bFLUSHDB\\b.*(redis|valkey)-cli|^ ?FLUSHDB\\b|\\.flushdb\\s*\\(",
|
|
4007
4009
|
flags: "i"
|
|
4008
4010
|
}
|
|
4009
4011
|
],
|
|
@@ -4017,7 +4019,7 @@ var init_dist = __esm({
|
|
|
4017
4019
|
{
|
|
4018
4020
|
field: "command",
|
|
4019
4021
|
op: "matches",
|
|
4020
|
-
value: "\\
|
|
4022
|
+
value: "(redis|valkey)-cli.*CONFIG\\s+RESETSTAT|^ ?CONFIG\\s+RESETSTAT",
|
|
4021
4023
|
flags: "i"
|
|
4022
4024
|
}
|
|
4023
4025
|
],
|
|
@@ -4031,7 +4033,7 @@ var init_dist = __esm({
|
|
|
4031
4033
|
{
|
|
4032
4034
|
field: "command",
|
|
4033
4035
|
op: "matches",
|
|
4034
|
-
value: "\\bCONFIG\\s+SET\\b",
|
|
4036
|
+
value: "(redis|valkey)-cli.*\\bCONFIG\\s+SET\\b|\\bCONFIG\\s+SET\\b.*(redis|valkey)-cli|^ ?CONFIG\\s+SET\\b",
|
|
4035
4037
|
flags: "i"
|
|
4036
4038
|
}
|
|
4037
4039
|
],
|
|
@@ -4045,7 +4047,7 @@ var init_dist = __esm({
|
|
|
4045
4047
|
{
|
|
4046
4048
|
field: "command",
|
|
4047
4049
|
op: "matches",
|
|
4048
|
-
value: "
|
|
4050
|
+
value: "(redis|valkey)-cli.*\\bDEL\\b.*[*?\\[]|^ ?DEL\\b.*[*?\\[]|-cli.*--scan.*xargs.*del",
|
|
4049
4051
|
flags: "i"
|
|
4050
4052
|
}
|
|
4051
4053
|
],
|
|
@@ -4615,6 +4617,48 @@ function getActiveEnvironment(config) {
|
|
|
4615
4617
|
const env = config.settings.environment || process.env.NODE_ENV || "development";
|
|
4616
4618
|
return config.environments[env] ?? null;
|
|
4617
4619
|
}
|
|
4620
|
+
function readRulesCacheResilient(cacheFile) {
|
|
4621
|
+
let existed = false;
|
|
4622
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
4623
|
+
let content;
|
|
4624
|
+
try {
|
|
4625
|
+
content = fs4.readFileSync(cacheFile, "utf-8");
|
|
4626
|
+
existed = true;
|
|
4627
|
+
} catch (err2) {
|
|
4628
|
+
if (err2.code === "ENOENT") return {};
|
|
4629
|
+
continue;
|
|
4630
|
+
}
|
|
4631
|
+
try {
|
|
4632
|
+
return JSON.parse(content);
|
|
4633
|
+
} catch {
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
if (existed) {
|
|
4637
|
+
const backup = path4.join(path4.dirname(cacheFile), "rules-cache.last-good.json");
|
|
4638
|
+
if (backup !== cacheFile) {
|
|
4639
|
+
try {
|
|
4640
|
+
const raw = JSON.parse(fs4.readFileSync(backup, "utf-8"));
|
|
4641
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
|
|
4642
|
+
return raw;
|
|
4643
|
+
} catch {
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_UNREADABLE");
|
|
4647
|
+
}
|
|
4648
|
+
return {};
|
|
4649
|
+
}
|
|
4650
|
+
function logCacheReadIssue(cacheFile, kind) {
|
|
4651
|
+
if (cacheReadFailureLogged) return;
|
|
4652
|
+
cacheReadFailureLogged = true;
|
|
4653
|
+
try {
|
|
4654
|
+
fs4.appendFileSync(
|
|
4655
|
+
path4.join(os4.homedir(), ".node9", "hook-debug.log"),
|
|
4656
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
|
|
4657
|
+
`
|
|
4658
|
+
);
|
|
4659
|
+
} catch {
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4618
4662
|
function getConfig(cwd) {
|
|
4619
4663
|
if (!cwd && cachedConfig) return cachedConfig;
|
|
4620
4664
|
const globalPath = path4.join(os4.homedir(), ".node9", "config.json");
|
|
@@ -4682,6 +4726,7 @@ function getConfig(cwd) {
|
|
|
4682
4726
|
if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
|
|
4683
4727
|
if (s.mcpReconcileIntervalMinutes !== void 0)
|
|
4684
4728
|
mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
|
|
4729
|
+
if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
|
|
4685
4730
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
4686
4731
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
4687
4732
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -4691,12 +4736,13 @@ function getConfig(cwd) {
|
|
|
4691
4736
|
if (p.smartRules) {
|
|
4692
4737
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
4693
4738
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
4694
|
-
const
|
|
4739
|
+
const localRules = p.smartRules.map(({ pinned: _pinned, ...r }) => r);
|
|
4740
|
+
const userRuleNames = new Set(localRules.filter((r) => r.name).map((r) => r.name));
|
|
4695
4741
|
const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
|
|
4696
4742
|
const filteredNonBlocks = defaultNonBlocks.filter(
|
|
4697
4743
|
(r) => !r.name || !userRuleNames.has(r.name)
|
|
4698
4744
|
);
|
|
4699
|
-
mergedPolicy.smartRules = [...filteredBlocks, ...
|
|
4745
|
+
mergedPolicy.smartRules = [...filteredBlocks, ...localRules, ...filteredNonBlocks];
|
|
4700
4746
|
}
|
|
4701
4747
|
if (p.snapshot) {
|
|
4702
4748
|
const s2 = p.snapshot;
|
|
@@ -4761,10 +4807,12 @@ function getConfig(cwd) {
|
|
|
4761
4807
|
applyLayer(globalConfig);
|
|
4762
4808
|
applyLayer(projectConfig);
|
|
4763
4809
|
let cloudManagedShields = [];
|
|
4810
|
+
let modeCloudControlled = false;
|
|
4811
|
+
let modeCloudStaged = false;
|
|
4764
4812
|
{
|
|
4765
4813
|
const cacheFile = path4.join(os4.homedir(), ".node9", "rules-cache.json");
|
|
4766
4814
|
try {
|
|
4767
|
-
const raw =
|
|
4815
|
+
const raw = readRulesCacheResilient(cacheFile);
|
|
4768
4816
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
4769
4817
|
applyLayer({ policy: { smartRules: raw.rules } });
|
|
4770
4818
|
}
|
|
@@ -4781,6 +4829,9 @@ function getConfig(cwd) {
|
|
|
4781
4829
|
locked.includes("mode")
|
|
4782
4830
|
);
|
|
4783
4831
|
}
|
|
4832
|
+
if (typeof mc.mode === "string" || locked.includes("mode")) {
|
|
4833
|
+
modeCloudControlled = true;
|
|
4834
|
+
}
|
|
4784
4835
|
if (mc.egress && typeof mc.egress === "object") {
|
|
4785
4836
|
const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
|
|
4786
4837
|
mergedPolicy.egress = applyManagedEgress(
|
|
@@ -4879,19 +4930,28 @@ function getConfig(cwd) {
|
|
|
4879
4930
|
}
|
|
4880
4931
|
if (raw.shadowMode === true) {
|
|
4881
4932
|
mergedSettings.mode = "observe";
|
|
4933
|
+
modeCloudStaged = true;
|
|
4882
4934
|
}
|
|
4883
4935
|
} catch {
|
|
4884
4936
|
}
|
|
4885
4937
|
}
|
|
4886
4938
|
const shieldOverrides = readShieldOverrides();
|
|
4887
4939
|
const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
|
|
4940
|
+
const cloudManagedSet = new Set(cloudManagedShields);
|
|
4888
4941
|
for (const shieldName of activeShieldNames) {
|
|
4889
|
-
const
|
|
4942
|
+
const isCloudMandated = cloudManagedSet.has(shieldName);
|
|
4943
|
+
const shield = isCloudMandated ? BUILTIN_SHIELDS[shieldName] : getShield(shieldName);
|
|
4890
4944
|
if (!shield) continue;
|
|
4891
4945
|
const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
4892
|
-
const ruleOverrides = shieldOverrides[shieldName] ?? {};
|
|
4946
|
+
const ruleOverrides = isCloudMandated ? {} : shieldOverrides[shieldName] ?? {};
|
|
4893
4947
|
for (const rule of shield.smartRules) {
|
|
4894
|
-
|
|
4948
|
+
const collides = rule.name ? existingRuleNames.has(rule.name) : false;
|
|
4949
|
+
if (isCloudMandated) {
|
|
4950
|
+
if (collides) {
|
|
4951
|
+
mergedPolicy.smartRules = mergedPolicy.smartRules.filter((r) => r.name !== rule.name);
|
|
4952
|
+
}
|
|
4953
|
+
mergedPolicy.smartRules.push({ ...rule, pinned: true });
|
|
4954
|
+
} else if (!collides) {
|
|
4895
4955
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
4896
4956
|
mergedPolicy.smartRules.push(
|
|
4897
4957
|
overrideVerdict !== void 0 ? { ...rule, verdict: overrideVerdict } : rule
|
|
@@ -4907,7 +4967,27 @@ function getConfig(cwd) {
|
|
|
4907
4967
|
for (const rule of ADVISORY_SMART_RULES) {
|
|
4908
4968
|
if (!existingAdvisoryNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
4909
4969
|
}
|
|
4910
|
-
|
|
4970
|
+
const envMode = process.env.NODE9_MODE;
|
|
4971
|
+
if (envMode && !modeCloudControlled && ["observe", "audit", "standard", "strict"].includes(envMode)) {
|
|
4972
|
+
mergedSettings.mode = envMode;
|
|
4973
|
+
}
|
|
4974
|
+
if (cloudManagedShields.length > 0 && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
|
|
4975
|
+
mergedSettings.mode = "standard";
|
|
4976
|
+
}
|
|
4977
|
+
const managedFloorActive = cloudManagedShields.length > 0 || modeCloudControlled && mergedSettings.mode === "strict";
|
|
4978
|
+
if (modeCloudControlled && mergedSettings.mode === "strict") {
|
|
4979
|
+
for (const name of Object.keys(mergedEnvironments)) {
|
|
4980
|
+
if (mergedEnvironments[name]?.requireApproval === false) {
|
|
4981
|
+
const cleaned = { ...mergedEnvironments[name] };
|
|
4982
|
+
delete cleaned.requireApproval;
|
|
4983
|
+
mergedEnvironments[name] = cleaned;
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
if (managedFloorActive) {
|
|
4988
|
+
mergedPolicy.ignoredTools = [...DEFAULT_CONFIG.policy.ignoredTools];
|
|
4989
|
+
mergedPolicy.sandboxPaths = [...DEFAULT_CONFIG.policy.sandboxPaths];
|
|
4990
|
+
}
|
|
4911
4991
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
4912
4992
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
4913
4993
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
@@ -4976,7 +5056,7 @@ ${error.replace("Invalid config:\n", "")}
|
|
|
4976
5056
|
}
|
|
4977
5057
|
return sanitized;
|
|
4978
5058
|
}
|
|
4979
|
-
var DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig;
|
|
5059
|
+
var DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig, cacheReadFailureLogged;
|
|
4980
5060
|
var init_config = __esm({
|
|
4981
5061
|
"src/config/index.ts"() {
|
|
4982
5062
|
"use strict";
|
|
@@ -4985,6 +5065,7 @@ var init_config = __esm({
|
|
|
4985
5065
|
init_managed();
|
|
4986
5066
|
init_build();
|
|
4987
5067
|
init_trusted_hosts();
|
|
5068
|
+
init_dist();
|
|
4988
5069
|
DANGEROUS_WORDS = [
|
|
4989
5070
|
"mkfs",
|
|
4990
5071
|
// formats/wipes a filesystem partition
|
|
@@ -5262,6 +5343,7 @@ var init_config = __esm({
|
|
|
5262
5343
|
}
|
|
5263
5344
|
];
|
|
5264
5345
|
cachedConfig = null;
|
|
5346
|
+
cacheReadFailureLogged = false;
|
|
5265
5347
|
}
|
|
5266
5348
|
});
|
|
5267
5349
|
|
|
@@ -5982,6 +6064,18 @@ async function isDaemonReachable(timeoutMs = 500) {
|
|
|
5982
6064
|
return false;
|
|
5983
6065
|
}
|
|
5984
6066
|
}
|
|
6067
|
+
async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
6068
|
+
try {
|
|
6069
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/approver`, {
|
|
6070
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6071
|
+
});
|
|
6072
|
+
if (!res.ok) return false;
|
|
6073
|
+
const body = await res.json();
|
|
6074
|
+
return body.interactive === true;
|
|
6075
|
+
} catch {
|
|
6076
|
+
return false;
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
5985
6079
|
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent) {
|
|
5986
6080
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
5987
6081
|
const ctrl = new AbortController();
|
|
@@ -6862,6 +6956,12 @@ function isNetworkTool(toolName, args) {
|
|
|
6862
6956
|
function notifyActivity(data) {
|
|
6863
6957
|
return notifyActivitySocket(data);
|
|
6864
6958
|
}
|
|
6959
|
+
async function hasReachableHumanApprover(opts) {
|
|
6960
|
+
const hasDisplay = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
6961
|
+
const nativeReachable = !opts.calledFromDaemon && opts.approvers.native !== false && hasDisplay;
|
|
6962
|
+
if (nativeReachable) return true;
|
|
6963
|
+
return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
|
|
6964
|
+
}
|
|
6865
6965
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
6866
6966
|
if (!options?.calledFromDaemon) {
|
|
6867
6967
|
const actId = randomUUID();
|
|
@@ -7176,6 +7276,35 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7176
7276
|
return { approved: true, checkedBy: "local-policy" };
|
|
7177
7277
|
}
|
|
7178
7278
|
if (policyResult.decision === "block") {
|
|
7279
|
+
const daemonUp = isDaemonRunning();
|
|
7280
|
+
let humanApproverReachable = false;
|
|
7281
|
+
if (!policyResult.dependsOnStatePredicates?.length && daemonUp && !isTestEnv2) {
|
|
7282
|
+
humanApproverReachable = await hasReachableHumanApprover({
|
|
7283
|
+
approvers,
|
|
7284
|
+
calledFromDaemon: options?.calledFromDaemon
|
|
7285
|
+
});
|
|
7286
|
+
}
|
|
7287
|
+
const mayDowngrade = daemonUp && !isTestEnv2 && humanApproverReachable;
|
|
7288
|
+
const hardBlock = () => {
|
|
7289
|
+
if (!isManual)
|
|
7290
|
+
appendLocalAudit(
|
|
7291
|
+
toolName,
|
|
7292
|
+
args,
|
|
7293
|
+
"deny",
|
|
7294
|
+
"smart-rule-block",
|
|
7295
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
7296
|
+
hashAuditArgs
|
|
7297
|
+
);
|
|
7298
|
+
return {
|
|
7299
|
+
approved: false,
|
|
7300
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7301
|
+
blockedBy: "local-config",
|
|
7302
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
7303
|
+
ruleHit: policyResult.ruleName,
|
|
7304
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7305
|
+
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7306
|
+
};
|
|
7307
|
+
};
|
|
7179
7308
|
if (policyResult.dependsOnStatePredicates?.length) {
|
|
7180
7309
|
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
7181
7310
|
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
@@ -7192,7 +7321,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7192
7321
|
if (predicatesMet && policyResult.recoveryCommand) {
|
|
7193
7322
|
statefulRecoveryCommand = policyResult.recoveryCommand;
|
|
7194
7323
|
}
|
|
7195
|
-
} else if (
|
|
7324
|
+
} else if (mayDowngrade) {
|
|
7196
7325
|
if (!isManual)
|
|
7197
7326
|
appendLocalAudit(
|
|
7198
7327
|
toolName,
|
|
@@ -7214,36 +7343,13 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7214
7343
|
};
|
|
7215
7344
|
}
|
|
7216
7345
|
} else {
|
|
7217
|
-
|
|
7218
|
-
appendLocalAudit(
|
|
7219
|
-
toolName,
|
|
7220
|
-
args,
|
|
7221
|
-
"deny",
|
|
7222
|
-
"smart-rule-block",
|
|
7223
|
-
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
7224
|
-
// panel can attribute this block to its specific shield
|
|
7225
|
-
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
7226
|
-
// rule→shield map. checkedBy stays as the generic
|
|
7227
|
-
// `smart-rule-block` for backward compat with existing
|
|
7228
|
-
// log readers.
|
|
7229
|
-
{ ...meta, ruleName: policyResult.ruleName },
|
|
7230
|
-
hashAuditArgs
|
|
7231
|
-
);
|
|
7232
|
-
return {
|
|
7233
|
-
approved: false,
|
|
7234
|
-
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7235
|
-
blockedBy: "local-config",
|
|
7236
|
-
blockedByLabel: policyResult.blockedByLabel,
|
|
7237
|
-
ruleHit: policyResult.ruleName,
|
|
7238
|
-
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7239
|
-
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7240
|
-
};
|
|
7346
|
+
return hardBlock();
|
|
7241
7347
|
}
|
|
7242
7348
|
}
|
|
7243
7349
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
7244
7350
|
policyMatchedField = policyResult.matchedField;
|
|
7245
7351
|
policyMatchedWord = policyResult.matchedWord;
|
|
7246
|
-
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
7352
|
+
if (policyResult.ruleName || policyResult.tier === 7) localSmartRuleMatched = true;
|
|
7247
7353
|
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
7248
7354
|
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
7249
7355
|
riskMetadata = computeRiskMetadata(
|
|
@@ -7255,7 +7361,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7255
7361
|
policyResult.ruleName
|
|
7256
7362
|
);
|
|
7257
7363
|
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
7258
|
-
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
7364
|
+
const persistent = policyResult.ruleName || policyResult.tier === 7 ? null : getPersistentDecision(toolName);
|
|
7259
7365
|
if (persistent === "allow" && !appPermReview) {
|
|
7260
7366
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
|
|
7261
7367
|
return { approved: true, checkedBy: "persistent" };
|
|
@@ -15924,6 +16030,9 @@ data: ${JSON.stringify(data)}
|
|
|
15924
16030
|
}
|
|
15925
16031
|
});
|
|
15926
16032
|
}
|
|
16033
|
+
function hasInteractiveClient() {
|
|
16034
|
+
return [...sseClients].some((c) => c.capabilities.includes("input"));
|
|
16035
|
+
}
|
|
15927
16036
|
function broadcastForensic(finding) {
|
|
15928
16037
|
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
15929
16038
|
const event = {
|
|
@@ -17154,18 +17263,27 @@ var init_score = __esm({
|
|
|
17154
17263
|
|
|
17155
17264
|
// src/posture/headline.ts
|
|
17156
17265
|
function worstFinding(findings) {
|
|
17157
|
-
|
|
17266
|
+
const cmp = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
17267
|
+
return [...findings].sort(
|
|
17268
|
+
(a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || cmp(a.category, b.category) || cmp(a.title, b.title)
|
|
17269
|
+
)[0];
|
|
17270
|
+
}
|
|
17271
|
+
function actionFromFinding(f) {
|
|
17272
|
+
if (!f?.fix) return null;
|
|
17273
|
+
const fix = f.fix.replace(/^fix it now:\s*/i, "");
|
|
17274
|
+
const where = f.detail.length === 0 ? "" : f.detail.length === 1 ? ` \u2014 found: ${f.detail[0]}` : ` \u2014 found: ${f.detail[0]} and ${f.detail.length - 1} more`;
|
|
17275
|
+
return fix + where;
|
|
17158
17276
|
}
|
|
17159
17277
|
function deriveHeadline(allFindings) {
|
|
17160
17278
|
const findings = allFindings.filter(
|
|
17161
17279
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
|
|
17162
17280
|
);
|
|
17163
17281
|
if (findings.length === 0 || findings.every((f) => f.severity === "advisory")) return null;
|
|
17164
|
-
const
|
|
17165
|
-
const secrets =
|
|
17166
|
-
const egressOpen =
|
|
17167
|
-
const noIsolation =
|
|
17168
|
-
const gateWeak =
|
|
17282
|
+
const has2 = (category) => findings.some((f) => f.category === category);
|
|
17283
|
+
const secrets = has2("Secrets");
|
|
17284
|
+
const egressOpen = has2("Egress");
|
|
17285
|
+
const noIsolation = has2("Isolation");
|
|
17286
|
+
const gateWeak = has2("Approval gate");
|
|
17169
17287
|
const notWired = findings.some((f) => f.category === "Coverage" && f.severity === "critical");
|
|
17170
17288
|
const observeOnly = findings.some((f) => f.category === "Coverage" && f.severity === "high");
|
|
17171
17289
|
let risk;
|
|
@@ -17188,13 +17306,16 @@ function deriveHeadline(allFindings) {
|
|
|
17188
17306
|
} else if (observeOnly) {
|
|
17189
17307
|
action = "Switch node9 to enforcing mode \u2014 right now it is only watching, not blocking.";
|
|
17190
17308
|
} else if (egressOpen) {
|
|
17191
|
-
|
|
17309
|
+
const egressFix = actionFromFinding(
|
|
17310
|
+
worstFinding(findings.filter((f) => f.category === "Egress"))
|
|
17311
|
+
);
|
|
17312
|
+
action = egressFix ? `${egressFix} Closing the exit breaks the exfiltration chain.` : "lock egress to an allowlist (node9 can enforce it) \u2014 it closes the exit the exfiltration needs.";
|
|
17192
17313
|
} else if (secrets) {
|
|
17193
|
-
action = "node9 can block reads of sensitive
|
|
17314
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Secrets"))) ?? "node9 can block reads of sensitive credential files in-path (`node9 shield enable project-jail`).";
|
|
17194
17315
|
} else if (gateWeak) {
|
|
17195
|
-
action = "node9 can enforce destructive-command blocking in-path.";
|
|
17316
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Approval gate"))) ?? "node9 can enforce destructive-command blocking in-path (`node9 shield enable bash-safe`).";
|
|
17196
17317
|
} else {
|
|
17197
|
-
action = worstFinding(findings)
|
|
17318
|
+
action = actionFromFinding(worstFinding(findings)) ?? "Review the findings below.";
|
|
17198
17319
|
}
|
|
17199
17320
|
return { risk, action };
|
|
17200
17321
|
}
|
|
@@ -17733,6 +17854,21 @@ function inventoryMcp(home = os32.homedir()) {
|
|
|
17733
17854
|
}
|
|
17734
17855
|
return out;
|
|
17735
17856
|
}
|
|
17857
|
+
function inventoryServerKeys(inv) {
|
|
17858
|
+
const keys = /* @__PURE__ */ new Set();
|
|
17859
|
+
for (const e of inv) {
|
|
17860
|
+
if (e.state === "gatewayed") {
|
|
17861
|
+
const i = e.args.indexOf("--upstream");
|
|
17862
|
+
if (i >= 0 && e.args[i + 1]) {
|
|
17863
|
+
keys.add(getServerKey(e.args[i + 1]));
|
|
17864
|
+
}
|
|
17865
|
+
} else if (e.state === "ungoverned") {
|
|
17866
|
+
const cmd = [e.command, ...e.args].map(quoteArg).join(" ");
|
|
17867
|
+
keys.add(getServerKey(cmd));
|
|
17868
|
+
}
|
|
17869
|
+
}
|
|
17870
|
+
return keys;
|
|
17871
|
+
}
|
|
17736
17872
|
function writeMcpEntry(mcpFile, format, name, entry) {
|
|
17737
17873
|
const key = format === "toml" ? "mcp_servers" : "mcpServers";
|
|
17738
17874
|
let root = {};
|
|
@@ -17756,6 +17892,7 @@ var init_mcp_wrap = __esm({
|
|
|
17756
17892
|
"use strict";
|
|
17757
17893
|
init_agent_wiring();
|
|
17758
17894
|
init_mcp_cmd();
|
|
17895
|
+
init_mcp_pin();
|
|
17759
17896
|
init_mcp_cmd();
|
|
17760
17897
|
}
|
|
17761
17898
|
});
|
|
@@ -18187,9 +18324,12 @@ function extractManagedConfig(body) {
|
|
|
18187
18324
|
return out.mode !== void 0 || out.egress !== void 0 || out.dlp !== void 0 || out.approvers !== void 0 || out.reviewChannel !== void 0 || out.approvalTimeoutMs !== void 0 || out.injectionScan !== void 0 || out.loopDetection !== void 0 || out.skillPinning !== void 0 || out.jailPaths !== void 0 || out.trustedHosts !== void 0 || out.appPermissions !== void 0 ? out : void 0;
|
|
18188
18325
|
}
|
|
18189
18326
|
function writeCache2(cache) {
|
|
18190
|
-
const
|
|
18191
|
-
|
|
18192
|
-
|
|
18327
|
+
const data = JSON.stringify(cache, null, 2) + "\n";
|
|
18328
|
+
atomicWriteSync2(rulesCacheFile(), data, "utf-8");
|
|
18329
|
+
try {
|
|
18330
|
+
atomicWriteSync2(rulesCacheBackupFile(), data, "utf-8");
|
|
18331
|
+
} catch {
|
|
18332
|
+
}
|
|
18193
18333
|
}
|
|
18194
18334
|
async function syncOnce() {
|
|
18195
18335
|
const creds = readCredentials();
|
|
@@ -18483,7 +18623,7 @@ function startForensicBroadcast() {
|
|
|
18483
18623
|
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
18484
18624
|
recurring.unref();
|
|
18485
18625
|
}
|
|
18486
|
-
var FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL2, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, syncHealthFile, STALE_MIN_MS, STALE_MAX_MS, STALE_FACTOR, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
18626
|
+
var FINDING_TO_SIGNAL3, rulesCacheFile, rulesCacheBackupFile, DEFAULT_API_URL2, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, syncHealthFile, STALE_MIN_MS, STALE_MAX_MS, STALE_FACTOR, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
18487
18627
|
var init_sync = __esm({
|
|
18488
18628
|
"src/daemon/sync.ts"() {
|
|
18489
18629
|
"use strict";
|
|
@@ -18493,6 +18633,7 @@ var init_sync = __esm({
|
|
|
18493
18633
|
init_ship();
|
|
18494
18634
|
init_build2();
|
|
18495
18635
|
init_mcp_tools();
|
|
18636
|
+
init_state2();
|
|
18496
18637
|
init_mcp_status();
|
|
18497
18638
|
init_ship2();
|
|
18498
18639
|
init_shields();
|
|
@@ -18513,6 +18654,7 @@ var init_sync = __esm({
|
|
|
18513
18654
|
"long-output-redacted": "longOutputRedactions"
|
|
18514
18655
|
};
|
|
18515
18656
|
rulesCacheFile = () => path35.join(os33.homedir(), ".node9", "rules-cache.json");
|
|
18657
|
+
rulesCacheBackupFile = () => path35.join(os33.homedir(), ".node9", "rules-cache.last-good.json");
|
|
18516
18658
|
DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
18517
18659
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
18518
18660
|
MIN_INTERVAL_SECONDS = 15;
|
|
@@ -18754,6 +18896,53 @@ var init_audit_shipper = __esm({
|
|
|
18754
18896
|
}
|
|
18755
18897
|
});
|
|
18756
18898
|
|
|
18899
|
+
// src/audit/decision.ts
|
|
18900
|
+
function classifyDecision(a, b) {
|
|
18901
|
+
const isRow = !!a && typeof a === "object";
|
|
18902
|
+
const decision = isRow ? a.decision : a;
|
|
18903
|
+
const attribution = isRow ? a.checkedBy ?? a.source : b;
|
|
18904
|
+
const raw = typeof decision === "string" ? decision : String(decision ?? "");
|
|
18905
|
+
const src = typeof attribution === "string" ? attribution.toLowerCase() : "";
|
|
18906
|
+
const d = raw.toLowerCase();
|
|
18907
|
+
if (src && has(src, "observe-mode")) {
|
|
18908
|
+
return { outcome: "observe", label: "Would block", raw };
|
|
18909
|
+
}
|
|
18910
|
+
if (d === "dlp") return { outcome: "info", label: "Finding", raw };
|
|
18911
|
+
if (d === "mcp-discovered") return { outcome: "info", label: "Info", raw };
|
|
18912
|
+
if (d === "allow" || d === "allowed") {
|
|
18913
|
+
if (src === "post-hook") return { outcome: "allow", label: "Ran", raw };
|
|
18914
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18915
|
+
return { outcome: "allow", label: "Approved", raw };
|
|
18916
|
+
}
|
|
18917
|
+
return { outcome: "allow", label: "Auto-allowed", raw };
|
|
18918
|
+
}
|
|
18919
|
+
if (d === "deny" || d === "auto-deny" || d === "block") {
|
|
18920
|
+
if (TIMEOUT_SOURCES.has(src)) {
|
|
18921
|
+
return { outcome: "deny", label: "Timed out", raw };
|
|
18922
|
+
}
|
|
18923
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18924
|
+
return { outcome: "deny", label: "Denied", raw };
|
|
18925
|
+
}
|
|
18926
|
+
return { outcome: "deny", label: "Blocked", raw };
|
|
18927
|
+
}
|
|
18928
|
+
if (d === "review" || d === "pending") {
|
|
18929
|
+
return { outcome: "info", label: "Pending", raw };
|
|
18930
|
+
}
|
|
18931
|
+
return { outcome: "unknown", label: raw ? `? ${raw}` : "? (none)", raw };
|
|
18932
|
+
}
|
|
18933
|
+
function decisionTag(view) {
|
|
18934
|
+
return `[${view.label}]`.padEnd(14);
|
|
18935
|
+
}
|
|
18936
|
+
var HUMAN_SOURCES, TIMEOUT_SOURCES, has;
|
|
18937
|
+
var init_decision = __esm({
|
|
18938
|
+
"src/audit/decision.ts"() {
|
|
18939
|
+
"use strict";
|
|
18940
|
+
HUMAN_SOURCES = /* @__PURE__ */ new Set(["daemon", "cloud", "local-decision", "inline-review-approved"]);
|
|
18941
|
+
TIMEOUT_SOURCES = /* @__PURE__ */ new Set(["timeout"]);
|
|
18942
|
+
has = (s, needle) => s.includes(needle);
|
|
18943
|
+
}
|
|
18944
|
+
});
|
|
18945
|
+
|
|
18757
18946
|
// src/daemon/dlp-scanner.ts
|
|
18758
18947
|
import fs38 from "fs";
|
|
18759
18948
|
import path37 from "path";
|
|
@@ -18993,6 +19182,7 @@ function runMcpReconcile() {
|
|
|
18993
19182
|
}
|
|
18994
19183
|
const baseline = loadBaseline();
|
|
18995
19184
|
const creds = getCredentials();
|
|
19185
|
+
reconcileStale(inv, creds);
|
|
18996
19186
|
const fresh = inv.filter((e) => e.state === "ungoverned" && !baseline.has(idKey(e)));
|
|
18997
19187
|
if (fresh.length === 0) return;
|
|
18998
19188
|
const wrappedAgents = /* @__PURE__ */ new Set();
|
|
@@ -19032,6 +19222,77 @@ function runMcpReconcile() {
|
|
|
19032
19222
|
}
|
|
19033
19223
|
saveBaseline(baseline);
|
|
19034
19224
|
}
|
|
19225
|
+
function reconcileStale(inv, creds) {
|
|
19226
|
+
let pins;
|
|
19227
|
+
try {
|
|
19228
|
+
pins = readMcpPins();
|
|
19229
|
+
} catch {
|
|
19230
|
+
return;
|
|
19231
|
+
}
|
|
19232
|
+
const serverKeys = Object.keys(pins.servers);
|
|
19233
|
+
if (serverKeys.length === 0) return;
|
|
19234
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
19235
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19236
|
+
let dirty = false;
|
|
19237
|
+
for (const sk of serverKeys) {
|
|
19238
|
+
const pin = pins.servers[sk];
|
|
19239
|
+
if (liveKeys.has(sk)) {
|
|
19240
|
+
if (pin.lastSeen !== now) {
|
|
19241
|
+
pin.lastSeen = now;
|
|
19242
|
+
dirty = true;
|
|
19243
|
+
}
|
|
19244
|
+
} else if (!pin.lastSeen) {
|
|
19245
|
+
pin.lastSeen = pin.pinnedAt;
|
|
19246
|
+
dirty = true;
|
|
19247
|
+
}
|
|
19248
|
+
}
|
|
19249
|
+
const staleDays = getConfig().settings.mcpStaleAfterDays ?? DEFAULT_STALE_DAYS;
|
|
19250
|
+
if (staleDays > 0 && liveKeys.size > 0) {
|
|
19251
|
+
const staleMs = staleDays * 864e5;
|
|
19252
|
+
for (const sk of serverKeys) {
|
|
19253
|
+
const pin = pins.servers[sk];
|
|
19254
|
+
if (liveKeys.has(sk)) continue;
|
|
19255
|
+
const age = Date.now() - Date.parse(pin.lastSeen ?? pin.pinnedAt);
|
|
19256
|
+
if (age >= staleMs) {
|
|
19257
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
19258
|
+
event: "mcp-pin-auto-removed",
|
|
19259
|
+
serverKey: sk,
|
|
19260
|
+
label: pin.label,
|
|
19261
|
+
lastSeen: pin.lastSeen,
|
|
19262
|
+
pinnedAt: pin.pinnedAt
|
|
19263
|
+
});
|
|
19264
|
+
if (creds) {
|
|
19265
|
+
try {
|
|
19266
|
+
void auditLocalAllow(
|
|
19267
|
+
`mcp-server:${sk}`,
|
|
19268
|
+
{
|
|
19269
|
+
serverKey: sk,
|
|
19270
|
+
label: pin.label,
|
|
19271
|
+
lastSeen: pin.lastSeen,
|
|
19272
|
+
reason: "stale",
|
|
19273
|
+
staleDays
|
|
19274
|
+
},
|
|
19275
|
+
"mcp-server-removed",
|
|
19276
|
+
creds,
|
|
19277
|
+
{ mcpServer: pin.label },
|
|
19278
|
+
void 0,
|
|
19279
|
+
false
|
|
19280
|
+
);
|
|
19281
|
+
} catch {
|
|
19282
|
+
}
|
|
19283
|
+
}
|
|
19284
|
+
delete pins.servers[sk];
|
|
19285
|
+
dirty = true;
|
|
19286
|
+
}
|
|
19287
|
+
}
|
|
19288
|
+
}
|
|
19289
|
+
if (dirty) {
|
|
19290
|
+
try {
|
|
19291
|
+
writeMcpPins(pins);
|
|
19292
|
+
} catch {
|
|
19293
|
+
}
|
|
19294
|
+
}
|
|
19295
|
+
}
|
|
19035
19296
|
function startMcpReconciler() {
|
|
19036
19297
|
setImmediate(() => {
|
|
19037
19298
|
try {
|
|
@@ -19053,7 +19314,7 @@ function startMcpReconciler() {
|
|
|
19053
19314
|
};
|
|
19054
19315
|
schedule();
|
|
19055
19316
|
}
|
|
19056
|
-
var BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN;
|
|
19317
|
+
var BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN, DEFAULT_STALE_DAYS;
|
|
19057
19318
|
var init_mcp_reconciler = __esm({
|
|
19058
19319
|
"src/daemon/mcp-reconciler.ts"() {
|
|
19059
19320
|
"use strict";
|
|
@@ -19062,9 +19323,11 @@ var init_mcp_reconciler = __esm({
|
|
|
19062
19323
|
init_config();
|
|
19063
19324
|
init_cloud();
|
|
19064
19325
|
init_audit();
|
|
19326
|
+
init_mcp_pin();
|
|
19065
19327
|
BASELINE_FILE2 = path38.join(os36.homedir(), ".node9", "mcp-baseline.json");
|
|
19066
19328
|
BASELINE_CAP = 500;
|
|
19067
19329
|
DEFAULT_INTERVAL_MIN = 60;
|
|
19330
|
+
DEFAULT_STALE_DAYS = 7;
|
|
19068
19331
|
}
|
|
19069
19332
|
});
|
|
19070
19333
|
|
|
@@ -19139,20 +19402,87 @@ var init_hook_heal = __esm({
|
|
|
19139
19402
|
import fs40 from "fs";
|
|
19140
19403
|
import path39 from "path";
|
|
19141
19404
|
import os37 from "os";
|
|
19405
|
+
function capStartupLog(file) {
|
|
19406
|
+
try {
|
|
19407
|
+
if (fs40.statSync(file).size > MAX_STARTUP_LOG_BYTES) fs40.truncateSync(file);
|
|
19408
|
+
} catch {
|
|
19409
|
+
}
|
|
19410
|
+
}
|
|
19142
19411
|
function openStartupLogFd() {
|
|
19143
19412
|
try {
|
|
19144
19413
|
const file = DAEMON_STARTUP_LOG();
|
|
19145
19414
|
const dir = path39.dirname(file);
|
|
19146
19415
|
if (!fs40.existsSync(dir)) fs40.mkdirSync(dir, { recursive: true });
|
|
19147
|
-
|
|
19148
|
-
if (fs40.statSync(file).size > MAX_STARTUP_LOG_BYTES) fs40.truncateSync(file);
|
|
19149
|
-
} catch {
|
|
19150
|
-
}
|
|
19416
|
+
capStartupLog(file);
|
|
19151
19417
|
return fs40.openSync(file, "a");
|
|
19152
19418
|
} catch {
|
|
19153
19419
|
return void 0;
|
|
19154
19420
|
}
|
|
19155
19421
|
}
|
|
19422
|
+
function recordStartupState(outcome, kind, detail) {
|
|
19423
|
+
try {
|
|
19424
|
+
if (outcome === "starting") {
|
|
19425
|
+
const prev = readStartupState();
|
|
19426
|
+
if (prev?.outcome === "starting") {
|
|
19427
|
+
const prevAt = new Date(prev.at).getTime();
|
|
19428
|
+
const age = Math.abs(Date.now() - prevAt);
|
|
19429
|
+
if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
|
|
19430
|
+
}
|
|
19431
|
+
}
|
|
19432
|
+
const file = DAEMON_STARTUP_STATE();
|
|
19433
|
+
const dir = path39.dirname(file);
|
|
19434
|
+
if (!fs40.existsSync(dir)) fs40.mkdirSync(dir, { recursive: true });
|
|
19435
|
+
const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
19436
|
+
if (kind) state.kind = kind;
|
|
19437
|
+
if (detail) state.detail = detail.slice(0, MAX_DETAIL);
|
|
19438
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
19439
|
+
try {
|
|
19440
|
+
fs40.writeFileSync(tmp, JSON.stringify(state), "utf-8");
|
|
19441
|
+
fs40.renameSync(tmp, file);
|
|
19442
|
+
} catch (err2) {
|
|
19443
|
+
try {
|
|
19444
|
+
fs40.unlinkSync(tmp);
|
|
19445
|
+
} catch {
|
|
19446
|
+
}
|
|
19447
|
+
throw err2;
|
|
19448
|
+
}
|
|
19449
|
+
} catch {
|
|
19450
|
+
}
|
|
19451
|
+
}
|
|
19452
|
+
function readStartupState() {
|
|
19453
|
+
try {
|
|
19454
|
+
const raw = fs40.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
|
|
19455
|
+
const s = JSON.parse(raw);
|
|
19456
|
+
if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
|
|
19457
|
+
return s;
|
|
19458
|
+
} catch {
|
|
19459
|
+
return null;
|
|
19460
|
+
}
|
|
19461
|
+
}
|
|
19462
|
+
function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
19463
|
+
const s = readStartupState();
|
|
19464
|
+
if (!s) return null;
|
|
19465
|
+
const at = new Date(s.at);
|
|
19466
|
+
if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
|
|
19467
|
+
switch (s.outcome) {
|
|
19468
|
+
case "ok":
|
|
19469
|
+
case "ok-elsewhere":
|
|
19470
|
+
return null;
|
|
19471
|
+
case "starting":
|
|
19472
|
+
if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
|
|
19473
|
+
return {
|
|
19474
|
+
kind: "did-not-start",
|
|
19475
|
+
detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
|
|
19476
|
+
at,
|
|
19477
|
+
// `at` is the FIRST attempt of the streak, not the most recent one.
|
|
19478
|
+
label: "start attempts failing since"
|
|
19479
|
+
};
|
|
19480
|
+
case "failed":
|
|
19481
|
+
return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
|
|
19482
|
+
default:
|
|
19483
|
+
return null;
|
|
19484
|
+
}
|
|
19485
|
+
}
|
|
19156
19486
|
function logDaemonStartup(kind, detail) {
|
|
19157
19487
|
try {
|
|
19158
19488
|
const file = DAEMON_STARTUP_LOG();
|
|
@@ -19164,12 +19494,15 @@ function logDaemonStartup(kind, detail) {
|
|
|
19164
19494
|
} catch {
|
|
19165
19495
|
}
|
|
19166
19496
|
}
|
|
19167
|
-
var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES;
|
|
19497
|
+
var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
|
|
19168
19498
|
var init_startup_log = __esm({
|
|
19169
19499
|
"src/daemon/startup-log.ts"() {
|
|
19170
19500
|
"use strict";
|
|
19171
19501
|
DAEMON_STARTUP_LOG = () => path39.join(os37.homedir(), ".node9", "daemon-startup.log");
|
|
19172
19502
|
MAX_STARTUP_LOG_BYTES = 256 * 1024;
|
|
19503
|
+
DAEMON_STARTUP_STATE = () => path39.join(os37.homedir(), ".node9", "daemon-startup-state.json");
|
|
19504
|
+
MAX_DETAIL = 200;
|
|
19505
|
+
STARTING_GRACE_MS = 90 * 1e3;
|
|
19173
19506
|
}
|
|
19174
19507
|
});
|
|
19175
19508
|
|
|
@@ -19181,6 +19514,76 @@ import os38 from "os";
|
|
|
19181
19514
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
19182
19515
|
import { spawnSync } from "child_process";
|
|
19183
19516
|
import chalk6 from "chalk";
|
|
19517
|
+
function buildDaemonReport(allEntries, period, now) {
|
|
19518
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19519
|
+
let start = new Date(todayStart);
|
|
19520
|
+
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19521
|
+
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19522
|
+
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19523
|
+
const entries = allEntries.filter((e) => {
|
|
19524
|
+
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19525
|
+
return new Date(e.ts) >= start;
|
|
19526
|
+
});
|
|
19527
|
+
const isBlocked = (e) => classifyDecision(e).outcome === "deny";
|
|
19528
|
+
const checkedBy = (e) => typeof e.checkedBy === "string" ? e.checkedBy : void 0;
|
|
19529
|
+
const summary = {
|
|
19530
|
+
total: entries.length,
|
|
19531
|
+
// The inline `startsWith('allow')` this replaces counted every non-allow
|
|
19532
|
+
// row as "blocked" — so DLP findings, MCP-discovery events and, worst, all
|
|
19533
|
+
// the observe-mode "would have blocked" rows inflated the blocked count
|
|
19534
|
+
// with things that were never refusals.
|
|
19535
|
+
allowed: entries.filter((e) => classifyDecision(e).outcome === "allow").length,
|
|
19536
|
+
blocked: entries.filter(isBlocked).length,
|
|
19537
|
+
dlp: entries.filter((e) => checkedBy(e)?.includes("dlp")).length,
|
|
19538
|
+
loops: entries.filter((e) => checkedBy(e) === "loop-detected").length
|
|
19539
|
+
};
|
|
19540
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
19541
|
+
if (period === "today") {
|
|
19542
|
+
for (let h = 0; h < 24; h++) {
|
|
19543
|
+
const key = String(h).padStart(2, "0") + ":00";
|
|
19544
|
+
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19545
|
+
}
|
|
19546
|
+
for (const e of entries) {
|
|
19547
|
+
const hour = new Date(e.ts).getHours();
|
|
19548
|
+
const key = String(hour).padStart(2, "0") + ":00";
|
|
19549
|
+
const d = dailyMap.get(key);
|
|
19550
|
+
d.calls++;
|
|
19551
|
+
if (isBlocked(e)) d.blocked++;
|
|
19552
|
+
}
|
|
19553
|
+
} else {
|
|
19554
|
+
for (const e of entries) {
|
|
19555
|
+
const date = e.ts.slice(0, 10);
|
|
19556
|
+
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19557
|
+
d.calls++;
|
|
19558
|
+
if (isBlocked(e)) d.blocked++;
|
|
19559
|
+
dailyMap.set(date, d);
|
|
19560
|
+
}
|
|
19561
|
+
}
|
|
19562
|
+
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19563
|
+
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19564
|
+
for (const e of entries) {
|
|
19565
|
+
const tool = String(e.tool ?? "");
|
|
19566
|
+
topToolsMap.set(tool, (topToolsMap.get(tool) || 0) + 1);
|
|
19567
|
+
if (isBlocked(e)) topBlockedMap.set(tool, (topBlockedMap.get(tool) || 0) + 1);
|
|
19568
|
+
}
|
|
19569
|
+
const top5 = (m) => [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19570
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
19571
|
+
for (const e of entries) {
|
|
19572
|
+
const key = e.agent || "unknown";
|
|
19573
|
+
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19574
|
+
a.total++;
|
|
19575
|
+
if (isBlocked(e)) a.blocked++;
|
|
19576
|
+
if (checkedBy(e)?.includes("dlp")) a.dlp++;
|
|
19577
|
+
agentMap.set(key, a);
|
|
19578
|
+
}
|
|
19579
|
+
return {
|
|
19580
|
+
summary,
|
|
19581
|
+
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19582
|
+
topTools: top5(topToolsMap),
|
|
19583
|
+
topBlockedTools: top5(topBlockedMap),
|
|
19584
|
+
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
19585
|
+
};
|
|
19586
|
+
}
|
|
19184
19587
|
function startDaemon() {
|
|
19185
19588
|
try {
|
|
19186
19589
|
startCostSync();
|
|
@@ -19195,6 +19598,7 @@ function startDaemon() {
|
|
|
19195
19598
|
const stack = err2 instanceof Error ? err2.stack ?? err2.message : String(err2);
|
|
19196
19599
|
console.error("\n\u{1F6D1} Node9 daemon startup failed:\n" + stack);
|
|
19197
19600
|
logDaemonStartup("startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19601
|
+
recordStartupState("failed", "startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19198
19602
|
process.exit(1);
|
|
19199
19603
|
}
|
|
19200
19604
|
const internalToken = randomUUID4();
|
|
@@ -19567,6 +19971,10 @@ data: ${JSON.stringify(item.data)}
|
|
|
19567
19971
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
19568
19972
|
}
|
|
19569
19973
|
}
|
|
19974
|
+
if (req.method === "GET" && pathname === "/approver") {
|
|
19975
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19976
|
+
return res.end(JSON.stringify({ interactive: hasInteractiveClient() }));
|
|
19977
|
+
}
|
|
19570
19978
|
if (req.method === "GET" && pathname === "/state/check") {
|
|
19571
19979
|
const predicatesParam = reqUrl.searchParams.get("predicates") ?? "";
|
|
19572
19980
|
const predicates = predicatesParam.split(",").filter(Boolean);
|
|
@@ -19668,75 +20076,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
19668
20076
|
return [];
|
|
19669
20077
|
}
|
|
19670
20078
|
});
|
|
19671
|
-
const now = /* @__PURE__ */ new Date();
|
|
19672
|
-
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19673
|
-
let start = new Date(todayStart);
|
|
19674
|
-
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19675
|
-
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19676
|
-
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19677
|
-
const entries = allEntries.filter((e) => {
|
|
19678
|
-
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19679
|
-
return new Date(e.ts) >= start;
|
|
19680
|
-
});
|
|
19681
|
-
const summary = {
|
|
19682
|
-
total: entries.length,
|
|
19683
|
-
allowed: entries.filter((e) => e.decision && e.decision.startsWith("allow")).length,
|
|
19684
|
-
blocked: entries.filter((e) => e.decision && !e.decision.startsWith("allow")).length,
|
|
19685
|
-
dlp: entries.filter((e) => e.checkedBy && e.checkedBy.includes("dlp")).length,
|
|
19686
|
-
loops: entries.filter((e) => e.checkedBy === "loop-detected").length
|
|
19687
|
-
};
|
|
19688
|
-
const dailyMap = /* @__PURE__ */ new Map();
|
|
19689
|
-
if (period === "today") {
|
|
19690
|
-
for (let h = 0; h < 24; h++) {
|
|
19691
|
-
const key = String(h).padStart(2, "0") + ":00";
|
|
19692
|
-
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19693
|
-
}
|
|
19694
|
-
for (const e of entries) {
|
|
19695
|
-
const hour = new Date(e.ts).getHours();
|
|
19696
|
-
const key = String(hour).padStart(2, "0") + ":00";
|
|
19697
|
-
const d = dailyMap.get(key);
|
|
19698
|
-
d.calls++;
|
|
19699
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19700
|
-
}
|
|
19701
|
-
} else {
|
|
19702
|
-
for (const e of entries) {
|
|
19703
|
-
const date = e.ts.slice(0, 10);
|
|
19704
|
-
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19705
|
-
d.calls++;
|
|
19706
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19707
|
-
dailyMap.set(date, d);
|
|
19708
|
-
}
|
|
19709
|
-
}
|
|
19710
|
-
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19711
|
-
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19712
|
-
for (const e of entries) {
|
|
19713
|
-
topToolsMap.set(e.tool, (topToolsMap.get(e.tool) || 0) + 1);
|
|
19714
|
-
if (e.decision && !e.decision.startsWith("allow")) {
|
|
19715
|
-
topBlockedMap.set(e.tool, (topBlockedMap.get(e.tool) || 0) + 1);
|
|
19716
|
-
}
|
|
19717
|
-
}
|
|
19718
|
-
const topTools = [...topToolsMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19719
|
-
const topBlockedTools = [...topBlockedMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19720
|
-
const agentMap = /* @__PURE__ */ new Map();
|
|
19721
|
-
for (const e of entries) {
|
|
19722
|
-
const key = e.agent || "unknown";
|
|
19723
|
-
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19724
|
-
a.total++;
|
|
19725
|
-
if (e.decision && !e.decision.startsWith("allow")) a.blocked++;
|
|
19726
|
-
if (e.checkedBy?.includes("dlp")) a.dlp++;
|
|
19727
|
-
agentMap.set(key, a);
|
|
19728
|
-
}
|
|
19729
|
-
const byAgent = [...agentMap.values()].sort((a, b) => b.total - a.total);
|
|
19730
20079
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19731
|
-
return res.end(
|
|
19732
|
-
JSON.stringify({
|
|
19733
|
-
summary,
|
|
19734
|
-
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19735
|
-
topTools,
|
|
19736
|
-
topBlockedTools,
|
|
19737
|
-
byAgent
|
|
19738
|
-
})
|
|
19739
|
-
);
|
|
20080
|
+
return res.end(JSON.stringify(buildDaemonReport(allEntries, period, /* @__PURE__ */ new Date())));
|
|
19740
20081
|
} catch {
|
|
19741
20082
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
19742
20083
|
return res.end(JSON.stringify({ error: "Failed to parse report" }));
|
|
@@ -20039,6 +20380,23 @@ data: ${JSON.stringify(item.data)}
|
|
|
20039
20380
|
res.writeHead(404).end();
|
|
20040
20381
|
});
|
|
20041
20382
|
setDaemonServer(server);
|
|
20383
|
+
let bindAttempts = 0;
|
|
20384
|
+
const MAX_BIND_ATTEMPTS = 3;
|
|
20385
|
+
function retryListen() {
|
|
20386
|
+
if (++bindAttempts >= MAX_BIND_ATTEMPTS) {
|
|
20387
|
+
logDaemonStartup(
|
|
20388
|
+
"port-unavailable",
|
|
20389
|
+
`:${DAEMON_PORT} is held by something that is not a node9 daemon`
|
|
20390
|
+
);
|
|
20391
|
+
recordStartupState(
|
|
20392
|
+
"failed",
|
|
20393
|
+
"port-unavailable",
|
|
20394
|
+
`:${DAEMON_PORT} is held by another process that is not a node9 daemon \u2014 free the port, then: node9 daemon --background`
|
|
20395
|
+
);
|
|
20396
|
+
return process.exit(0);
|
|
20397
|
+
}
|
|
20398
|
+
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
20399
|
+
}
|
|
20042
20400
|
server.on("error", (e) => {
|
|
20043
20401
|
if (e.code === "EADDRINUSE") {
|
|
20044
20402
|
try {
|
|
@@ -20046,6 +20404,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
20046
20404
|
const { pid } = JSON.parse(fs41.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
20047
20405
|
process.kill(pid, 0);
|
|
20048
20406
|
logDaemonStartup("port-in-use", `another daemon (pid ${pid}) owns :${DAEMON_PORT}`);
|
|
20407
|
+
recordStartupState("ok-elsewhere");
|
|
20049
20408
|
return process.exit(0);
|
|
20050
20409
|
}
|
|
20051
20410
|
} catch {
|
|
@@ -20053,13 +20412,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
20053
20412
|
fs41.unlinkSync(DAEMON_PID_FILE);
|
|
20054
20413
|
} catch {
|
|
20055
20414
|
}
|
|
20056
|
-
|
|
20415
|
+
retryListen();
|
|
20057
20416
|
return;
|
|
20058
20417
|
}
|
|
20059
20418
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/settings`, {
|
|
20060
20419
|
signal: AbortSignal.timeout(1e3)
|
|
20061
20420
|
}).then((res) => {
|
|
20062
20421
|
if (res.ok) {
|
|
20422
|
+
let adopted = false;
|
|
20063
20423
|
try {
|
|
20064
20424
|
let orphanPid = null;
|
|
20065
20425
|
const ss = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
@@ -20087,19 +20447,38 @@ data: ${JSON.stringify(item.data)}
|
|
|
20087
20447
|
JSON.stringify({ pid: orphanPid, port: DAEMON_PORT, internalToken, autoStarted }),
|
|
20088
20448
|
{ mode: 384 }
|
|
20089
20449
|
);
|
|
20450
|
+
adopted = true;
|
|
20090
20451
|
}
|
|
20091
20452
|
} catch {
|
|
20092
20453
|
}
|
|
20454
|
+
if (adopted) {
|
|
20455
|
+
logDaemonStartup(
|
|
20456
|
+
"port-in-use-orphan",
|
|
20457
|
+
`adopted the daemon already on :${DAEMON_PORT}`
|
|
20458
|
+
);
|
|
20459
|
+
recordStartupState("ok-elsewhere");
|
|
20460
|
+
} else {
|
|
20461
|
+
logDaemonStartup(
|
|
20462
|
+
"orphan-unidentified",
|
|
20463
|
+
`healthy daemon on :${DAEMON_PORT} could not be identified \u2014 no pid file written`
|
|
20464
|
+
);
|
|
20465
|
+
recordStartupState(
|
|
20466
|
+
"failed",
|
|
20467
|
+
"orphan-unidentified",
|
|
20468
|
+
`a healthy daemon is running on :${DAEMON_PORT} but its process could not be identified, so node9 cannot track it \u2014 install \`ss\` or \`lsof\`, or restart it with: node9 daemon --background`
|
|
20469
|
+
);
|
|
20470
|
+
}
|
|
20093
20471
|
process.exit(0);
|
|
20094
20472
|
} else {
|
|
20095
|
-
|
|
20473
|
+
retryListen();
|
|
20096
20474
|
}
|
|
20097
20475
|
}).catch(() => {
|
|
20098
|
-
|
|
20476
|
+
retryListen();
|
|
20099
20477
|
});
|
|
20100
20478
|
return;
|
|
20101
20479
|
}
|
|
20102
20480
|
logDaemonStartup("bind-failed", e.message);
|
|
20481
|
+
recordStartupState("failed", "bind-failed", e.message);
|
|
20103
20482
|
console.error(chalk6.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
|
|
20104
20483
|
process.exit(1);
|
|
20105
20484
|
});
|
|
@@ -20117,6 +20496,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
20117
20496
|
{ mode: 384 }
|
|
20118
20497
|
);
|
|
20119
20498
|
console.error(chalk6.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE on 127.0.0.1:${DAEMON_PORT}`));
|
|
20499
|
+
logDaemonStartup("ok", `listening on ${DAEMON_HOST}:${DAEMON_PORT}`);
|
|
20500
|
+
recordStartupState("ok");
|
|
20120
20501
|
});
|
|
20121
20502
|
if (watchMode) {
|
|
20122
20503
|
console.error(chalk6.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
@@ -20134,6 +20515,7 @@ var init_server = __esm({
|
|
|
20134
20515
|
init_costSync();
|
|
20135
20516
|
init_sync();
|
|
20136
20517
|
init_audit_shipper();
|
|
20518
|
+
init_decision();
|
|
20137
20519
|
init_dlp_scanner();
|
|
20138
20520
|
init_mcp_reconciler();
|
|
20139
20521
|
init_hook_heal();
|
|
@@ -45316,7 +45698,7 @@ __export(tail_exports, {
|
|
|
45316
45698
|
});
|
|
45317
45699
|
import http5 from "http";
|
|
45318
45700
|
import chalk40 from "chalk";
|
|
45319
|
-
import
|
|
45701
|
+
import fs72 from "fs";
|
|
45320
45702
|
import os61 from "os";
|
|
45321
45703
|
import path68 from "path";
|
|
45322
45704
|
import readline6 from "readline";
|
|
@@ -45343,19 +45725,19 @@ function getModelContextLimit(model) {
|
|
|
45343
45725
|
}
|
|
45344
45726
|
function readSessionUsage() {
|
|
45345
45727
|
const projectsDir = path68.join(os61.homedir(), ".claude", "projects");
|
|
45346
|
-
if (!
|
|
45728
|
+
if (!fs72.existsSync(projectsDir)) return null;
|
|
45347
45729
|
let latestFile = null;
|
|
45348
45730
|
let latestMtime = 0;
|
|
45349
45731
|
try {
|
|
45350
|
-
for (const dir of
|
|
45732
|
+
for (const dir of fs72.readdirSync(projectsDir)) {
|
|
45351
45733
|
const dirPath = path68.join(projectsDir, dir);
|
|
45352
45734
|
try {
|
|
45353
|
-
if (!
|
|
45354
|
-
for (const file of
|
|
45735
|
+
if (!fs72.statSync(dirPath).isDirectory()) continue;
|
|
45736
|
+
for (const file of fs72.readdirSync(dirPath)) {
|
|
45355
45737
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
45356
45738
|
const filePath = path68.join(dirPath, file);
|
|
45357
45739
|
try {
|
|
45358
|
-
const mtime =
|
|
45740
|
+
const mtime = fs72.statSync(filePath).mtimeMs;
|
|
45359
45741
|
if (mtime > latestMtime) {
|
|
45360
45742
|
latestMtime = mtime;
|
|
45361
45743
|
latestFile = filePath;
|
|
@@ -45370,7 +45752,7 @@ function readSessionUsage() {
|
|
|
45370
45752
|
}
|
|
45371
45753
|
if (!latestFile) return null;
|
|
45372
45754
|
try {
|
|
45373
|
-
const lines =
|
|
45755
|
+
const lines = fs72.readFileSync(latestFile, "utf-8").split("\n");
|
|
45374
45756
|
let lastModel = "";
|
|
45375
45757
|
let lastInput = 0;
|
|
45376
45758
|
let lastOutput = 0;
|
|
@@ -45470,9 +45852,9 @@ function renderPending(activity) {
|
|
|
45470
45852
|
}
|
|
45471
45853
|
async function ensureDaemon() {
|
|
45472
45854
|
let pidPort = null;
|
|
45473
|
-
if (
|
|
45855
|
+
if (fs72.existsSync(PID_FILE)) {
|
|
45474
45856
|
try {
|
|
45475
|
-
const { port } = JSON.parse(
|
|
45857
|
+
const { port } = JSON.parse(fs72.readFileSync(PID_FILE, "utf-8"));
|
|
45476
45858
|
pidPort = port;
|
|
45477
45859
|
} catch {
|
|
45478
45860
|
console.error(chalk40.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -45487,12 +45869,21 @@ async function ensureDaemon() {
|
|
|
45487
45869
|
} catch {
|
|
45488
45870
|
}
|
|
45489
45871
|
console.log(chalk40.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
45872
|
+
const startupFd = openStartupLogFd();
|
|
45873
|
+
recordStartupState("starting");
|
|
45490
45874
|
const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
|
|
45491
45875
|
detached: true,
|
|
45492
|
-
stdio: "ignore",
|
|
45876
|
+
stdio: ["ignore", "ignore", startupFd ?? "ignore"],
|
|
45493
45877
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
45494
45878
|
});
|
|
45879
|
+
child.on("error", (err2) => recordStartupState("failed", "spawn-failed", err2.message));
|
|
45495
45880
|
child.unref();
|
|
45881
|
+
if (startupFd !== void 0) {
|
|
45882
|
+
try {
|
|
45883
|
+
fs72.closeSync(startupFd);
|
|
45884
|
+
} catch {
|
|
45885
|
+
}
|
|
45886
|
+
}
|
|
45496
45887
|
for (let i = 0; i < 20; i++) {
|
|
45497
45888
|
await new Promise((r) => setTimeout(r, 250));
|
|
45498
45889
|
try {
|
|
@@ -45630,7 +46021,7 @@ function buildRecoveryCardLines(req) {
|
|
|
45630
46021
|
function readApproversFromDisk() {
|
|
45631
46022
|
const configPath = path68.join(os61.homedir(), ".node9", "config.json");
|
|
45632
46023
|
try {
|
|
45633
|
-
const raw = JSON.parse(
|
|
46024
|
+
const raw = JSON.parse(fs72.readFileSync(configPath, "utf-8"));
|
|
45634
46025
|
const settings = raw.settings ?? {};
|
|
45635
46026
|
return settings.approvers ?? {};
|
|
45636
46027
|
} catch {
|
|
@@ -45648,13 +46039,13 @@ function approverStatusLine() {
|
|
|
45648
46039
|
function toggleApprover(channel) {
|
|
45649
46040
|
const configPath = path68.join(os61.homedir(), ".node9", "config.json");
|
|
45650
46041
|
try {
|
|
45651
|
-
const raw = JSON.parse(
|
|
46042
|
+
const raw = JSON.parse(fs72.readFileSync(configPath, "utf-8"));
|
|
45652
46043
|
const settings = raw.settings ?? {};
|
|
45653
46044
|
const approvers = settings.approvers ?? {};
|
|
45654
46045
|
approvers[channel] = approvers[channel] === false;
|
|
45655
46046
|
settings.approvers = approvers;
|
|
45656
46047
|
raw.settings = settings;
|
|
45657
|
-
|
|
46048
|
+
fs72.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
45658
46049
|
} catch (err2) {
|
|
45659
46050
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
45660
46051
|
`);
|
|
@@ -45826,7 +46217,7 @@ async function startTail(options = {}) {
|
|
|
45826
46217
|
}
|
|
45827
46218
|
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
|
|
45828
46219
|
try {
|
|
45829
|
-
|
|
46220
|
+
fs72.appendFileSync(
|
|
45830
46221
|
path68.join(os61.homedir(), ".node9", "hook-debug.log"),
|
|
45831
46222
|
`[tail] POST /decision failed: ${String(err2)}
|
|
45832
46223
|
`
|
|
@@ -45893,7 +46284,7 @@ async function startTail(options = {}) {
|
|
|
45893
46284
|
}
|
|
45894
46285
|
const auditLog = path68.join(os61.homedir(), ".node9", "audit.log");
|
|
45895
46286
|
try {
|
|
45896
|
-
const unackedDlp =
|
|
46287
|
+
const unackedDlp = fs72.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
45897
46288
|
if (unackedDlp > 0) {
|
|
45898
46289
|
console.log("");
|
|
45899
46290
|
console.log(
|
|
@@ -45933,7 +46324,7 @@ async function startTail(options = {}) {
|
|
|
45933
46324
|
if (stallWarned) return;
|
|
45934
46325
|
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
45935
46326
|
try {
|
|
45936
|
-
const auditMtime =
|
|
46327
|
+
const auditMtime = fs72.statSync(auditLog).mtimeMs;
|
|
45937
46328
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
45938
46329
|
console.log("");
|
|
45939
46330
|
console.log(
|
|
@@ -46122,6 +46513,7 @@ var PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRA
|
|
|
46122
46513
|
var init_tail = __esm({
|
|
46123
46514
|
"src/tui/tail.ts"() {
|
|
46124
46515
|
"use strict";
|
|
46516
|
+
init_startup_log();
|
|
46125
46517
|
init_daemon2();
|
|
46126
46518
|
init_daemon();
|
|
46127
46519
|
PID_FILE = path68.join(os61.homedir(), ".node9", "daemon.pid");
|
|
@@ -46172,7 +46564,7 @@ __export(hud_exports, {
|
|
|
46172
46564
|
main: () => main,
|
|
46173
46565
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
46174
46566
|
});
|
|
46175
|
-
import
|
|
46567
|
+
import fs73 from "fs";
|
|
46176
46568
|
import path69 from "path";
|
|
46177
46569
|
import os62 from "os";
|
|
46178
46570
|
import http6 from "http";
|
|
@@ -46250,9 +46642,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
46250
46642
|
return ` (${m}m left)`;
|
|
46251
46643
|
}
|
|
46252
46644
|
function safeReadJson(filePath) {
|
|
46253
|
-
if (!
|
|
46645
|
+
if (!fs73.existsSync(filePath)) return null;
|
|
46254
46646
|
try {
|
|
46255
|
-
return JSON.parse(
|
|
46647
|
+
return JSON.parse(fs73.readFileSync(filePath, "utf-8"));
|
|
46256
46648
|
} catch {
|
|
46257
46649
|
return null;
|
|
46258
46650
|
}
|
|
@@ -46273,10 +46665,10 @@ function countHooksInFile(filePath) {
|
|
|
46273
46665
|
return Object.keys(cfg.hooks).length;
|
|
46274
46666
|
}
|
|
46275
46667
|
function countRulesInDir(rulesDir) {
|
|
46276
|
-
if (!
|
|
46668
|
+
if (!fs73.existsSync(rulesDir)) return 0;
|
|
46277
46669
|
let count = 0;
|
|
46278
46670
|
try {
|
|
46279
|
-
for (const entry of
|
|
46671
|
+
for (const entry of fs73.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
46280
46672
|
if (entry.isDirectory()) {
|
|
46281
46673
|
count += countRulesInDir(path69.join(rulesDir, entry.name));
|
|
46282
46674
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -46302,7 +46694,7 @@ function countConfigs(cwd) {
|
|
|
46302
46694
|
let hooksCount = 0;
|
|
46303
46695
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
46304
46696
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
46305
|
-
if (
|
|
46697
|
+
if (fs73.existsSync(path69.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46306
46698
|
rulesCount += countRulesInDir(path69.join(claudeDir, "rules"));
|
|
46307
46699
|
const userSettings = path69.join(claudeDir, "settings.json");
|
|
46308
46700
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
@@ -46313,18 +46705,18 @@ function countConfigs(cwd) {
|
|
|
46313
46705
|
userMcpServers.delete(name);
|
|
46314
46706
|
}
|
|
46315
46707
|
if (cwd) {
|
|
46316
|
-
if (
|
|
46317
|
-
if (
|
|
46708
|
+
if (fs73.existsSync(path69.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
46709
|
+
if (fs73.existsSync(path69.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46318
46710
|
const projectClaudeDir = path69.join(cwd, ".claude");
|
|
46319
46711
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
46320
46712
|
if (!overlapsUserScope) {
|
|
46321
|
-
if (
|
|
46713
|
+
if (fs73.existsSync(path69.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46322
46714
|
rulesCount += countRulesInDir(path69.join(projectClaudeDir, "rules"));
|
|
46323
46715
|
const projSettings = path69.join(projectClaudeDir, "settings.json");
|
|
46324
46716
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
46325
46717
|
hooksCount += countHooksInFile(projSettings);
|
|
46326
46718
|
}
|
|
46327
|
-
if (
|
|
46719
|
+
if (fs73.existsSync(path69.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46328
46720
|
const localSettings = path69.join(projectClaudeDir, "settings.local.json");
|
|
46329
46721
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
46330
46722
|
hooksCount += countHooksInFile(localSettings);
|
|
@@ -46362,11 +46754,11 @@ function readActiveShieldsHud() {
|
|
|
46362
46754
|
}
|
|
46363
46755
|
try {
|
|
46364
46756
|
const shieldsPath = path69.join(os62.homedir(), ".node9", "shields.json");
|
|
46365
|
-
if (!
|
|
46757
|
+
if (!fs73.existsSync(shieldsPath)) {
|
|
46366
46758
|
shieldsCache = { value: [], ts: now };
|
|
46367
46759
|
return [];
|
|
46368
46760
|
}
|
|
46369
|
-
const parsed = JSON.parse(
|
|
46761
|
+
const parsed = JSON.parse(fs73.readFileSync(shieldsPath, "utf-8"));
|
|
46370
46762
|
if (!Array.isArray(parsed.active)) {
|
|
46371
46763
|
shieldsCache = { value: [], ts: now };
|
|
46372
46764
|
return [];
|
|
@@ -46468,17 +46860,17 @@ function renderContextLine(stdin) {
|
|
|
46468
46860
|
async function main() {
|
|
46469
46861
|
try {
|
|
46470
46862
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
46471
|
-
if (
|
|
46863
|
+
if (fs73.existsSync(path69.join(os62.homedir(), ".node9", "hud-debug"))) {
|
|
46472
46864
|
try {
|
|
46473
46865
|
const logPath = path69.join(os62.homedir(), ".node9", "hud-debug.log");
|
|
46474
46866
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
46475
46867
|
let size = 0;
|
|
46476
46868
|
try {
|
|
46477
|
-
size =
|
|
46869
|
+
size = fs73.statSync(logPath).size;
|
|
46478
46870
|
} catch {
|
|
46479
46871
|
}
|
|
46480
46872
|
if (size < MAX_LOG_SIZE) {
|
|
46481
|
-
|
|
46873
|
+
fs73.appendFileSync(
|
|
46482
46874
|
logPath,
|
|
46483
46875
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
46484
46876
|
);
|
|
@@ -46502,8 +46894,8 @@ async function main() {
|
|
|
46502
46894
|
path69.join(cwd, "node9.config.json"),
|
|
46503
46895
|
path69.join(os62.homedir(), ".node9", "config.json")
|
|
46504
46896
|
]) {
|
|
46505
|
-
if (!
|
|
46506
|
-
const cfg = JSON.parse(
|
|
46897
|
+
if (!fs73.existsSync(configPath)) continue;
|
|
46898
|
+
const cfg = JSON.parse(fs73.readFileSync(configPath, "utf-8"));
|
|
46507
46899
|
const hud = cfg.settings?.hud;
|
|
46508
46900
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
46509
46901
|
}
|
|
@@ -46645,7 +47037,7 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
|
|
|
46645
47037
|
// src/cli.ts
|
|
46646
47038
|
init_daemon2();
|
|
46647
47039
|
import chalk41 from "chalk";
|
|
46648
|
-
import
|
|
47040
|
+
import fs74 from "fs";
|
|
46649
47041
|
import path70 from "path";
|
|
46650
47042
|
import os63 from "os";
|
|
46651
47043
|
import { spawn as spawn9 } from "child_process";
|
|
@@ -46851,9 +47243,11 @@ function logAutostartSkipThrottled(reason) {
|
|
|
46851
47243
|
if (Date.now() - fs44.statSync(stamp).mtimeMs < SKIP_THROTTLE_MS) return;
|
|
46852
47244
|
} catch {
|
|
46853
47245
|
}
|
|
47246
|
+
const dir = path42.join(os40.homedir(), ".node9");
|
|
47247
|
+
if (!fs44.existsSync(dir)) fs44.mkdirSync(dir, { recursive: true });
|
|
46854
47248
|
fs44.writeFileSync(stamp, "", "utf-8");
|
|
46855
47249
|
fs44.appendFileSync(
|
|
46856
|
-
path42.join(
|
|
47250
|
+
path42.join(dir, "hook-debug.log"),
|
|
46857
47251
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-skip: ${reason}
|
|
46858
47252
|
`,
|
|
46859
47253
|
"utf-8"
|
|
@@ -46872,6 +47266,8 @@ async function autoStartDaemonAndWait() {
|
|
|
46872
47266
|
}
|
|
46873
47267
|
if (!resolvedArgv1.endsWith(".js")) return false;
|
|
46874
47268
|
const startupFd = openStartupLogFd();
|
|
47269
|
+
recordStartupState("starting");
|
|
47270
|
+
let spawned = false;
|
|
46875
47271
|
try {
|
|
46876
47272
|
const child = spawn3(process.execPath, [resolvedArgv1, "daemon"], {
|
|
46877
47273
|
detached: true,
|
|
@@ -46881,13 +47277,24 @@ async function autoStartDaemonAndWait() {
|
|
|
46881
47277
|
NODE9_AUTO_STARTED: "1"
|
|
46882
47278
|
}
|
|
46883
47279
|
});
|
|
47280
|
+
child.on("error", (err2) => {
|
|
47281
|
+
if (readStartupState()?.outcome !== "starting") return;
|
|
47282
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
47283
|
+
});
|
|
46884
47284
|
child.unref();
|
|
47285
|
+
spawned = true;
|
|
46885
47286
|
for (let i = 0; i < 20; i++) {
|
|
46886
47287
|
await new Promise((r) => setTimeout(r, 250));
|
|
46887
47288
|
if (!isDaemonRunning()) continue;
|
|
46888
47289
|
if (await isDaemonReachable()) return true;
|
|
46889
47290
|
}
|
|
46890
|
-
} catch {
|
|
47291
|
+
} catch (err2) {
|
|
47292
|
+
if (!spawned)
|
|
47293
|
+
recordStartupState(
|
|
47294
|
+
"failed",
|
|
47295
|
+
"spawn-failed",
|
|
47296
|
+
err2 instanceof Error ? err2.message : String(err2)
|
|
47297
|
+
);
|
|
46891
47298
|
} finally {
|
|
46892
47299
|
if (startupFd !== void 0) {
|
|
46893
47300
|
try {
|
|
@@ -47700,12 +48107,16 @@ RAW: ${raw}
|
|
|
47700
48107
|
delete safeEnv[key];
|
|
47701
48108
|
}
|
|
47702
48109
|
const startupFd = openStartupLogFd();
|
|
48110
|
+
recordStartupState("starting");
|
|
47703
48111
|
try {
|
|
47704
48112
|
const d = spawn5(process.execPath, [scriptPath, "daemon"], {
|
|
47705
48113
|
detached: true,
|
|
47706
48114
|
stdio: ["ignore", "ignore", startupFd ?? "ignore"],
|
|
47707
48115
|
env: { ...safeEnv, NODE9_AUTO_STARTED: "1" }
|
|
47708
48116
|
});
|
|
48117
|
+
d.on("error", (err2) => {
|
|
48118
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
48119
|
+
});
|
|
47709
48120
|
d.unref();
|
|
47710
48121
|
} finally {
|
|
47711
48122
|
if (startupFd !== void 0) {
|
|
@@ -47718,6 +48129,7 @@ RAW: ${raw}
|
|
|
47718
48129
|
} catch (spawnErr) {
|
|
47719
48130
|
const logPath = path46.join(os44.homedir(), ".node9", "hook-debug.log");
|
|
47720
48131
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
48132
|
+
recordStartupState("failed", "spawn-aborted", msg);
|
|
47721
48133
|
try {
|
|
47722
48134
|
fs48.appendFileSync(
|
|
47723
48135
|
logPath,
|
|
@@ -48882,6 +49294,7 @@ function agoLabel(iso, now = Date.now()) {
|
|
|
48882
49294
|
}
|
|
48883
49295
|
|
|
48884
49296
|
// src/cli/commands/doctor.ts
|
|
49297
|
+
init_startup_log();
|
|
48885
49298
|
function registerDoctorCommand(program2, version2) {
|
|
48886
49299
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
|
|
48887
49300
|
const homeDir2 = os47.homedir();
|
|
@@ -48993,6 +49406,15 @@ function registerDoctorCommand(program2, version2) {
|
|
|
48993
49406
|
"Daemon not running \u2014 terminal & native approvals unavailable",
|
|
48994
49407
|
"Run: node9 daemon --background"
|
|
48995
49408
|
);
|
|
49409
|
+
const cause = readStartupCause();
|
|
49410
|
+
if (cause) {
|
|
49411
|
+
const suffix = cause.detail ? ` \u2014 ${cause.detail}` : "";
|
|
49412
|
+
console.log(
|
|
49413
|
+
chalk11.gray(
|
|
49414
|
+
` ${cause.label} ${agoLabel(cause.at.toISOString())}: ${cause.kind}${suffix}`
|
|
49415
|
+
)
|
|
49416
|
+
);
|
|
49417
|
+
}
|
|
48996
49418
|
}
|
|
48997
49419
|
const autostart = autostartAdvice({
|
|
48998
49420
|
installed: isDaemonServiceInstalled(),
|
|
@@ -49067,6 +49489,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
49067
49489
|
}
|
|
49068
49490
|
|
|
49069
49491
|
// src/cli/commands/audit.ts
|
|
49492
|
+
init_decision();
|
|
49070
49493
|
import chalk12 from "chalk";
|
|
49071
49494
|
import fs53 from "fs";
|
|
49072
49495
|
import path51 from "path";
|
|
@@ -49101,10 +49524,16 @@ function registerAuditCommand(program2) {
|
|
|
49101
49524
|
});
|
|
49102
49525
|
entries = entries.map((e) => ({
|
|
49103
49526
|
...e,
|
|
49104
|
-
|
|
49527
|
+
// classifyDecision is the ONE mapper (audit/decision.ts). The inline
|
|
49528
|
+
// `startsWith('allow') ? allow : deny` this replaces bucketed `dlp` and
|
|
49529
|
+
// `mcp-discovered` — findings, not verdicts — as DENY, inventing
|
|
49530
|
+
// refusals that never happened.
|
|
49531
|
+
// Pass the ROW, never a field pair — the attribution key differs by
|
|
49532
|
+
// producer (`checkedBy` from the gate, `source` from the hook/daemon).
|
|
49533
|
+
view: classifyDecision(e)
|
|
49105
49534
|
}));
|
|
49106
49535
|
if (options.tool) entries = entries.filter((e) => String(e.tool).includes(options.tool));
|
|
49107
|
-
if (options.deny) entries = entries.filter((e) => e.
|
|
49536
|
+
if (options.deny) entries = entries.filter((e) => e.view.outcome === "deny");
|
|
49108
49537
|
const limit = Math.max(1, parseInt(options.tail, 10) || 20);
|
|
49109
49538
|
entries = entries.slice(-limit);
|
|
49110
49539
|
if (options.json) {
|
|
@@ -49127,13 +49556,13 @@ function registerAuditCommand(program2) {
|
|
|
49127
49556
|
for (const e of entries) {
|
|
49128
49557
|
const time = formatRelativeTime(String(e.ts)).padEnd(12);
|
|
49129
49558
|
const tool = String(e.tool).slice(0, 17).padEnd(18);
|
|
49130
|
-
const result = e.
|
|
49559
|
+
const result = e.view.outcome === "allow" ? chalk12.green(e.view.label.padEnd(14)) : e.view.outcome === "deny" ? chalk12.red(e.view.label.padEnd(14)) : e.view.outcome === "observe" ? chalk12.yellow(e.view.label.padEnd(14)) : chalk12.gray(e.view.label.padEnd(14));
|
|
49131
49560
|
const checker = String(e.checkedBy || "unknown").slice(0, 14).padEnd(15);
|
|
49132
49561
|
const agent = String(e.agent || "unknown");
|
|
49133
49562
|
console.log(` ${time} ${tool} ${result} ${checker} ${agent}`);
|
|
49134
49563
|
}
|
|
49135
|
-
const allowed = entries.filter((e) => e.
|
|
49136
|
-
const denied = entries.filter((e) => e.
|
|
49564
|
+
const allowed = entries.filter((e) => e.view.outcome === "allow").length;
|
|
49565
|
+
const denied = entries.filter((e) => e.view.outcome === "deny").length;
|
|
49137
49566
|
console.log(chalk12.dim(" " + "\u2500".repeat(65)));
|
|
49138
49567
|
console.log(
|
|
49139
49568
|
` ${entries.length} entries | ${chalk12.green(allowed + " allowed")} | ${chalk12.red(denied + " denied")}
|
|
@@ -49149,6 +49578,7 @@ import chalk13 from "chalk";
|
|
|
49149
49578
|
init_costSync();
|
|
49150
49579
|
init_litellm();
|
|
49151
49580
|
init_cost_codex();
|
|
49581
|
+
init_decision();
|
|
49152
49582
|
import fs54 from "fs";
|
|
49153
49583
|
import os49 from "os";
|
|
49154
49584
|
import path52 from "path";
|
|
@@ -49242,8 +49672,15 @@ function parseAuditLog(logPath) {
|
|
|
49242
49672
|
}
|
|
49243
49673
|
});
|
|
49244
49674
|
}
|
|
49245
|
-
function
|
|
49246
|
-
|
|
49675
|
+
function viewOf(e) {
|
|
49676
|
+
const outcome = classifyDecision(e).outcome;
|
|
49677
|
+
const observed = outcome === "observe";
|
|
49678
|
+
return {
|
|
49679
|
+
observed,
|
|
49680
|
+
ran: outcome === "allow" || observed,
|
|
49681
|
+
blocked: outcome === "deny" || outcome === "unknown",
|
|
49682
|
+
info: outcome === "info"
|
|
49683
|
+
};
|
|
49247
49684
|
}
|
|
49248
49685
|
function isDlp(checkedBy) {
|
|
49249
49686
|
return !!checkedBy?.includes("dlp");
|
|
@@ -49656,12 +50093,12 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49656
50093
|
const priorStart = new Date(start.getTime() - periodMs);
|
|
49657
50094
|
const priorEntries = allEntries.filter((e) => {
|
|
49658
50095
|
if (e.source === "post-hook") return false;
|
|
50096
|
+
if (e.source === "response-dlp") return false;
|
|
50097
|
+
if (typeof e.decision !== "string") return false;
|
|
49659
50098
|
const ts = new Date(e.ts);
|
|
49660
50099
|
return ts >= priorStart && ts <= priorEnd;
|
|
49661
50100
|
});
|
|
49662
|
-
const priorBlocked = priorEntries.filter(
|
|
49663
|
-
(e) => typeof e.decision === "string" && !isAllow(e.decision)
|
|
49664
|
-
).length;
|
|
50101
|
+
const priorBlocked = priorEntries.filter((e) => viewOf(e).blocked).length;
|
|
49665
50102
|
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
49666
50103
|
const excludeTests = opts.excludeTests === true;
|
|
49667
50104
|
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
@@ -49704,15 +50141,17 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49704
50141
|
let dimInjectionBlocked = 0;
|
|
49705
50142
|
for (const e of entries) {
|
|
49706
50143
|
if (superseded.has(supersedeKey(e))) continue;
|
|
49707
|
-
const
|
|
50144
|
+
const view = viewOf(e);
|
|
49708
50145
|
const dateKey = e.ts.slice(0, 10);
|
|
49709
50146
|
const userInteracted = e.source === "daemon";
|
|
49710
|
-
if (
|
|
49711
|
-
if (
|
|
50147
|
+
if (view.observed) {
|
|
50148
|
+
if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
50149
|
+
} else if (view.info) {
|
|
50150
|
+
} else if (userInteracted) {
|
|
50151
|
+
if (view.ran) userApproved++;
|
|
49712
50152
|
else userDenied++;
|
|
49713
|
-
} else if (
|
|
50153
|
+
} else if (view.blocked) {
|
|
49714
50154
|
if (e.checkedBy === "timeout") timedOut++;
|
|
49715
|
-
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
49716
50155
|
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
49717
50156
|
else if (e.checkedBy === "local-decision") userDenied++;
|
|
49718
50157
|
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
@@ -49724,7 +50163,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49724
50163
|
if (cb.includes("would-block") && (cb.includes("pii") || cb.includes("dlp"))) {
|
|
49725
50164
|
dimDataObserved++;
|
|
49726
50165
|
}
|
|
49727
|
-
if (
|
|
50166
|
+
if (view.blocked && !userInteracted) {
|
|
49728
50167
|
switch (dimensionOfBlock(cb, e.ruleName ?? "")) {
|
|
49729
50168
|
case "network":
|
|
49730
50169
|
dimNetworkBlocked++;
|
|
@@ -49742,15 +50181,15 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49742
50181
|
}
|
|
49743
50182
|
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
49744
50183
|
t.calls++;
|
|
49745
|
-
if (
|
|
50184
|
+
if (view.blocked) t.blocked++;
|
|
49746
50185
|
toolMap.set(e.tool, t);
|
|
49747
|
-
if (
|
|
50186
|
+
if (view.blocked) {
|
|
49748
50187
|
const key = e.checkedBy ?? (e.source === "daemon" ? "local-decision" : null);
|
|
49749
50188
|
if (key) {
|
|
49750
50189
|
blockMap.set(key, (blockMap.get(key) ?? 0) + 1);
|
|
49751
50190
|
}
|
|
49752
50191
|
}
|
|
49753
|
-
if (
|
|
50192
|
+
if ((view.blocked || view.observed) && e.ruleName) {
|
|
49754
50193
|
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
49755
50194
|
}
|
|
49756
50195
|
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
@@ -49759,7 +50198,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49759
50198
|
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
49760
50199
|
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
49761
50200
|
d.calls++;
|
|
49762
|
-
if (
|
|
50201
|
+
if (view.blocked) d.blocked++;
|
|
49763
50202
|
dailyMap.set(dateKey, d);
|
|
49764
50203
|
}
|
|
49765
50204
|
for (const e of allEntries) {
|
|
@@ -50311,9 +50750,11 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
50311
50750
|
}
|
|
50312
50751
|
|
|
50313
50752
|
// src/cli/commands/daemon-cmd.ts
|
|
50753
|
+
init_startup_log();
|
|
50314
50754
|
init_daemon2();
|
|
50315
50755
|
import chalk14 from "chalk";
|
|
50316
50756
|
import { spawn as spawn6 } from "child_process";
|
|
50757
|
+
import fs55 from "fs";
|
|
50317
50758
|
var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
|
|
50318
50759
|
function registerDaemonCommand(program2) {
|
|
50319
50760
|
program2.command("daemon").description("Manage the local approval daemon").argument("[action]", `${VALID_ACTIONS} (default: start)`).option("-b, --background", "Start the daemon in the background (detached)").option(
|
|
@@ -50351,14 +50792,27 @@ function registerDaemonCommand(program2) {
|
|
|
50351
50792
|
if (cmd === "restart") {
|
|
50352
50793
|
stopDaemon();
|
|
50353
50794
|
await new Promise((r) => setTimeout(r, 500));
|
|
50795
|
+
const restartFd = openStartupLogFd();
|
|
50796
|
+
recordStartupState("starting");
|
|
50354
50797
|
const child = spawn6(process.execPath, [process.argv[1], "daemon"], {
|
|
50355
50798
|
detached: true,
|
|
50356
|
-
stdio: "ignore",
|
|
50799
|
+
stdio: ["ignore", "ignore", restartFd ?? "ignore"],
|
|
50357
50800
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
50358
50801
|
});
|
|
50802
|
+
child.on(
|
|
50803
|
+
"error",
|
|
50804
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50805
|
+
);
|
|
50359
50806
|
child.unref();
|
|
50807
|
+
if (restartFd !== void 0) {
|
|
50808
|
+
try {
|
|
50809
|
+
fs55.closeSync(restartFd);
|
|
50810
|
+
} catch {
|
|
50811
|
+
}
|
|
50812
|
+
}
|
|
50360
50813
|
if (child.pid) {
|
|
50361
|
-
console.log(chalk14.green(`\u2713 Daemon
|
|
50814
|
+
console.log(chalk14.green(`\u2713 Daemon relaunching (PID ${child.pid})`));
|
|
50815
|
+
console.log(chalk14.gray(" Confirm with: node9 status"));
|
|
50362
50816
|
} else {
|
|
50363
50817
|
console.error(chalk14.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
|
|
50364
50818
|
process.exit(1);
|
|
@@ -50381,13 +50835,33 @@ function registerDaemonCommand(program2) {
|
|
|
50381
50835
|
return;
|
|
50382
50836
|
}
|
|
50383
50837
|
if (options.background) {
|
|
50384
|
-
const
|
|
50385
|
-
|
|
50386
|
-
|
|
50387
|
-
|
|
50388
|
-
|
|
50389
|
-
|
|
50390
|
-
|
|
50838
|
+
const startupFd = openStartupLogFd();
|
|
50839
|
+
try {
|
|
50840
|
+
recordStartupState("starting");
|
|
50841
|
+
const child = spawn6(process.execPath, [process.argv[1], "daemon"], {
|
|
50842
|
+
detached: true,
|
|
50843
|
+
// Capture the child's stderr: a module-load crash prints its stack there
|
|
50844
|
+
// and dies before it can record anything itself.
|
|
50845
|
+
stdio: ["ignore", "ignore", startupFd ?? "ignore"]
|
|
50846
|
+
});
|
|
50847
|
+
child.on(
|
|
50848
|
+
"error",
|
|
50849
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50850
|
+
);
|
|
50851
|
+
child.unref();
|
|
50852
|
+
console.log(
|
|
50853
|
+
chalk14.green(`
|
|
50854
|
+
\u{1F6E1}\uFE0F Node9 daemon launching in background (PID ${child.pid})`)
|
|
50855
|
+
);
|
|
50856
|
+
console.log(chalk14.gray(" Confirm with: node9 status"));
|
|
50857
|
+
} finally {
|
|
50858
|
+
if (startupFd !== void 0) {
|
|
50859
|
+
try {
|
|
50860
|
+
fs55.closeSync(startupFd);
|
|
50861
|
+
} catch {
|
|
50862
|
+
}
|
|
50863
|
+
}
|
|
50864
|
+
}
|
|
50391
50865
|
process.exit(0);
|
|
50392
50866
|
}
|
|
50393
50867
|
startDaemon();
|
|
@@ -50402,7 +50876,7 @@ init_agent_wiring();
|
|
|
50402
50876
|
init_sync();
|
|
50403
50877
|
init_service();
|
|
50404
50878
|
import chalk15 from "chalk";
|
|
50405
|
-
import
|
|
50879
|
+
import fs56 from "fs";
|
|
50406
50880
|
import path53 from "path";
|
|
50407
50881
|
import os50 from "os";
|
|
50408
50882
|
function printAgentSection(label2, hookPairs, wrapped) {
|
|
@@ -50480,10 +50954,10 @@ function registerStatusCommand(program2) {
|
|
|
50480
50954
|
const projectConfig = path53.join(process.cwd(), "node9.config.json");
|
|
50481
50955
|
const globalConfig = path53.join(os50.homedir(), ".node9", "config.json");
|
|
50482
50956
|
console.log(
|
|
50483
|
-
` Local: ${
|
|
50957
|
+
` Local: ${fs56.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
|
|
50484
50958
|
);
|
|
50485
50959
|
console.log(
|
|
50486
|
-
` Global: ${
|
|
50960
|
+
` Global: ${fs56.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
|
|
50487
50961
|
);
|
|
50488
50962
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
50489
50963
|
console.log(
|
|
@@ -50530,7 +51004,7 @@ init_shields();
|
|
|
50530
51004
|
init_service();
|
|
50531
51005
|
init_core();
|
|
50532
51006
|
import chalk16 from "chalk";
|
|
50533
|
-
import
|
|
51007
|
+
import fs57 from "fs";
|
|
50534
51008
|
import path54 from "path";
|
|
50535
51009
|
import os51 from "os";
|
|
50536
51010
|
import https6 from "https";
|
|
@@ -50619,15 +51093,15 @@ function registerInitCommand(program2) {
|
|
|
50619
51093
|
console.log("");
|
|
50620
51094
|
}
|
|
50621
51095
|
const configPath = path54.join(os51.homedir(), ".node9", "config.json");
|
|
50622
|
-
const isFirstInstall = !
|
|
50623
|
-
if (
|
|
51096
|
+
const isFirstInstall = !fs57.existsSync(configPath);
|
|
51097
|
+
if (fs57.existsSync(configPath) && !options.force) {
|
|
50624
51098
|
try {
|
|
50625
|
-
const existing = JSON.parse(
|
|
51099
|
+
const existing = JSON.parse(fs57.readFileSync(configPath, "utf-8"));
|
|
50626
51100
|
const settings = existing.settings ?? {};
|
|
50627
51101
|
if (settings.mode !== chosenMode) {
|
|
50628
51102
|
settings.mode = chosenMode;
|
|
50629
51103
|
existing.settings = settings;
|
|
50630
|
-
|
|
51104
|
+
fs57.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
50631
51105
|
console.log(chalk16.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
50632
51106
|
} else {
|
|
50633
51107
|
console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -50641,8 +51115,8 @@ function registerInitCommand(program2) {
|
|
|
50641
51115
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
50642
51116
|
};
|
|
50643
51117
|
const dir = path54.dirname(configPath);
|
|
50644
|
-
if (!
|
|
50645
|
-
|
|
51118
|
+
if (!fs57.existsSync(dir)) fs57.mkdirSync(dir, { recursive: true });
|
|
51119
|
+
fs57.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
50646
51120
|
console.log(chalk16.green(`\u2705 Config created: ${configPath}`));
|
|
50647
51121
|
console.log(chalk16.gray(` Mode: ${chosenMode}`));
|
|
50648
51122
|
}
|
|
@@ -50745,11 +51219,11 @@ init_agent_wiring();
|
|
|
50745
51219
|
init_setup();
|
|
50746
51220
|
init_hook_baseline();
|
|
50747
51221
|
import chalk17 from "chalk";
|
|
50748
|
-
import
|
|
51222
|
+
import fs58 from "fs";
|
|
50749
51223
|
var hasHookSurface = (a) => a.hooks.length > 0;
|
|
50750
51224
|
function backupForHeal(file) {
|
|
50751
51225
|
try {
|
|
50752
|
-
if (file &&
|
|
51226
|
+
if (file && fs58.existsSync(file)) fs58.copyFileSync(file, `${file}.node9-heal-bak`);
|
|
50753
51227
|
} catch {
|
|
50754
51228
|
}
|
|
50755
51229
|
}
|
|
@@ -51709,16 +52183,17 @@ function registerMcpGatewayCommand(program2) {
|
|
|
51709
52183
|
|
|
51710
52184
|
// src/mcp-server/index.ts
|
|
51711
52185
|
import readline5 from "readline";
|
|
51712
|
-
import
|
|
52186
|
+
import fs60 from "fs";
|
|
51713
52187
|
import os53 from "os";
|
|
51714
52188
|
import path57 from "path";
|
|
51715
52189
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
52190
|
+
init_decision();
|
|
51716
52191
|
init_core();
|
|
51717
52192
|
init_daemon();
|
|
51718
52193
|
init_shields();
|
|
51719
52194
|
|
|
51720
52195
|
// src/auth/egress-config.ts
|
|
51721
|
-
import
|
|
52196
|
+
import fs59 from "fs";
|
|
51722
52197
|
import os52 from "os";
|
|
51723
52198
|
import path56 from "path";
|
|
51724
52199
|
var DEFAULT_EGRESS = {
|
|
@@ -51734,7 +52209,7 @@ function egressConfigPath() {
|
|
|
51734
52209
|
function readEgressRawConfig() {
|
|
51735
52210
|
let text;
|
|
51736
52211
|
try {
|
|
51737
|
-
text =
|
|
52212
|
+
text = fs59.readFileSync(egressConfigPath(), "utf8");
|
|
51738
52213
|
} catch (err2) {
|
|
51739
52214
|
if (err2.code === "ENOENT") return {};
|
|
51740
52215
|
throw err2;
|
|
@@ -51749,8 +52224,8 @@ function readEgressRawConfig() {
|
|
|
51749
52224
|
}
|
|
51750
52225
|
function writeEgressRawConfig(config) {
|
|
51751
52226
|
const p = egressConfigPath();
|
|
51752
|
-
|
|
51753
|
-
|
|
52227
|
+
fs59.mkdirSync(path56.dirname(p), { recursive: true });
|
|
52228
|
+
fs59.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
51754
52229
|
}
|
|
51755
52230
|
function applyEgress(config, change) {
|
|
51756
52231
|
const policy = config.policy = config.policy ?? {};
|
|
@@ -51945,7 +52420,7 @@ var TOOLS = [
|
|
|
51945
52420
|
},
|
|
51946
52421
|
{
|
|
51947
52422
|
name: "node9_audit_get",
|
|
51948
|
-
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, tool name,
|
|
52423
|
+
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, outcome, tool name, the command, and the rule that fired. Outcomes use the same words as the dashboard: Auto-allowed, Approved, Ran, Blocked, Denied (a human refused), Timed out (nobody answered), Would block (shadow mode let it through), Finding, Info. Use this to review what AI actions were taken, especially refused ones.",
|
|
51949
52424
|
inputSchema: {
|
|
51950
52425
|
type: "object",
|
|
51951
52426
|
properties: {
|
|
@@ -51955,8 +52430,8 @@ var TOOLS = [
|
|
|
51955
52430
|
},
|
|
51956
52431
|
filter: {
|
|
51957
52432
|
type: "string",
|
|
51958
|
-
enum: ["all", "
|
|
51959
|
-
description: 'Filter by
|
|
52433
|
+
enum: ["all", "allow", "deny", "observe", "info", "block"],
|
|
52434
|
+
description: 'Filter by outcome. "deny" covers every refusal (rule block, human denial, timeout); "observe" is shadow-mode would-blocks; "block" is an alias for "deny". Omit or use "all" for every entry.'
|
|
51960
52435
|
}
|
|
51961
52436
|
},
|
|
51962
52437
|
required: []
|
|
@@ -52138,10 +52613,10 @@ function handleStatus() {
|
|
|
52138
52613
|
const projectConfig = path57.join(process.cwd(), "node9.config.json");
|
|
52139
52614
|
const globalConfig = path57.join(os53.homedir(), ".node9", "config.json");
|
|
52140
52615
|
lines.push(
|
|
52141
|
-
`Project config (node9.config.json): ${
|
|
52616
|
+
`Project config (node9.config.json): ${fs60.existsSync(projectConfig) ? "present" : "not found"}`
|
|
52142
52617
|
);
|
|
52143
52618
|
lines.push(
|
|
52144
|
-
`Global config (~/.node9/config.json): ${
|
|
52619
|
+
`Global config (~/.node9/config.json): ${fs60.existsSync(globalConfig) ? "present" : "not found"}`
|
|
52145
52620
|
);
|
|
52146
52621
|
return lines.join("\n");
|
|
52147
52622
|
}
|
|
@@ -52251,8 +52726,8 @@ var GLOBAL_CONFIG_PATH = path57.join(os53.homedir(), ".node9", "config.json");
|
|
|
52251
52726
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
52252
52727
|
function readGlobalConfigRaw() {
|
|
52253
52728
|
try {
|
|
52254
|
-
if (
|
|
52255
|
-
return JSON.parse(
|
|
52729
|
+
if (fs60.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
52730
|
+
return JSON.parse(fs60.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
52256
52731
|
}
|
|
52257
52732
|
} catch {
|
|
52258
52733
|
}
|
|
@@ -52260,8 +52735,8 @@ function readGlobalConfigRaw() {
|
|
|
52260
52735
|
}
|
|
52261
52736
|
function writeGlobalConfigRaw(data) {
|
|
52262
52737
|
const dir = path57.dirname(GLOBAL_CONFIG_PATH);
|
|
52263
|
-
if (!
|
|
52264
|
-
|
|
52738
|
+
if (!fs60.existsSync(dir)) fs60.mkdirSync(dir, { recursive: true });
|
|
52739
|
+
fs60.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
|
|
52265
52740
|
}
|
|
52266
52741
|
function handleApproverList() {
|
|
52267
52742
|
const config = getConfig();
|
|
@@ -52306,35 +52781,37 @@ function handleAuditGet(args) {
|
|
|
52306
52781
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
52307
52782
|
const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
|
|
52308
52783
|
const auditPath = path57.join(os53.homedir(), ".node9", "audit.log");
|
|
52309
|
-
if (!
|
|
52310
|
-
const rawLines =
|
|
52784
|
+
if (!fs60.existsSync(auditPath)) return "No audit log found.";
|
|
52785
|
+
const rawLines = fs60.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
52786
|
+
const wanted = filter === "block" ? "deny" : filter;
|
|
52311
52787
|
const parsed = [];
|
|
52312
52788
|
for (const line of rawLines) {
|
|
52313
52789
|
try {
|
|
52314
52790
|
const e = JSON.parse(line);
|
|
52315
|
-
const
|
|
52316
|
-
if (
|
|
52791
|
+
const view = classifyDecision(e);
|
|
52792
|
+
if (wanted && view.outcome !== wanted) continue;
|
|
52317
52793
|
const argsObj = e.args;
|
|
52318
52794
|
let detail = "";
|
|
52319
52795
|
if (argsObj) {
|
|
52320
52796
|
const cmd = argsObj.command ?? argsObj.file_path ?? argsObj.path ?? argsObj.sql;
|
|
52321
|
-
if (typeof cmd === "string" && cmd)
|
|
52322
|
-
detail = cmd.length > 80 ? cmd.slice(0, 80) + "\u2026" : cmd;
|
|
52323
|
-
}
|
|
52797
|
+
if (typeof cmd === "string" && cmd) detail = cmd;
|
|
52324
52798
|
}
|
|
52325
|
-
|
|
52799
|
+
if (!detail && typeof e.argsPreview === "string") detail = e.argsPreview;
|
|
52800
|
+
detail = detail.replace(/\s+/g, " ").trim();
|
|
52801
|
+
if (detail.length > 80) detail = detail.slice(0, 80) + "\u2026";
|
|
52802
|
+
const why = typeof e.ruleName === "string" && e.ruleName ? ` (${e.ruleName})` : "";
|
|
52326
52803
|
const toolPad = String(e.tool ?? "").padEnd(20);
|
|
52327
|
-
const line2 = `${e.ts} ${
|
|
52328
|
-
parsed.push({ raw: line,
|
|
52804
|
+
const line2 = `${e.ts} ${decisionTag(view)} ${toolPad} ${detail}${why}`;
|
|
52805
|
+
parsed.push({ raw: line, outcome: view.outcome, formatted: line2 });
|
|
52329
52806
|
} catch {
|
|
52330
|
-
parsed.push({ raw: line,
|
|
52807
|
+
parsed.push({ raw: line, outcome: "unknown", formatted: `[? unparseable] ${line}` });
|
|
52331
52808
|
}
|
|
52332
52809
|
}
|
|
52333
52810
|
const recent = parsed.slice(-limit);
|
|
52334
52811
|
if (recent.length === 0) {
|
|
52335
|
-
return filter ? `No ${
|
|
52812
|
+
return filter ? `No ${wanted} entries found in audit log.` : "Audit log is empty.";
|
|
52336
52813
|
}
|
|
52337
|
-
const header = filter ? `Last ${recent.length} ${
|
|
52814
|
+
const header = filter ? `Last ${recent.length} ${String(wanted).toUpperCase()} entries:` : `Last ${recent.length} audit entries:`;
|
|
52338
52815
|
return `${header}
|
|
52339
52816
|
|
|
52340
52817
|
${recent.map((e) => e.formatted).join("\n")}`;
|
|
@@ -52681,7 +53158,7 @@ function registerTrustCommand(program2) {
|
|
|
52681
53158
|
// src/cli/commands/mcp-pin.ts
|
|
52682
53159
|
init_mcp_pin();
|
|
52683
53160
|
import chalk24 from "chalk";
|
|
52684
|
-
import
|
|
53161
|
+
import fs61 from "fs";
|
|
52685
53162
|
|
|
52686
53163
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
52687
53164
|
init_mcp_wrap();
|
|
@@ -52877,6 +53354,7 @@ Restart ${[...agents].join(", ")}`) + chalk23.gray(" to activate. Undo any serve
|
|
|
52877
53354
|
}
|
|
52878
53355
|
|
|
52879
53356
|
// src/cli/commands/mcp-pin.ts
|
|
53357
|
+
init_mcp_wrap();
|
|
52880
53358
|
function registerMcpPinCommand(program2) {
|
|
52881
53359
|
const pinCmd = program2.command("mcp").description("Manage MCP servers \u2014 governance (gateway) + tool-definition pinning");
|
|
52882
53360
|
registerMcpGatewayCommand2(pinCmd);
|
|
@@ -52888,7 +53366,7 @@ function registerMcpPinCommand(program2) {
|
|
|
52888
53366
|
let repoCorrupt = false;
|
|
52889
53367
|
if (found.source === "repo") {
|
|
52890
53368
|
try {
|
|
52891
|
-
const raw =
|
|
53369
|
+
const raw = fs61.readFileSync(found.path, "utf-8");
|
|
52892
53370
|
const parsed = JSON.parse(raw);
|
|
52893
53371
|
repoEntries = parsed.servers ?? {};
|
|
52894
53372
|
} catch {
|
|
@@ -53002,6 +53480,88 @@ function registerMcpPinCommand(program2) {
|
|
|
53002
53480
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
53003
53481
|
console.log(chalk24.gray(" Next connection to each server will re-pin.\n"));
|
|
53004
53482
|
});
|
|
53483
|
+
pinCmd.command("forget [serverKey]").option("--stale", "Remove all stale (orphaned) servers at once").description("Remove a server pin that is no longer configured in any agent").action((serverKey, opts) => {
|
|
53484
|
+
if (opts.stale) {
|
|
53485
|
+
forgetAllStale();
|
|
53486
|
+
return;
|
|
53487
|
+
}
|
|
53488
|
+
if (!serverKey) {
|
|
53489
|
+
console.error(
|
|
53490
|
+
chalk24.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
53491
|
+
);
|
|
53492
|
+
process.exit(1);
|
|
53493
|
+
}
|
|
53494
|
+
let pins;
|
|
53495
|
+
try {
|
|
53496
|
+
pins = readMcpPins();
|
|
53497
|
+
} catch {
|
|
53498
|
+
console.error(chalk24.red("\n\u274C Pin file is corrupt."));
|
|
53499
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53500
|
+
process.exit(1);
|
|
53501
|
+
}
|
|
53502
|
+
if (!pins.servers[serverKey]) {
|
|
53503
|
+
console.error(chalk24.red(`
|
|
53504
|
+
\u274C No pin found for server key "${serverKey}"
|
|
53505
|
+
`));
|
|
53506
|
+
console.error(`Run ${chalk24.cyan("node9 mcp pin list")} to see pinned servers.
|
|
53507
|
+
`);
|
|
53508
|
+
process.exit(1);
|
|
53509
|
+
}
|
|
53510
|
+
const inv = inventoryMcp();
|
|
53511
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53512
|
+
if (liveKeys.has(serverKey)) {
|
|
53513
|
+
const agent = "an agent config";
|
|
53514
|
+
console.error(chalk24.red(`
|
|
53515
|
+
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
53516
|
+
console.error(
|
|
53517
|
+
chalk24.yellow(
|
|
53518
|
+
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
53519
|
+
`
|
|
53520
|
+
)
|
|
53521
|
+
);
|
|
53522
|
+
process.exit(1);
|
|
53523
|
+
}
|
|
53524
|
+
const label2 = pins.servers[serverKey].label;
|
|
53525
|
+
removePin(serverKey);
|
|
53526
|
+
console.log(chalk24.green(`
|
|
53527
|
+
\u2713 Forgot server ${chalk24.cyan(serverKey)}`));
|
|
53528
|
+
console.log(chalk24.gray(` Was: ${label2}`));
|
|
53529
|
+
console.log(chalk24.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
53530
|
+
});
|
|
53531
|
+
}
|
|
53532
|
+
function forgetAllStale() {
|
|
53533
|
+
let pins;
|
|
53534
|
+
try {
|
|
53535
|
+
pins = readMcpPins();
|
|
53536
|
+
} catch {
|
|
53537
|
+
console.error(chalk24.red("\n\u274C Pin file is corrupt."));
|
|
53538
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53539
|
+
process.exit(1);
|
|
53540
|
+
}
|
|
53541
|
+
const inv = inventoryMcp();
|
|
53542
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53543
|
+
if (liveKeys.size === 0) {
|
|
53544
|
+
console.error(chalk24.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
53545
|
+
console.error(
|
|
53546
|
+
chalk24.yellow(
|
|
53547
|
+
" This usually means an agent config is missing or unreadable, not that\n every server is gone. Check your agent configs, or remove one at a\n time: node9 mcp forget <key>\n"
|
|
53548
|
+
)
|
|
53549
|
+
);
|
|
53550
|
+
process.exit(1);
|
|
53551
|
+
}
|
|
53552
|
+
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
53553
|
+
if (stale.length === 0) {
|
|
53554
|
+
console.log(chalk24.gray("\nNo stale servers to remove.\n"));
|
|
53555
|
+
return;
|
|
53556
|
+
}
|
|
53557
|
+
for (const [sk, pin] of stale) {
|
|
53558
|
+
delete pins.servers[sk];
|
|
53559
|
+
console.log(chalk24.green(` \u2713 ${chalk24.cyan(sk)} ${chalk24.gray(pin.label)}`));
|
|
53560
|
+
}
|
|
53561
|
+
writeMcpPins(pins);
|
|
53562
|
+
console.log(chalk24.green(`
|
|
53563
|
+
Removed ${stale.length} stale server(s).
|
|
53564
|
+
`));
|
|
53005
53565
|
}
|
|
53006
53566
|
|
|
53007
53567
|
// src/cli/commands/sync.ts
|
|
@@ -53246,10 +53806,16 @@ var LABEL_WIDTH = 14;
|
|
|
53246
53806
|
function label(category) {
|
|
53247
53807
|
return chalk27.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
53248
53808
|
}
|
|
53249
|
-
function
|
|
53809
|
+
function guardedWhat(f) {
|
|
53810
|
+
if (f.detail.length === 0) return "this";
|
|
53811
|
+
const reads = f.coverageProbe?.kind === "fileRead" ? "reads of " : "";
|
|
53812
|
+
const more = f.detail.length > 1 ? ` and ${f.detail.length - 1} more` : "";
|
|
53813
|
+
return `${reads}${f.detail[0]}${more}`;
|
|
53814
|
+
}
|
|
53815
|
+
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
53250
53816
|
const lines = [];
|
|
53251
53817
|
const wt = showWeight && f.scoreWeight ? chalk27.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
53252
|
-
lines.push(` ${ICON[f.severity]} ${label(
|
|
53818
|
+
lines.push(` ${ICON[f.severity]} ${label(displayLabel)}${wt}${f.title}`);
|
|
53253
53819
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
53254
53820
|
const width = 80 - indent.length;
|
|
53255
53821
|
for (const s of [f.what, f.why, f.who]) {
|
|
@@ -53286,11 +53852,11 @@ function renderPosture(result) {
|
|
|
53286
53852
|
);
|
|
53287
53853
|
const headroom = openHeadroom(result.findings);
|
|
53288
53854
|
if (headroom > 0) {
|
|
53289
|
-
|
|
53290
|
-
"
|
|
53291
|
-
|
|
53292
|
-
|
|
53293
|
-
);
|
|
53855
|
+
const openExposures = result.findings.filter(
|
|
53856
|
+
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
53857
|
+
).length;
|
|
53858
|
+
const note = openExposures > 0 ? `Of the gap to 100: ${headroom} pts is optional hardening you can choose to turn on (the \u{1F512} tier below, each at some cost to flexibility); the rest is the open findings \u2014 the flagged rows below. Fix those first.` : `${headroom} pts of headroom \u2014 optional hardening you can choose to turn on (the \u{1F512} tier below), each at some cost to flexibility.`;
|
|
53859
|
+
for (const l of wrap(note, 76)) lines.push(" " + chalk27.gray(l));
|
|
53294
53860
|
}
|
|
53295
53861
|
lines.push("");
|
|
53296
53862
|
if (result.headline) {
|
|
@@ -53303,13 +53869,18 @@ function renderPosture(result) {
|
|
|
53303
53869
|
}
|
|
53304
53870
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
53305
53871
|
const open = result.findings.filter((f) => f.coverage?.state !== "covered");
|
|
53872
|
+
const collision = new Set(
|
|
53873
|
+
covered.map((f) => f.category).filter((c) => open.some((o) => o.category === c))
|
|
53874
|
+
);
|
|
53875
|
+
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
53306
53876
|
if (covered.length > 0) {
|
|
53307
53877
|
lines.push(" " + chalk27.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
53308
53878
|
for (const f of covered) {
|
|
53309
53879
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
53310
53880
|
const via = f.coverage?.via ?? "node9";
|
|
53881
|
+
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
53311
53882
|
lines.push(
|
|
53312
|
-
` ${chalk27.green("\u2705")} ${label(
|
|
53883
|
+
` ${chalk27.green("\u2705")} ${label(lbl)}${chalk27.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
53313
53884
|
);
|
|
53314
53885
|
}
|
|
53315
53886
|
lines.push("");
|
|
@@ -53319,17 +53890,17 @@ function renderPosture(result) {
|
|
|
53319
53890
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
53320
53891
|
if (node9Open.length > 0) {
|
|
53321
53892
|
lines.push(" " + chalk27.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
53322
|
-
for (const f of node9Open) lines.push(...renderFinding(f, true));
|
|
53893
|
+
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53323
53894
|
}
|
|
53324
53895
|
if (reduceOpen.length > 0) {
|
|
53325
53896
|
if (node9Open.length > 0) lines.push("");
|
|
53326
53897
|
lines.push(" " + chalk27.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
53327
|
-
for (const f of reduceOpen) lines.push(...renderFinding(f, true));
|
|
53898
|
+
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53328
53899
|
}
|
|
53329
53900
|
if (osOpen.length > 0) {
|
|
53330
53901
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
53331
53902
|
lines.push(" " + chalk27.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
53332
|
-
for (const f of osOpen) lines.push(...renderFinding(f));
|
|
53903
|
+
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
53333
53904
|
}
|
|
53334
53905
|
for (const cat of result.passedCategories) {
|
|
53335
53906
|
lines.push(` ${chalk27.green("\u2705")} ${label(cat)}${chalk27.gray("no issues found")}`);
|
|
@@ -53389,7 +53960,7 @@ import chalk30 from "chalk";
|
|
|
53389
53960
|
|
|
53390
53961
|
// src/ci-check/fetch.ts
|
|
53391
53962
|
var import_undici = __toESM(require_undici());
|
|
53392
|
-
import
|
|
53963
|
+
import fs62 from "fs";
|
|
53393
53964
|
import path58 from "path";
|
|
53394
53965
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
53395
53966
|
var cachedGhToken;
|
|
@@ -53473,7 +54044,7 @@ function parseRepoUrl(input) {
|
|
|
53473
54044
|
function isLocalPath(input) {
|
|
53474
54045
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) return true;
|
|
53475
54046
|
try {
|
|
53476
|
-
return
|
|
54047
|
+
return fs62.existsSync(input) && fs62.statSync(input).isDirectory();
|
|
53477
54048
|
} catch {
|
|
53478
54049
|
return false;
|
|
53479
54050
|
}
|
|
@@ -53590,8 +54161,8 @@ function readLocalTree(dir) {
|
|
|
53590
54161
|
const add = (rel) => {
|
|
53591
54162
|
const abs = path58.join(root, rel);
|
|
53592
54163
|
try {
|
|
53593
|
-
if (
|
|
53594
|
-
files.push({ path: rel, content:
|
|
54164
|
+
if (fs62.existsSync(abs) && fs62.statSync(abs).isFile()) {
|
|
54165
|
+
files.push({ path: rel, content: fs62.readFileSync(abs, "utf8") });
|
|
53595
54166
|
}
|
|
53596
54167
|
} catch {
|
|
53597
54168
|
}
|
|
@@ -53611,7 +54182,7 @@ function readLocalTree(dir) {
|
|
|
53611
54182
|
dirsVisited++;
|
|
53612
54183
|
let entries;
|
|
53613
54184
|
try {
|
|
53614
|
-
entries =
|
|
54185
|
+
entries = fs62.readdirSync(path58.join(root, relDir), { withFileTypes: true });
|
|
53615
54186
|
} catch {
|
|
53616
54187
|
return;
|
|
53617
54188
|
}
|
|
@@ -53634,8 +54205,8 @@ function readLocalTree(dir) {
|
|
|
53634
54205
|
for (const rel of matches) collect(rel);
|
|
53635
54206
|
const wfDir = path58.join(root, WORKFLOW_DIR);
|
|
53636
54207
|
try {
|
|
53637
|
-
if (
|
|
53638
|
-
for (const name of
|
|
54208
|
+
if (fs62.existsSync(wfDir)) {
|
|
54209
|
+
for (const name of fs62.readdirSync(wfDir)) {
|
|
53639
54210
|
if (/\.ya?ml$/.test(name)) add(path58.join(WORKFLOW_DIR, name));
|
|
53640
54211
|
}
|
|
53641
54212
|
}
|
|
@@ -54776,7 +55347,7 @@ import chalk32 from "chalk";
|
|
|
54776
55347
|
// src/shields/jail.ts
|
|
54777
55348
|
init_build();
|
|
54778
55349
|
init_shields();
|
|
54779
|
-
import
|
|
55350
|
+
import fs63 from "fs";
|
|
54780
55351
|
import os54 from "os";
|
|
54781
55352
|
import path59 from "path";
|
|
54782
55353
|
var USER_JAIL_SHIELD = "user-jail";
|
|
@@ -54786,7 +55357,7 @@ function jailStorePath() {
|
|
|
54786
55357
|
function readJailPaths() {
|
|
54787
55358
|
let text;
|
|
54788
55359
|
try {
|
|
54789
|
-
text =
|
|
55360
|
+
text = fs63.readFileSync(jailStorePath(), "utf8");
|
|
54790
55361
|
} catch (err2) {
|
|
54791
55362
|
if (err2.code === "ENOENT") return [];
|
|
54792
55363
|
throw err2;
|
|
@@ -54804,8 +55375,8 @@ function readJailPaths() {
|
|
|
54804
55375
|
}
|
|
54805
55376
|
function writeJailPaths(paths) {
|
|
54806
55377
|
const p = jailStorePath();
|
|
54807
|
-
|
|
54808
|
-
|
|
55378
|
+
fs63.mkdirSync(path59.dirname(p), { recursive: true });
|
|
55379
|
+
fs63.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
|
|
54809
55380
|
}
|
|
54810
55381
|
function addJailPath(rawPath, verdict) {
|
|
54811
55382
|
const norm = rawPath.trim();
|
|
@@ -54834,7 +55405,7 @@ function regenerateUserJail(paths) {
|
|
|
54834
55405
|
writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
|
|
54835
55406
|
}
|
|
54836
55407
|
try {
|
|
54837
|
-
|
|
55408
|
+
fs63.rmSync(file, { force: true });
|
|
54838
55409
|
} catch {
|
|
54839
55410
|
}
|
|
54840
55411
|
return;
|
|
@@ -54949,12 +55520,12 @@ function registerJailCommand(program2) {
|
|
|
54949
55520
|
// src/cli/commands/sandbox.ts
|
|
54950
55521
|
init_config();
|
|
54951
55522
|
import chalk33 from "chalk";
|
|
54952
|
-
import
|
|
55523
|
+
import fs66 from "fs";
|
|
54953
55524
|
import path62 from "path";
|
|
54954
55525
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
54955
55526
|
|
|
54956
55527
|
// src/sandbox/config.ts
|
|
54957
|
-
import
|
|
55528
|
+
import fs64 from "fs";
|
|
54958
55529
|
import path60 from "path";
|
|
54959
55530
|
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
54960
55531
|
var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
|
|
@@ -55032,12 +55603,12 @@ function sandboxConfigPath(cwd = process.cwd()) {
|
|
|
55032
55603
|
}
|
|
55033
55604
|
function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
|
|
55034
55605
|
const p = sandboxConfigPath(cwd);
|
|
55035
|
-
if (!
|
|
55606
|
+
if (!fs64.existsSync(p)) {
|
|
55036
55607
|
throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
|
|
55037
55608
|
}
|
|
55038
55609
|
let raw;
|
|
55039
55610
|
try {
|
|
55040
|
-
raw = parseYaml2(
|
|
55611
|
+
raw = parseYaml2(fs64.readFileSync(p, "utf-8"));
|
|
55041
55612
|
} catch (err2) {
|
|
55042
55613
|
throw new Error(
|
|
55043
55614
|
`sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
|
|
@@ -55096,7 +55667,7 @@ init_templates();
|
|
|
55096
55667
|
|
|
55097
55668
|
// src/sandbox/runtime.ts
|
|
55098
55669
|
init_templates();
|
|
55099
|
-
import
|
|
55670
|
+
import fs65 from "fs";
|
|
55100
55671
|
import os55 from "os";
|
|
55101
55672
|
import path61 from "path";
|
|
55102
55673
|
import crypto9 from "crypto";
|
|
@@ -55123,7 +55694,7 @@ function buildRunArgs(opts) {
|
|
|
55123
55694
|
args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
|
|
55124
55695
|
if (config.node9.mountAgentCredentials) {
|
|
55125
55696
|
const creds = agentCredentialsMount(config.agent);
|
|
55126
|
-
if (
|
|
55697
|
+
if (fs65.existsSync(creds.hostPath)) {
|
|
55127
55698
|
args.push("-v", `${creds.hostPath}:${creds.target}`);
|
|
55128
55699
|
}
|
|
55129
55700
|
}
|
|
@@ -55145,16 +55716,16 @@ function sandboxBuildDir(cwd = process.cwd()) {
|
|
|
55145
55716
|
}
|
|
55146
55717
|
function writeBuildContext(cwd, dockerfile, entrypoint) {
|
|
55147
55718
|
const dir = sandboxBuildDir(cwd);
|
|
55148
|
-
|
|
55149
|
-
|
|
55150
|
-
|
|
55719
|
+
fs65.mkdirSync(dir, { recursive: true });
|
|
55720
|
+
fs65.writeFileSync(path61.join(dir, "Dockerfile"), dockerfile);
|
|
55721
|
+
fs65.writeFileSync(path61.join(dir, "entrypoint.sh"), entrypoint);
|
|
55151
55722
|
return dir;
|
|
55152
55723
|
}
|
|
55153
55724
|
function writeAllowlist(cwd, hosts) {
|
|
55154
55725
|
const dir = path61.join(cwd, ".node9", "sandbox");
|
|
55155
|
-
|
|
55726
|
+
fs65.mkdirSync(dir, { recursive: true });
|
|
55156
55727
|
const p = path61.join(dir, "allowed-domains.txt");
|
|
55157
|
-
|
|
55728
|
+
fs65.writeFileSync(p, hosts.join("\n") + "\n");
|
|
55158
55729
|
return p;
|
|
55159
55730
|
}
|
|
55160
55731
|
function resolveHomePath(p) {
|
|
@@ -55163,7 +55734,7 @@ function resolveHomePath(p) {
|
|
|
55163
55734
|
|
|
55164
55735
|
// src/cli/commands/sandbox.ts
|
|
55165
55736
|
function seedDataDirConfig(dataDir, sandbox) {
|
|
55166
|
-
|
|
55737
|
+
fs66.mkdirSync(dataDir, { recursive: true });
|
|
55167
55738
|
const configPath = path62.join(dataDir, "config.json");
|
|
55168
55739
|
const seed = {
|
|
55169
55740
|
settings: {
|
|
@@ -55175,7 +55746,7 @@ function seedDataDirConfig(dataDir, sandbox) {
|
|
|
55175
55746
|
}
|
|
55176
55747
|
}
|
|
55177
55748
|
};
|
|
55178
|
-
|
|
55749
|
+
fs66.writeFileSync(configPath, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
55179
55750
|
}
|
|
55180
55751
|
function registerSandboxCommand(program2, version2) {
|
|
55181
55752
|
const node9Version2 = pinnedNode9Version(version2);
|
|
@@ -55183,13 +55754,13 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55183
55754
|
cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
|
|
55184
55755
|
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
55185
55756
|
const p = sandboxConfigPath();
|
|
55186
|
-
if (
|
|
55757
|
+
if (fs66.existsSync(p)) {
|
|
55187
55758
|
console.log(
|
|
55188
55759
|
chalk33.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
55189
55760
|
);
|
|
55190
55761
|
return;
|
|
55191
55762
|
}
|
|
55192
|
-
|
|
55763
|
+
fs66.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
55193
55764
|
console.log(
|
|
55194
55765
|
chalk33.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + chalk33.dim(` (agent: ${agent})`)
|
|
55195
55766
|
);
|
|
@@ -55230,7 +55801,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55230
55801
|
const hash = imageContentHash(dockerfile, entrypoint);
|
|
55231
55802
|
const image = sandbox.runtime.image;
|
|
55232
55803
|
const hashFile = path62.join(sandboxBuildDir(cwd), ".image-hash");
|
|
55233
|
-
const lastHash =
|
|
55804
|
+
const lastHash = fs66.existsSync(hashFile) ? fs66.readFileSync(hashFile, "utf-8").trim() : "";
|
|
55234
55805
|
const imageExists = spawnSync6(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
55235
55806
|
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
55236
55807
|
if (needBuild) {
|
|
@@ -55242,7 +55813,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55242
55813
|
console.error(chalk33.red(" build failed."));
|
|
55243
55814
|
process.exit(b.status ?? 1);
|
|
55244
55815
|
}
|
|
55245
|
-
|
|
55816
|
+
fs66.writeFileSync(hashFile, hash);
|
|
55246
55817
|
}
|
|
55247
55818
|
const dataDir = sandboxDataDir(cwd);
|
|
55248
55819
|
seedDataDirConfig(dataDir, sandbox);
|
|
@@ -55256,7 +55827,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55256
55827
|
});
|
|
55257
55828
|
if (sandbox.node9.mountAgentCredentials) {
|
|
55258
55829
|
const creds = agentCredentialsMount(sandbox.agent);
|
|
55259
|
-
if (
|
|
55830
|
+
if (fs66.existsSync(creds.hostPath)) {
|
|
55260
55831
|
console.log(chalk33.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
55261
55832
|
} else {
|
|
55262
55833
|
console.log(
|
|
@@ -55273,7 +55844,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55273
55844
|
});
|
|
55274
55845
|
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
55275
55846
|
const auditPath = path62.join(sandboxDataDir(), "audit.log");
|
|
55276
|
-
if (!
|
|
55847
|
+
if (!fs66.existsSync(auditPath)) {
|
|
55277
55848
|
console.log(chalk33.dim(" no sandbox audit yet."));
|
|
55278
55849
|
return;
|
|
55279
55850
|
}
|
|
@@ -55281,11 +55852,11 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55281
55852
|
});
|
|
55282
55853
|
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
55283
55854
|
const auditPath = path62.join(sandboxDataDir(), "audit.log");
|
|
55284
|
-
if (!
|
|
55855
|
+
if (!fs66.existsSync(auditPath)) {
|
|
55285
55856
|
console.log(chalk33.dim(" no sandbox audit yet."));
|
|
55286
55857
|
return;
|
|
55287
55858
|
}
|
|
55288
|
-
process.stdout.write(
|
|
55859
|
+
process.stdout.write(fs66.readFileSync(auditPath, "utf-8"));
|
|
55289
55860
|
});
|
|
55290
55861
|
cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
|
|
55291
55862
|
const cwd = process.cwd();
|
|
@@ -55299,18 +55870,19 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55299
55870
|
stdio: "ignore"
|
|
55300
55871
|
});
|
|
55301
55872
|
}
|
|
55302
|
-
|
|
55873
|
+
fs66.rmSync(path62.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
55303
55874
|
console.log(chalk33.green(" \u2713 sandbox image + build + data removed."));
|
|
55304
55875
|
});
|
|
55305
55876
|
}
|
|
55306
55877
|
|
|
55307
55878
|
// src/cli/commands/sessions.ts
|
|
55879
|
+
init_decision();
|
|
55308
55880
|
init_scan_summary();
|
|
55309
55881
|
init_litellm();
|
|
55310
55882
|
init_cost_gemini();
|
|
55311
55883
|
init_cost_codex();
|
|
55312
55884
|
import chalk34 from "chalk";
|
|
55313
|
-
import
|
|
55885
|
+
import fs67 from "fs";
|
|
55314
55886
|
import path63 from "path";
|
|
55315
55887
|
import os56 from "os";
|
|
55316
55888
|
function modelPrice(model) {
|
|
@@ -55404,7 +55976,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55404
55976
|
const aPath = auditPath ?? path63.join(os56.homedir(), ".node9", "audit.log");
|
|
55405
55977
|
let raw;
|
|
55406
55978
|
try {
|
|
55407
|
-
raw =
|
|
55979
|
+
raw = fs67.readFileSync(aPath, "utf-8");
|
|
55408
55980
|
} catch {
|
|
55409
55981
|
return [];
|
|
55410
55982
|
}
|
|
@@ -55414,7 +55986,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55414
55986
|
try {
|
|
55415
55987
|
const e = JSON.parse(line);
|
|
55416
55988
|
if (!e.ts || !e.tool || !e.decision) continue;
|
|
55417
|
-
if (e.
|
|
55989
|
+
if (classifyDecision(e).outcome === "allow") continue;
|
|
55418
55990
|
entries.push(e);
|
|
55419
55991
|
} catch {
|
|
55420
55992
|
}
|
|
@@ -55441,7 +56013,7 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
55441
56013
|
}
|
|
55442
56014
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
55443
56015
|
const tmpDir = path63.join(os56.homedir(), ".gemini", "tmp");
|
|
55444
|
-
if (!
|
|
56016
|
+
if (!fs67.existsSync(tmpDir)) return [];
|
|
55445
56017
|
const cutoff = days !== null ? (() => {
|
|
55446
56018
|
const d = /* @__PURE__ */ new Date();
|
|
55447
56019
|
d.setDate(d.getDate() - days);
|
|
@@ -55450,7 +56022,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55450
56022
|
})() : null;
|
|
55451
56023
|
let slugDirs;
|
|
55452
56024
|
try {
|
|
55453
|
-
slugDirs =
|
|
56025
|
+
slugDirs = fs67.readdirSync(tmpDir);
|
|
55454
56026
|
} catch {
|
|
55455
56027
|
return [];
|
|
55456
56028
|
}
|
|
@@ -55458,27 +56030,27 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55458
56030
|
for (const slug2 of slugDirs) {
|
|
55459
56031
|
const slugPath = path63.join(tmpDir, slug2);
|
|
55460
56032
|
try {
|
|
55461
|
-
if (!
|
|
56033
|
+
if (!fs67.statSync(slugPath).isDirectory()) continue;
|
|
55462
56034
|
} catch {
|
|
55463
56035
|
continue;
|
|
55464
56036
|
}
|
|
55465
56037
|
let projectRoot = path63.join(os56.homedir(), slug2);
|
|
55466
56038
|
try {
|
|
55467
|
-
projectRoot =
|
|
56039
|
+
projectRoot = fs67.readFileSync(path63.join(slugPath, ".project_root"), "utf-8").trim();
|
|
55468
56040
|
} catch {
|
|
55469
56041
|
}
|
|
55470
56042
|
const chatsDir = path63.join(slugPath, "chats");
|
|
55471
|
-
if (!
|
|
56043
|
+
if (!fs67.existsSync(chatsDir)) continue;
|
|
55472
56044
|
let chatFiles;
|
|
55473
56045
|
try {
|
|
55474
|
-
chatFiles =
|
|
56046
|
+
chatFiles = fs67.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
55475
56047
|
} catch {
|
|
55476
56048
|
continue;
|
|
55477
56049
|
}
|
|
55478
56050
|
for (const chatFile of chatFiles) {
|
|
55479
56051
|
let raw;
|
|
55480
56052
|
try {
|
|
55481
|
-
raw =
|
|
56053
|
+
raw = fs67.readFileSync(path63.join(chatsDir, chatFile), "utf-8");
|
|
55482
56054
|
} catch {
|
|
55483
56055
|
continue;
|
|
55484
56056
|
}
|
|
@@ -55559,7 +56131,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55559
56131
|
}
|
|
55560
56132
|
function buildCodexSessions(days, allAuditEntries) {
|
|
55561
56133
|
const sessionsBase = path63.join(os56.homedir(), ".codex", "sessions");
|
|
55562
|
-
if (!
|
|
56134
|
+
if (!fs67.existsSync(sessionsBase)) return [];
|
|
55563
56135
|
const cutoff = days !== null ? (() => {
|
|
55564
56136
|
const d = /* @__PURE__ */ new Date();
|
|
55565
56137
|
d.setDate(d.getDate() - days);
|
|
@@ -55568,28 +56140,28 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55568
56140
|
})() : null;
|
|
55569
56141
|
const jsonlFiles = [];
|
|
55570
56142
|
try {
|
|
55571
|
-
for (const year of
|
|
56143
|
+
for (const year of fs67.readdirSync(sessionsBase)) {
|
|
55572
56144
|
const yearPath = path63.join(sessionsBase, year);
|
|
55573
56145
|
try {
|
|
55574
|
-
if (!
|
|
56146
|
+
if (!fs67.statSync(yearPath).isDirectory()) continue;
|
|
55575
56147
|
} catch {
|
|
55576
56148
|
continue;
|
|
55577
56149
|
}
|
|
55578
|
-
for (const month of
|
|
56150
|
+
for (const month of fs67.readdirSync(yearPath)) {
|
|
55579
56151
|
const monthPath = path63.join(yearPath, month);
|
|
55580
56152
|
try {
|
|
55581
|
-
if (!
|
|
56153
|
+
if (!fs67.statSync(monthPath).isDirectory()) continue;
|
|
55582
56154
|
} catch {
|
|
55583
56155
|
continue;
|
|
55584
56156
|
}
|
|
55585
|
-
for (const day of
|
|
56157
|
+
for (const day of fs67.readdirSync(monthPath)) {
|
|
55586
56158
|
const dayPath = path63.join(monthPath, day);
|
|
55587
56159
|
try {
|
|
55588
|
-
if (!
|
|
56160
|
+
if (!fs67.statSync(dayPath).isDirectory()) continue;
|
|
55589
56161
|
} catch {
|
|
55590
56162
|
continue;
|
|
55591
56163
|
}
|
|
55592
|
-
for (const file of
|
|
56164
|
+
for (const file of fs67.readdirSync(dayPath)) {
|
|
55593
56165
|
if (file.endsWith(".jsonl")) jsonlFiles.push(path63.join(dayPath, file));
|
|
55594
56166
|
}
|
|
55595
56167
|
}
|
|
@@ -55602,7 +56174,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55602
56174
|
for (const filePath of jsonlFiles) {
|
|
55603
56175
|
let lines;
|
|
55604
56176
|
try {
|
|
55605
|
-
lines =
|
|
56177
|
+
lines = fs67.readFileSync(filePath, "utf-8").split("\n");
|
|
55606
56178
|
} catch {
|
|
55607
56179
|
continue;
|
|
55608
56180
|
}
|
|
@@ -55691,7 +56263,7 @@ function buildSessions(days, historyPath) {
|
|
|
55691
56263
|
const hPath = historyPath ?? path63.join(os56.homedir(), ".claude", "history.jsonl");
|
|
55692
56264
|
let historyRaw = "";
|
|
55693
56265
|
try {
|
|
55694
|
-
historyRaw =
|
|
56266
|
+
historyRaw = fs67.readFileSync(hPath, "utf-8");
|
|
55695
56267
|
} catch {
|
|
55696
56268
|
}
|
|
55697
56269
|
const cutoff = days !== null ? (() => {
|
|
@@ -55715,7 +56287,7 @@ function buildSessions(days, historyPath) {
|
|
|
55715
56287
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
55716
56288
|
let sessionLines = [];
|
|
55717
56289
|
try {
|
|
55718
|
-
sessionLines =
|
|
56290
|
+
sessionLines = fs67.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
55719
56291
|
} catch {
|
|
55720
56292
|
}
|
|
55721
56293
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -56109,12 +56681,12 @@ function registerSessionTaintCommand(program2) {
|
|
|
56109
56681
|
|
|
56110
56682
|
// src/cli/commands/skill-pin.ts
|
|
56111
56683
|
import chalk36 from "chalk";
|
|
56112
|
-
import
|
|
56684
|
+
import fs68 from "fs";
|
|
56113
56685
|
import os57 from "os";
|
|
56114
56686
|
import path64 from "path";
|
|
56115
56687
|
function wipeSkillSessions() {
|
|
56116
56688
|
try {
|
|
56117
|
-
|
|
56689
|
+
fs68.rmSync(path64.join(os57.homedir(), ".node9", "skill-sessions"), {
|
|
56118
56690
|
recursive: true,
|
|
56119
56691
|
force: true
|
|
56120
56692
|
});
|
|
@@ -56196,15 +56768,15 @@ function registerSkillPinCommand(program2) {
|
|
|
56196
56768
|
}
|
|
56197
56769
|
|
|
56198
56770
|
// src/cli/commands/decisions.ts
|
|
56199
|
-
import
|
|
56771
|
+
import fs69 from "fs";
|
|
56200
56772
|
import os58 from "os";
|
|
56201
56773
|
import path65 from "path";
|
|
56202
56774
|
import chalk37 from "chalk";
|
|
56203
56775
|
var DECISIONS_FILE2 = path65.join(os58.homedir(), ".node9", "decisions.json");
|
|
56204
56776
|
function readDecisions() {
|
|
56205
56777
|
try {
|
|
56206
|
-
if (!
|
|
56207
|
-
const raw =
|
|
56778
|
+
if (!fs69.existsSync(DECISIONS_FILE2)) return {};
|
|
56779
|
+
const raw = fs69.readFileSync(DECISIONS_FILE2, "utf-8");
|
|
56208
56780
|
const parsed = JSON.parse(raw);
|
|
56209
56781
|
const out = {};
|
|
56210
56782
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -56217,10 +56789,10 @@ function readDecisions() {
|
|
|
56217
56789
|
}
|
|
56218
56790
|
function writeDecisions(d) {
|
|
56219
56791
|
const dir = path65.dirname(DECISIONS_FILE2);
|
|
56220
|
-
if (!
|
|
56792
|
+
if (!fs69.existsSync(dir)) fs69.mkdirSync(dir, { recursive: true });
|
|
56221
56793
|
const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
|
|
56222
|
-
|
|
56223
|
-
|
|
56794
|
+
fs69.writeFileSync(tmp, JSON.stringify(d, null, 2));
|
|
56795
|
+
fs69.renameSync(tmp, DECISIONS_FILE2);
|
|
56224
56796
|
}
|
|
56225
56797
|
function registerDecisionsCommand(program2) {
|
|
56226
56798
|
const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
|
|
@@ -56277,7 +56849,7 @@ Persistent decisions (${entries.length})
|
|
|
56277
56849
|
|
|
56278
56850
|
// src/cli/commands/dlp.ts
|
|
56279
56851
|
import chalk38 from "chalk";
|
|
56280
|
-
import
|
|
56852
|
+
import fs70 from "fs";
|
|
56281
56853
|
import path66 from "path";
|
|
56282
56854
|
import os59 from "os";
|
|
56283
56855
|
var AUDIT_LOG = path66.join(os59.homedir(), ".node9", "audit.log");
|
|
@@ -56288,7 +56860,7 @@ function stripAnsi(s) {
|
|
|
56288
56860
|
}
|
|
56289
56861
|
function loadResolved() {
|
|
56290
56862
|
try {
|
|
56291
|
-
const raw = JSON.parse(
|
|
56863
|
+
const raw = JSON.parse(fs70.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
56292
56864
|
return new Set(raw);
|
|
56293
56865
|
} catch {
|
|
56294
56866
|
return /* @__PURE__ */ new Set();
|
|
@@ -56296,13 +56868,13 @@ function loadResolved() {
|
|
|
56296
56868
|
}
|
|
56297
56869
|
function saveResolved(resolved) {
|
|
56298
56870
|
try {
|
|
56299
|
-
|
|
56871
|
+
fs70.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
56300
56872
|
} catch {
|
|
56301
56873
|
}
|
|
56302
56874
|
}
|
|
56303
56875
|
function loadDlpFindings() {
|
|
56304
|
-
if (!
|
|
56305
|
-
return
|
|
56876
|
+
if (!fs70.existsSync(AUDIT_LOG)) return [];
|
|
56877
|
+
return fs70.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
56306
56878
|
if (!line.trim()) return [];
|
|
56307
56879
|
try {
|
|
56308
56880
|
const e = JSON.parse(line);
|
|
@@ -56401,13 +56973,13 @@ function registerDlpCommand(program2) {
|
|
|
56401
56973
|
// src/cli/commands/mask.ts
|
|
56402
56974
|
init_dlp();
|
|
56403
56975
|
import chalk39 from "chalk";
|
|
56404
|
-
import
|
|
56976
|
+
import fs71 from "fs";
|
|
56405
56977
|
import path67 from "path";
|
|
56406
56978
|
import os60 from "os";
|
|
56407
56979
|
function findJsonlFiles(dir) {
|
|
56408
56980
|
const results = [];
|
|
56409
|
-
if (!
|
|
56410
|
-
for (const entry of
|
|
56981
|
+
if (!fs71.existsSync(dir)) return results;
|
|
56982
|
+
for (const entry of fs71.readdirSync(dir, { withFileTypes: true })) {
|
|
56411
56983
|
const full = path67.join(dir, entry.name);
|
|
56412
56984
|
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
56413
56985
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
@@ -56451,7 +57023,7 @@ function redactJson(obj) {
|
|
|
56451
57023
|
function processFile(filePath, dryRun) {
|
|
56452
57024
|
let raw;
|
|
56453
57025
|
try {
|
|
56454
|
-
raw =
|
|
57026
|
+
raw = fs71.readFileSync(filePath, "utf-8");
|
|
56455
57027
|
} catch {
|
|
56456
57028
|
return { redactedLines: 0, patterns: [] };
|
|
56457
57029
|
}
|
|
@@ -56483,14 +57055,14 @@ function processFile(filePath, dryRun) {
|
|
|
56483
57055
|
}
|
|
56484
57056
|
}
|
|
56485
57057
|
if (!dryRun && redactedLines > 0) {
|
|
56486
|
-
|
|
57058
|
+
fs71.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
56487
57059
|
}
|
|
56488
57060
|
return { redactedLines, patterns };
|
|
56489
57061
|
}
|
|
56490
57062
|
function processJsonFile(filePath, dryRun) {
|
|
56491
57063
|
let raw;
|
|
56492
57064
|
try {
|
|
56493
|
-
raw =
|
|
57065
|
+
raw = fs71.readFileSync(filePath, "utf-8");
|
|
56494
57066
|
} catch {
|
|
56495
57067
|
return { redactedLines: 0, patterns: [] };
|
|
56496
57068
|
}
|
|
@@ -56503,14 +57075,14 @@ function processJsonFile(filePath, dryRun) {
|
|
|
56503
57075
|
const { value, modified, found } = redactJson(parsed);
|
|
56504
57076
|
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
56505
57077
|
if (!dryRun) {
|
|
56506
|
-
|
|
57078
|
+
fs71.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
56507
57079
|
}
|
|
56508
57080
|
return { redactedLines: 1, patterns: found };
|
|
56509
57081
|
}
|
|
56510
57082
|
function findJsonFiles(dir) {
|
|
56511
57083
|
const results = [];
|
|
56512
|
-
if (!
|
|
56513
|
-
for (const entry of
|
|
57084
|
+
if (!fs71.existsSync(dir)) return results;
|
|
57085
|
+
for (const entry of fs71.readdirSync(dir, { withFileTypes: true })) {
|
|
56514
57086
|
const full = path67.join(dir, entry.name);
|
|
56515
57087
|
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
56516
57088
|
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
@@ -56530,7 +57102,7 @@ function registerMaskCommand(program2) {
|
|
|
56530
57102
|
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
56531
57103
|
const filtered = cutoff ? allFiles.filter((f) => {
|
|
56532
57104
|
try {
|
|
56533
|
-
return
|
|
57105
|
+
return fs71.statSync(f.path).mtime >= cutoff;
|
|
56534
57106
|
} catch {
|
|
56535
57107
|
return false;
|
|
56536
57108
|
}
|
|
@@ -56586,7 +57158,7 @@ function registerMaskCommand(program2) {
|
|
|
56586
57158
|
// src/cli.ts
|
|
56587
57159
|
init_blast();
|
|
56588
57160
|
var { version } = JSON.parse(
|
|
56589
|
-
|
|
57161
|
+
fs74.readFileSync(path70.join(__dirname, "../package.json"), "utf-8")
|
|
56590
57162
|
);
|
|
56591
57163
|
var program = new Command();
|
|
56592
57164
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
@@ -56766,14 +57338,14 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
56766
57338
|
}
|
|
56767
57339
|
if (options.purge) {
|
|
56768
57340
|
const node9Dir = path70.join(os63.homedir(), ".node9");
|
|
56769
|
-
if (
|
|
57341
|
+
if (fs74.existsSync(node9Dir)) {
|
|
56770
57342
|
const confirmed = await confirm2({
|
|
56771
57343
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
56772
57344
|
default: false
|
|
56773
57345
|
});
|
|
56774
57346
|
if (confirmed) {
|
|
56775
|
-
|
|
56776
|
-
if (
|
|
57347
|
+
fs74.rmSync(node9Dir, { recursive: true });
|
|
57348
|
+
if (fs74.existsSync(node9Dir)) {
|
|
56777
57349
|
console.error(
|
|
56778
57350
|
chalk41.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
56779
57351
|
);
|
|
@@ -56938,12 +57510,12 @@ Run "node9 addto claude" to register it as the statusLine.`
|
|
|
56938
57510
|
if (subcommand === "debug") {
|
|
56939
57511
|
const flagFile = path70.join(os63.homedir(), ".node9", "hud-debug");
|
|
56940
57512
|
if (state === "on") {
|
|
56941
|
-
|
|
56942
|
-
|
|
57513
|
+
fs74.mkdirSync(path70.dirname(flagFile), { recursive: true });
|
|
57514
|
+
fs74.writeFileSync(flagFile, "");
|
|
56943
57515
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
56944
57516
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
56945
57517
|
} else if (state === "off") {
|
|
56946
|
-
if (
|
|
57518
|
+
if (fs74.existsSync(flagFile)) fs74.unlinkSync(flagFile);
|
|
56947
57519
|
console.log("HUD debug logging disabled.");
|
|
56948
57520
|
} else {
|
|
56949
57521
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -57068,7 +57640,7 @@ if (process.argv[2] !== "daemon") {
|
|
|
57068
57640
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
57069
57641
|
const logPath = path70.join(os63.homedir(), ".node9", "hook-debug.log");
|
|
57070
57642
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
57071
|
-
|
|
57643
|
+
fs74.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
57072
57644
|
`);
|
|
57073
57645
|
}
|
|
57074
57646
|
process.exit(0);
|