@node9/proxy 1.0.18 → 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 +6 -1
- package/dist/cli.js +528 -238
- package/dist/cli.mjs +534 -244
- package/dist/index.js +207 -57
- package/dist/index.mjs +207 -57
- 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",
|
|
@@ -2404,7 +2549,7 @@ var init_core = __esm({
|
|
|
2404
2549
|
{
|
|
2405
2550
|
field: "command",
|
|
2406
2551
|
op: "matches",
|
|
2407
|
-
value: "git
|
|
2552
|
+
value: "^\\s*git\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
|
|
2408
2553
|
flags: "i"
|
|
2409
2554
|
}
|
|
2410
2555
|
],
|
|
@@ -2415,7 +2560,14 @@ var init_core = __esm({
|
|
|
2415
2560
|
{
|
|
2416
2561
|
name: "review-git-push",
|
|
2417
2562
|
tool: "bash",
|
|
2418
|
-
conditions: [
|
|
2563
|
+
conditions: [
|
|
2564
|
+
{
|
|
2565
|
+
field: "command",
|
|
2566
|
+
op: "matches",
|
|
2567
|
+
value: "^\\s*git\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
|
|
2568
|
+
flags: "i"
|
|
2569
|
+
}
|
|
2570
|
+
],
|
|
2419
2571
|
conditionMode: "all",
|
|
2420
2572
|
verdict: "review",
|
|
2421
2573
|
reason: "git push sends changes to a shared remote"
|
|
@@ -2427,7 +2579,7 @@ var init_core = __esm({
|
|
|
2427
2579
|
{
|
|
2428
2580
|
field: "command",
|
|
2429
2581
|
op: "matches",
|
|
2430
|
-
value: "git\\
|
|
2582
|
+
value: "^\\s*git\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
2431
2583
|
flags: "i"
|
|
2432
2584
|
}
|
|
2433
2585
|
],
|
|
@@ -2492,7 +2644,7 @@ var init_core = __esm({
|
|
|
2492
2644
|
cachedConfig = null;
|
|
2493
2645
|
DAEMON_PORT = 7391;
|
|
2494
2646
|
DAEMON_HOST = "127.0.0.1";
|
|
2495
|
-
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");
|
|
2496
2648
|
}
|
|
2497
2649
|
});
|
|
2498
2650
|
|
|
@@ -3950,18 +4102,18 @@ var init_ui2 = __esm({
|
|
|
3950
4102
|
|
|
3951
4103
|
// src/daemon/index.ts
|
|
3952
4104
|
function atomicWriteSync2(filePath, data, options) {
|
|
3953
|
-
const dir =
|
|
3954
|
-
if (!
|
|
4105
|
+
const dir = import_path7.default.dirname(filePath);
|
|
4106
|
+
if (!import_fs5.default.existsSync(dir)) import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
3955
4107
|
const tmpPath = `${filePath}.${(0, import_crypto3.randomUUID)()}.tmp`;
|
|
3956
|
-
|
|
3957
|
-
|
|
4108
|
+
import_fs5.default.writeFileSync(tmpPath, data, options);
|
|
4109
|
+
import_fs5.default.renameSync(tmpPath, filePath);
|
|
3958
4110
|
}
|
|
3959
4111
|
function writeTrustEntry(toolName, durationMs) {
|
|
3960
4112
|
try {
|
|
3961
4113
|
let trust = { entries: [] };
|
|
3962
4114
|
try {
|
|
3963
|
-
if (
|
|
3964
|
-
trust = JSON.parse(
|
|
4115
|
+
if (import_fs5.default.existsSync(TRUST_FILE2))
|
|
4116
|
+
trust = JSON.parse(import_fs5.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
3965
4117
|
} catch {
|
|
3966
4118
|
}
|
|
3967
4119
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -3988,16 +4140,16 @@ function appendAuditLog(data) {
|
|
|
3988
4140
|
decision: data.decision,
|
|
3989
4141
|
source: "daemon"
|
|
3990
4142
|
};
|
|
3991
|
-
const dir =
|
|
3992
|
-
if (!
|
|
3993
|
-
|
|
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");
|
|
3994
4146
|
} catch {
|
|
3995
4147
|
}
|
|
3996
4148
|
}
|
|
3997
4149
|
function getAuditHistory(limit = 20) {
|
|
3998
4150
|
try {
|
|
3999
|
-
if (!
|
|
4000
|
-
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");
|
|
4001
4153
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
4002
4154
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
4003
4155
|
} catch {
|
|
@@ -4006,7 +4158,7 @@ function getAuditHistory(limit = 20) {
|
|
|
4006
4158
|
}
|
|
4007
4159
|
function getOrgName() {
|
|
4008
4160
|
try {
|
|
4009
|
-
if (
|
|
4161
|
+
if (import_fs5.default.existsSync(CREDENTIALS_FILE)) {
|
|
4010
4162
|
return "Node9 Cloud";
|
|
4011
4163
|
}
|
|
4012
4164
|
} catch {
|
|
@@ -4014,13 +4166,13 @@ function getOrgName() {
|
|
|
4014
4166
|
return null;
|
|
4015
4167
|
}
|
|
4016
4168
|
function hasStoredSlackKey() {
|
|
4017
|
-
return
|
|
4169
|
+
return import_fs5.default.existsSync(CREDENTIALS_FILE);
|
|
4018
4170
|
}
|
|
4019
4171
|
function writeGlobalSetting(key, value) {
|
|
4020
4172
|
let config = {};
|
|
4021
4173
|
try {
|
|
4022
|
-
if (
|
|
4023
|
-
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"));
|
|
4024
4176
|
}
|
|
4025
4177
|
} catch {
|
|
4026
4178
|
}
|
|
@@ -4039,7 +4191,7 @@ function abandonPending() {
|
|
|
4039
4191
|
});
|
|
4040
4192
|
if (autoStarted) {
|
|
4041
4193
|
try {
|
|
4042
|
-
|
|
4194
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4043
4195
|
} catch {
|
|
4044
4196
|
}
|
|
4045
4197
|
setTimeout(() => {
|
|
@@ -4089,8 +4241,8 @@ function readBody(req) {
|
|
|
4089
4241
|
}
|
|
4090
4242
|
function readPersistentDecisions() {
|
|
4091
4243
|
try {
|
|
4092
|
-
if (
|
|
4093
|
-
return JSON.parse(
|
|
4244
|
+
if (import_fs5.default.existsSync(DECISIONS_FILE)) {
|
|
4245
|
+
return JSON.parse(import_fs5.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
4094
4246
|
}
|
|
4095
4247
|
} catch {
|
|
4096
4248
|
}
|
|
@@ -4119,7 +4271,7 @@ function startDaemon() {
|
|
|
4119
4271
|
idleTimer = setTimeout(() => {
|
|
4120
4272
|
if (autoStarted) {
|
|
4121
4273
|
try {
|
|
4122
|
-
|
|
4274
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4123
4275
|
} catch {
|
|
4124
4276
|
}
|
|
4125
4277
|
}
|
|
@@ -4504,14 +4656,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
4504
4656
|
server.on("error", (e) => {
|
|
4505
4657
|
if (e.code === "EADDRINUSE") {
|
|
4506
4658
|
try {
|
|
4507
|
-
if (
|
|
4508
|
-
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"));
|
|
4509
4661
|
process.kill(pid, 0);
|
|
4510
4662
|
return process.exit(0);
|
|
4511
4663
|
}
|
|
4512
4664
|
} catch {
|
|
4513
4665
|
try {
|
|
4514
|
-
|
|
4666
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4515
4667
|
} catch {
|
|
4516
4668
|
}
|
|
4517
4669
|
server.listen(DAEMON_PORT2, DAEMON_HOST2);
|
|
@@ -4562,7 +4714,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4562
4714
|
console.log(import_chalk4.default.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
4563
4715
|
}
|
|
4564
4716
|
try {
|
|
4565
|
-
|
|
4717
|
+
import_fs5.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4566
4718
|
} catch {
|
|
4567
4719
|
}
|
|
4568
4720
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -4604,30 +4756,30 @@ data: ${JSON.stringify(item.data)}
|
|
|
4604
4756
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
4605
4757
|
process.on("exit", () => {
|
|
4606
4758
|
try {
|
|
4607
|
-
|
|
4759
|
+
import_fs5.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4608
4760
|
} catch {
|
|
4609
4761
|
}
|
|
4610
4762
|
});
|
|
4611
4763
|
}
|
|
4612
4764
|
function stopDaemon() {
|
|
4613
|
-
if (!
|
|
4765
|
+
if (!import_fs5.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk4.default.yellow("Not running."));
|
|
4614
4766
|
try {
|
|
4615
|
-
const { pid } = JSON.parse(
|
|
4767
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4616
4768
|
process.kill(pid, "SIGTERM");
|
|
4617
4769
|
console.log(import_chalk4.default.green("\u2705 Stopped."));
|
|
4618
4770
|
} catch {
|
|
4619
4771
|
console.log(import_chalk4.default.gray("Cleaned up stale PID file."));
|
|
4620
4772
|
} finally {
|
|
4621
4773
|
try {
|
|
4622
|
-
|
|
4774
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4623
4775
|
} catch {
|
|
4624
4776
|
}
|
|
4625
4777
|
}
|
|
4626
4778
|
}
|
|
4627
4779
|
function daemonStatus() {
|
|
4628
|
-
if (
|
|
4780
|
+
if (import_fs5.default.existsSync(DAEMON_PID_FILE)) {
|
|
4629
4781
|
try {
|
|
4630
|
-
const { pid } = JSON.parse(
|
|
4782
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4631
4783
|
process.kill(pid, 0);
|
|
4632
4784
|
console.log(import_chalk4.default.green("Node9 daemon: running"));
|
|
4633
4785
|
return;
|
|
@@ -4646,31 +4798,31 @@ function daemonStatus() {
|
|
|
4646
4798
|
console.log(import_chalk4.default.yellow("Node9 daemon: not running"));
|
|
4647
4799
|
}
|
|
4648
4800
|
}
|
|
4649
|
-
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;
|
|
4650
4802
|
var init_daemon = __esm({
|
|
4651
4803
|
"src/daemon/index.ts"() {
|
|
4652
4804
|
"use strict";
|
|
4653
4805
|
init_ui2();
|
|
4654
4806
|
import_http = __toESM(require("http"));
|
|
4655
4807
|
import_net2 = __toESM(require("net"));
|
|
4656
|
-
|
|
4657
|
-
|
|
4808
|
+
import_fs5 = __toESM(require("fs"));
|
|
4809
|
+
import_path7 = __toESM(require("path"));
|
|
4658
4810
|
import_os4 = __toESM(require("os"));
|
|
4659
4811
|
import_child_process3 = require("child_process");
|
|
4660
4812
|
import_crypto3 = require("crypto");
|
|
4661
4813
|
import_chalk4 = __toESM(require("chalk"));
|
|
4662
4814
|
init_core();
|
|
4663
4815
|
init_shields();
|
|
4664
|
-
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");
|
|
4665
4817
|
DAEMON_PORT2 = 7391;
|
|
4666
4818
|
DAEMON_HOST2 = "127.0.0.1";
|
|
4667
4819
|
homeDir = import_os4.default.homedir();
|
|
4668
|
-
DAEMON_PID_FILE =
|
|
4669
|
-
DECISIONS_FILE =
|
|
4670
|
-
GLOBAL_CONFIG_FILE =
|
|
4671
|
-
CREDENTIALS_FILE =
|
|
4672
|
-
AUDIT_LOG_FILE =
|
|
4673
|
-
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");
|
|
4674
4826
|
TRUST_DURATIONS = {
|
|
4675
4827
|
"30m": 30 * 6e4,
|
|
4676
4828
|
"1h": 60 * 6e4,
|
|
@@ -4731,9 +4883,9 @@ function renderPending(activity) {
|
|
|
4731
4883
|
}
|
|
4732
4884
|
async function ensureDaemon() {
|
|
4733
4885
|
let pidPort = null;
|
|
4734
|
-
if (
|
|
4886
|
+
if (import_fs7.default.existsSync(PID_FILE)) {
|
|
4735
4887
|
try {
|
|
4736
|
-
const { port } = JSON.parse(
|
|
4888
|
+
const { port } = JSON.parse(import_fs7.default.readFileSync(PID_FILE, "utf-8"));
|
|
4737
4889
|
pidPort = port;
|
|
4738
4890
|
} catch {
|
|
4739
4891
|
console.error(import_chalk5.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -4888,19 +5040,19 @@ async function startTail(options = {}) {
|
|
|
4888
5040
|
process.exit(1);
|
|
4889
5041
|
});
|
|
4890
5042
|
}
|
|
4891
|
-
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;
|
|
4892
5044
|
var init_tail = __esm({
|
|
4893
5045
|
"src/tui/tail.ts"() {
|
|
4894
5046
|
"use strict";
|
|
4895
5047
|
import_http2 = __toESM(require("http"));
|
|
4896
5048
|
import_chalk5 = __toESM(require("chalk"));
|
|
4897
|
-
|
|
5049
|
+
import_fs7 = __toESM(require("fs"));
|
|
4898
5050
|
import_os6 = __toESM(require("os"));
|
|
4899
|
-
|
|
5051
|
+
import_path9 = __toESM(require("path"));
|
|
4900
5052
|
import_readline = __toESM(require("readline"));
|
|
4901
5053
|
import_child_process5 = require("child_process");
|
|
4902
5054
|
init_daemon();
|
|
4903
|
-
PID_FILE =
|
|
5055
|
+
PID_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
|
|
4904
5056
|
ICONS = {
|
|
4905
5057
|
bash: "\u{1F4BB}",
|
|
4906
5058
|
shell: "\u{1F4BB}",
|
|
@@ -4926,8 +5078,8 @@ var import_commander = require("commander");
|
|
|
4926
5078
|
init_core();
|
|
4927
5079
|
|
|
4928
5080
|
// src/setup.ts
|
|
4929
|
-
var
|
|
4930
|
-
var
|
|
5081
|
+
var import_fs4 = __toESM(require("fs"));
|
|
5082
|
+
var import_path6 = __toESM(require("path"));
|
|
4931
5083
|
var import_os3 = __toESM(require("os"));
|
|
4932
5084
|
var import_chalk3 = __toESM(require("chalk"));
|
|
4933
5085
|
var import_prompts2 = require("@inquirer/prompts");
|
|
@@ -4945,17 +5097,17 @@ function fullPathCommand(subcommand) {
|
|
|
4945
5097
|
}
|
|
4946
5098
|
function readJson(filePath) {
|
|
4947
5099
|
try {
|
|
4948
|
-
if (
|
|
4949
|
-
return JSON.parse(
|
|
5100
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
5101
|
+
return JSON.parse(import_fs4.default.readFileSync(filePath, "utf-8"));
|
|
4950
5102
|
}
|
|
4951
5103
|
} catch {
|
|
4952
5104
|
}
|
|
4953
5105
|
return null;
|
|
4954
5106
|
}
|
|
4955
5107
|
function writeJson(filePath, data) {
|
|
4956
|
-
const dir =
|
|
4957
|
-
if (!
|
|
4958
|
-
|
|
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");
|
|
4959
5111
|
}
|
|
4960
5112
|
function isNode9Hook(cmd) {
|
|
4961
5113
|
if (!cmd) return false;
|
|
@@ -4963,8 +5115,8 @@ function isNode9Hook(cmd) {
|
|
|
4963
5115
|
}
|
|
4964
5116
|
function teardownClaude() {
|
|
4965
5117
|
const homeDir2 = import_os3.default.homedir();
|
|
4966
|
-
const hooksPath =
|
|
4967
|
-
const mcpPath =
|
|
5118
|
+
const hooksPath = import_path6.default.join(homeDir2, ".claude", "settings.json");
|
|
5119
|
+
const mcpPath = import_path6.default.join(homeDir2, ".claude.json");
|
|
4968
5120
|
let changed = false;
|
|
4969
5121
|
const settings = readJson(hooksPath);
|
|
4970
5122
|
if (settings?.hooks) {
|
|
@@ -5013,7 +5165,7 @@ function teardownClaude() {
|
|
|
5013
5165
|
}
|
|
5014
5166
|
function teardownGemini() {
|
|
5015
5167
|
const homeDir2 = import_os3.default.homedir();
|
|
5016
|
-
const settingsPath =
|
|
5168
|
+
const settingsPath = import_path6.default.join(homeDir2, ".gemini", "settings.json");
|
|
5017
5169
|
const settings = readJson(settingsPath);
|
|
5018
5170
|
if (!settings) {
|
|
5019
5171
|
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -5052,7 +5204,7 @@ function teardownGemini() {
|
|
|
5052
5204
|
}
|
|
5053
5205
|
function teardownCursor() {
|
|
5054
5206
|
const homeDir2 = import_os3.default.homedir();
|
|
5055
|
-
const mcpPath =
|
|
5207
|
+
const mcpPath = import_path6.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5056
5208
|
const mcpConfig = readJson(mcpPath);
|
|
5057
5209
|
if (!mcpConfig?.mcpServers) {
|
|
5058
5210
|
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -5079,8 +5231,8 @@ function teardownCursor() {
|
|
|
5079
5231
|
}
|
|
5080
5232
|
async function setupClaude() {
|
|
5081
5233
|
const homeDir2 = import_os3.default.homedir();
|
|
5082
|
-
const mcpPath =
|
|
5083
|
-
const hooksPath =
|
|
5234
|
+
const mcpPath = import_path6.default.join(homeDir2, ".claude.json");
|
|
5235
|
+
const hooksPath = import_path6.default.join(homeDir2, ".claude", "settings.json");
|
|
5084
5236
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
5085
5237
|
const settings = readJson(hooksPath) ?? {};
|
|
5086
5238
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -5155,7 +5307,7 @@ async function setupClaude() {
|
|
|
5155
5307
|
}
|
|
5156
5308
|
async function setupGemini() {
|
|
5157
5309
|
const homeDir2 = import_os3.default.homedir();
|
|
5158
|
-
const settingsPath =
|
|
5310
|
+
const settingsPath = import_path6.default.join(homeDir2, ".gemini", "settings.json");
|
|
5159
5311
|
const settings = readJson(settingsPath) ?? {};
|
|
5160
5312
|
const servers = settings.mcpServers ?? {};
|
|
5161
5313
|
let anythingChanged = false;
|
|
@@ -5238,7 +5390,7 @@ async function setupGemini() {
|
|
|
5238
5390
|
}
|
|
5239
5391
|
async function setupCursor() {
|
|
5240
5392
|
const homeDir2 = import_os3.default.homedir();
|
|
5241
|
-
const mcpPath =
|
|
5393
|
+
const mcpPath = import_path6.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5242
5394
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
5243
5395
|
const servers = mcpConfig.mcpServers ?? {};
|
|
5244
5396
|
let anythingChanged = false;
|
|
@@ -5299,30 +5451,32 @@ var import_execa = require("execa");
|
|
|
5299
5451
|
var import_execa2 = require("execa");
|
|
5300
5452
|
var import_chalk6 = __toESM(require("chalk"));
|
|
5301
5453
|
var import_readline2 = __toESM(require("readline"));
|
|
5302
|
-
var
|
|
5303
|
-
var
|
|
5454
|
+
var import_fs8 = __toESM(require("fs"));
|
|
5455
|
+
var import_path10 = __toESM(require("path"));
|
|
5304
5456
|
var import_os7 = __toESM(require("os"));
|
|
5305
5457
|
|
|
5306
5458
|
// src/undo.ts
|
|
5307
5459
|
var import_child_process4 = require("child_process");
|
|
5308
|
-
var
|
|
5309
|
-
var
|
|
5460
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
5461
|
+
var import_fs6 = __toESM(require("fs"));
|
|
5462
|
+
var import_path8 = __toESM(require("path"));
|
|
5310
5463
|
var import_os5 = __toESM(require("os"));
|
|
5311
|
-
var SNAPSHOT_STACK_PATH =
|
|
5312
|
-
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");
|
|
5313
5466
|
var MAX_SNAPSHOTS = 10;
|
|
5467
|
+
var GIT_TIMEOUT = 15e3;
|
|
5314
5468
|
function readStack() {
|
|
5315
5469
|
try {
|
|
5316
|
-
if (
|
|
5317
|
-
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"));
|
|
5318
5472
|
} catch {
|
|
5319
5473
|
}
|
|
5320
5474
|
return [];
|
|
5321
5475
|
}
|
|
5322
5476
|
function writeStack(stack) {
|
|
5323
|
-
const dir =
|
|
5324
|
-
if (!
|
|
5325
|
-
|
|
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));
|
|
5326
5480
|
}
|
|
5327
5481
|
function buildArgsSummary(tool, args) {
|
|
5328
5482
|
if (!args || typeof args !== "object") return "";
|
|
@@ -5335,54 +5489,177 @@ function buildArgsSummary(tool, args) {
|
|
|
5335
5489
|
if (typeof sql === "string") return sql.slice(0, 80);
|
|
5336
5490
|
return tool;
|
|
5337
5491
|
}
|
|
5338
|
-
|
|
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;
|
|
5339
5592
|
try {
|
|
5340
5593
|
const cwd = process.cwd();
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
const
|
|
5346
|
-
|
|
5347
|
-
|
|
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();
|
|
5348
5607
|
if (!treeHash || treeRes.status !== 0) return null;
|
|
5349
|
-
const commitRes = (0, import_child_process4.spawnSync)(
|
|
5350
|
-
"
|
|
5351
|
-
treeHash,
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
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();
|
|
5356
5614
|
if (!commitHash || commitRes.status !== 0) return null;
|
|
5357
5615
|
const stack = readStack();
|
|
5358
|
-
|
|
5616
|
+
stack.push({
|
|
5359
5617
|
hash: commitHash,
|
|
5360
5618
|
tool,
|
|
5361
5619
|
argsSummary: buildArgsSummary(tool, args),
|
|
5362
5620
|
cwd,
|
|
5363
5621
|
timestamp: Date.now()
|
|
5364
|
-
};
|
|
5365
|
-
stack.
|
|
5622
|
+
});
|
|
5623
|
+
const shouldGc = stack.length % 5 === 0;
|
|
5366
5624
|
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
|
|
5367
5625
|
writeStack(stack);
|
|
5368
|
-
|
|
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
|
+
}
|
|
5369
5630
|
return commitHash;
|
|
5370
5631
|
} catch (err) {
|
|
5371
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
|
+
}
|
|
5372
5641
|
}
|
|
5373
|
-
return null;
|
|
5374
5642
|
}
|
|
5375
5643
|
function getSnapshotHistory() {
|
|
5376
5644
|
return readStack();
|
|
5377
5645
|
}
|
|
5378
5646
|
function computeUndoDiff(hash, cwd) {
|
|
5379
5647
|
try {
|
|
5380
|
-
const
|
|
5381
|
-
const
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
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;
|
|
5386
5663
|
const lines = raw.split("\n").filter(
|
|
5387
5664
|
(l) => !l.startsWith("diff --git") && !l.startsWith("index ") && !l.startsWith("Binary")
|
|
5388
5665
|
);
|
|
@@ -5394,18 +5671,31 @@ function computeUndoDiff(hash, cwd) {
|
|
|
5394
5671
|
function applyUndo(hash, cwd) {
|
|
5395
5672
|
try {
|
|
5396
5673
|
const dir = cwd ?? process.cwd();
|
|
5674
|
+
const env = buildGitEnv(dir);
|
|
5397
5675
|
const restore = (0, import_child_process4.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
|
|
5398
|
-
cwd: dir
|
|
5676
|
+
cwd: dir,
|
|
5677
|
+
env,
|
|
5678
|
+
timeout: GIT_TIMEOUT
|
|
5399
5679
|
});
|
|
5400
5680
|
if (restore.status !== 0) return false;
|
|
5401
|
-
const lsTree = (0, import_child_process4.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
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) ?? [];
|
|
5405
5695
|
for (const file of [...tracked, ...untracked]) {
|
|
5406
|
-
const fullPath =
|
|
5407
|
-
if (!snapshotFiles.has(file) &&
|
|
5408
|
-
|
|
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);
|
|
5409
5699
|
}
|
|
5410
5700
|
}
|
|
5411
5701
|
return true;
|
|
@@ -5418,7 +5708,7 @@ function applyUndo(hash, cwd) {
|
|
|
5418
5708
|
init_shields();
|
|
5419
5709
|
var import_prompts3 = require("@inquirer/prompts");
|
|
5420
5710
|
var { version } = JSON.parse(
|
|
5421
|
-
|
|
5711
|
+
import_fs8.default.readFileSync(import_path10.default.join(__dirname, "../package.json"), "utf-8")
|
|
5422
5712
|
);
|
|
5423
5713
|
function parseDuration(str) {
|
|
5424
5714
|
const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
|
|
@@ -5620,14 +5910,14 @@ async function runProxy(targetCommand) {
|
|
|
5620
5910
|
}
|
|
5621
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) => {
|
|
5622
5912
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
5623
|
-
const credPath =
|
|
5624
|
-
if (!
|
|
5625
|
-
|
|
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 });
|
|
5626
5916
|
const profileName = options.profile || "default";
|
|
5627
5917
|
let existingCreds = {};
|
|
5628
5918
|
try {
|
|
5629
|
-
if (
|
|
5630
|
-
const raw = JSON.parse(
|
|
5919
|
+
if (import_fs8.default.existsSync(credPath)) {
|
|
5920
|
+
const raw = JSON.parse(import_fs8.default.readFileSync(credPath, "utf-8"));
|
|
5631
5921
|
if (raw.apiKey) {
|
|
5632
5922
|
existingCreds = {
|
|
5633
5923
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -5639,13 +5929,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
5639
5929
|
} catch {
|
|
5640
5930
|
}
|
|
5641
5931
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
5642
|
-
|
|
5932
|
+
import_fs8.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
5643
5933
|
if (profileName === "default") {
|
|
5644
|
-
const configPath =
|
|
5934
|
+
const configPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
5645
5935
|
let config = {};
|
|
5646
5936
|
try {
|
|
5647
|
-
if (
|
|
5648
|
-
config = JSON.parse(
|
|
5937
|
+
if (import_fs8.default.existsSync(configPath))
|
|
5938
|
+
config = JSON.parse(import_fs8.default.readFileSync(configPath, "utf-8"));
|
|
5649
5939
|
} catch {
|
|
5650
5940
|
}
|
|
5651
5941
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -5660,9 +5950,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
5660
5950
|
approvers.cloud = false;
|
|
5661
5951
|
}
|
|
5662
5952
|
s.approvers = approvers;
|
|
5663
|
-
if (!
|
|
5664
|
-
|
|
5665
|
-
|
|
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 });
|
|
5666
5956
|
}
|
|
5667
5957
|
if (options.profile && profileName !== "default") {
|
|
5668
5958
|
console.log(import_chalk6.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -5748,15 +6038,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
5748
6038
|
}
|
|
5749
6039
|
}
|
|
5750
6040
|
if (options.purge) {
|
|
5751
|
-
const node9Dir =
|
|
5752
|
-
if (
|
|
6041
|
+
const node9Dir = import_path10.default.join(import_os7.default.homedir(), ".node9");
|
|
6042
|
+
if (import_fs8.default.existsSync(node9Dir)) {
|
|
5753
6043
|
const confirmed = await (0, import_prompts3.confirm)({
|
|
5754
6044
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
5755
6045
|
default: false
|
|
5756
6046
|
});
|
|
5757
6047
|
if (confirmed) {
|
|
5758
|
-
|
|
5759
|
-
if (
|
|
6048
|
+
import_fs8.default.rmSync(node9Dir, { recursive: true });
|
|
6049
|
+
if (import_fs8.default.existsSync(node9Dir)) {
|
|
5760
6050
|
console.error(
|
|
5761
6051
|
import_chalk6.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
5762
6052
|
);
|
|
@@ -5828,10 +6118,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5828
6118
|
);
|
|
5829
6119
|
}
|
|
5830
6120
|
section("Configuration");
|
|
5831
|
-
const globalConfigPath =
|
|
5832
|
-
if (
|
|
6121
|
+
const globalConfigPath = import_path10.default.join(homeDir2, ".node9", "config.json");
|
|
6122
|
+
if (import_fs8.default.existsSync(globalConfigPath)) {
|
|
5833
6123
|
try {
|
|
5834
|
-
JSON.parse(
|
|
6124
|
+
JSON.parse(import_fs8.default.readFileSync(globalConfigPath, "utf-8"));
|
|
5835
6125
|
pass("~/.node9/config.json found and valid");
|
|
5836
6126
|
} catch {
|
|
5837
6127
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -5839,17 +6129,17 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5839
6129
|
} else {
|
|
5840
6130
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
5841
6131
|
}
|
|
5842
|
-
const projectConfigPath =
|
|
5843
|
-
if (
|
|
6132
|
+
const projectConfigPath = import_path10.default.join(process.cwd(), "node9.config.json");
|
|
6133
|
+
if (import_fs8.default.existsSync(projectConfigPath)) {
|
|
5844
6134
|
try {
|
|
5845
|
-
JSON.parse(
|
|
6135
|
+
JSON.parse(import_fs8.default.readFileSync(projectConfigPath, "utf-8"));
|
|
5846
6136
|
pass("node9.config.json found and valid (project)");
|
|
5847
6137
|
} catch {
|
|
5848
6138
|
fail("node9.config.json is invalid JSON", "Fix the JSON or delete it and run: node9 init");
|
|
5849
6139
|
}
|
|
5850
6140
|
}
|
|
5851
|
-
const credsPath =
|
|
5852
|
-
if (
|
|
6141
|
+
const credsPath = import_path10.default.join(homeDir2, ".node9", "credentials.json");
|
|
6142
|
+
if (import_fs8.default.existsSync(credsPath)) {
|
|
5853
6143
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
5854
6144
|
} else {
|
|
5855
6145
|
warn(
|
|
@@ -5858,10 +6148,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5858
6148
|
);
|
|
5859
6149
|
}
|
|
5860
6150
|
section("Agent Hooks");
|
|
5861
|
-
const claudeSettingsPath =
|
|
5862
|
-
if (
|
|
6151
|
+
const claudeSettingsPath = import_path10.default.join(homeDir2, ".claude", "settings.json");
|
|
6152
|
+
if (import_fs8.default.existsSync(claudeSettingsPath)) {
|
|
5863
6153
|
try {
|
|
5864
|
-
const cs = JSON.parse(
|
|
6154
|
+
const cs = JSON.parse(import_fs8.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
5865
6155
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
5866
6156
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
5867
6157
|
);
|
|
@@ -5874,10 +6164,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5874
6164
|
} else {
|
|
5875
6165
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
5876
6166
|
}
|
|
5877
|
-
const geminiSettingsPath =
|
|
5878
|
-
if (
|
|
6167
|
+
const geminiSettingsPath = import_path10.default.join(homeDir2, ".gemini", "settings.json");
|
|
6168
|
+
if (import_fs8.default.existsSync(geminiSettingsPath)) {
|
|
5879
6169
|
try {
|
|
5880
|
-
const gs = JSON.parse(
|
|
6170
|
+
const gs = JSON.parse(import_fs8.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
5881
6171
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
5882
6172
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
5883
6173
|
);
|
|
@@ -5890,10 +6180,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5890
6180
|
} else {
|
|
5891
6181
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
5892
6182
|
}
|
|
5893
|
-
const cursorHooksPath =
|
|
5894
|
-
if (
|
|
6183
|
+
const cursorHooksPath = import_path10.default.join(homeDir2, ".cursor", "hooks.json");
|
|
6184
|
+
if (import_fs8.default.existsSync(cursorHooksPath)) {
|
|
5895
6185
|
try {
|
|
5896
|
-
const cur = JSON.parse(
|
|
6186
|
+
const cur = JSON.parse(import_fs8.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
5897
6187
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
5898
6188
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
5899
6189
|
);
|
|
@@ -5995,8 +6285,8 @@ program.command("explain").description(
|
|
|
5995
6285
|
console.log("");
|
|
5996
6286
|
});
|
|
5997
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) => {
|
|
5998
|
-
const configPath =
|
|
5999
|
-
if (
|
|
6288
|
+
const configPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
6289
|
+
if (import_fs8.default.existsSync(configPath) && !options.force) {
|
|
6000
6290
|
console.log(import_chalk6.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
|
|
6001
6291
|
console.log(import_chalk6.default.gray(` Run with --force to overwrite.`));
|
|
6002
6292
|
return;
|
|
@@ -6010,9 +6300,9 @@ program.command("init").description("Create ~/.node9/config.json with default po
|
|
|
6010
6300
|
mode: safeMode
|
|
6011
6301
|
}
|
|
6012
6302
|
};
|
|
6013
|
-
const dir =
|
|
6014
|
-
if (!
|
|
6015
|
-
|
|
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));
|
|
6016
6306
|
console.log(import_chalk6.default.green(`\u2705 Global config created: ${configPath}`));
|
|
6017
6307
|
console.log(import_chalk6.default.cyan(` Mode set to: ${safeMode}`));
|
|
6018
6308
|
console.log(
|
|
@@ -6030,14 +6320,14 @@ function formatRelativeTime(timestamp) {
|
|
|
6030
6320
|
return new Date(timestamp).toLocaleDateString();
|
|
6031
6321
|
}
|
|
6032
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) => {
|
|
6033
|
-
const logPath =
|
|
6034
|
-
if (!
|
|
6323
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "audit.log");
|
|
6324
|
+
if (!import_fs8.default.existsSync(logPath)) {
|
|
6035
6325
|
console.log(
|
|
6036
6326
|
import_chalk6.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
6037
6327
|
);
|
|
6038
6328
|
return;
|
|
6039
6329
|
}
|
|
6040
|
-
const raw =
|
|
6330
|
+
const raw = import_fs8.default.readFileSync(logPath, "utf-8");
|
|
6041
6331
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
6042
6332
|
let entries = lines.flatMap((line) => {
|
|
6043
6333
|
try {
|
|
@@ -6120,13 +6410,13 @@ program.command("status").description("Show current Node9 mode, policy source, a
|
|
|
6120
6410
|
console.log("");
|
|
6121
6411
|
const modeLabel = settings.mode === "audit" ? import_chalk6.default.blue("audit") : settings.mode === "strict" ? import_chalk6.default.red("strict") : import_chalk6.default.white("standard");
|
|
6122
6412
|
console.log(` Mode: ${modeLabel}`);
|
|
6123
|
-
const projectConfig =
|
|
6124
|
-
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");
|
|
6125
6415
|
console.log(
|
|
6126
|
-
` Local: ${
|
|
6416
|
+
` Local: ${import_fs8.default.existsSync(projectConfig) ? import_chalk6.default.green("Active (node9.config.json)") : import_chalk6.default.gray("Not present")}`
|
|
6127
6417
|
);
|
|
6128
6418
|
console.log(
|
|
6129
|
-
` Global: ${
|
|
6419
|
+
` Global: ${import_fs8.default.existsSync(globalConfig) ? import_chalk6.default.green("Active (~/.node9/config.json)") : import_chalk6.default.gray("Not present")}`
|
|
6130
6420
|
);
|
|
6131
6421
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
6132
6422
|
console.log(
|
|
@@ -6210,9 +6500,9 @@ program.command("check").description("Hook handler \u2014 evaluates a tool call
|
|
|
6210
6500
|
} catch (err) {
|
|
6211
6501
|
const tempConfig = getConfig();
|
|
6212
6502
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
6213
|
-
const logPath =
|
|
6503
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6214
6504
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6215
|
-
|
|
6505
|
+
import_fs8.default.appendFileSync(
|
|
6216
6506
|
logPath,
|
|
6217
6507
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
6218
6508
|
RAW: ${raw}
|
|
@@ -6230,10 +6520,10 @@ RAW: ${raw}
|
|
|
6230
6520
|
}
|
|
6231
6521
|
const config = getConfig();
|
|
6232
6522
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
6233
|
-
const logPath =
|
|
6234
|
-
if (!
|
|
6235
|
-
|
|
6236
|
-
|
|
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}
|
|
6237
6527
|
`);
|
|
6238
6528
|
}
|
|
6239
6529
|
const toolName = sanitize(payload.tool_name ?? payload.name ?? "");
|
|
@@ -6277,7 +6567,7 @@ RAW: ${raw}
|
|
|
6277
6567
|
}
|
|
6278
6568
|
const meta = { agent, mcpServer };
|
|
6279
6569
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
6280
|
-
await createShadowSnapshot(toolName, toolInput);
|
|
6570
|
+
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
6281
6571
|
}
|
|
6282
6572
|
const result = await authorizeHeadless(toolName, toolInput, false, meta);
|
|
6283
6573
|
if (result.approved) {
|
|
@@ -6310,9 +6600,9 @@ RAW: ${raw}
|
|
|
6310
6600
|
});
|
|
6311
6601
|
} catch (err) {
|
|
6312
6602
|
if (process.env.NODE9_DEBUG === "1") {
|
|
6313
|
-
const logPath =
|
|
6603
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6314
6604
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6315
|
-
|
|
6605
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
6316
6606
|
`);
|
|
6317
6607
|
}
|
|
6318
6608
|
process.exit(0);
|
|
@@ -6357,13 +6647,13 @@ program.command("log").description("PostToolUse hook \u2014 records executed too
|
|
|
6357
6647
|
decision: "allowed",
|
|
6358
6648
|
source: "post-hook"
|
|
6359
6649
|
};
|
|
6360
|
-
const logPath =
|
|
6361
|
-
if (!
|
|
6362
|
-
|
|
6363
|
-
|
|
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");
|
|
6364
6654
|
const config = getConfig();
|
|
6365
6655
|
if (shouldSnapshot(tool, {}, config)) {
|
|
6366
|
-
await createShadowSnapshot();
|
|
6656
|
+
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
6367
6657
|
}
|
|
6368
6658
|
} catch {
|
|
6369
6659
|
}
|
|
@@ -6636,9 +6926,9 @@ process.on("unhandledRejection", (reason) => {
|
|
|
6636
6926
|
const isCheckHook = process.argv[2] === "check";
|
|
6637
6927
|
if (isCheckHook) {
|
|
6638
6928
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
6639
|
-
const logPath =
|
|
6929
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6640
6930
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
6641
|
-
|
|
6931
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
6642
6932
|
`);
|
|
6643
6933
|
}
|
|
6644
6934
|
process.exit(0);
|