@solongate/proxy 0.82.42 → 0.82.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +617 -386
- package/dist/logs-server.js +4 -1
- package/dist/self-update.d.ts +7 -0
- package/dist/tui/index.js +934 -920
- package/package.json +1 -1
package/dist/tui/index.js
CHANGED
|
@@ -1,714 +1,1024 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
2
6
|
var __export = (target, all) => {
|
|
3
7
|
for (var name in all)
|
|
4
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
9
|
};
|
|
6
10
|
|
|
7
|
-
// src/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// src/cli-utils.ts
|
|
18
|
-
var c = {
|
|
19
|
-
reset: "\x1B[0m",
|
|
20
|
-
bold: "\x1B[1m",
|
|
21
|
-
dim: "\x1B[2m",
|
|
22
|
-
italic: "\x1B[3m",
|
|
23
|
-
white: "\x1B[97m",
|
|
24
|
-
gray: "\x1B[90m",
|
|
25
|
-
blue1: "\x1B[38;2;20;50;160m",
|
|
26
|
-
blue2: "\x1B[38;2;40;80;190m",
|
|
27
|
-
blue3: "\x1B[38;2;60;110;215m",
|
|
28
|
-
blue4: "\x1B[38;2;90;140;230m",
|
|
29
|
-
blue5: "\x1B[38;2;130;170;240m",
|
|
30
|
-
blue6: "\x1B[38;2;170;200;250m",
|
|
31
|
-
green: "\x1B[38;2;80;200;120m",
|
|
32
|
-
red: "\x1B[38;2;220;80;80m",
|
|
33
|
-
cyan: "\x1B[38;2;100;200;220m",
|
|
34
|
-
yellow: "\x1B[38;2;220;200;80m",
|
|
35
|
-
bgBlue: "\x1B[48;2;20;50;160m"
|
|
36
|
-
};
|
|
37
|
-
var BANNER_FULL = [
|
|
38
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
39
|
-
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
40
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
41
|
-
" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
42
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
43
|
-
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
44
|
-
];
|
|
45
|
-
var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
46
|
-
|
|
47
|
-
// src/tui/components.tsx
|
|
48
|
-
import { Box, Text } from "ink";
|
|
49
|
-
|
|
50
|
-
// src/tui/config.ts
|
|
51
|
-
import { readFileSync } from "fs";
|
|
52
|
-
import { homedir } from "os";
|
|
53
|
-
import { join } from "path";
|
|
54
|
-
var cached = null;
|
|
55
|
-
function loadConfig() {
|
|
56
|
-
if (cached) return cached;
|
|
57
|
-
const defaults = { notifications: true };
|
|
11
|
+
// src/global-install.ts
|
|
12
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync2, mkdirSync as mkdirSync4, rmSync } from "fs";
|
|
13
|
+
import { resolve as resolve2, join as join6, dirname } from "path";
|
|
14
|
+
import { homedir as homedir6 } from "os";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
import { createInterface } from "readline";
|
|
17
|
+
import { execFileSync } from "child_process";
|
|
18
|
+
function lockFile(file) {
|
|
19
|
+
if (!existsSync2(file)) return;
|
|
58
20
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
21
|
+
if (process.platform === "win32") {
|
|
22
|
+
try {
|
|
23
|
+
execFileSync("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
execFileSync("attrib", ["+R", file], { stdio: "ignore" });
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
} else if (process.platform === "darwin") {
|
|
31
|
+
try {
|
|
32
|
+
execFileSync("chflags", ["uchg", file], { stdio: "ignore" });
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
try {
|
|
37
|
+
execFileSync("chattr", ["+i", file], { stdio: "ignore" });
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
66
41
|
} catch {
|
|
67
|
-
cached = defaults;
|
|
68
42
|
}
|
|
69
|
-
return cached;
|
|
70
43
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
44
|
+
function unlockFile(file) {
|
|
45
|
+
if (!existsSync2(file)) return;
|
|
46
|
+
try {
|
|
47
|
+
if (process.platform === "win32") {
|
|
48
|
+
try {
|
|
49
|
+
execFileSync("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
execFileSync("icacls", [file, "/reset"], { stdio: "ignore" });
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
execFileSync("attrib", ["-R", file], { stdio: "ignore" });
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
} else if (process.platform === "darwin") {
|
|
61
|
+
try {
|
|
62
|
+
execFileSync("chflags", ["nouchg", file], { stdio: "ignore" });
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
try {
|
|
67
|
+
execFileSync("chattr", ["-i", file], { stdio: "ignore" });
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
87
73
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
74
|
+
function protectedTargets() {
|
|
75
|
+
const p = globalPaths();
|
|
76
|
+
return [
|
|
77
|
+
join6(p.hooksDir, "guard.mjs"),
|
|
78
|
+
join6(p.hooksDir, "audit.mjs"),
|
|
79
|
+
join6(p.hooksDir, "stop.mjs"),
|
|
80
|
+
join6(p.hooksDir, "shield.mjs"),
|
|
81
|
+
p.configPath,
|
|
82
|
+
p.settingsPath,
|
|
83
|
+
p.antigravityHooksPath
|
|
84
|
+
];
|
|
92
85
|
}
|
|
93
|
-
function
|
|
86
|
+
function lockProtected() {
|
|
87
|
+
for (const f of protectedTargets()) lockFile(f);
|
|
88
|
+
}
|
|
89
|
+
function unlockProtected() {
|
|
90
|
+
for (const f of protectedTargets()) unlockFile(f);
|
|
91
|
+
}
|
|
92
|
+
function globalPaths() {
|
|
93
|
+
const home = homedir6();
|
|
94
|
+
const sgDir = join6(home, ".solongate");
|
|
95
|
+
const hooksDir = join6(sgDir, "hooks");
|
|
96
|
+
const claudeDir = join6(home, ".claude");
|
|
97
|
+
const antigravityDir = join6(home, ".gemini", "config");
|
|
98
|
+
return {
|
|
99
|
+
home,
|
|
100
|
+
sgDir,
|
|
101
|
+
hooksDir,
|
|
102
|
+
claudeDir,
|
|
103
|
+
antigravityDir,
|
|
104
|
+
settingsPath: join6(claudeDir, "settings.json"),
|
|
105
|
+
backupPath: join6(claudeDir, "settings.solongate.bak"),
|
|
106
|
+
configPath: join6(sgDir, "cloud-guard.json"),
|
|
107
|
+
// Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
|
|
108
|
+
// guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
|
|
109
|
+
// covered by the passive session-log collector, not a hook.
|
|
110
|
+
antigravityHooksPath: join6(antigravityDir, "hooks.json"),
|
|
111
|
+
antigravityBackupPath: join6(antigravityDir, "hooks.solongate.bak")
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function readHook(filename) {
|
|
115
|
+
return readFileSync4(join6(HOOKS_DIR, filename), "utf-8");
|
|
116
|
+
}
|
|
117
|
+
function firstAccountCredential() {
|
|
94
118
|
try {
|
|
95
|
-
|
|
119
|
+
const raw = JSON.parse(readFileSync4(join6(homedir6(), ".solongate", "accounts.json"), "utf-8"));
|
|
120
|
+
if (Array.isArray(raw)) {
|
|
121
|
+
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
122
|
+
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
123
|
+
}
|
|
96
124
|
} catch {
|
|
97
|
-
return s;
|
|
98
125
|
}
|
|
126
|
+
return {};
|
|
99
127
|
}
|
|
100
|
-
function
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
function readGuard() {
|
|
129
|
+
const bundled = join6(HOOKS_DIR, "guard.bundled.mjs");
|
|
130
|
+
return existsSync2(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
|
|
131
|
+
}
|
|
132
|
+
function antigravityHookCommand(guardAbs) {
|
|
133
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
134
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
135
|
+
return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
|
|
136
|
+
}
|
|
137
|
+
function installAntigravityGuard(p, guardAbs) {
|
|
138
|
+
mkdirSync4(p.antigravityDir, { recursive: true });
|
|
139
|
+
let existing = {};
|
|
140
|
+
if (existsSync2(p.antigravityHooksPath)) {
|
|
141
|
+
const raw = readFileSync4(p.antigravityHooksPath, "utf-8");
|
|
142
|
+
if (!existsSync2(p.antigravityBackupPath)) writeFileSync5(p.antigravityBackupPath, raw);
|
|
143
|
+
try {
|
|
144
|
+
existing = JSON.parse(raw);
|
|
145
|
+
} catch {
|
|
146
|
+
existing = {};
|
|
107
147
|
}
|
|
108
|
-
for (let i = 0; i < raw.length; i += w) out.push(raw.slice(i, i + w));
|
|
109
148
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
149
|
+
const merged = {
|
|
150
|
+
...existing,
|
|
151
|
+
[ANTIGRAVITY_GROUP]: {
|
|
152
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }]
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
writeFileSync5(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
115
156
|
}
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
157
|
+
function removeAntigravityGuard(p) {
|
|
158
|
+
if (!existsSync2(p.antigravityHooksPath)) return;
|
|
159
|
+
try {
|
|
160
|
+
const s = JSON.parse(readFileSync4(p.antigravityHooksPath, "utf-8"));
|
|
161
|
+
if (!(ANTIGRAVITY_GROUP in s)) return;
|
|
162
|
+
delete s[ANTIGRAVITY_GROUP];
|
|
163
|
+
writeFileSync5(p.antigravityHooksPath, JSON.stringify(s, null, 2) + "\n");
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
124
166
|
}
|
|
125
|
-
|
|
126
|
-
// src/tui/components.tsx
|
|
127
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
128
|
-
var hhmmss = (ts) => {
|
|
129
|
-
const d = new Date(ts);
|
|
130
|
-
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
131
|
-
};
|
|
132
|
-
var ymd = (ts) => {
|
|
133
|
-
const d = new Date(ts);
|
|
134
|
-
if (Number.isNaN(d.getTime())) return "----------";
|
|
135
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
136
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
137
|
-
};
|
|
138
|
-
function PaneTitle({ label, extra, width }) {
|
|
139
|
-
const tail = extra ? ` ${extra} ` : " ";
|
|
140
|
-
const used = 2 + 1 + label.length + tail.length;
|
|
141
|
-
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
|
|
142
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "\u2500 " }),
|
|
143
|
-
/* @__PURE__ */ jsxs(Text, { color: theme.accentBright, bold: true, children: [
|
|
144
|
-
"\u258E",
|
|
145
|
-
label
|
|
146
|
-
] }),
|
|
147
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
|
|
148
|
-
] });
|
|
149
|
-
}
|
|
150
|
-
function StreamLine({ e, loc, selected, date }) {
|
|
151
|
-
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
|
|
152
|
-
/* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
|
|
153
|
-
/* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
154
|
-
"[",
|
|
155
|
-
date ? ymd(e.at) + " " : "",
|
|
156
|
-
hhmmss(e.at),
|
|
157
|
-
" "
|
|
158
|
-
] }),
|
|
159
|
-
/* @__PURE__ */ jsx(Text, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
|
|
160
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "] " }),
|
|
161
|
-
/* @__PURE__ */ jsx(Text, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
162
|
-
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
|
|
163
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: (e.permission ?? "").padEnd(5) }),
|
|
164
|
-
/* @__PURE__ */ jsx(Text, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
165
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
|
|
166
|
-
/* @__PURE__ */ jsx(Text, { color: e.dlp ? theme.bad : theme.dim, bold: e.dlp, children: ("dlp:" + (e.dlp ? "yes" : "no")).padEnd(8) }),
|
|
167
|
-
/* @__PURE__ */ jsx(Text, { color: e.burst ? theme.warn : theme.dim, bold: e.burst, children: ("rl:" + (e.burst ? "yes" : "no")).padEnd(7) }),
|
|
168
|
-
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx(Text, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
|
|
169
|
-
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: e.detail })
|
|
170
|
-
] });
|
|
171
|
-
}
|
|
172
|
-
function DataView({
|
|
173
|
-
loading,
|
|
174
|
-
error,
|
|
175
|
-
empty,
|
|
176
|
-
emptyText,
|
|
177
|
-
children
|
|
178
|
-
}) {
|
|
179
|
-
if (error) return /* @__PURE__ */ jsxs(Text, { color: theme.bad, children: [
|
|
180
|
-
"\u2717 ",
|
|
181
|
-
error
|
|
182
|
-
] });
|
|
183
|
-
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
|
|
184
|
-
if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
|
|
185
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
186
|
-
}
|
|
187
|
-
function Table({ columns, rows }) {
|
|
188
|
-
const fit = (s, w) => {
|
|
189
|
-
const v = s ?? "";
|
|
190
|
-
if (v.length > w) return v.slice(0, Math.max(0, w - 1)) + "\u2026";
|
|
191
|
-
return v.padEnd(w);
|
|
192
|
-
};
|
|
193
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
194
|
-
/* @__PURE__ */ jsx(Box, { children: columns.map((c2, i) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: fit(c2.header, c2.width) + " " }, i)) }),
|
|
195
|
-
rows.map((row, ri) => /* @__PURE__ */ jsx(Box, { children: row.map((cell, ci) => /* @__PURE__ */ jsx(Text, { color: cell.color, dimColor: cell.dim, bold: cell.bold, children: fit(cell.value, columns[ci]?.width ?? 10) + " " }, ci)) }, ri))
|
|
196
|
-
] });
|
|
197
|
-
}
|
|
198
|
-
function KeyHints({ hints }) {
|
|
199
|
-
return /* @__PURE__ */ jsx(Box, { children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
200
|
-
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: key }),
|
|
201
|
-
" ",
|
|
202
|
-
label,
|
|
203
|
-
i < hints.length - 1 ? " " : ""
|
|
204
|
-
] }, i)) });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// src/tui/panels/Live.tsx
|
|
208
|
-
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
209
|
-
import TextInput from "ink-text-input";
|
|
210
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
211
|
-
import { homedir as homedir4 } from "os";
|
|
212
|
-
import { join as join4 } from "path";
|
|
213
|
-
|
|
214
|
-
// src/tui/local-log.ts
|
|
215
|
-
import { closeSync, openSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
|
|
216
|
-
import { homedir as homedir2 } from "os";
|
|
217
|
-
import { join as join2 } from "path";
|
|
218
|
-
var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
219
|
-
function tailLines(file, maxBytes = 131072) {
|
|
220
|
-
try {
|
|
221
|
-
const size = statSync(file).size;
|
|
222
|
-
const start = Math.max(0, size - maxBytes);
|
|
223
|
-
const fd = openSync(file, "r");
|
|
224
|
-
const buf = Buffer.alloc(size - start);
|
|
225
|
-
readSync(fd, buf, 0, buf.length, start);
|
|
226
|
-
closeSync(fd);
|
|
227
|
-
const lines = buf.toString("utf-8").split("\n").filter(Boolean);
|
|
228
|
-
if (start > 0) lines.shift();
|
|
229
|
-
return lines;
|
|
230
|
-
} catch {
|
|
231
|
-
return [];
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function deleteLocalEntry(at, tool, session) {
|
|
167
|
+
function installGlobalQuiet() {
|
|
235
168
|
try {
|
|
236
|
-
const
|
|
237
|
-
let
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
169
|
+
const p = globalPaths();
|
|
170
|
+
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
171
|
+
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
172
|
+
try {
|
|
173
|
+
const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
|
|
174
|
+
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
175
|
+
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
if (!apiKey) {
|
|
179
|
+
const acc = firstAccountCredential();
|
|
180
|
+
if (acc.apiKey) {
|
|
181
|
+
apiKey = acc.apiKey;
|
|
182
|
+
if (acc.apiUrl) apiUrl = acc.apiUrl;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
186
|
+
mkdirSync4(p.hooksDir, { recursive: true });
|
|
187
|
+
mkdirSync4(p.claudeDir, { recursive: true });
|
|
188
|
+
unlockProtected();
|
|
189
|
+
writeFileSync5(join6(p.hooksDir, "guard.mjs"), readGuard());
|
|
190
|
+
writeFileSync5(join6(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
191
|
+
writeFileSync5(join6(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
192
|
+
writeFileSync5(join6(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
193
|
+
writeFileSync5(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
194
|
+
let existing = {};
|
|
195
|
+
if (existsSync2(p.settingsPath)) {
|
|
196
|
+
const raw = readFileSync4(p.settingsPath, "utf-8");
|
|
197
|
+
if (!existsSync2(p.backupPath)) writeFileSync5(p.backupPath, raw);
|
|
241
198
|
try {
|
|
242
|
-
|
|
243
|
-
const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
|
|
244
|
-
if (hit) {
|
|
245
|
-
removed++;
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
199
|
+
existing = JSON.parse(raw);
|
|
248
200
|
} catch {
|
|
201
|
+
existing = {};
|
|
249
202
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
} catch {
|
|
264
|
-
return 0;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
var DLP_REASON = /security layer \(dlp\)/i;
|
|
268
|
-
var RL_REASON = /security layer \(rate limit\)|rate[- ]?limit(?:ed)?\b.*exceed|exceeded \d+ calls/i;
|
|
269
|
-
function reasonSignals(reason) {
|
|
270
|
-
const r = reason || "";
|
|
271
|
-
const dlp = [];
|
|
272
|
-
if (DLP_REASON.test(r)) {
|
|
273
|
-
const m = r.match(/contain(?:s)? (?:a |an )?(.+?)(?:\.|$)/i);
|
|
274
|
-
dlp.push(m ? m[1].trim() : "DLP");
|
|
275
|
-
}
|
|
276
|
-
return { dlp, burst: RL_REASON.test(r) };
|
|
277
|
-
}
|
|
278
|
-
function parseLocalLines(lines) {
|
|
279
|
-
const out = [];
|
|
280
|
-
for (const line of lines) {
|
|
203
|
+
}
|
|
204
|
+
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
205
|
+
const call = process.platform === "win32" ? "& " : "";
|
|
206
|
+
const hookCmd = (script) => `${call}"${nodeBin}" "${join6(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
207
|
+
const merged = {
|
|
208
|
+
...existing,
|
|
209
|
+
hooks: {
|
|
210
|
+
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
211
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
212
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] }]
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
281
216
|
try {
|
|
282
|
-
|
|
283
|
-
const at = Date.parse(j.ts ?? "");
|
|
284
|
-
if (Number.isFinite(at)) out.push({ ...j, at });
|
|
217
|
+
installAntigravityGuard(p, join6(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
|
|
285
218
|
} catch {
|
|
286
219
|
}
|
|
220
|
+
if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
|
|
221
|
+
return { ok: true, message: "guard installed (open a new session)" };
|
|
222
|
+
} catch (e) {
|
|
223
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
287
224
|
}
|
|
288
|
-
return out;
|
|
289
225
|
}
|
|
290
|
-
|
|
291
|
-
// src/tui/panels/Live.tsx
|
|
292
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
293
|
-
|
|
294
|
-
// src/api-client/client.ts
|
|
295
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync } from "fs";
|
|
296
|
-
import { resolve, join as join3 } from "path";
|
|
297
|
-
import { homedir as homedir3 } from "os";
|
|
298
|
-
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
299
|
-
var accountsFile = () => join3(homedir3(), ".solongate", "accounts.json");
|
|
300
|
-
function listAccounts() {
|
|
301
|
-
let list5 = [];
|
|
226
|
+
function installedGuardVersion() {
|
|
302
227
|
try {
|
|
303
|
-
const
|
|
304
|
-
|
|
228
|
+
const p = globalPaths();
|
|
229
|
+
const s = readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8");
|
|
230
|
+
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
231
|
+
return m ? parseInt(m[1], 10) : null;
|
|
305
232
|
} catch {
|
|
233
|
+
return null;
|
|
306
234
|
}
|
|
307
|
-
const active2 = loginCredentialFile();
|
|
308
|
-
if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
|
|
309
|
-
list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL });
|
|
310
|
-
}
|
|
311
|
-
return list5;
|
|
312
235
|
}
|
|
313
|
-
function
|
|
236
|
+
function guardHookOutdated() {
|
|
314
237
|
try {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
|
|
318
|
-
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
319
|
-
} catch {
|
|
320
|
-
return [];
|
|
321
|
-
}
|
|
322
|
-
})();
|
|
323
|
-
const next = list5.filter((a) => a.apiKey !== acc.apiKey);
|
|
324
|
-
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
325
|
-
mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
|
|
326
|
-
writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
|
|
238
|
+
const p = globalPaths();
|
|
239
|
+
return readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
327
240
|
} catch {
|
|
241
|
+
return false;
|
|
328
242
|
}
|
|
329
243
|
}
|
|
330
|
-
function
|
|
244
|
+
function isGuardInstalled() {
|
|
331
245
|
try {
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
} catch {
|
|
337
|
-
return [];
|
|
338
|
-
}
|
|
339
|
-
})();
|
|
340
|
-
writeFileSync2(accountsFile(), JSON.stringify(list5.filter((a) => a.apiKey !== apiKey), null, 2));
|
|
246
|
+
const p = globalPaths();
|
|
247
|
+
if (!existsSync2(p.settingsPath)) return false;
|
|
248
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
249
|
+
return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
|
|
341
250
|
} catch {
|
|
251
|
+
return false;
|
|
342
252
|
}
|
|
343
253
|
}
|
|
344
|
-
|
|
345
|
-
function setViewCredentials(creds) {
|
|
346
|
-
viewOverride = creds;
|
|
347
|
-
cached2 = null;
|
|
348
|
-
}
|
|
349
|
-
function isActiveAccount(apiKey) {
|
|
350
|
-
return loginCredentialFile().apiKey === apiKey;
|
|
351
|
-
}
|
|
352
|
-
function setActiveAccount(creds) {
|
|
254
|
+
function uninstallGlobalQuiet() {
|
|
353
255
|
try {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
let existing = {};
|
|
256
|
+
const p = globalPaths();
|
|
257
|
+
unlockProtected();
|
|
258
|
+
removeClaudeShim();
|
|
358
259
|
try {
|
|
359
|
-
|
|
260
|
+
removeAntigravityGuard(p);
|
|
360
261
|
} catch {
|
|
361
262
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return
|
|
263
|
+
if (!existsSync2(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
264
|
+
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
265
|
+
delete s.hooks;
|
|
266
|
+
writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
267
|
+
return { ok: true, message: "guard removed (open a new session)" };
|
|
268
|
+
} catch (e) {
|
|
269
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
367
270
|
}
|
|
368
271
|
}
|
|
369
|
-
function
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return true;
|
|
375
|
-
}
|
|
376
|
-
let existing = {};
|
|
272
|
+
function escapeRe(s) {
|
|
273
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
274
|
+
}
|
|
275
|
+
function shimTargets() {
|
|
276
|
+
if (process.platform === "win32") {
|
|
377
277
|
try {
|
|
378
|
-
|
|
278
|
+
const prof = execFileSync("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
279
|
+
return prof ? [prof] : [];
|
|
379
280
|
} catch {
|
|
281
|
+
return [];
|
|
380
282
|
}
|
|
381
|
-
delete existing.apiKey;
|
|
382
|
-
delete existing.apiUrl;
|
|
383
|
-
writeFileSync2(p, JSON.stringify(existing, null, 2));
|
|
384
|
-
cached2 = null;
|
|
385
|
-
viewOverride = null;
|
|
386
|
-
return true;
|
|
387
|
-
} catch {
|
|
388
|
-
return false;
|
|
389
283
|
}
|
|
284
|
+
return [".bashrc", ".zshrc", ".profile"].map((f) => join6(homedir6(), f)).filter((f) => existsSync2(f));
|
|
390
285
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
this.code = code;
|
|
286
|
+
function writeShimBlock(file, block2) {
|
|
287
|
+
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
288
|
+
let content = existsSync2(file) ? readFileSync4(file, "utf-8") : "";
|
|
289
|
+
content = content.replace(re, "");
|
|
290
|
+
if (block2) {
|
|
291
|
+
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
292
|
+
content += block2 + "\n";
|
|
399
293
|
}
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
294
|
+
mkdirSync4(dirname(file), { recursive: true });
|
|
295
|
+
writeFileSync5(file, content);
|
|
296
|
+
}
|
|
297
|
+
function removeClaudeShim() {
|
|
298
|
+
for (const file of shimTargets()) {
|
|
299
|
+
try {
|
|
300
|
+
writeShimBlock(file, null);
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, SHIM_BEGIN, SHIM_END;
|
|
306
|
+
var init_global_install = __esm({
|
|
307
|
+
"src/global-install.ts"() {
|
|
308
|
+
"use strict";
|
|
309
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
310
|
+
HOOKS_DIR = resolve2(__dirname, "..", "hooks");
|
|
311
|
+
ANTIGRAVITY_GROUP = "solongate-guard";
|
|
312
|
+
SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
313
|
+
SHIM_END = "# <<< SolonGate shield <<<";
|
|
405
314
|
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// src/tui/index.tsx
|
|
318
|
+
import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
|
|
319
|
+
import { homedir as homedir9 } from "os";
|
|
320
|
+
import { join as join9 } from "path";
|
|
321
|
+
import { render } from "ink";
|
|
322
|
+
|
|
323
|
+
// src/tui/App.tsx
|
|
324
|
+
import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
|
|
325
|
+
import { useEffect as useEffect8, useState as useState9 } from "react";
|
|
326
|
+
|
|
327
|
+
// src/cli-utils.ts
|
|
328
|
+
var c = {
|
|
329
|
+
reset: "\x1B[0m",
|
|
330
|
+
bold: "\x1B[1m",
|
|
331
|
+
dim: "\x1B[2m",
|
|
332
|
+
italic: "\x1B[3m",
|
|
333
|
+
white: "\x1B[97m",
|
|
334
|
+
gray: "\x1B[90m",
|
|
335
|
+
blue1: "\x1B[38;2;20;50;160m",
|
|
336
|
+
blue2: "\x1B[38;2;40;80;190m",
|
|
337
|
+
blue3: "\x1B[38;2;60;110;215m",
|
|
338
|
+
blue4: "\x1B[38;2;90;140;230m",
|
|
339
|
+
blue5: "\x1B[38;2;130;170;240m",
|
|
340
|
+
blue6: "\x1B[38;2;170;200;250m",
|
|
341
|
+
green: "\x1B[38;2;80;200;120m",
|
|
342
|
+
red: "\x1B[38;2;220;80;80m",
|
|
343
|
+
cyan: "\x1B[38;2;100;200;220m",
|
|
344
|
+
yellow: "\x1B[38;2;220;200;80m",
|
|
345
|
+
bgBlue: "\x1B[48;2;20;50;160m"
|
|
406
346
|
};
|
|
407
|
-
|
|
347
|
+
var BANNER_FULL = [
|
|
348
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
349
|
+
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
350
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
351
|
+
" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
352
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
353
|
+
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
354
|
+
];
|
|
355
|
+
var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
356
|
+
|
|
357
|
+
// src/tui/components.tsx
|
|
358
|
+
import { Box, Text } from "ink";
|
|
359
|
+
|
|
360
|
+
// src/tui/config.ts
|
|
361
|
+
import { readFileSync } from "fs";
|
|
362
|
+
import { homedir } from "os";
|
|
363
|
+
import { join } from "path";
|
|
364
|
+
var cached = null;
|
|
365
|
+
function loadConfig() {
|
|
366
|
+
if (cached) return cached;
|
|
367
|
+
const defaults = { notifications: true };
|
|
408
368
|
try {
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
369
|
+
const raw = readFileSync(join(homedir(), ".solongate", "tui-config.json"), "utf-8");
|
|
370
|
+
const j = JSON.parse(raw);
|
|
371
|
+
cached = {
|
|
372
|
+
notifications: j.notifications !== false,
|
|
373
|
+
accent: typeof j.accent === "string" ? j.accent : void 0,
|
|
374
|
+
pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
|
|
375
|
+
};
|
|
413
376
|
} catch {
|
|
414
|
-
|
|
377
|
+
cached = defaults;
|
|
415
378
|
}
|
|
379
|
+
return cached;
|
|
416
380
|
}
|
|
417
|
-
|
|
381
|
+
|
|
382
|
+
// src/tui/theme.ts
|
|
383
|
+
var accent = loadConfig().accent;
|
|
384
|
+
var theme = {
|
|
385
|
+
accent: accent || "white",
|
|
386
|
+
accentBright: accent || "white",
|
|
387
|
+
ok: "green",
|
|
388
|
+
warn: "yellow",
|
|
389
|
+
bad: "red",
|
|
390
|
+
dim: "gray"
|
|
391
|
+
};
|
|
392
|
+
function decisionColor(decision) {
|
|
393
|
+
const d = (decision || "").toUpperCase();
|
|
394
|
+
if (d === "ALLOW") return theme.ok;
|
|
395
|
+
if (d === "DENY" || d === "DENIED") return theme.bad;
|
|
396
|
+
return theme.dim;
|
|
397
|
+
}
|
|
398
|
+
function modeColor(m) {
|
|
399
|
+
if (m === "block" || m === "active" || m === "on") return theme.ok;
|
|
400
|
+
if (m === "detect" || m === "idle") return theme.warn;
|
|
401
|
+
return theme.dim;
|
|
402
|
+
}
|
|
403
|
+
function prettyJson(s) {
|
|
418
404
|
try {
|
|
419
|
-
|
|
420
|
-
if (!existsSync(envPath)) return void 0;
|
|
421
|
-
for (const line of readFileSync3(envPath, "utf-8").split("\n")) {
|
|
422
|
-
const trimmed = line.trim();
|
|
423
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
424
|
-
const eq = trimmed.indexOf("=");
|
|
425
|
-
if (eq === -1) continue;
|
|
426
|
-
const key = trimmed.slice(0, eq).trim();
|
|
427
|
-
if (key !== "SOLONGATE_API_KEY") continue;
|
|
428
|
-
return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
429
|
-
}
|
|
405
|
+
return JSON.stringify(JSON.parse(s), null, 2);
|
|
430
406
|
} catch {
|
|
407
|
+
return s;
|
|
431
408
|
}
|
|
432
|
-
return void 0;
|
|
433
409
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL;
|
|
442
|
-
const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
|
|
443
|
-
if (!apiUrlOverride) cached2 = creds;
|
|
444
|
-
return creds;
|
|
445
|
-
}
|
|
446
|
-
function buildUrl(base, path, query) {
|
|
447
|
-
const url = new URL(`${base}/api/v1${path}`);
|
|
448
|
-
if (query) {
|
|
449
|
-
for (const [k, v] of Object.entries(query)) {
|
|
450
|
-
if (v === void 0) continue;
|
|
451
|
-
url.searchParams.set(k, String(v));
|
|
410
|
+
function wrapLines(s, width) {
|
|
411
|
+
const w = Math.max(8, width);
|
|
412
|
+
const out = [];
|
|
413
|
+
for (const raw of (s ?? "").split("\n")) {
|
|
414
|
+
if (raw.length <= w) {
|
|
415
|
+
out.push(raw);
|
|
416
|
+
continue;
|
|
452
417
|
}
|
|
418
|
+
for (let i = 0; i < raw.length; i += w) out.push(raw.slice(i, i + w));
|
|
453
419
|
}
|
|
454
|
-
return
|
|
420
|
+
return out;
|
|
455
421
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
method,
|
|
469
|
-
headers: { ...headers, Connection: "close" },
|
|
470
|
-
body: bodyInit,
|
|
471
|
-
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
472
|
-
});
|
|
473
|
-
const maxTries = method === "GET" ? 3 : 1;
|
|
474
|
-
let res;
|
|
475
|
-
let lastErr;
|
|
476
|
-
for (let i = 0; i < maxTries; i++) {
|
|
477
|
-
try {
|
|
478
|
-
res = await attempt();
|
|
479
|
-
break;
|
|
480
|
-
} catch (err) {
|
|
481
|
-
lastErr = err;
|
|
482
|
-
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
if (!res) {
|
|
486
|
-
const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
487
|
-
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
488
|
-
}
|
|
489
|
-
const text = await res.text().catch(() => "");
|
|
490
|
-
let json = void 0;
|
|
491
|
-
if (text) {
|
|
492
|
-
try {
|
|
493
|
-
json = JSON.parse(text);
|
|
494
|
-
} catch {
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (!res.ok) {
|
|
498
|
-
const envelope = json?.error;
|
|
499
|
-
if (envelope && typeof envelope === "object") {
|
|
500
|
-
throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
|
|
501
|
-
}
|
|
502
|
-
if (typeof envelope === "string") {
|
|
503
|
-
throw new ApiError(res.status, "ERROR", envelope);
|
|
504
|
-
}
|
|
505
|
-
if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate` and log in from the Accounts panel.");
|
|
506
|
-
if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
|
|
507
|
-
if (res.status >= 500) throw new ApiError(res.status, "SERVER_ERROR", text || "SolonGate API had a problem (server error). Please try again in a moment.");
|
|
508
|
-
throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
|
|
509
|
-
}
|
|
510
|
-
return json;
|
|
422
|
+
function truncate(s, n) {
|
|
423
|
+
if (!s) return "";
|
|
424
|
+
return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
425
|
+
}
|
|
426
|
+
function ago(ts) {
|
|
427
|
+
const t = typeof ts === "number" ? ts : Date.parse(ts);
|
|
428
|
+
if (Number.isNaN(t)) return String(ts ?? "");
|
|
429
|
+
const s = Math.max(0, (Date.now() - t) / 1e3);
|
|
430
|
+
if (s < 60) return `${Math.floor(s)}s`;
|
|
431
|
+
if (s < 3600) return `${Math.floor(s / 60)}m`;
|
|
432
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h`;
|
|
433
|
+
return `${Math.floor(s / 86400)}d`;
|
|
511
434
|
}
|
|
512
435
|
|
|
513
|
-
// src/
|
|
514
|
-
import {
|
|
515
|
-
|
|
436
|
+
// src/tui/components.tsx
|
|
437
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
438
|
+
var hhmmss = (ts) => {
|
|
439
|
+
const d = new Date(ts);
|
|
440
|
+
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
441
|
+
};
|
|
442
|
+
var ymd = (ts) => {
|
|
443
|
+
const d = new Date(ts);
|
|
444
|
+
if (Number.isNaN(d.getTime())) return "----------";
|
|
445
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
446
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
447
|
+
};
|
|
448
|
+
function PaneTitle({ label, extra, width }) {
|
|
449
|
+
const tail = extra ? ` ${extra} ` : " ";
|
|
450
|
+
const used = 2 + 1 + label.length + tail.length;
|
|
451
|
+
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
|
|
452
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "\u2500 " }),
|
|
453
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.accentBright, bold: true, children: [
|
|
454
|
+
"\u258E",
|
|
455
|
+
label
|
|
456
|
+
] }),
|
|
457
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
|
|
458
|
+
] });
|
|
459
|
+
}
|
|
460
|
+
function StreamLine({ e, loc, selected, date }) {
|
|
461
|
+
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
|
|
462
|
+
/* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
|
|
463
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
464
|
+
"[",
|
|
465
|
+
date ? ymd(e.at) + " " : "",
|
|
466
|
+
hhmmss(e.at),
|
|
467
|
+
" "
|
|
468
|
+
] }),
|
|
469
|
+
/* @__PURE__ */ jsx(Text, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
|
|
470
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "] " }),
|
|
471
|
+
/* @__PURE__ */ jsx(Text, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
472
|
+
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
|
|
473
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: (e.permission ?? "").padEnd(5) }),
|
|
474
|
+
/* @__PURE__ */ jsx(Text, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
475
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
|
|
476
|
+
/* @__PURE__ */ jsx(Text, { color: e.dlp ? theme.bad : theme.dim, bold: e.dlp, children: ("dlp:" + (e.dlp ? "yes" : "no")).padEnd(8) }),
|
|
477
|
+
/* @__PURE__ */ jsx(Text, { color: e.burst ? theme.warn : theme.dim, bold: e.burst, children: ("rl:" + (e.burst ? "yes" : "no")).padEnd(7) }),
|
|
478
|
+
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx(Text, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
|
|
479
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: e.detail })
|
|
480
|
+
] });
|
|
481
|
+
}
|
|
482
|
+
function DataView({
|
|
483
|
+
loading,
|
|
484
|
+
error,
|
|
485
|
+
empty,
|
|
486
|
+
emptyText,
|
|
487
|
+
children
|
|
488
|
+
}) {
|
|
489
|
+
if (error) return /* @__PURE__ */ jsxs(Text, { color: theme.bad, children: [
|
|
490
|
+
"\u2717 ",
|
|
491
|
+
error
|
|
492
|
+
] });
|
|
493
|
+
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
|
|
494
|
+
if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
|
|
495
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
496
|
+
}
|
|
497
|
+
function Table({ columns, rows }) {
|
|
498
|
+
const fit = (s, w) => {
|
|
499
|
+
const v = s ?? "";
|
|
500
|
+
if (v.length > w) return v.slice(0, Math.max(0, w - 1)) + "\u2026";
|
|
501
|
+
return v.padEnd(w);
|
|
502
|
+
};
|
|
503
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
504
|
+
/* @__PURE__ */ jsx(Box, { children: columns.map((c2, i) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: fit(c2.header, c2.width) + " " }, i)) }),
|
|
505
|
+
rows.map((row, ri) => /* @__PURE__ */ jsx(Box, { children: row.map((cell, ci) => /* @__PURE__ */ jsx(Text, { color: cell.color, dimColor: cell.dim, bold: cell.bold, children: fit(cell.value, columns[ci]?.width ?? 10) + " " }, ci)) }, ri))
|
|
506
|
+
] });
|
|
507
|
+
}
|
|
508
|
+
function KeyHints({ hints }) {
|
|
509
|
+
return /* @__PURE__ */ jsx(Box, { children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
510
|
+
/* @__PURE__ */ jsx(Text, { color: theme.accent, children: key }),
|
|
511
|
+
" ",
|
|
512
|
+
label,
|
|
513
|
+
i < hints.length - 1 ? " " : ""
|
|
514
|
+
] }, i)) });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/tui/panels/Live.tsx
|
|
518
|
+
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
519
|
+
import TextInput from "ink-text-input";
|
|
520
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
521
|
+
import { homedir as homedir4 } from "os";
|
|
522
|
+
import { join as join4 } from "path";
|
|
523
|
+
|
|
524
|
+
// src/tui/local-log.ts
|
|
525
|
+
import { closeSync, openSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
|
|
526
|
+
import { homedir as homedir2 } from "os";
|
|
527
|
+
import { join as join2 } from "path";
|
|
528
|
+
var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
529
|
+
function tailLines(file, maxBytes = 131072) {
|
|
516
530
|
try {
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
531
|
+
const size = statSync(file).size;
|
|
532
|
+
const start = Math.max(0, size - maxBytes);
|
|
533
|
+
const fd = openSync(file, "r");
|
|
534
|
+
const buf = Buffer.alloc(size - start);
|
|
535
|
+
readSync(fd, buf, 0, buf.length, start);
|
|
536
|
+
closeSync(fd);
|
|
537
|
+
const lines = buf.toString("utf-8").split("\n").filter(Boolean);
|
|
538
|
+
if (start > 0) lines.shift();
|
|
539
|
+
return lines;
|
|
523
540
|
} catch {
|
|
541
|
+
return [];
|
|
524
542
|
}
|
|
525
543
|
}
|
|
526
|
-
|
|
527
|
-
const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
|
|
528
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
529
|
-
const j = await res.json();
|
|
530
|
-
return {
|
|
531
|
-
deviceCode: j.device_code,
|
|
532
|
-
verifyUrl: j.verification_uri_complete || j.verification_uri || "",
|
|
533
|
-
intervalMs: Math.max(2, Number(j.interval) || 3) * 1e3,
|
|
534
|
-
expiresAt: Date.now() + (Number(j.expires_in) || 600) * 1e3
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
async function pollDeviceLogin(apiUrl, deviceCode) {
|
|
544
|
+
function deleteLocalEntry(at, tool, session) {
|
|
538
545
|
try {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
546
|
+
const lines = readFileSync2(LOCAL_LOG, "utf-8").split("\n");
|
|
547
|
+
let removed = 0;
|
|
548
|
+
const kept = lines.filter((line) => {
|
|
549
|
+
if (!line.trim()) return false;
|
|
550
|
+
if (removed) return true;
|
|
551
|
+
try {
|
|
552
|
+
const j = JSON.parse(line);
|
|
553
|
+
const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
|
|
554
|
+
if (hit) {
|
|
555
|
+
removed++;
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
return true;
|
|
543
561
|
});
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email, email: j.user?.email };
|
|
547
|
-
}
|
|
548
|
-
if (j.status === "expired" || j.status === "not_found") return { status: j.status };
|
|
549
|
-
return { status: "pending" };
|
|
562
|
+
if (removed) writeFileSync(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
|
|
563
|
+
return removed;
|
|
550
564
|
} catch {
|
|
551
|
-
return
|
|
565
|
+
return 0;
|
|
552
566
|
}
|
|
553
567
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// src/api-client/policies.ts
|
|
565
|
-
var policies_exports = {};
|
|
566
|
-
__export(policies_exports, {
|
|
567
|
-
active: () => active,
|
|
568
|
-
addRule: () => addRule,
|
|
569
|
-
backtest: () => backtest,
|
|
570
|
-
create: () => create,
|
|
571
|
-
dryRun: () => dryRun,
|
|
572
|
-
get: () => get,
|
|
573
|
-
list: () => list,
|
|
574
|
-
remove: () => remove,
|
|
575
|
-
revokeRule: () => revokeRule,
|
|
576
|
-
rollback: () => rollback,
|
|
577
|
-
setActive: () => setActive,
|
|
578
|
-
update: () => update,
|
|
579
|
-
versions: () => versions
|
|
580
|
-
});
|
|
581
|
-
function list() {
|
|
582
|
-
return request("GET", "/policies");
|
|
583
|
-
}
|
|
584
|
-
function get(id, version) {
|
|
585
|
-
return request("GET", `/policies/${encodeURIComponent(id)}`, {
|
|
586
|
-
query: version !== void 0 ? { version } : void 0
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
function create(policy) {
|
|
590
|
-
return request("POST", "/policies", { body: policy });
|
|
568
|
+
function clearLocalLog() {
|
|
569
|
+
try {
|
|
570
|
+
const n = readFileSync2(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
|
|
571
|
+
writeFileSync(LOCAL_LOG, "");
|
|
572
|
+
return n;
|
|
573
|
+
} catch {
|
|
574
|
+
return 0;
|
|
575
|
+
}
|
|
591
576
|
}
|
|
592
|
-
|
|
593
|
-
|
|
577
|
+
var DLP_REASON = /security layer \(dlp\)/i;
|
|
578
|
+
var RL_REASON = /security layer \(rate limit\)|rate[- ]?limit(?:ed)?\b.*exceed|exceeded \d+ calls/i;
|
|
579
|
+
function reasonSignals(reason) {
|
|
580
|
+
const r = reason || "";
|
|
581
|
+
const dlp = [];
|
|
582
|
+
if (DLP_REASON.test(r)) {
|
|
583
|
+
const m = r.match(/contain(?:s)? (?:a |an )?(.+?)(?:\.|$)/i);
|
|
584
|
+
dlp.push(m ? m[1].trim() : "DLP");
|
|
585
|
+
}
|
|
586
|
+
return { dlp, burst: RL_REASON.test(r) };
|
|
594
587
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return
|
|
606
|
-
}
|
|
607
|
-
function rollback(id, version) {
|
|
608
|
-
return request("POST", `/policies/${encodeURIComponent(id)}/rollback`, { body: { version } });
|
|
609
|
-
}
|
|
610
|
-
function active(agentId) {
|
|
611
|
-
return request("GET", "/policies/active", { query: agentId ? { agent_id: agentId } : void 0 });
|
|
612
|
-
}
|
|
613
|
-
function setActive(policyId) {
|
|
614
|
-
return request("POST", "/policies/active", { body: { policyId: policyId ?? "" } });
|
|
615
|
-
}
|
|
616
|
-
function dryRun(body) {
|
|
617
|
-
return request("POST", "/policies/dry-run", { body });
|
|
618
|
-
}
|
|
619
|
-
function backtest(body) {
|
|
620
|
-
return request("POST", "/policies/backtest", { body });
|
|
588
|
+
function parseLocalLines(lines) {
|
|
589
|
+
const out = [];
|
|
590
|
+
for (const line of lines) {
|
|
591
|
+
try {
|
|
592
|
+
const j = JSON.parse(line);
|
|
593
|
+
const at = Date.parse(j.ts ?? "");
|
|
594
|
+
if (Number.isFinite(at)) out.push({ ...j, at });
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return out;
|
|
621
599
|
}
|
|
622
600
|
|
|
623
|
-
// src/
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
updateWebhook: () => updateWebhook
|
|
645
|
-
});
|
|
646
|
-
function getSecurityLayers() {
|
|
647
|
-
return request("GET", "/settings/security-layers");
|
|
648
|
-
}
|
|
649
|
-
function setSecurityLayers(layers) {
|
|
650
|
-
return request("PUT", "/settings/security-layers", { body: { layers } });
|
|
651
|
-
}
|
|
652
|
-
function getRateLimitHistory() {
|
|
653
|
-
return request("GET", "/settings/rate-limit-history");
|
|
654
|
-
}
|
|
655
|
-
function clearRateLimitHistory() {
|
|
656
|
-
return request("DELETE", "/settings/rate-limit-history", { query: { all: 1 } });
|
|
657
|
-
}
|
|
658
|
-
function getGuardStatus() {
|
|
659
|
-
return request("GET", "/settings/guard-status");
|
|
660
|
-
}
|
|
661
|
-
function getSelfProtection() {
|
|
662
|
-
return request("GET", "/settings/self-protection");
|
|
663
|
-
}
|
|
664
|
-
function setSelfProtection(enabled) {
|
|
665
|
-
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
666
|
-
}
|
|
667
|
-
function getLocalLogs() {
|
|
668
|
-
return request("GET", "/settings/local-logs");
|
|
669
|
-
}
|
|
670
|
-
function setLocalLogs(cfg) {
|
|
671
|
-
return request("PUT", "/settings/local-logs", { body: cfg });
|
|
601
|
+
// src/tui/panels/Live.tsx
|
|
602
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
603
|
+
|
|
604
|
+
// src/api-client/client.ts
|
|
605
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync } from "fs";
|
|
606
|
+
import { resolve, join as join3 } from "path";
|
|
607
|
+
import { homedir as homedir3 } from "os";
|
|
608
|
+
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
609
|
+
var accountsFile = () => join3(homedir3(), ".solongate", "accounts.json");
|
|
610
|
+
function listAccounts() {
|
|
611
|
+
let list5 = [];
|
|
612
|
+
try {
|
|
613
|
+
const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
|
|
614
|
+
if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
|
|
615
|
+
} catch {
|
|
616
|
+
}
|
|
617
|
+
const active2 = loginCredentialFile();
|
|
618
|
+
if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
|
|
619
|
+
list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL });
|
|
620
|
+
}
|
|
621
|
+
return list5;
|
|
672
622
|
}
|
|
673
|
-
function
|
|
674
|
-
|
|
623
|
+
function saveAccount(acc) {
|
|
624
|
+
try {
|
|
625
|
+
const list5 = (() => {
|
|
626
|
+
try {
|
|
627
|
+
const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
|
|
628
|
+
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
629
|
+
} catch {
|
|
630
|
+
return [];
|
|
631
|
+
}
|
|
632
|
+
})();
|
|
633
|
+
const next = list5.filter((a) => a.apiKey !== acc.apiKey);
|
|
634
|
+
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
635
|
+
mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
|
|
636
|
+
writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
|
|
637
|
+
} catch {
|
|
638
|
+
}
|
|
675
639
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
640
|
+
function removeAccount(apiKey) {
|
|
641
|
+
try {
|
|
642
|
+
const list5 = (() => {
|
|
643
|
+
try {
|
|
644
|
+
const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
|
|
645
|
+
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
646
|
+
} catch {
|
|
647
|
+
return [];
|
|
648
|
+
}
|
|
649
|
+
})();
|
|
650
|
+
writeFileSync2(accountsFile(), JSON.stringify(list5.filter((a) => a.apiKey !== apiKey), null, 2));
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
678
653
|
}
|
|
679
|
-
|
|
680
|
-
|
|
654
|
+
var viewOverride = null;
|
|
655
|
+
function setViewCredentials(creds) {
|
|
656
|
+
viewOverride = creds;
|
|
657
|
+
cached2 = null;
|
|
681
658
|
}
|
|
682
|
-
function
|
|
683
|
-
return
|
|
659
|
+
function isActiveAccount(apiKey) {
|
|
660
|
+
return loginCredentialFile().apiKey === apiKey;
|
|
684
661
|
}
|
|
685
|
-
function
|
|
686
|
-
|
|
662
|
+
function setActiveAccount(creds) {
|
|
663
|
+
try {
|
|
664
|
+
const dir = join3(homedir3(), ".solongate");
|
|
665
|
+
mkdirSync(dir, { recursive: true });
|
|
666
|
+
const p = join3(dir, ["cloud", "guard.json"].join("-"));
|
|
667
|
+
let existing = {};
|
|
668
|
+
try {
|
|
669
|
+
existing = JSON.parse(readFileSync3(p, "utf-8"));
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
writeFileSync2(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2));
|
|
673
|
+
cached2 = null;
|
|
674
|
+
return true;
|
|
675
|
+
} catch {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
687
678
|
}
|
|
688
|
-
function
|
|
689
|
-
|
|
679
|
+
function clearActiveCredential() {
|
|
680
|
+
try {
|
|
681
|
+
const p = join3(homedir3(), ".solongate", ["cloud", "guard.json"].join("-"));
|
|
682
|
+
if (!existsSync(p)) {
|
|
683
|
+
cached2 = null;
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
let existing = {};
|
|
687
|
+
try {
|
|
688
|
+
existing = JSON.parse(readFileSync3(p, "utf-8"));
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
delete existing.apiKey;
|
|
692
|
+
delete existing.apiUrl;
|
|
693
|
+
writeFileSync2(p, JSON.stringify(existing, null, 2));
|
|
694
|
+
cached2 = null;
|
|
695
|
+
viewOverride = null;
|
|
696
|
+
return true;
|
|
697
|
+
} catch {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
690
700
|
}
|
|
691
|
-
|
|
692
|
-
|
|
701
|
+
var ApiError = class extends Error {
|
|
702
|
+
status;
|
|
703
|
+
code;
|
|
704
|
+
constructor(status, code, message) {
|
|
705
|
+
super(message);
|
|
706
|
+
this.name = "ApiError";
|
|
707
|
+
this.status = status;
|
|
708
|
+
this.code = code;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
var NotAuthenticatedError = class extends Error {
|
|
712
|
+
constructor() {
|
|
713
|
+
super("Not logged in. Run `solongate` and log in from the Accounts panel.");
|
|
714
|
+
this.name = "NotAuthenticatedError";
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
function loginCredentialFile() {
|
|
718
|
+
try {
|
|
719
|
+
const p = join3(homedir3(), ".solongate", "cloud-guard.json");
|
|
720
|
+
if (!existsSync(p)) return {};
|
|
721
|
+
const c2 = JSON.parse(readFileSync3(p, "utf-8"));
|
|
722
|
+
return c2 && typeof c2 === "object" ? c2 : {};
|
|
723
|
+
} catch {
|
|
724
|
+
return {};
|
|
725
|
+
}
|
|
693
726
|
}
|
|
694
|
-
function
|
|
695
|
-
|
|
727
|
+
function dotenvApiKey() {
|
|
728
|
+
try {
|
|
729
|
+
const envPath = resolve(".env");
|
|
730
|
+
if (!existsSync(envPath)) return void 0;
|
|
731
|
+
for (const line of readFileSync3(envPath, "utf-8").split("\n")) {
|
|
732
|
+
const trimmed = line.trim();
|
|
733
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
734
|
+
const eq = trimmed.indexOf("=");
|
|
735
|
+
if (eq === -1) continue;
|
|
736
|
+
const key = trimmed.slice(0, eq).trim();
|
|
737
|
+
if (key !== "SOLONGATE_API_KEY") continue;
|
|
738
|
+
return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
739
|
+
}
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
return void 0;
|
|
696
743
|
}
|
|
697
|
-
|
|
698
|
-
|
|
744
|
+
var cached2 = null;
|
|
745
|
+
function resolveCredentials(apiUrlOverride) {
|
|
746
|
+
if (viewOverride && !apiUrlOverride) return viewOverride;
|
|
747
|
+
if (cached2 && !apiUrlOverride) return cached2;
|
|
748
|
+
const file = loginCredentialFile();
|
|
749
|
+
const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
|
|
750
|
+
if (!apiKey) throw new NotAuthenticatedError();
|
|
751
|
+
const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL;
|
|
752
|
+
const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
|
|
753
|
+
if (!apiUrlOverride) cached2 = creds;
|
|
754
|
+
return creds;
|
|
699
755
|
}
|
|
700
|
-
function
|
|
701
|
-
|
|
756
|
+
function buildUrl(base, path, query) {
|
|
757
|
+
const url = new URL(`${base}/api/v1${path}`);
|
|
758
|
+
if (query) {
|
|
759
|
+
for (const [k, v] of Object.entries(query)) {
|
|
760
|
+
if (v === void 0) continue;
|
|
761
|
+
url.searchParams.set(k, String(v));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return url.toString();
|
|
702
765
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
766
|
+
async function request(method, path, opts = {}) {
|
|
767
|
+
const creds = resolveCredentials(opts.apiUrl);
|
|
768
|
+
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
769
|
+
const headers = {
|
|
770
|
+
Authorization: `Bearer ${creds.apiKey}`
|
|
771
|
+
};
|
|
772
|
+
let bodyInit;
|
|
773
|
+
if (opts.body !== void 0) {
|
|
774
|
+
headers["Content-Type"] = "application/json";
|
|
775
|
+
bodyInit = JSON.stringify(opts.body);
|
|
776
|
+
}
|
|
777
|
+
const attempt = () => fetch(url, {
|
|
778
|
+
method,
|
|
779
|
+
headers: { ...headers, Connection: "close" },
|
|
780
|
+
body: bodyInit,
|
|
781
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
782
|
+
});
|
|
783
|
+
const maxTries = method === "GET" ? 3 : 1;
|
|
784
|
+
let res;
|
|
785
|
+
let lastErr;
|
|
786
|
+
for (let i = 0; i < maxTries; i++) {
|
|
787
|
+
try {
|
|
788
|
+
res = await attempt();
|
|
789
|
+
break;
|
|
790
|
+
} catch (err) {
|
|
791
|
+
lastErr = err;
|
|
792
|
+
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (!res) {
|
|
796
|
+
const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
|
|
797
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
798
|
+
}
|
|
799
|
+
const text = await res.text().catch(() => "");
|
|
800
|
+
let json = void 0;
|
|
801
|
+
if (text) {
|
|
802
|
+
try {
|
|
803
|
+
json = JSON.parse(text);
|
|
804
|
+
} catch {
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (!res.ok) {
|
|
808
|
+
const envelope = json?.error;
|
|
809
|
+
if (envelope && typeof envelope === "object") {
|
|
810
|
+
throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
|
|
811
|
+
}
|
|
812
|
+
if (typeof envelope === "string") {
|
|
813
|
+
throw new ApiError(res.status, "ERROR", envelope);
|
|
814
|
+
}
|
|
815
|
+
if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate` and log in from the Accounts panel.");
|
|
816
|
+
if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
|
|
817
|
+
if (res.status >= 500) throw new ApiError(res.status, "SERVER_ERROR", text || "SolonGate API had a problem (server error). Please try again in a moment.");
|
|
818
|
+
throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
|
|
819
|
+
}
|
|
820
|
+
return json;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/api-client/device-login.ts
|
|
824
|
+
import { spawn } from "child_process";
|
|
825
|
+
function openBrowser(url) {
|
|
826
|
+
try {
|
|
827
|
+
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
828
|
+
const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
829
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
830
|
+
child.on("error", () => {
|
|
831
|
+
});
|
|
832
|
+
child.unref();
|
|
833
|
+
} catch {
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function startDeviceLogin(apiUrl = DEFAULT_API_URL) {
|
|
837
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
|
|
838
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
839
|
+
const j = await res.json();
|
|
840
|
+
return {
|
|
841
|
+
deviceCode: j.device_code,
|
|
842
|
+
verifyUrl: j.verification_uri_complete || j.verification_uri || "",
|
|
843
|
+
intervalMs: Math.max(2, Number(j.interval) || 3) * 1e3,
|
|
844
|
+
expiresAt: Date.now() + (Number(j.expires_in) || 600) * 1e3
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
async function pollDeviceLogin(apiUrl, deviceCode) {
|
|
848
|
+
try {
|
|
849
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
|
|
850
|
+
method: "POST",
|
|
851
|
+
headers: { "Content-Type": "application/json" },
|
|
852
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
853
|
+
});
|
|
854
|
+
const j = await res.json().catch(() => ({}));
|
|
855
|
+
if (j.status === "approved" && j.api_key) {
|
|
856
|
+
return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email, email: j.user?.email };
|
|
857
|
+
}
|
|
858
|
+
if (j.status === "expired" || j.status === "not_found") return { status: j.status };
|
|
859
|
+
return { status: "pending" };
|
|
860
|
+
} catch {
|
|
861
|
+
return { status: "pending" };
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/api-client/auth.ts
|
|
866
|
+
var auth_exports = {};
|
|
867
|
+
__export(auth_exports, {
|
|
868
|
+
me: () => me
|
|
869
|
+
});
|
|
870
|
+
function me() {
|
|
871
|
+
return request("GET", "/auth/me");
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/api-client/policies.ts
|
|
875
|
+
var policies_exports = {};
|
|
876
|
+
__export(policies_exports, {
|
|
877
|
+
active: () => active,
|
|
878
|
+
addRule: () => addRule,
|
|
879
|
+
backtest: () => backtest,
|
|
880
|
+
create: () => create,
|
|
881
|
+
dryRun: () => dryRun,
|
|
882
|
+
get: () => get,
|
|
883
|
+
list: () => list,
|
|
884
|
+
remove: () => remove,
|
|
885
|
+
revokeRule: () => revokeRule,
|
|
886
|
+
rollback: () => rollback,
|
|
887
|
+
setActive: () => setActive,
|
|
888
|
+
update: () => update,
|
|
889
|
+
versions: () => versions
|
|
890
|
+
});
|
|
891
|
+
function list() {
|
|
892
|
+
return request("GET", "/policies");
|
|
893
|
+
}
|
|
894
|
+
function get(id, version) {
|
|
895
|
+
return request("GET", `/policies/${encodeURIComponent(id)}`, {
|
|
896
|
+
query: version !== void 0 ? { version } : void 0
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function create(policy) {
|
|
900
|
+
return request("POST", "/policies", { body: policy });
|
|
901
|
+
}
|
|
902
|
+
function update(id, policy) {
|
|
903
|
+
return request("PUT", `/policies/${encodeURIComponent(id)}`, { body: policy });
|
|
904
|
+
}
|
|
905
|
+
function remove(id) {
|
|
906
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}`);
|
|
907
|
+
}
|
|
908
|
+
function addRule(id, spec) {
|
|
909
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rules`, { body: spec });
|
|
910
|
+
}
|
|
911
|
+
function revokeRule(id, ruleId) {
|
|
912
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}/rules/${encodeURIComponent(ruleId)}`);
|
|
913
|
+
}
|
|
914
|
+
function versions(id, opts = {}) {
|
|
915
|
+
return request("GET", `/policies/${encodeURIComponent(id)}/versions`, { query: opts });
|
|
916
|
+
}
|
|
917
|
+
function rollback(id, version) {
|
|
918
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rollback`, { body: { version } });
|
|
919
|
+
}
|
|
920
|
+
function active(agentId) {
|
|
921
|
+
return request("GET", "/policies/active", { query: agentId ? { agent_id: agentId } : void 0 });
|
|
922
|
+
}
|
|
923
|
+
function setActive(policyId) {
|
|
924
|
+
return request("POST", "/policies/active", { body: { policyId: policyId ?? "" } });
|
|
925
|
+
}
|
|
926
|
+
function dryRun(body) {
|
|
927
|
+
return request("POST", "/policies/dry-run", { body });
|
|
928
|
+
}
|
|
929
|
+
function backtest(body) {
|
|
930
|
+
return request("POST", "/policies/backtest", { body });
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/api-client/settings.ts
|
|
934
|
+
var settings_exports = {};
|
|
935
|
+
__export(settings_exports, {
|
|
936
|
+
clearRateLimitHistory: () => clearRateLimitHistory,
|
|
937
|
+
createAlert: () => createAlert,
|
|
938
|
+
createWebhook: () => createWebhook,
|
|
939
|
+
deleteAlert: () => deleteAlert,
|
|
940
|
+
deleteWebhook: () => deleteWebhook,
|
|
941
|
+
getAlerts: () => getAlerts,
|
|
942
|
+
getGuardStatus: () => getGuardStatus,
|
|
943
|
+
getLocalLogs: () => getLocalLogs,
|
|
944
|
+
getRateLimitHistory: () => getRateLimitHistory,
|
|
945
|
+
getSecurityLayers: () => getSecurityLayers,
|
|
946
|
+
getSelfProtection: () => getSelfProtection,
|
|
947
|
+
getWebhooks: () => getWebhooks,
|
|
948
|
+
sendTestWebhook: () => sendTestWebhook,
|
|
949
|
+
setAlertEnabled: () => setAlertEnabled,
|
|
950
|
+
setLocalLogs: () => setLocalLogs,
|
|
951
|
+
setSecurityLayers: () => setSecurityLayers,
|
|
952
|
+
setSelfProtection: () => setSelfProtection,
|
|
953
|
+
updateAlert: () => updateAlert,
|
|
954
|
+
updateWebhook: () => updateWebhook
|
|
955
|
+
});
|
|
956
|
+
function getSecurityLayers() {
|
|
957
|
+
return request("GET", "/settings/security-layers");
|
|
958
|
+
}
|
|
959
|
+
function setSecurityLayers(layers) {
|
|
960
|
+
return request("PUT", "/settings/security-layers", { body: { layers } });
|
|
961
|
+
}
|
|
962
|
+
function getRateLimitHistory() {
|
|
963
|
+
return request("GET", "/settings/rate-limit-history");
|
|
964
|
+
}
|
|
965
|
+
function clearRateLimitHistory() {
|
|
966
|
+
return request("DELETE", "/settings/rate-limit-history", { query: { all: 1 } });
|
|
967
|
+
}
|
|
968
|
+
function getGuardStatus() {
|
|
969
|
+
return request("GET", "/settings/guard-status");
|
|
970
|
+
}
|
|
971
|
+
function getSelfProtection() {
|
|
972
|
+
return request("GET", "/settings/self-protection");
|
|
973
|
+
}
|
|
974
|
+
function setSelfProtection(enabled) {
|
|
975
|
+
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
976
|
+
}
|
|
977
|
+
function getLocalLogs() {
|
|
978
|
+
return request("GET", "/settings/local-logs");
|
|
979
|
+
}
|
|
980
|
+
function setLocalLogs(cfg) {
|
|
981
|
+
return request("PUT", "/settings/local-logs", { body: cfg });
|
|
982
|
+
}
|
|
983
|
+
function getAlerts() {
|
|
984
|
+
return request("GET", "/settings/denial-alerts");
|
|
985
|
+
}
|
|
986
|
+
function createAlert(body) {
|
|
987
|
+
return request("POST", "/settings/denial-alerts", { body });
|
|
988
|
+
}
|
|
989
|
+
function deleteAlert(id) {
|
|
990
|
+
return request("DELETE", "/settings/denial-alerts", { query: { id } });
|
|
991
|
+
}
|
|
992
|
+
function setAlertEnabled(id, enabled) {
|
|
993
|
+
return request("PATCH", "/settings/denial-alerts", { query: { id }, body: { enabled } });
|
|
994
|
+
}
|
|
995
|
+
function updateAlert(id, patch) {
|
|
996
|
+
return request("PATCH", "/settings/denial-alerts", { query: { id }, body: patch });
|
|
997
|
+
}
|
|
998
|
+
function getWebhooks() {
|
|
999
|
+
return request("GET", "/settings/denial-webhook");
|
|
1000
|
+
}
|
|
1001
|
+
function createWebhook(body) {
|
|
1002
|
+
return request("POST", "/settings/denial-webhook", { body });
|
|
1003
|
+
}
|
|
1004
|
+
function deleteWebhook(id) {
|
|
1005
|
+
return request("DELETE", "/settings/denial-webhook", { query: { id } });
|
|
1006
|
+
}
|
|
1007
|
+
function sendTestWebhook(id) {
|
|
1008
|
+
return request("POST", "/settings/denial-webhook/send-test", { body: { id } });
|
|
1009
|
+
}
|
|
1010
|
+
function updateWebhook(id, patch) {
|
|
1011
|
+
return request("PATCH", "/settings/denial-webhook", { body: { id, ...patch } });
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/api-client/stats.ts
|
|
1015
|
+
var stats_exports = {};
|
|
1016
|
+
__export(stats_exports, {
|
|
1017
|
+
drift: () => drift,
|
|
1018
|
+
get: () => get2,
|
|
1019
|
+
securityInsights: () => securityInsights,
|
|
1020
|
+
timeseries: () => timeseries
|
|
1021
|
+
});
|
|
712
1022
|
function get2() {
|
|
713
1023
|
return request("GET", "/stats");
|
|
714
1024
|
}
|
|
@@ -3428,306 +3738,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
3428
3738
|
import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
|
|
3429
3739
|
import TextInput6 from "ink-text-input";
|
|
3430
3740
|
import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
|
|
3431
|
-
|
|
3432
|
-
// src/global-install.ts
|
|
3433
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync2, mkdirSync as mkdirSync4, rmSync } from "fs";
|
|
3434
|
-
import { resolve as resolve2, join as join6, dirname } from "path";
|
|
3435
|
-
import { homedir as homedir6 } from "os";
|
|
3436
|
-
import { fileURLToPath } from "url";
|
|
3437
|
-
import { createInterface } from "readline";
|
|
3438
|
-
import { execFileSync } from "child_process";
|
|
3439
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3440
|
-
var HOOKS_DIR = resolve2(__dirname, "..", "hooks");
|
|
3441
|
-
function lockFile(file) {
|
|
3442
|
-
if (!existsSync2(file)) return;
|
|
3443
|
-
try {
|
|
3444
|
-
if (process.platform === "win32") {
|
|
3445
|
-
try {
|
|
3446
|
-
execFileSync("icacls", [file, "/deny", "*S-1-1-0:(WD,AD,DC,DE)"], { stdio: "ignore" });
|
|
3447
|
-
} catch {
|
|
3448
|
-
}
|
|
3449
|
-
try {
|
|
3450
|
-
execFileSync("attrib", ["+R", file], { stdio: "ignore" });
|
|
3451
|
-
} catch {
|
|
3452
|
-
}
|
|
3453
|
-
} else if (process.platform === "darwin") {
|
|
3454
|
-
try {
|
|
3455
|
-
execFileSync("chflags", ["uchg", file], { stdio: "ignore" });
|
|
3456
|
-
} catch {
|
|
3457
|
-
}
|
|
3458
|
-
} else {
|
|
3459
|
-
try {
|
|
3460
|
-
execFileSync("chattr", ["+i", file], { stdio: "ignore" });
|
|
3461
|
-
} catch {
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
} catch {
|
|
3465
|
-
}
|
|
3466
|
-
}
|
|
3467
|
-
function unlockFile(file) {
|
|
3468
|
-
if (!existsSync2(file)) return;
|
|
3469
|
-
try {
|
|
3470
|
-
if (process.platform === "win32") {
|
|
3471
|
-
try {
|
|
3472
|
-
execFileSync("icacls", [file, "/remove:d", "*S-1-1-0"], { stdio: "ignore" });
|
|
3473
|
-
} catch {
|
|
3474
|
-
}
|
|
3475
|
-
try {
|
|
3476
|
-
execFileSync("icacls", [file, "/reset"], { stdio: "ignore" });
|
|
3477
|
-
} catch {
|
|
3478
|
-
}
|
|
3479
|
-
try {
|
|
3480
|
-
execFileSync("attrib", ["-R", file], { stdio: "ignore" });
|
|
3481
|
-
} catch {
|
|
3482
|
-
}
|
|
3483
|
-
} else if (process.platform === "darwin") {
|
|
3484
|
-
try {
|
|
3485
|
-
execFileSync("chflags", ["nouchg", file], { stdio: "ignore" });
|
|
3486
|
-
} catch {
|
|
3487
|
-
}
|
|
3488
|
-
} else {
|
|
3489
|
-
try {
|
|
3490
|
-
execFileSync("chattr", ["-i", file], { stdio: "ignore" });
|
|
3491
|
-
} catch {
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
} catch {
|
|
3495
|
-
}
|
|
3496
|
-
}
|
|
3497
|
-
function protectedTargets() {
|
|
3498
|
-
const p = globalPaths();
|
|
3499
|
-
return [
|
|
3500
|
-
join6(p.hooksDir, "guard.mjs"),
|
|
3501
|
-
join6(p.hooksDir, "audit.mjs"),
|
|
3502
|
-
join6(p.hooksDir, "stop.mjs"),
|
|
3503
|
-
join6(p.hooksDir, "shield.mjs"),
|
|
3504
|
-
p.configPath,
|
|
3505
|
-
p.settingsPath,
|
|
3506
|
-
p.antigravityHooksPath
|
|
3507
|
-
];
|
|
3508
|
-
}
|
|
3509
|
-
function lockProtected() {
|
|
3510
|
-
for (const f of protectedTargets()) lockFile(f);
|
|
3511
|
-
}
|
|
3512
|
-
function unlockProtected() {
|
|
3513
|
-
for (const f of protectedTargets()) unlockFile(f);
|
|
3514
|
-
}
|
|
3515
|
-
function globalPaths() {
|
|
3516
|
-
const home = homedir6();
|
|
3517
|
-
const sgDir = join6(home, ".solongate");
|
|
3518
|
-
const hooksDir = join6(sgDir, "hooks");
|
|
3519
|
-
const claudeDir = join6(home, ".claude");
|
|
3520
|
-
const antigravityDir = join6(home, ".gemini", "config");
|
|
3521
|
-
return {
|
|
3522
|
-
home,
|
|
3523
|
-
sgDir,
|
|
3524
|
-
hooksDir,
|
|
3525
|
-
claudeDir,
|
|
3526
|
-
antigravityDir,
|
|
3527
|
-
settingsPath: join6(claudeDir, "settings.json"),
|
|
3528
|
-
backupPath: join6(claudeDir, "settings.solongate.bak"),
|
|
3529
|
-
configPath: join6(sgDir, "cloud-guard.json"),
|
|
3530
|
-
// Antigravity reads global hooks from ~/.gemini/config/hooks.json. Only the
|
|
3531
|
-
// guard is registered there (PreToolUse); Antigravity's ALLOW-path audit is
|
|
3532
|
-
// covered by the passive session-log collector, not a hook.
|
|
3533
|
-
antigravityHooksPath: join6(antigravityDir, "hooks.json"),
|
|
3534
|
-
antigravityBackupPath: join6(antigravityDir, "hooks.solongate.bak")
|
|
3535
|
-
};
|
|
3536
|
-
}
|
|
3537
|
-
function readHook(filename) {
|
|
3538
|
-
return readFileSync4(join6(HOOKS_DIR, filename), "utf-8");
|
|
3539
|
-
}
|
|
3540
|
-
function firstAccountCredential() {
|
|
3541
|
-
try {
|
|
3542
|
-
const raw = JSON.parse(readFileSync4(join6(homedir6(), ".solongate", "accounts.json"), "utf-8"));
|
|
3543
|
-
if (Array.isArray(raw)) {
|
|
3544
|
-
const acc = raw.find((a) => a && typeof a.apiKey === "string" && a.apiKey);
|
|
3545
|
-
if (acc) return { apiKey: acc.apiKey, apiUrl: typeof acc.apiUrl === "string" ? acc.apiUrl : void 0 };
|
|
3546
|
-
}
|
|
3547
|
-
} catch {
|
|
3548
|
-
}
|
|
3549
|
-
return {};
|
|
3550
|
-
}
|
|
3551
|
-
function readGuard() {
|
|
3552
|
-
const bundled = join6(HOOKS_DIR, "guard.bundled.mjs");
|
|
3553
|
-
return existsSync2(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
|
|
3554
|
-
}
|
|
3555
|
-
var ANTIGRAVITY_GROUP = "solongate-guard";
|
|
3556
|
-
function antigravityHookCommand(guardAbs) {
|
|
3557
|
-
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
3558
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
3559
|
-
return `${call}"${nodeBin}" "${guardAbs.replace(/\\/g, "/")}" antigravity "Antigravity"`;
|
|
3560
|
-
}
|
|
3561
|
-
function installAntigravityGuard(p, guardAbs) {
|
|
3562
|
-
mkdirSync4(p.antigravityDir, { recursive: true });
|
|
3563
|
-
let existing = {};
|
|
3564
|
-
if (existsSync2(p.antigravityHooksPath)) {
|
|
3565
|
-
const raw = readFileSync4(p.antigravityHooksPath, "utf-8");
|
|
3566
|
-
if (!existsSync2(p.antigravityBackupPath)) writeFileSync5(p.antigravityBackupPath, raw);
|
|
3567
|
-
try {
|
|
3568
|
-
existing = JSON.parse(raw);
|
|
3569
|
-
} catch {
|
|
3570
|
-
existing = {};
|
|
3571
|
-
}
|
|
3572
|
-
}
|
|
3573
|
-
const merged = {
|
|
3574
|
-
...existing,
|
|
3575
|
-
[ANTIGRAVITY_GROUP]: {
|
|
3576
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: antigravityHookCommand(guardAbs) }] }]
|
|
3577
|
-
}
|
|
3578
|
-
};
|
|
3579
|
-
writeFileSync5(p.antigravityHooksPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3580
|
-
}
|
|
3581
|
-
function removeAntigravityGuard(p) {
|
|
3582
|
-
if (!existsSync2(p.antigravityHooksPath)) return;
|
|
3583
|
-
try {
|
|
3584
|
-
const s = JSON.parse(readFileSync4(p.antigravityHooksPath, "utf-8"));
|
|
3585
|
-
if (!(ANTIGRAVITY_GROUP in s)) return;
|
|
3586
|
-
delete s[ANTIGRAVITY_GROUP];
|
|
3587
|
-
writeFileSync5(p.antigravityHooksPath, JSON.stringify(s, null, 2) + "\n");
|
|
3588
|
-
} catch {
|
|
3589
|
-
}
|
|
3590
|
-
}
|
|
3591
|
-
function installGlobalQuiet() {
|
|
3592
|
-
try {
|
|
3593
|
-
const p = globalPaths();
|
|
3594
|
-
let apiKey = process.env["SOLONGATE_API_KEY"] || "";
|
|
3595
|
-
let apiUrl = process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
|
|
3596
|
-
try {
|
|
3597
|
-
const cfg = JSON.parse(readFileSync4(p.configPath, "utf-8"));
|
|
3598
|
-
if (cfg && typeof cfg.apiKey === "string") apiKey = apiKey || cfg.apiKey;
|
|
3599
|
-
if (cfg && typeof cfg.apiUrl === "string") apiUrl = cfg.apiUrl;
|
|
3600
|
-
} catch {
|
|
3601
|
-
}
|
|
3602
|
-
if (!apiKey) {
|
|
3603
|
-
const acc = firstAccountCredential();
|
|
3604
|
-
if (acc.apiKey) {
|
|
3605
|
-
apiKey = acc.apiKey;
|
|
3606
|
-
if (acc.apiUrl) apiUrl = acc.apiUrl;
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
if (!apiKey) return { ok: false, message: "no login on this device \u2014 add an account first (Accounts \u2192 + add)" };
|
|
3610
|
-
mkdirSync4(p.hooksDir, { recursive: true });
|
|
3611
|
-
mkdirSync4(p.claudeDir, { recursive: true });
|
|
3612
|
-
unlockProtected();
|
|
3613
|
-
writeFileSync5(join6(p.hooksDir, "guard.mjs"), readGuard());
|
|
3614
|
-
writeFileSync5(join6(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
|
|
3615
|
-
writeFileSync5(join6(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
|
|
3616
|
-
writeFileSync5(join6(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
|
|
3617
|
-
writeFileSync5(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
|
|
3618
|
-
let existing = {};
|
|
3619
|
-
if (existsSync2(p.settingsPath)) {
|
|
3620
|
-
const raw = readFileSync4(p.settingsPath, "utf-8");
|
|
3621
|
-
if (!existsSync2(p.backupPath)) writeFileSync5(p.backupPath, raw);
|
|
3622
|
-
try {
|
|
3623
|
-
existing = JSON.parse(raw);
|
|
3624
|
-
} catch {
|
|
3625
|
-
existing = {};
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
const nodeBin = process.execPath.replace(/\\/g, "/");
|
|
3629
|
-
const call = process.platform === "win32" ? "& " : "";
|
|
3630
|
-
const hookCmd = (script) => `${call}"${nodeBin}" "${join6(p.hooksDir, script).replace(/\\/g, "/")}" claude-code "Claude Code"`;
|
|
3631
|
-
const merged = {
|
|
3632
|
-
...existing,
|
|
3633
|
-
hooks: {
|
|
3634
|
-
PreToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("guard.mjs") }] }],
|
|
3635
|
-
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("audit.mjs") }] }],
|
|
3636
|
-
Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd("stop.mjs") }] }]
|
|
3637
|
-
}
|
|
3638
|
-
};
|
|
3639
|
-
writeFileSync5(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3640
|
-
try {
|
|
3641
|
-
installAntigravityGuard(p, join6(p.hooksDir, "guard.mjs").replace(/\\/g, "/"));
|
|
3642
|
-
} catch {
|
|
3643
|
-
}
|
|
3644
|
-
if (process.env["SOLONGATE_OS_LOCK"] === "1") lockProtected();
|
|
3645
|
-
return { ok: true, message: "guard installed (open a new session)" };
|
|
3646
|
-
} catch (e) {
|
|
3647
|
-
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
3648
|
-
}
|
|
3649
|
-
}
|
|
3650
|
-
function installedGuardVersion() {
|
|
3651
|
-
try {
|
|
3652
|
-
const p = globalPaths();
|
|
3653
|
-
const s = readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8");
|
|
3654
|
-
const m = s.match(/HOOK_VERSION\s*=\s*(\d+)/);
|
|
3655
|
-
return m ? parseInt(m[1], 10) : null;
|
|
3656
|
-
} catch {
|
|
3657
|
-
return null;
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
function guardHookOutdated() {
|
|
3661
|
-
try {
|
|
3662
|
-
const p = globalPaths();
|
|
3663
|
-
return readFileSync4(join6(p.hooksDir, "guard.mjs"), "utf-8") !== readGuard();
|
|
3664
|
-
} catch {
|
|
3665
|
-
return false;
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
function isGuardInstalled() {
|
|
3669
|
-
try {
|
|
3670
|
-
const p = globalPaths();
|
|
3671
|
-
if (!existsSync2(p.settingsPath)) return false;
|
|
3672
|
-
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
3673
|
-
return !!s.hooks && JSON.stringify(s.hooks).includes(".solongate");
|
|
3674
|
-
} catch {
|
|
3675
|
-
return false;
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
function uninstallGlobalQuiet() {
|
|
3679
|
-
try {
|
|
3680
|
-
const p = globalPaths();
|
|
3681
|
-
unlockProtected();
|
|
3682
|
-
removeClaudeShim();
|
|
3683
|
-
try {
|
|
3684
|
-
removeAntigravityGuard(p);
|
|
3685
|
-
} catch {
|
|
3686
|
-
}
|
|
3687
|
-
if (!existsSync2(p.settingsPath)) return { ok: true, message: "guard removed (open a new session)" };
|
|
3688
|
-
const s = JSON.parse(readFileSync4(p.settingsPath, "utf-8"));
|
|
3689
|
-
delete s.hooks;
|
|
3690
|
-
writeFileSync5(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
|
|
3691
|
-
return { ok: true, message: "guard removed (open a new session)" };
|
|
3692
|
-
} catch (e) {
|
|
3693
|
-
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
var SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
3697
|
-
var SHIM_END = "# <<< SolonGate shield <<<";
|
|
3698
|
-
function escapeRe(s) {
|
|
3699
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3700
|
-
}
|
|
3701
|
-
function shimTargets() {
|
|
3702
|
-
if (process.platform === "win32") {
|
|
3703
|
-
try {
|
|
3704
|
-
const prof = execFileSync("powershell", ["-NoProfile", "-Command", "$PROFILE.CurrentUserAllHosts"], { encoding: "utf-8" }).trim();
|
|
3705
|
-
return prof ? [prof] : [];
|
|
3706
|
-
} catch {
|
|
3707
|
-
return [];
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
|
-
return [".bashrc", ".zshrc", ".profile"].map((f) => join6(homedir6(), f)).filter((f) => existsSync2(f));
|
|
3711
|
-
}
|
|
3712
|
-
function writeShimBlock(file, block2) {
|
|
3713
|
-
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
3714
|
-
let content = existsSync2(file) ? readFileSync4(file, "utf-8") : "";
|
|
3715
|
-
content = content.replace(re, "");
|
|
3716
|
-
if (block2) {
|
|
3717
|
-
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
3718
|
-
content += block2 + "\n";
|
|
3719
|
-
}
|
|
3720
|
-
mkdirSync4(dirname(file), { recursive: true });
|
|
3721
|
-
writeFileSync5(file, content);
|
|
3722
|
-
}
|
|
3723
|
-
function removeClaudeShim() {
|
|
3724
|
-
for (const file of shimTargets()) {
|
|
3725
|
-
try {
|
|
3726
|
-
writeShimBlock(file, null);
|
|
3727
|
-
} catch {
|
|
3728
|
-
}
|
|
3729
|
-
}
|
|
3730
|
-
}
|
|
3741
|
+
init_global_install();
|
|
3731
3742
|
|
|
3732
3743
|
// src/logs-server-daemon.ts
|
|
3733
3744
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -3781,7 +3792,10 @@ function startLogsServerDaemon() {
|
|
|
3781
3792
|
const p = spawn3(process.execPath, [cli, "logs-server"], {
|
|
3782
3793
|
detached: true,
|
|
3783
3794
|
stdio: ["ignore", log, log],
|
|
3784
|
-
windowsHide: true
|
|
3795
|
+
windowsHide: true,
|
|
3796
|
+
// Spawned by US, not by an agent: exempt from the human-only TTY gate
|
|
3797
|
+
// (a detached daemon has no terminal by definition).
|
|
3798
|
+
env: { ...process.env, SOLONGATE_INTERNAL: "1" }
|
|
3785
3799
|
});
|
|
3786
3800
|
p.on("error", () => {
|
|
3787
3801
|
});
|