@node9/proxy 1.0.19 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +518 -235
- package/dist/cli.mjs +524 -241
- package/dist/index.js +197 -54
- package/dist/index.mjs +197 -54
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -380,8 +380,8 @@ function sanitizeConfig(raw) {
|
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
const lines = result.error.issues.map((issue) => {
|
|
383
|
-
const
|
|
384
|
-
return ` \u2022 ${
|
|
383
|
+
const path11 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
384
|
+
return ` \u2022 ${path11}: ${issue.message}`;
|
|
385
385
|
});
|
|
386
386
|
return {
|
|
387
387
|
sanitized,
|
|
@@ -696,6 +696,39 @@ var init_shields = __esm({
|
|
|
696
696
|
});
|
|
697
697
|
|
|
698
698
|
// src/dlp.ts
|
|
699
|
+
function scanFilePath(filePath, cwd = process.cwd()) {
|
|
700
|
+
if (!filePath) return null;
|
|
701
|
+
let resolved;
|
|
702
|
+
try {
|
|
703
|
+
const absolute = import_path4.default.resolve(cwd, filePath);
|
|
704
|
+
resolved = import_fs2.default.realpathSync.native(absolute);
|
|
705
|
+
} catch (err) {
|
|
706
|
+
const code = err.code;
|
|
707
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
708
|
+
resolved = import_path4.default.resolve(cwd, filePath);
|
|
709
|
+
} else {
|
|
710
|
+
return {
|
|
711
|
+
patternName: "Sensitive File Path",
|
|
712
|
+
fieldPath: "file_path",
|
|
713
|
+
redactedSample: filePath,
|
|
714
|
+
severity: "block"
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const normalised = resolved.replace(/\\/g, "/");
|
|
719
|
+
for (const pattern of SENSITIVE_PATH_PATTERNS) {
|
|
720
|
+
if (pattern.test(normalised)) {
|
|
721
|
+
return {
|
|
722
|
+
patternName: "Sensitive File Path",
|
|
723
|
+
fieldPath: "file_path",
|
|
724
|
+
redactedSample: filePath,
|
|
725
|
+
// show original path in alert, not resolved
|
|
726
|
+
severity: "block"
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
699
732
|
function maskSecret(raw, pattern) {
|
|
700
733
|
const match = raw.match(pattern);
|
|
701
734
|
if (!match) return "****";
|
|
@@ -748,10 +781,12 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
|
748
781
|
}
|
|
749
782
|
return null;
|
|
750
783
|
}
|
|
751
|
-
var DLP_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
784
|
+
var import_fs2, import_path4, DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
752
785
|
var init_dlp = __esm({
|
|
753
786
|
"src/dlp.ts"() {
|
|
754
787
|
"use strict";
|
|
788
|
+
import_fs2 = __toESM(require("fs"));
|
|
789
|
+
import_path4 = __toESM(require("path"));
|
|
755
790
|
DLP_PATTERNS = [
|
|
756
791
|
{ name: "AWS Access Key ID", regex: /\bAKIA[0-9A-Z]{16}\b/, severity: "block" },
|
|
757
792
|
{ name: "GitHub Token", regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/, severity: "block" },
|
|
@@ -763,8 +798,43 @@ var init_dlp = __esm({
|
|
|
763
798
|
regex: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/,
|
|
764
799
|
severity: "block"
|
|
765
800
|
},
|
|
801
|
+
// GCP service account JSON (detects the type field that uniquely identifies it)
|
|
802
|
+
{
|
|
803
|
+
name: "GCP Service Account",
|
|
804
|
+
regex: /"type"\s*:\s*"service_account"/,
|
|
805
|
+
severity: "block"
|
|
806
|
+
},
|
|
807
|
+
// NPM auth token in .npmrc format
|
|
808
|
+
{
|
|
809
|
+
name: "NPM Auth Token",
|
|
810
|
+
regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
|
|
811
|
+
severity: "block"
|
|
812
|
+
},
|
|
766
813
|
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]+=*/i, severity: "review" }
|
|
767
814
|
];
|
|
815
|
+
SENSITIVE_PATH_PATTERNS = [
|
|
816
|
+
/[/\\]\.ssh[/\\]/i,
|
|
817
|
+
/[/\\]\.aws[/\\]/i,
|
|
818
|
+
/[/\\]\.config[/\\]gcloud[/\\]/i,
|
|
819
|
+
/[/\\]\.azure[/\\]/i,
|
|
820
|
+
/[/\\]\.kube[/\\]config$/i,
|
|
821
|
+
/[/\\]\.env($|\.)/i,
|
|
822
|
+
// .env, .env.local, .env.production — not .envoy
|
|
823
|
+
/[/\\]\.git-credentials$/i,
|
|
824
|
+
/[/\\]\.npmrc$/i,
|
|
825
|
+
/[/\\]\.docker[/\\]config\.json$/i,
|
|
826
|
+
/[/\\][^/\\]+\.pem$/i,
|
|
827
|
+
/[/\\][^/\\]+\.key$/i,
|
|
828
|
+
/[/\\][^/\\]+\.p12$/i,
|
|
829
|
+
/[/\\][^/\\]+\.pfx$/i,
|
|
830
|
+
/^\/etc\/passwd$/,
|
|
831
|
+
/^\/etc\/shadow$/,
|
|
832
|
+
/^\/etc\/sudoers$/,
|
|
833
|
+
/[/\\]credentials\.json$/i,
|
|
834
|
+
/[/\\]id_rsa$/i,
|
|
835
|
+
/[/\\]id_ed25519$/i,
|
|
836
|
+
/[/\\]id_ecdsa$/i
|
|
837
|
+
];
|
|
768
838
|
MAX_DEPTH = 5;
|
|
769
839
|
MAX_STRING_BYTES = 1e5;
|
|
770
840
|
MAX_JSON_PARSE_BYTES = 1e4;
|
|
@@ -774,11 +844,11 @@ var init_dlp = __esm({
|
|
|
774
844
|
// src/core.ts
|
|
775
845
|
function checkPause() {
|
|
776
846
|
try {
|
|
777
|
-
if (!
|
|
778
|
-
const state = JSON.parse(
|
|
847
|
+
if (!import_fs3.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
848
|
+
const state = JSON.parse(import_fs3.default.readFileSync(PAUSED_FILE, "utf-8"));
|
|
779
849
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
780
850
|
try {
|
|
781
|
-
|
|
851
|
+
import_fs3.default.unlinkSync(PAUSED_FILE);
|
|
782
852
|
} catch {
|
|
783
853
|
}
|
|
784
854
|
return { paused: false };
|
|
@@ -789,11 +859,11 @@ function checkPause() {
|
|
|
789
859
|
}
|
|
790
860
|
}
|
|
791
861
|
function atomicWriteSync(filePath, data, options) {
|
|
792
|
-
const dir =
|
|
793
|
-
if (!
|
|
862
|
+
const dir = import_path5.default.dirname(filePath);
|
|
863
|
+
if (!import_fs3.default.existsSync(dir)) import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
794
864
|
const tmpPath = `${filePath}.${import_os2.default.hostname()}.${process.pid}.tmp`;
|
|
795
|
-
|
|
796
|
-
|
|
865
|
+
import_fs3.default.writeFileSync(tmpPath, data, options);
|
|
866
|
+
import_fs3.default.renameSync(tmpPath, filePath);
|
|
797
867
|
}
|
|
798
868
|
function pauseNode9(durationMs, durationStr) {
|
|
799
869
|
const state = { expiry: Date.now() + durationMs, duration: durationStr };
|
|
@@ -801,18 +871,88 @@ function pauseNode9(durationMs, durationStr) {
|
|
|
801
871
|
}
|
|
802
872
|
function resumeNode9() {
|
|
803
873
|
try {
|
|
804
|
-
if (
|
|
874
|
+
if (import_fs3.default.existsSync(PAUSED_FILE)) import_fs3.default.unlinkSync(PAUSED_FILE);
|
|
805
875
|
} catch {
|
|
806
876
|
}
|
|
807
877
|
}
|
|
878
|
+
function validateRegex(pattern) {
|
|
879
|
+
if (!pattern) return "Pattern is required";
|
|
880
|
+
if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
|
|
881
|
+
let parens = 0, brackets = 0, isEscaped = false, inCharClass = false;
|
|
882
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
883
|
+
const char = pattern[i];
|
|
884
|
+
if (isEscaped) {
|
|
885
|
+
isEscaped = false;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (char === "\\") {
|
|
889
|
+
isEscaped = true;
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (char === "[" && !inCharClass) {
|
|
893
|
+
inCharClass = true;
|
|
894
|
+
brackets++;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
if (char === "]" && inCharClass) {
|
|
898
|
+
inCharClass = false;
|
|
899
|
+
brackets--;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (inCharClass) continue;
|
|
903
|
+
if (char === "(") parens++;
|
|
904
|
+
else if (char === ")") parens--;
|
|
905
|
+
}
|
|
906
|
+
if (parens !== 0) return "Unbalanced parentheses";
|
|
907
|
+
if (brackets !== 0) return "Unbalanced brackets";
|
|
908
|
+
if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
|
|
909
|
+
if (!(0, import_safe_regex2.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
|
|
910
|
+
try {
|
|
911
|
+
new RegExp(pattern);
|
|
912
|
+
} catch (e) {
|
|
913
|
+
return `Invalid regex syntax: ${e.message}`;
|
|
914
|
+
}
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
function getCompiledRegex(pattern, flags = "") {
|
|
918
|
+
if (flags && !/^[gimsuy]+$/.test(flags)) {
|
|
919
|
+
if (process.env.NODE9_DEBUG === "1") console.error(`[Node9] Invalid regex flags: "${flags}"`);
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
const key = `${pattern}\0${flags}`;
|
|
923
|
+
if (regexCache.has(key)) {
|
|
924
|
+
const cached = regexCache.get(key);
|
|
925
|
+
regexCache.delete(key);
|
|
926
|
+
regexCache.set(key, cached);
|
|
927
|
+
return cached;
|
|
928
|
+
}
|
|
929
|
+
const err = validateRegex(pattern);
|
|
930
|
+
if (err) {
|
|
931
|
+
if (process.env.NODE9_DEBUG === "1")
|
|
932
|
+
console.error(`[Node9] Regex blocked: ${err} \u2014 pattern: "${pattern}"`);
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
try {
|
|
936
|
+
const re = new RegExp(pattern, flags);
|
|
937
|
+
if (regexCache.size >= REGEX_CACHE_MAX) {
|
|
938
|
+
const oldest = regexCache.keys().next().value;
|
|
939
|
+
if (oldest) regexCache.delete(oldest);
|
|
940
|
+
}
|
|
941
|
+
regexCache.set(key, re);
|
|
942
|
+
return re;
|
|
943
|
+
} catch (e) {
|
|
944
|
+
if (process.env.NODE9_DEBUG === "1") console.error(`[Node9] Regex compile failed:`, e);
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
808
948
|
function getActiveTrustSession(toolName) {
|
|
809
949
|
try {
|
|
810
|
-
if (!
|
|
811
|
-
const trust = JSON.parse(
|
|
950
|
+
if (!import_fs3.default.existsSync(TRUST_FILE)) return false;
|
|
951
|
+
const trust = JSON.parse(import_fs3.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
812
952
|
const now = Date.now();
|
|
813
953
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
814
954
|
if (active.length !== trust.entries.length) {
|
|
815
|
-
|
|
955
|
+
import_fs3.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
816
956
|
}
|
|
817
957
|
return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
|
|
818
958
|
} catch {
|
|
@@ -823,8 +963,8 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
823
963
|
try {
|
|
824
964
|
let trust = { entries: [] };
|
|
825
965
|
try {
|
|
826
|
-
if (
|
|
827
|
-
trust = JSON.parse(
|
|
966
|
+
if (import_fs3.default.existsSync(TRUST_FILE)) {
|
|
967
|
+
trust = JSON.parse(import_fs3.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
828
968
|
}
|
|
829
969
|
} catch {
|
|
830
970
|
}
|
|
@@ -840,9 +980,9 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
840
980
|
}
|
|
841
981
|
function appendToLog(logPath, entry) {
|
|
842
982
|
try {
|
|
843
|
-
const dir =
|
|
844
|
-
if (!
|
|
845
|
-
|
|
983
|
+
const dir = import_path5.default.dirname(logPath);
|
|
984
|
+
if (!import_fs3.default.existsSync(dir)) import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
985
|
+
import_fs3.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
846
986
|
} catch {
|
|
847
987
|
}
|
|
848
988
|
}
|
|
@@ -884,9 +1024,9 @@ function matchesPattern(text, patterns) {
|
|
|
884
1024
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
885
1025
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
886
1026
|
}
|
|
887
|
-
function getNestedValue(obj,
|
|
1027
|
+
function getNestedValue(obj, path11) {
|
|
888
1028
|
if (!obj || typeof obj !== "object") return null;
|
|
889
|
-
return
|
|
1029
|
+
return path11.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
890
1030
|
}
|
|
891
1031
|
function shouldSnapshot(toolName, args, config) {
|
|
892
1032
|
if (!config.settings.enableUndo) return false;
|
|
@@ -917,19 +1057,16 @@ function evaluateSmartConditions(args, rule) {
|
|
|
917
1057
|
return val !== null && cond.value ? !val.includes(cond.value) : true;
|
|
918
1058
|
case "matches": {
|
|
919
1059
|
if (val === null || !cond.value) return false;
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
1060
|
+
const reM = getCompiledRegex(cond.value, cond.flags ?? "");
|
|
1061
|
+
if (!reM) return false;
|
|
1062
|
+
return reM.test(val);
|
|
925
1063
|
}
|
|
926
1064
|
case "notMatches": {
|
|
927
|
-
if (
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
}
|
|
1065
|
+
if (!cond.value) return false;
|
|
1066
|
+
if (val === null) return true;
|
|
1067
|
+
const reN = getCompiledRegex(cond.value, cond.flags ?? "");
|
|
1068
|
+
if (!reN) return false;
|
|
1069
|
+
return !reN.test(val);
|
|
933
1070
|
}
|
|
934
1071
|
case "matchesGlob":
|
|
935
1072
|
return val !== null && cond.value ? import_picomatch.default.isMatch(val, cond.value) : false;
|
|
@@ -1042,9 +1179,9 @@ function _resetConfigCache() {
|
|
|
1042
1179
|
}
|
|
1043
1180
|
function getGlobalSettings() {
|
|
1044
1181
|
try {
|
|
1045
|
-
const globalConfigPath =
|
|
1046
|
-
if (
|
|
1047
|
-
const parsed = JSON.parse(
|
|
1182
|
+
const globalConfigPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
|
|
1183
|
+
if (import_fs3.default.existsSync(globalConfigPath)) {
|
|
1184
|
+
const parsed = JSON.parse(import_fs3.default.readFileSync(globalConfigPath, "utf-8"));
|
|
1048
1185
|
const settings = parsed.settings || {};
|
|
1049
1186
|
return {
|
|
1050
1187
|
mode: settings.mode || "audit",
|
|
@@ -1066,9 +1203,9 @@ function getGlobalSettings() {
|
|
|
1066
1203
|
}
|
|
1067
1204
|
function getInternalToken() {
|
|
1068
1205
|
try {
|
|
1069
|
-
const pidFile =
|
|
1070
|
-
if (!
|
|
1071
|
-
const data = JSON.parse(
|
|
1206
|
+
const pidFile = import_path5.default.join(import_os2.default.homedir(), ".node9", "daemon.pid");
|
|
1207
|
+
if (!import_fs3.default.existsSync(pidFile)) return null;
|
|
1208
|
+
const data = JSON.parse(import_fs3.default.readFileSync(pidFile, "utf-8"));
|
|
1072
1209
|
process.kill(data.pid, 0);
|
|
1073
1210
|
return data.internalToken ?? null;
|
|
1074
1211
|
} catch {
|
|
@@ -1183,9 +1320,9 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1183
1320
|
}
|
|
1184
1321
|
async function explainPolicy(toolName, args) {
|
|
1185
1322
|
const steps = [];
|
|
1186
|
-
const globalPath =
|
|
1187
|
-
const projectPath =
|
|
1188
|
-
const credsPath =
|
|
1323
|
+
const globalPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
|
|
1324
|
+
const projectPath = import_path5.default.join(process.cwd(), "node9.config.json");
|
|
1325
|
+
const credsPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "credentials.json");
|
|
1189
1326
|
const waterfall = [
|
|
1190
1327
|
{
|
|
1191
1328
|
tier: 1,
|
|
@@ -1196,19 +1333,19 @@ async function explainPolicy(toolName, args) {
|
|
|
1196
1333
|
{
|
|
1197
1334
|
tier: 2,
|
|
1198
1335
|
label: "Cloud policy",
|
|
1199
|
-
status:
|
|
1200
|
-
note:
|
|
1336
|
+
status: import_fs3.default.existsSync(credsPath) ? "active" : "missing",
|
|
1337
|
+
note: import_fs3.default.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
|
|
1201
1338
|
},
|
|
1202
1339
|
{
|
|
1203
1340
|
tier: 3,
|
|
1204
1341
|
label: "Project config",
|
|
1205
|
-
status:
|
|
1342
|
+
status: import_fs3.default.existsSync(projectPath) ? "active" : "missing",
|
|
1206
1343
|
path: projectPath
|
|
1207
1344
|
},
|
|
1208
1345
|
{
|
|
1209
1346
|
tier: 4,
|
|
1210
1347
|
label: "Global config",
|
|
1211
|
-
status:
|
|
1348
|
+
status: import_fs3.default.existsSync(globalPath) ? "active" : "missing",
|
|
1212
1349
|
path: globalPath
|
|
1213
1350
|
},
|
|
1214
1351
|
{
|
|
@@ -1221,7 +1358,9 @@ async function explainPolicy(toolName, args) {
|
|
|
1221
1358
|
const config = getConfig();
|
|
1222
1359
|
const wouldBeIgnored = matchesPattern(toolName, config.policy.ignoredTools);
|
|
1223
1360
|
if (config.policy.dlp.enabled && (!wouldBeIgnored || config.policy.dlp.scanIgnoredTools)) {
|
|
1224
|
-
const
|
|
1361
|
+
const argsObjE = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
1362
|
+
const filePathE = String(argsObjE.file_path ?? argsObjE.path ?? argsObjE.filename ?? "");
|
|
1363
|
+
const dlpMatch = (filePathE ? scanFilePath(filePathE) : null) ?? (args !== void 0 ? scanArgs(args) : null);
|
|
1225
1364
|
if (dlpMatch) {
|
|
1226
1365
|
steps.push({
|
|
1227
1366
|
name: "DLP Content Scanner",
|
|
@@ -1444,10 +1583,10 @@ function isIgnoredTool(toolName) {
|
|
|
1444
1583
|
return matchesPattern(toolName, config.policy.ignoredTools);
|
|
1445
1584
|
}
|
|
1446
1585
|
function isDaemonRunning() {
|
|
1447
|
-
const pidFile =
|
|
1448
|
-
if (
|
|
1586
|
+
const pidFile = import_path5.default.join(import_os2.default.homedir(), ".node9", "daemon.pid");
|
|
1587
|
+
if (import_fs3.default.existsSync(pidFile)) {
|
|
1449
1588
|
try {
|
|
1450
|
-
const { pid, port } = JSON.parse(
|
|
1589
|
+
const { pid, port } = JSON.parse(import_fs3.default.readFileSync(pidFile, "utf-8"));
|
|
1451
1590
|
if (port !== DAEMON_PORT) return false;
|
|
1452
1591
|
process.kill(pid, 0);
|
|
1453
1592
|
return true;
|
|
@@ -1467,9 +1606,9 @@ function isDaemonRunning() {
|
|
|
1467
1606
|
}
|
|
1468
1607
|
function getPersistentDecision(toolName) {
|
|
1469
1608
|
try {
|
|
1470
|
-
const file =
|
|
1471
|
-
if (!
|
|
1472
|
-
const decisions = JSON.parse(
|
|
1609
|
+
const file = import_path5.default.join(import_os2.default.homedir(), ".node9", "decisions.json");
|
|
1610
|
+
if (!import_fs3.default.existsSync(file)) return null;
|
|
1611
|
+
const decisions = JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
|
|
1473
1612
|
const d = decisions[toolName];
|
|
1474
1613
|
if (d === "allow" || d === "deny") return d;
|
|
1475
1614
|
} catch {
|
|
@@ -1612,7 +1751,9 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
1612
1751
|
let policyMatchedWord;
|
|
1613
1752
|
let riskMetadata;
|
|
1614
1753
|
if (config.policy.dlp.enabled && (!isIgnoredTool(toolName) || config.policy.dlp.scanIgnoredTools)) {
|
|
1615
|
-
const
|
|
1754
|
+
const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
1755
|
+
const filePath = String(argsObj.file_path ?? argsObj.path ?? argsObj.filename ?? "");
|
|
1756
|
+
const dlpMatch = (filePath ? scanFilePath(filePath) : null) ?? scanArgs(args);
|
|
1616
1757
|
if (dlpMatch) {
|
|
1617
1758
|
const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
|
|
1618
1759
|
if (dlpMatch.severity === "block") {
|
|
@@ -1986,8 +2127,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
1986
2127
|
}
|
|
1987
2128
|
function getConfig() {
|
|
1988
2129
|
if (cachedConfig) return cachedConfig;
|
|
1989
|
-
const globalPath =
|
|
1990
|
-
const projectPath =
|
|
2130
|
+
const globalPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
|
|
2131
|
+
const projectPath = import_path5.default.join(process.cwd(), "node9.config.json");
|
|
1991
2132
|
const globalConfig = tryLoadConfig(globalPath);
|
|
1992
2133
|
const projectConfig = tryLoadConfig(projectPath);
|
|
1993
2134
|
const mergedSettings = {
|
|
@@ -2082,10 +2223,10 @@ function getConfig() {
|
|
|
2082
2223
|
return cachedConfig;
|
|
2083
2224
|
}
|
|
2084
2225
|
function tryLoadConfig(filePath) {
|
|
2085
|
-
if (!
|
|
2226
|
+
if (!import_fs3.default.existsSync(filePath)) return null;
|
|
2086
2227
|
let raw;
|
|
2087
2228
|
try {
|
|
2088
|
-
raw = JSON.parse(
|
|
2229
|
+
raw = JSON.parse(import_fs3.default.readFileSync(filePath, "utf-8"));
|
|
2089
2230
|
} catch (err) {
|
|
2090
2231
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2091
2232
|
process.stderr.write(
|
|
@@ -2147,9 +2288,9 @@ function getCredentials() {
|
|
|
2147
2288
|
};
|
|
2148
2289
|
}
|
|
2149
2290
|
try {
|
|
2150
|
-
const credPath =
|
|
2151
|
-
if (
|
|
2152
|
-
const creds = JSON.parse(
|
|
2291
|
+
const credPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "credentials.json");
|
|
2292
|
+
if (import_fs3.default.existsSync(credPath)) {
|
|
2293
|
+
const creds = JSON.parse(import_fs3.default.readFileSync(credPath, "utf-8"));
|
|
2153
2294
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
2154
2295
|
const profile = creds[profileName];
|
|
2155
2296
|
if (profile?.apiKey) {
|
|
@@ -2262,29 +2403,33 @@ async function resolveNode9SaaS(requestId, creds, approved) {
|
|
|
2262
2403
|
} catch {
|
|
2263
2404
|
}
|
|
2264
2405
|
}
|
|
2265
|
-
var import_chalk2, import_prompts,
|
|
2406
|
+
var import_chalk2, import_prompts, import_fs3, import_path5, import_os2, import_net, import_crypto2, import_child_process2, import_picomatch, import_safe_regex2, import_sh_syntax, PAUSED_FILE, TRUST_FILE, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, SQL_DML_KEYWORDS, DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig, DAEMON_PORT, DAEMON_HOST, ACTIVITY_SOCKET_PATH;
|
|
2266
2407
|
var init_core = __esm({
|
|
2267
2408
|
"src/core.ts"() {
|
|
2268
2409
|
"use strict";
|
|
2269
2410
|
import_chalk2 = __toESM(require("chalk"));
|
|
2270
2411
|
import_prompts = require("@inquirer/prompts");
|
|
2271
|
-
|
|
2272
|
-
|
|
2412
|
+
import_fs3 = __toESM(require("fs"));
|
|
2413
|
+
import_path5 = __toESM(require("path"));
|
|
2273
2414
|
import_os2 = __toESM(require("os"));
|
|
2274
2415
|
import_net = __toESM(require("net"));
|
|
2275
2416
|
import_crypto2 = require("crypto");
|
|
2276
2417
|
import_child_process2 = require("child_process");
|
|
2277
2418
|
import_picomatch = __toESM(require("picomatch"));
|
|
2419
|
+
import_safe_regex2 = __toESM(require("safe-regex2"));
|
|
2278
2420
|
import_sh_syntax = require("sh-syntax");
|
|
2279
2421
|
init_native();
|
|
2280
2422
|
init_context_sniper();
|
|
2281
2423
|
init_config_schema();
|
|
2282
2424
|
init_shields();
|
|
2283
2425
|
init_dlp();
|
|
2284
|
-
PAUSED_FILE =
|
|
2285
|
-
TRUST_FILE =
|
|
2286
|
-
LOCAL_AUDIT_LOG =
|
|
2287
|
-
HOOK_DEBUG_LOG =
|
|
2426
|
+
PAUSED_FILE = import_path5.default.join(import_os2.default.homedir(), ".node9", "PAUSED");
|
|
2427
|
+
TRUST_FILE = import_path5.default.join(import_os2.default.homedir(), ".node9", "trust.json");
|
|
2428
|
+
LOCAL_AUDIT_LOG = import_path5.default.join(import_os2.default.homedir(), ".node9", "audit.log");
|
|
2429
|
+
HOOK_DEBUG_LOG = import_path5.default.join(import_os2.default.homedir(), ".node9", "hook-debug.log");
|
|
2430
|
+
MAX_REGEX_LENGTH = 100;
|
|
2431
|
+
REGEX_CACHE_MAX = 500;
|
|
2432
|
+
regexCache = /* @__PURE__ */ new Map();
|
|
2288
2433
|
SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
2289
2434
|
DANGEROUS_WORDS = [
|
|
2290
2435
|
"mkfs",
|
|
@@ -2499,7 +2644,7 @@ var init_core = __esm({
|
|
|
2499
2644
|
cachedConfig = null;
|
|
2500
2645
|
DAEMON_PORT = 7391;
|
|
2501
2646
|
DAEMON_HOST = "127.0.0.1";
|
|
2502
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
2647
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path5.default.join(import_os2.default.tmpdir(), "node9-activity.sock");
|
|
2503
2648
|
}
|
|
2504
2649
|
});
|
|
2505
2650
|
|
|
@@ -3957,18 +4102,18 @@ var init_ui2 = __esm({
|
|
|
3957
4102
|
|
|
3958
4103
|
// src/daemon/index.ts
|
|
3959
4104
|
function atomicWriteSync2(filePath, data, options) {
|
|
3960
|
-
const dir =
|
|
3961
|
-
if (!
|
|
4105
|
+
const dir = import_path7.default.dirname(filePath);
|
|
4106
|
+
if (!import_fs5.default.existsSync(dir)) import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
3962
4107
|
const tmpPath = `${filePath}.${(0, import_crypto3.randomUUID)()}.tmp`;
|
|
3963
|
-
|
|
3964
|
-
|
|
4108
|
+
import_fs5.default.writeFileSync(tmpPath, data, options);
|
|
4109
|
+
import_fs5.default.renameSync(tmpPath, filePath);
|
|
3965
4110
|
}
|
|
3966
4111
|
function writeTrustEntry(toolName, durationMs) {
|
|
3967
4112
|
try {
|
|
3968
4113
|
let trust = { entries: [] };
|
|
3969
4114
|
try {
|
|
3970
|
-
if (
|
|
3971
|
-
trust = JSON.parse(
|
|
4115
|
+
if (import_fs5.default.existsSync(TRUST_FILE2))
|
|
4116
|
+
trust = JSON.parse(import_fs5.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
3972
4117
|
} catch {
|
|
3973
4118
|
}
|
|
3974
4119
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -3995,16 +4140,16 @@ function appendAuditLog(data) {
|
|
|
3995
4140
|
decision: data.decision,
|
|
3996
4141
|
source: "daemon"
|
|
3997
4142
|
};
|
|
3998
|
-
const dir =
|
|
3999
|
-
if (!
|
|
4000
|
-
|
|
4143
|
+
const dir = import_path7.default.dirname(AUDIT_LOG_FILE);
|
|
4144
|
+
if (!import_fs5.default.existsSync(dir)) import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
4145
|
+
import_fs5.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
4001
4146
|
} catch {
|
|
4002
4147
|
}
|
|
4003
4148
|
}
|
|
4004
4149
|
function getAuditHistory(limit = 20) {
|
|
4005
4150
|
try {
|
|
4006
|
-
if (!
|
|
4007
|
-
const lines =
|
|
4151
|
+
if (!import_fs5.default.existsSync(AUDIT_LOG_FILE)) return [];
|
|
4152
|
+
const lines = import_fs5.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
4008
4153
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
4009
4154
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
4010
4155
|
} catch {
|
|
@@ -4013,7 +4158,7 @@ function getAuditHistory(limit = 20) {
|
|
|
4013
4158
|
}
|
|
4014
4159
|
function getOrgName() {
|
|
4015
4160
|
try {
|
|
4016
|
-
if (
|
|
4161
|
+
if (import_fs5.default.existsSync(CREDENTIALS_FILE)) {
|
|
4017
4162
|
return "Node9 Cloud";
|
|
4018
4163
|
}
|
|
4019
4164
|
} catch {
|
|
@@ -4021,13 +4166,13 @@ function getOrgName() {
|
|
|
4021
4166
|
return null;
|
|
4022
4167
|
}
|
|
4023
4168
|
function hasStoredSlackKey() {
|
|
4024
|
-
return
|
|
4169
|
+
return import_fs5.default.existsSync(CREDENTIALS_FILE);
|
|
4025
4170
|
}
|
|
4026
4171
|
function writeGlobalSetting(key, value) {
|
|
4027
4172
|
let config = {};
|
|
4028
4173
|
try {
|
|
4029
|
-
if (
|
|
4030
|
-
config = JSON.parse(
|
|
4174
|
+
if (import_fs5.default.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
4175
|
+
config = JSON.parse(import_fs5.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
4031
4176
|
}
|
|
4032
4177
|
} catch {
|
|
4033
4178
|
}
|
|
@@ -4046,7 +4191,7 @@ function abandonPending() {
|
|
|
4046
4191
|
});
|
|
4047
4192
|
if (autoStarted) {
|
|
4048
4193
|
try {
|
|
4049
|
-
|
|
4194
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4050
4195
|
} catch {
|
|
4051
4196
|
}
|
|
4052
4197
|
setTimeout(() => {
|
|
@@ -4096,8 +4241,8 @@ function readBody(req) {
|
|
|
4096
4241
|
}
|
|
4097
4242
|
function readPersistentDecisions() {
|
|
4098
4243
|
try {
|
|
4099
|
-
if (
|
|
4100
|
-
return JSON.parse(
|
|
4244
|
+
if (import_fs5.default.existsSync(DECISIONS_FILE)) {
|
|
4245
|
+
return JSON.parse(import_fs5.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
4101
4246
|
}
|
|
4102
4247
|
} catch {
|
|
4103
4248
|
}
|
|
@@ -4126,7 +4271,7 @@ function startDaemon() {
|
|
|
4126
4271
|
idleTimer = setTimeout(() => {
|
|
4127
4272
|
if (autoStarted) {
|
|
4128
4273
|
try {
|
|
4129
|
-
|
|
4274
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4130
4275
|
} catch {
|
|
4131
4276
|
}
|
|
4132
4277
|
}
|
|
@@ -4511,14 +4656,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
4511
4656
|
server.on("error", (e) => {
|
|
4512
4657
|
if (e.code === "EADDRINUSE") {
|
|
4513
4658
|
try {
|
|
4514
|
-
if (
|
|
4515
|
-
const { pid } = JSON.parse(
|
|
4659
|
+
if (import_fs5.default.existsSync(DAEMON_PID_FILE)) {
|
|
4660
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4516
4661
|
process.kill(pid, 0);
|
|
4517
4662
|
return process.exit(0);
|
|
4518
4663
|
}
|
|
4519
4664
|
} catch {
|
|
4520
4665
|
try {
|
|
4521
|
-
|
|
4666
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4522
4667
|
} catch {
|
|
4523
4668
|
}
|
|
4524
4669
|
server.listen(DAEMON_PORT2, DAEMON_HOST2);
|
|
@@ -4569,7 +4714,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4569
4714
|
console.log(import_chalk4.default.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
4570
4715
|
}
|
|
4571
4716
|
try {
|
|
4572
|
-
|
|
4717
|
+
import_fs5.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4573
4718
|
} catch {
|
|
4574
4719
|
}
|
|
4575
4720
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -4611,30 +4756,30 @@ data: ${JSON.stringify(item.data)}
|
|
|
4611
4756
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
4612
4757
|
process.on("exit", () => {
|
|
4613
4758
|
try {
|
|
4614
|
-
|
|
4759
|
+
import_fs5.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4615
4760
|
} catch {
|
|
4616
4761
|
}
|
|
4617
4762
|
});
|
|
4618
4763
|
}
|
|
4619
4764
|
function stopDaemon() {
|
|
4620
|
-
if (!
|
|
4765
|
+
if (!import_fs5.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk4.default.yellow("Not running."));
|
|
4621
4766
|
try {
|
|
4622
|
-
const { pid } = JSON.parse(
|
|
4767
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4623
4768
|
process.kill(pid, "SIGTERM");
|
|
4624
4769
|
console.log(import_chalk4.default.green("\u2705 Stopped."));
|
|
4625
4770
|
} catch {
|
|
4626
4771
|
console.log(import_chalk4.default.gray("Cleaned up stale PID file."));
|
|
4627
4772
|
} finally {
|
|
4628
4773
|
try {
|
|
4629
|
-
|
|
4774
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4630
4775
|
} catch {
|
|
4631
4776
|
}
|
|
4632
4777
|
}
|
|
4633
4778
|
}
|
|
4634
4779
|
function daemonStatus() {
|
|
4635
|
-
if (
|
|
4780
|
+
if (import_fs5.default.existsSync(DAEMON_PID_FILE)) {
|
|
4636
4781
|
try {
|
|
4637
|
-
const { pid } = JSON.parse(
|
|
4782
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4638
4783
|
process.kill(pid, 0);
|
|
4639
4784
|
console.log(import_chalk4.default.green("Node9 daemon: running"));
|
|
4640
4785
|
return;
|
|
@@ -4653,31 +4798,31 @@ function daemonStatus() {
|
|
|
4653
4798
|
console.log(import_chalk4.default.yellow("Node9 daemon: not running"));
|
|
4654
4799
|
}
|
|
4655
4800
|
}
|
|
4656
|
-
var import_http, import_net2,
|
|
4801
|
+
var import_http, import_net2, import_fs5, import_path7, import_os4, import_child_process3, import_crypto3, import_chalk4, ACTIVITY_SOCKET_PATH2, DAEMON_PORT2, DAEMON_HOST2, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, TRUST_DURATIONS, SECRET_KEY_RE, AUTO_DENY_MS, autoStarted, pending, sseClients, abandonTimer, daemonServer, hadBrowserClient, ACTIVITY_RING_SIZE, activityRing;
|
|
4657
4802
|
var init_daemon = __esm({
|
|
4658
4803
|
"src/daemon/index.ts"() {
|
|
4659
4804
|
"use strict";
|
|
4660
4805
|
init_ui2();
|
|
4661
4806
|
import_http = __toESM(require("http"));
|
|
4662
4807
|
import_net2 = __toESM(require("net"));
|
|
4663
|
-
|
|
4664
|
-
|
|
4808
|
+
import_fs5 = __toESM(require("fs"));
|
|
4809
|
+
import_path7 = __toESM(require("path"));
|
|
4665
4810
|
import_os4 = __toESM(require("os"));
|
|
4666
4811
|
import_child_process3 = require("child_process");
|
|
4667
4812
|
import_crypto3 = require("crypto");
|
|
4668
4813
|
import_chalk4 = __toESM(require("chalk"));
|
|
4669
4814
|
init_core();
|
|
4670
4815
|
init_shields();
|
|
4671
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
4816
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path7.default.join(import_os4.default.tmpdir(), "node9-activity.sock");
|
|
4672
4817
|
DAEMON_PORT2 = 7391;
|
|
4673
4818
|
DAEMON_HOST2 = "127.0.0.1";
|
|
4674
4819
|
homeDir = import_os4.default.homedir();
|
|
4675
|
-
DAEMON_PID_FILE =
|
|
4676
|
-
DECISIONS_FILE =
|
|
4677
|
-
GLOBAL_CONFIG_FILE =
|
|
4678
|
-
CREDENTIALS_FILE =
|
|
4679
|
-
AUDIT_LOG_FILE =
|
|
4680
|
-
TRUST_FILE2 =
|
|
4820
|
+
DAEMON_PID_FILE = import_path7.default.join(homeDir, ".node9", "daemon.pid");
|
|
4821
|
+
DECISIONS_FILE = import_path7.default.join(homeDir, ".node9", "decisions.json");
|
|
4822
|
+
GLOBAL_CONFIG_FILE = import_path7.default.join(homeDir, ".node9", "config.json");
|
|
4823
|
+
CREDENTIALS_FILE = import_path7.default.join(homeDir, ".node9", "credentials.json");
|
|
4824
|
+
AUDIT_LOG_FILE = import_path7.default.join(homeDir, ".node9", "audit.log");
|
|
4825
|
+
TRUST_FILE2 = import_path7.default.join(homeDir, ".node9", "trust.json");
|
|
4681
4826
|
TRUST_DURATIONS = {
|
|
4682
4827
|
"30m": 30 * 6e4,
|
|
4683
4828
|
"1h": 60 * 6e4,
|
|
@@ -4738,9 +4883,9 @@ function renderPending(activity) {
|
|
|
4738
4883
|
}
|
|
4739
4884
|
async function ensureDaemon() {
|
|
4740
4885
|
let pidPort = null;
|
|
4741
|
-
if (
|
|
4886
|
+
if (import_fs7.default.existsSync(PID_FILE)) {
|
|
4742
4887
|
try {
|
|
4743
|
-
const { port } = JSON.parse(
|
|
4888
|
+
const { port } = JSON.parse(import_fs7.default.readFileSync(PID_FILE, "utf-8"));
|
|
4744
4889
|
pidPort = port;
|
|
4745
4890
|
} catch {
|
|
4746
4891
|
console.error(import_chalk5.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -4895,19 +5040,19 @@ async function startTail(options = {}) {
|
|
|
4895
5040
|
process.exit(1);
|
|
4896
5041
|
});
|
|
4897
5042
|
}
|
|
4898
|
-
var import_http2, import_chalk5,
|
|
5043
|
+
var import_http2, import_chalk5, import_fs7, import_os6, import_path9, import_readline, import_child_process5, PID_FILE, ICONS;
|
|
4899
5044
|
var init_tail = __esm({
|
|
4900
5045
|
"src/tui/tail.ts"() {
|
|
4901
5046
|
"use strict";
|
|
4902
5047
|
import_http2 = __toESM(require("http"));
|
|
4903
5048
|
import_chalk5 = __toESM(require("chalk"));
|
|
4904
|
-
|
|
5049
|
+
import_fs7 = __toESM(require("fs"));
|
|
4905
5050
|
import_os6 = __toESM(require("os"));
|
|
4906
|
-
|
|
5051
|
+
import_path9 = __toESM(require("path"));
|
|
4907
5052
|
import_readline = __toESM(require("readline"));
|
|
4908
5053
|
import_child_process5 = require("child_process");
|
|
4909
5054
|
init_daemon();
|
|
4910
|
-
PID_FILE =
|
|
5055
|
+
PID_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
|
|
4911
5056
|
ICONS = {
|
|
4912
5057
|
bash: "\u{1F4BB}",
|
|
4913
5058
|
shell: "\u{1F4BB}",
|
|
@@ -4933,8 +5078,8 @@ var import_commander = require("commander");
|
|
|
4933
5078
|
init_core();
|
|
4934
5079
|
|
|
4935
5080
|
// src/setup.ts
|
|
4936
|
-
var
|
|
4937
|
-
var
|
|
5081
|
+
var import_fs4 = __toESM(require("fs"));
|
|
5082
|
+
var import_path6 = __toESM(require("path"));
|
|
4938
5083
|
var import_os3 = __toESM(require("os"));
|
|
4939
5084
|
var import_chalk3 = __toESM(require("chalk"));
|
|
4940
5085
|
var import_prompts2 = require("@inquirer/prompts");
|
|
@@ -4952,17 +5097,17 @@ function fullPathCommand(subcommand) {
|
|
|
4952
5097
|
}
|
|
4953
5098
|
function readJson(filePath) {
|
|
4954
5099
|
try {
|
|
4955
|
-
if (
|
|
4956
|
-
return JSON.parse(
|
|
5100
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
5101
|
+
return JSON.parse(import_fs4.default.readFileSync(filePath, "utf-8"));
|
|
4957
5102
|
}
|
|
4958
5103
|
} catch {
|
|
4959
5104
|
}
|
|
4960
5105
|
return null;
|
|
4961
5106
|
}
|
|
4962
5107
|
function writeJson(filePath, data) {
|
|
4963
|
-
const dir =
|
|
4964
|
-
if (!
|
|
4965
|
-
|
|
5108
|
+
const dir = import_path6.default.dirname(filePath);
|
|
5109
|
+
if (!import_fs4.default.existsSync(dir)) import_fs4.default.mkdirSync(dir, { recursive: true });
|
|
5110
|
+
import_fs4.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
4966
5111
|
}
|
|
4967
5112
|
function isNode9Hook(cmd) {
|
|
4968
5113
|
if (!cmd) return false;
|
|
@@ -4970,8 +5115,8 @@ function isNode9Hook(cmd) {
|
|
|
4970
5115
|
}
|
|
4971
5116
|
function teardownClaude() {
|
|
4972
5117
|
const homeDir2 = import_os3.default.homedir();
|
|
4973
|
-
const hooksPath =
|
|
4974
|
-
const mcpPath =
|
|
5118
|
+
const hooksPath = import_path6.default.join(homeDir2, ".claude", "settings.json");
|
|
5119
|
+
const mcpPath = import_path6.default.join(homeDir2, ".claude.json");
|
|
4975
5120
|
let changed = false;
|
|
4976
5121
|
const settings = readJson(hooksPath);
|
|
4977
5122
|
if (settings?.hooks) {
|
|
@@ -5020,7 +5165,7 @@ function teardownClaude() {
|
|
|
5020
5165
|
}
|
|
5021
5166
|
function teardownGemini() {
|
|
5022
5167
|
const homeDir2 = import_os3.default.homedir();
|
|
5023
|
-
const settingsPath =
|
|
5168
|
+
const settingsPath = import_path6.default.join(homeDir2, ".gemini", "settings.json");
|
|
5024
5169
|
const settings = readJson(settingsPath);
|
|
5025
5170
|
if (!settings) {
|
|
5026
5171
|
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -5059,7 +5204,7 @@ function teardownGemini() {
|
|
|
5059
5204
|
}
|
|
5060
5205
|
function teardownCursor() {
|
|
5061
5206
|
const homeDir2 = import_os3.default.homedir();
|
|
5062
|
-
const mcpPath =
|
|
5207
|
+
const mcpPath = import_path6.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5063
5208
|
const mcpConfig = readJson(mcpPath);
|
|
5064
5209
|
if (!mcpConfig?.mcpServers) {
|
|
5065
5210
|
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -5086,8 +5231,8 @@ function teardownCursor() {
|
|
|
5086
5231
|
}
|
|
5087
5232
|
async function setupClaude() {
|
|
5088
5233
|
const homeDir2 = import_os3.default.homedir();
|
|
5089
|
-
const mcpPath =
|
|
5090
|
-
const hooksPath =
|
|
5234
|
+
const mcpPath = import_path6.default.join(homeDir2, ".claude.json");
|
|
5235
|
+
const hooksPath = import_path6.default.join(homeDir2, ".claude", "settings.json");
|
|
5091
5236
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
5092
5237
|
const settings = readJson(hooksPath) ?? {};
|
|
5093
5238
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -5162,7 +5307,7 @@ async function setupClaude() {
|
|
|
5162
5307
|
}
|
|
5163
5308
|
async function setupGemini() {
|
|
5164
5309
|
const homeDir2 = import_os3.default.homedir();
|
|
5165
|
-
const settingsPath =
|
|
5310
|
+
const settingsPath = import_path6.default.join(homeDir2, ".gemini", "settings.json");
|
|
5166
5311
|
const settings = readJson(settingsPath) ?? {};
|
|
5167
5312
|
const servers = settings.mcpServers ?? {};
|
|
5168
5313
|
let anythingChanged = false;
|
|
@@ -5245,7 +5390,7 @@ async function setupGemini() {
|
|
|
5245
5390
|
}
|
|
5246
5391
|
async function setupCursor() {
|
|
5247
5392
|
const homeDir2 = import_os3.default.homedir();
|
|
5248
|
-
const mcpPath =
|
|
5393
|
+
const mcpPath = import_path6.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5249
5394
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
5250
5395
|
const servers = mcpConfig.mcpServers ?? {};
|
|
5251
5396
|
let anythingChanged = false;
|
|
@@ -5306,30 +5451,32 @@ var import_execa = require("execa");
|
|
|
5306
5451
|
var import_execa2 = require("execa");
|
|
5307
5452
|
var import_chalk6 = __toESM(require("chalk"));
|
|
5308
5453
|
var import_readline2 = __toESM(require("readline"));
|
|
5309
|
-
var
|
|
5310
|
-
var
|
|
5454
|
+
var import_fs8 = __toESM(require("fs"));
|
|
5455
|
+
var import_path10 = __toESM(require("path"));
|
|
5311
5456
|
var import_os7 = __toESM(require("os"));
|
|
5312
5457
|
|
|
5313
5458
|
// src/undo.ts
|
|
5314
5459
|
var import_child_process4 = require("child_process");
|
|
5315
|
-
var
|
|
5316
|
-
var
|
|
5460
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
5461
|
+
var import_fs6 = __toESM(require("fs"));
|
|
5462
|
+
var import_path8 = __toESM(require("path"));
|
|
5317
5463
|
var import_os5 = __toESM(require("os"));
|
|
5318
|
-
var SNAPSHOT_STACK_PATH =
|
|
5319
|
-
var UNDO_LATEST_PATH =
|
|
5464
|
+
var SNAPSHOT_STACK_PATH = import_path8.default.join(import_os5.default.homedir(), ".node9", "snapshots.json");
|
|
5465
|
+
var UNDO_LATEST_PATH = import_path8.default.join(import_os5.default.homedir(), ".node9", "undo_latest.txt");
|
|
5320
5466
|
var MAX_SNAPSHOTS = 10;
|
|
5467
|
+
var GIT_TIMEOUT = 15e3;
|
|
5321
5468
|
function readStack() {
|
|
5322
5469
|
try {
|
|
5323
|
-
if (
|
|
5324
|
-
return JSON.parse(
|
|
5470
|
+
if (import_fs6.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
5471
|
+
return JSON.parse(import_fs6.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
5325
5472
|
} catch {
|
|
5326
5473
|
}
|
|
5327
5474
|
return [];
|
|
5328
5475
|
}
|
|
5329
5476
|
function writeStack(stack) {
|
|
5330
|
-
const dir =
|
|
5331
|
-
if (!
|
|
5332
|
-
|
|
5477
|
+
const dir = import_path8.default.dirname(SNAPSHOT_STACK_PATH);
|
|
5478
|
+
if (!import_fs6.default.existsSync(dir)) import_fs6.default.mkdirSync(dir, { recursive: true });
|
|
5479
|
+
import_fs6.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
5333
5480
|
}
|
|
5334
5481
|
function buildArgsSummary(tool, args) {
|
|
5335
5482
|
if (!args || typeof args !== "object") return "";
|
|
@@ -5342,54 +5489,177 @@ function buildArgsSummary(tool, args) {
|
|
|
5342
5489
|
if (typeof sql === "string") return sql.slice(0, 80);
|
|
5343
5490
|
return tool;
|
|
5344
5491
|
}
|
|
5345
|
-
|
|
5492
|
+
function normalizeCwdForHash(cwd) {
|
|
5493
|
+
let normalized;
|
|
5494
|
+
try {
|
|
5495
|
+
normalized = import_fs6.default.realpathSync(cwd);
|
|
5496
|
+
} catch {
|
|
5497
|
+
normalized = cwd;
|
|
5498
|
+
}
|
|
5499
|
+
normalized = normalized.replace(/\\/g, "/");
|
|
5500
|
+
if (process.platform === "win32") normalized = normalized.toLowerCase();
|
|
5501
|
+
return normalized;
|
|
5502
|
+
}
|
|
5503
|
+
function getShadowRepoDir(cwd) {
|
|
5504
|
+
const hash = import_crypto4.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
5505
|
+
return import_path8.default.join(import_os5.default.homedir(), ".node9", "snapshots", hash);
|
|
5506
|
+
}
|
|
5507
|
+
function cleanOrphanedIndexFiles(shadowDir) {
|
|
5508
|
+
try {
|
|
5509
|
+
const cutoff = Date.now() - 6e4;
|
|
5510
|
+
for (const f of import_fs6.default.readdirSync(shadowDir)) {
|
|
5511
|
+
if (f.startsWith("index_")) {
|
|
5512
|
+
const fp = import_path8.default.join(shadowDir, f);
|
|
5513
|
+
try {
|
|
5514
|
+
if (import_fs6.default.statSync(fp).mtimeMs < cutoff) import_fs6.default.unlinkSync(fp);
|
|
5515
|
+
} catch {
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
} catch {
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
5523
|
+
const hardcoded = [".git", ".node9"];
|
|
5524
|
+
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
5525
|
+
try {
|
|
5526
|
+
import_fs6.default.writeFileSync(import_path8.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
5527
|
+
} catch {
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
function ensureShadowRepo(shadowDir, cwd) {
|
|
5531
|
+
cleanOrphanedIndexFiles(shadowDir);
|
|
5532
|
+
const normalizedCwd = normalizeCwdForHash(cwd);
|
|
5533
|
+
const shadowEnvBase = { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
5534
|
+
const check = (0, import_child_process4.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
5535
|
+
env: shadowEnvBase,
|
|
5536
|
+
timeout: 3e3
|
|
5537
|
+
});
|
|
5538
|
+
if (check.status === 0) {
|
|
5539
|
+
const ptPath = import_path8.default.join(shadowDir, "project-path.txt");
|
|
5540
|
+
try {
|
|
5541
|
+
const stored = import_fs6.default.readFileSync(ptPath, "utf8").trim();
|
|
5542
|
+
if (stored === normalizedCwd) return true;
|
|
5543
|
+
if (process.env.NODE9_DEBUG === "1")
|
|
5544
|
+
console.error(
|
|
5545
|
+
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
5546
|
+
);
|
|
5547
|
+
import_fs6.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
5548
|
+
} catch {
|
|
5549
|
+
try {
|
|
5550
|
+
import_fs6.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
5551
|
+
} catch {
|
|
5552
|
+
}
|
|
5553
|
+
return true;
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
try {
|
|
5557
|
+
import_fs6.default.mkdirSync(shadowDir, { recursive: true });
|
|
5558
|
+
} catch {
|
|
5559
|
+
}
|
|
5560
|
+
const init = (0, import_child_process4.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
5561
|
+
if (init.status !== 0) {
|
|
5562
|
+
if (process.env.NODE9_DEBUG === "1")
|
|
5563
|
+
console.error("[Node9] git init --bare failed:", init.stderr?.toString());
|
|
5564
|
+
return false;
|
|
5565
|
+
}
|
|
5566
|
+
const configFile = import_path8.default.join(shadowDir, "config");
|
|
5567
|
+
(0, import_child_process4.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
5568
|
+
timeout: 3e3
|
|
5569
|
+
});
|
|
5570
|
+
(0, import_child_process4.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
|
|
5571
|
+
timeout: 3e3
|
|
5572
|
+
});
|
|
5573
|
+
try {
|
|
5574
|
+
import_fs6.default.writeFileSync(import_path8.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
5575
|
+
} catch {
|
|
5576
|
+
}
|
|
5577
|
+
return true;
|
|
5578
|
+
}
|
|
5579
|
+
function buildGitEnv(cwd) {
|
|
5580
|
+
const shadowDir = getShadowRepoDir(cwd);
|
|
5581
|
+
const check = (0, import_child_process4.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
5582
|
+
env: { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd },
|
|
5583
|
+
timeout: 2e3
|
|
5584
|
+
});
|
|
5585
|
+
if (check.status === 0) {
|
|
5586
|
+
return { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
5587
|
+
}
|
|
5588
|
+
return { ...process.env };
|
|
5589
|
+
}
|
|
5590
|
+
async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = []) {
|
|
5591
|
+
let indexFile = null;
|
|
5346
5592
|
try {
|
|
5347
5593
|
const cwd = process.cwd();
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
const
|
|
5353
|
-
|
|
5354
|
-
|
|
5594
|
+
const shadowDir = getShadowRepoDir(cwd);
|
|
5595
|
+
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
5596
|
+
writeShadowExcludes(shadowDir, ignorePaths);
|
|
5597
|
+
indexFile = import_path8.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
5598
|
+
const shadowEnv = {
|
|
5599
|
+
...process.env,
|
|
5600
|
+
GIT_DIR: shadowDir,
|
|
5601
|
+
GIT_WORK_TREE: cwd,
|
|
5602
|
+
GIT_INDEX_FILE: indexFile
|
|
5603
|
+
};
|
|
5604
|
+
(0, import_child_process4.spawnSync)("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
5605
|
+
const treeRes = (0, import_child_process4.spawnSync)("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
5606
|
+
const treeHash = treeRes.stdout?.toString().trim();
|
|
5355
5607
|
if (!treeHash || treeRes.status !== 0) return null;
|
|
5356
|
-
const commitRes = (0, import_child_process4.spawnSync)(
|
|
5357
|
-
"
|
|
5358
|
-
treeHash,
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
const commitHash = commitRes.stdout.toString().trim();
|
|
5608
|
+
const commitRes = (0, import_child_process4.spawnSync)(
|
|
5609
|
+
"git",
|
|
5610
|
+
["commit-tree", treeHash, "-m", `Node9 AI Snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`],
|
|
5611
|
+
{ env: shadowEnv, timeout: GIT_TIMEOUT }
|
|
5612
|
+
);
|
|
5613
|
+
const commitHash = commitRes.stdout?.toString().trim();
|
|
5363
5614
|
if (!commitHash || commitRes.status !== 0) return null;
|
|
5364
5615
|
const stack = readStack();
|
|
5365
|
-
|
|
5616
|
+
stack.push({
|
|
5366
5617
|
hash: commitHash,
|
|
5367
5618
|
tool,
|
|
5368
5619
|
argsSummary: buildArgsSummary(tool, args),
|
|
5369
5620
|
cwd,
|
|
5370
5621
|
timestamp: Date.now()
|
|
5371
|
-
};
|
|
5372
|
-
stack.
|
|
5622
|
+
});
|
|
5623
|
+
const shouldGc = stack.length % 5 === 0;
|
|
5373
5624
|
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
|
|
5374
5625
|
writeStack(stack);
|
|
5375
|
-
|
|
5626
|
+
import_fs6.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
5627
|
+
if (shouldGc) {
|
|
5628
|
+
(0, import_child_process4.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
5629
|
+
}
|
|
5376
5630
|
return commitHash;
|
|
5377
5631
|
} catch (err) {
|
|
5378
5632
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9 Undo Engine Error]:", err);
|
|
5633
|
+
return null;
|
|
5634
|
+
} finally {
|
|
5635
|
+
if (indexFile) {
|
|
5636
|
+
try {
|
|
5637
|
+
import_fs6.default.unlinkSync(indexFile);
|
|
5638
|
+
} catch {
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5379
5641
|
}
|
|
5380
|
-
return null;
|
|
5381
5642
|
}
|
|
5382
5643
|
function getSnapshotHistory() {
|
|
5383
5644
|
return readStack();
|
|
5384
5645
|
}
|
|
5385
5646
|
function computeUndoDiff(hash, cwd) {
|
|
5386
5647
|
try {
|
|
5387
|
-
const
|
|
5388
|
-
const
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5648
|
+
const env = buildGitEnv(cwd);
|
|
5649
|
+
const statRes = (0, import_child_process4.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
|
|
5650
|
+
cwd,
|
|
5651
|
+
env,
|
|
5652
|
+
timeout: GIT_TIMEOUT
|
|
5653
|
+
});
|
|
5654
|
+
const stat = statRes.stdout?.toString().trim();
|
|
5655
|
+
if (!stat || statRes.status !== 0) return null;
|
|
5656
|
+
const diffRes = (0, import_child_process4.spawnSync)("git", ["diff", hash, "--", "."], {
|
|
5657
|
+
cwd,
|
|
5658
|
+
env,
|
|
5659
|
+
timeout: GIT_TIMEOUT
|
|
5660
|
+
});
|
|
5661
|
+
const raw = diffRes.stdout?.toString();
|
|
5662
|
+
if (!raw || diffRes.status !== 0) return null;
|
|
5393
5663
|
const lines = raw.split("\n").filter(
|
|
5394
5664
|
(l) => !l.startsWith("diff --git") && !l.startsWith("index ") && !l.startsWith("Binary")
|
|
5395
5665
|
);
|
|
@@ -5401,18 +5671,31 @@ function computeUndoDiff(hash, cwd) {
|
|
|
5401
5671
|
function applyUndo(hash, cwd) {
|
|
5402
5672
|
try {
|
|
5403
5673
|
const dir = cwd ?? process.cwd();
|
|
5674
|
+
const env = buildGitEnv(dir);
|
|
5404
5675
|
const restore = (0, import_child_process4.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
|
|
5405
|
-
cwd: dir
|
|
5676
|
+
cwd: dir,
|
|
5677
|
+
env,
|
|
5678
|
+
timeout: GIT_TIMEOUT
|
|
5406
5679
|
});
|
|
5407
5680
|
if (restore.status !== 0) return false;
|
|
5408
|
-
const lsTree = (0, import_child_process4.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5681
|
+
const lsTree = (0, import_child_process4.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
5682
|
+
cwd: dir,
|
|
5683
|
+
env,
|
|
5684
|
+
timeout: GIT_TIMEOUT
|
|
5685
|
+
});
|
|
5686
|
+
const snapshotFiles = new Set(
|
|
5687
|
+
lsTree.stdout?.toString().trim().split("\n").filter(Boolean) ?? []
|
|
5688
|
+
);
|
|
5689
|
+
const tracked = (0, import_child_process4.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
5690
|
+
const untracked = (0, import_child_process4.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
5691
|
+
cwd: dir,
|
|
5692
|
+
env,
|
|
5693
|
+
timeout: GIT_TIMEOUT
|
|
5694
|
+
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
5412
5695
|
for (const file of [...tracked, ...untracked]) {
|
|
5413
|
-
const fullPath =
|
|
5414
|
-
if (!snapshotFiles.has(file) &&
|
|
5415
|
-
|
|
5696
|
+
const fullPath = import_path8.default.join(dir, file);
|
|
5697
|
+
if (!snapshotFiles.has(file) && import_fs6.default.existsSync(fullPath)) {
|
|
5698
|
+
import_fs6.default.unlinkSync(fullPath);
|
|
5416
5699
|
}
|
|
5417
5700
|
}
|
|
5418
5701
|
return true;
|
|
@@ -5425,7 +5708,7 @@ function applyUndo(hash, cwd) {
|
|
|
5425
5708
|
init_shields();
|
|
5426
5709
|
var import_prompts3 = require("@inquirer/prompts");
|
|
5427
5710
|
var { version } = JSON.parse(
|
|
5428
|
-
|
|
5711
|
+
import_fs8.default.readFileSync(import_path10.default.join(__dirname, "../package.json"), "utf-8")
|
|
5429
5712
|
);
|
|
5430
5713
|
function parseDuration(str) {
|
|
5431
5714
|
const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
|
|
@@ -5627,14 +5910,14 @@ async function runProxy(targetCommand) {
|
|
|
5627
5910
|
}
|
|
5628
5911
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
5629
5912
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
5630
|
-
const credPath =
|
|
5631
|
-
if (!
|
|
5632
|
-
|
|
5913
|
+
const credPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "credentials.json");
|
|
5914
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(credPath)))
|
|
5915
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(credPath), { recursive: true });
|
|
5633
5916
|
const profileName = options.profile || "default";
|
|
5634
5917
|
let existingCreds = {};
|
|
5635
5918
|
try {
|
|
5636
|
-
if (
|
|
5637
|
-
const raw = JSON.parse(
|
|
5919
|
+
if (import_fs8.default.existsSync(credPath)) {
|
|
5920
|
+
const raw = JSON.parse(import_fs8.default.readFileSync(credPath, "utf-8"));
|
|
5638
5921
|
if (raw.apiKey) {
|
|
5639
5922
|
existingCreds = {
|
|
5640
5923
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -5646,13 +5929,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
5646
5929
|
} catch {
|
|
5647
5930
|
}
|
|
5648
5931
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
5649
|
-
|
|
5932
|
+
import_fs8.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
5650
5933
|
if (profileName === "default") {
|
|
5651
|
-
const configPath =
|
|
5934
|
+
const configPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
5652
5935
|
let config = {};
|
|
5653
5936
|
try {
|
|
5654
|
-
if (
|
|
5655
|
-
config = JSON.parse(
|
|
5937
|
+
if (import_fs8.default.existsSync(configPath))
|
|
5938
|
+
config = JSON.parse(import_fs8.default.readFileSync(configPath, "utf-8"));
|
|
5656
5939
|
} catch {
|
|
5657
5940
|
}
|
|
5658
5941
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -5667,9 +5950,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
5667
5950
|
approvers.cloud = false;
|
|
5668
5951
|
}
|
|
5669
5952
|
s.approvers = approvers;
|
|
5670
|
-
if (!
|
|
5671
|
-
|
|
5672
|
-
|
|
5953
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(configPath)))
|
|
5954
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(configPath), { recursive: true });
|
|
5955
|
+
import_fs8.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5673
5956
|
}
|
|
5674
5957
|
if (options.profile && profileName !== "default") {
|
|
5675
5958
|
console.log(import_chalk6.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -5755,15 +6038,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
5755
6038
|
}
|
|
5756
6039
|
}
|
|
5757
6040
|
if (options.purge) {
|
|
5758
|
-
const node9Dir =
|
|
5759
|
-
if (
|
|
6041
|
+
const node9Dir = import_path10.default.join(import_os7.default.homedir(), ".node9");
|
|
6042
|
+
if (import_fs8.default.existsSync(node9Dir)) {
|
|
5760
6043
|
const confirmed = await (0, import_prompts3.confirm)({
|
|
5761
6044
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
5762
6045
|
default: false
|
|
5763
6046
|
});
|
|
5764
6047
|
if (confirmed) {
|
|
5765
|
-
|
|
5766
|
-
if (
|
|
6048
|
+
import_fs8.default.rmSync(node9Dir, { recursive: true });
|
|
6049
|
+
if (import_fs8.default.existsSync(node9Dir)) {
|
|
5767
6050
|
console.error(
|
|
5768
6051
|
import_chalk6.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
5769
6052
|
);
|
|
@@ -5835,10 +6118,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5835
6118
|
);
|
|
5836
6119
|
}
|
|
5837
6120
|
section("Configuration");
|
|
5838
|
-
const globalConfigPath =
|
|
5839
|
-
if (
|
|
6121
|
+
const globalConfigPath = import_path10.default.join(homeDir2, ".node9", "config.json");
|
|
6122
|
+
if (import_fs8.default.existsSync(globalConfigPath)) {
|
|
5840
6123
|
try {
|
|
5841
|
-
JSON.parse(
|
|
6124
|
+
JSON.parse(import_fs8.default.readFileSync(globalConfigPath, "utf-8"));
|
|
5842
6125
|
pass("~/.node9/config.json found and valid");
|
|
5843
6126
|
} catch {
|
|
5844
6127
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -5846,17 +6129,17 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5846
6129
|
} else {
|
|
5847
6130
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
5848
6131
|
}
|
|
5849
|
-
const projectConfigPath =
|
|
5850
|
-
if (
|
|
6132
|
+
const projectConfigPath = import_path10.default.join(process.cwd(), "node9.config.json");
|
|
6133
|
+
if (import_fs8.default.existsSync(projectConfigPath)) {
|
|
5851
6134
|
try {
|
|
5852
|
-
JSON.parse(
|
|
6135
|
+
JSON.parse(import_fs8.default.readFileSync(projectConfigPath, "utf-8"));
|
|
5853
6136
|
pass("node9.config.json found and valid (project)");
|
|
5854
6137
|
} catch {
|
|
5855
6138
|
fail("node9.config.json is invalid JSON", "Fix the JSON or delete it and run: node9 init");
|
|
5856
6139
|
}
|
|
5857
6140
|
}
|
|
5858
|
-
const credsPath =
|
|
5859
|
-
if (
|
|
6141
|
+
const credsPath = import_path10.default.join(homeDir2, ".node9", "credentials.json");
|
|
6142
|
+
if (import_fs8.default.existsSync(credsPath)) {
|
|
5860
6143
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
5861
6144
|
} else {
|
|
5862
6145
|
warn(
|
|
@@ -5865,10 +6148,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5865
6148
|
);
|
|
5866
6149
|
}
|
|
5867
6150
|
section("Agent Hooks");
|
|
5868
|
-
const claudeSettingsPath =
|
|
5869
|
-
if (
|
|
6151
|
+
const claudeSettingsPath = import_path10.default.join(homeDir2, ".claude", "settings.json");
|
|
6152
|
+
if (import_fs8.default.existsSync(claudeSettingsPath)) {
|
|
5870
6153
|
try {
|
|
5871
|
-
const cs = JSON.parse(
|
|
6154
|
+
const cs = JSON.parse(import_fs8.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
5872
6155
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
5873
6156
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
5874
6157
|
);
|
|
@@ -5881,10 +6164,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5881
6164
|
} else {
|
|
5882
6165
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
5883
6166
|
}
|
|
5884
|
-
const geminiSettingsPath =
|
|
5885
|
-
if (
|
|
6167
|
+
const geminiSettingsPath = import_path10.default.join(homeDir2, ".gemini", "settings.json");
|
|
6168
|
+
if (import_fs8.default.existsSync(geminiSettingsPath)) {
|
|
5886
6169
|
try {
|
|
5887
|
-
const gs = JSON.parse(
|
|
6170
|
+
const gs = JSON.parse(import_fs8.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
5888
6171
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
5889
6172
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
5890
6173
|
);
|
|
@@ -5897,10 +6180,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5897
6180
|
} else {
|
|
5898
6181
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
5899
6182
|
}
|
|
5900
|
-
const cursorHooksPath =
|
|
5901
|
-
if (
|
|
6183
|
+
const cursorHooksPath = import_path10.default.join(homeDir2, ".cursor", "hooks.json");
|
|
6184
|
+
if (import_fs8.default.existsSync(cursorHooksPath)) {
|
|
5902
6185
|
try {
|
|
5903
|
-
const cur = JSON.parse(
|
|
6186
|
+
const cur = JSON.parse(import_fs8.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
5904
6187
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
5905
6188
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
5906
6189
|
);
|
|
@@ -6002,8 +6285,8 @@ program.command("explain").description(
|
|
|
6002
6285
|
console.log("");
|
|
6003
6286
|
});
|
|
6004
6287
|
program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
|
|
6005
|
-
const configPath =
|
|
6006
|
-
if (
|
|
6288
|
+
const configPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
6289
|
+
if (import_fs8.default.existsSync(configPath) && !options.force) {
|
|
6007
6290
|
console.log(import_chalk6.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
|
|
6008
6291
|
console.log(import_chalk6.default.gray(` Run with --force to overwrite.`));
|
|
6009
6292
|
return;
|
|
@@ -6017,9 +6300,9 @@ program.command("init").description("Create ~/.node9/config.json with default po
|
|
|
6017
6300
|
mode: safeMode
|
|
6018
6301
|
}
|
|
6019
6302
|
};
|
|
6020
|
-
const dir =
|
|
6021
|
-
if (!
|
|
6022
|
-
|
|
6303
|
+
const dir = import_path10.default.dirname(configPath);
|
|
6304
|
+
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
6305
|
+
import_fs8.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
6023
6306
|
console.log(import_chalk6.default.green(`\u2705 Global config created: ${configPath}`));
|
|
6024
6307
|
console.log(import_chalk6.default.cyan(` Mode set to: ${safeMode}`));
|
|
6025
6308
|
console.log(
|
|
@@ -6037,14 +6320,14 @@ function formatRelativeTime(timestamp) {
|
|
|
6037
6320
|
return new Date(timestamp).toLocaleDateString();
|
|
6038
6321
|
}
|
|
6039
6322
|
program.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
6040
|
-
const logPath =
|
|
6041
|
-
if (!
|
|
6323
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "audit.log");
|
|
6324
|
+
if (!import_fs8.default.existsSync(logPath)) {
|
|
6042
6325
|
console.log(
|
|
6043
6326
|
import_chalk6.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
6044
6327
|
);
|
|
6045
6328
|
return;
|
|
6046
6329
|
}
|
|
6047
|
-
const raw =
|
|
6330
|
+
const raw = import_fs8.default.readFileSync(logPath, "utf-8");
|
|
6048
6331
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
6049
6332
|
let entries = lines.flatMap((line) => {
|
|
6050
6333
|
try {
|
|
@@ -6127,13 +6410,13 @@ program.command("status").description("Show current Node9 mode, policy source, a
|
|
|
6127
6410
|
console.log("");
|
|
6128
6411
|
const modeLabel = settings.mode === "audit" ? import_chalk6.default.blue("audit") : settings.mode === "strict" ? import_chalk6.default.red("strict") : import_chalk6.default.white("standard");
|
|
6129
6412
|
console.log(` Mode: ${modeLabel}`);
|
|
6130
|
-
const projectConfig =
|
|
6131
|
-
const globalConfig =
|
|
6413
|
+
const projectConfig = import_path10.default.join(process.cwd(), "node9.config.json");
|
|
6414
|
+
const globalConfig = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
6132
6415
|
console.log(
|
|
6133
|
-
` Local: ${
|
|
6416
|
+
` Local: ${import_fs8.default.existsSync(projectConfig) ? import_chalk6.default.green("Active (node9.config.json)") : import_chalk6.default.gray("Not present")}`
|
|
6134
6417
|
);
|
|
6135
6418
|
console.log(
|
|
6136
|
-
` Global: ${
|
|
6419
|
+
` Global: ${import_fs8.default.existsSync(globalConfig) ? import_chalk6.default.green("Active (~/.node9/config.json)") : import_chalk6.default.gray("Not present")}`
|
|
6137
6420
|
);
|
|
6138
6421
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
6139
6422
|
console.log(
|
|
@@ -6217,9 +6500,9 @@ program.command("check").description("Hook handler \u2014 evaluates a tool call
|
|
|
6217
6500
|
} catch (err) {
|
|
6218
6501
|
const tempConfig = getConfig();
|
|
6219
6502
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
6220
|
-
const logPath =
|
|
6503
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6221
6504
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6222
|
-
|
|
6505
|
+
import_fs8.default.appendFileSync(
|
|
6223
6506
|
logPath,
|
|
6224
6507
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
6225
6508
|
RAW: ${raw}
|
|
@@ -6237,10 +6520,10 @@ RAW: ${raw}
|
|
|
6237
6520
|
}
|
|
6238
6521
|
const config = getConfig();
|
|
6239
6522
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
6240
|
-
const logPath =
|
|
6241
|
-
if (!
|
|
6242
|
-
|
|
6243
|
-
|
|
6523
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6524
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(logPath)))
|
|
6525
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(logPath), { recursive: true });
|
|
6526
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
6244
6527
|
`);
|
|
6245
6528
|
}
|
|
6246
6529
|
const toolName = sanitize(payload.tool_name ?? payload.name ?? "");
|
|
@@ -6284,7 +6567,7 @@ RAW: ${raw}
|
|
|
6284
6567
|
}
|
|
6285
6568
|
const meta = { agent, mcpServer };
|
|
6286
6569
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
6287
|
-
await createShadowSnapshot(toolName, toolInput);
|
|
6570
|
+
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
6288
6571
|
}
|
|
6289
6572
|
const result = await authorizeHeadless(toolName, toolInput, false, meta);
|
|
6290
6573
|
if (result.approved) {
|
|
@@ -6317,9 +6600,9 @@ RAW: ${raw}
|
|
|
6317
6600
|
});
|
|
6318
6601
|
} catch (err) {
|
|
6319
6602
|
if (process.env.NODE9_DEBUG === "1") {
|
|
6320
|
-
const logPath =
|
|
6603
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6321
6604
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6322
|
-
|
|
6605
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
6323
6606
|
`);
|
|
6324
6607
|
}
|
|
6325
6608
|
process.exit(0);
|
|
@@ -6364,13 +6647,13 @@ program.command("log").description("PostToolUse hook \u2014 records executed too
|
|
|
6364
6647
|
decision: "allowed",
|
|
6365
6648
|
source: "post-hook"
|
|
6366
6649
|
};
|
|
6367
|
-
const logPath =
|
|
6368
|
-
if (!
|
|
6369
|
-
|
|
6370
|
-
|
|
6650
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "audit.log");
|
|
6651
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(logPath)))
|
|
6652
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(logPath), { recursive: true });
|
|
6653
|
+
import_fs8.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
6371
6654
|
const config = getConfig();
|
|
6372
6655
|
if (shouldSnapshot(tool, {}, config)) {
|
|
6373
|
-
await createShadowSnapshot();
|
|
6656
|
+
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
6374
6657
|
}
|
|
6375
6658
|
} catch {
|
|
6376
6659
|
}
|
|
@@ -6643,9 +6926,9 @@ process.on("unhandledRejection", (reason) => {
|
|
|
6643
6926
|
const isCheckHook = process.argv[2] === "check";
|
|
6644
6927
|
if (isCheckHook) {
|
|
6645
6928
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
6646
|
-
const logPath =
|
|
6929
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6647
6930
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
6648
|
-
|
|
6931
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
6649
6932
|
`);
|
|
6650
6933
|
}
|
|
6651
6934
|
process.exit(0);
|