@solongate/proxy 0.82.42 → 0.82.43

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/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/tui/index.tsx
8
- import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
9
- import { homedir as homedir9 } from "os";
10
- import { join as join9 } from "path";
11
- import { render } from "ink";
12
-
13
- // src/tui/App.tsx
14
- import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
15
- import { useEffect as useEffect8, useState as useState9 } from "react";
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
- const raw = readFileSync(join(homedir(), ".solongate", "tui-config.json"), "utf-8");
60
- const j = JSON.parse(raw);
61
- cached = {
62
- notifications: j.notifications !== false,
63
- accent: typeof j.accent === "string" ? j.accent : void 0,
64
- pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
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
- // src/tui/theme.ts
73
- var accent = loadConfig().accent;
74
- var theme = {
75
- accent: accent || "white",
76
- accentBright: accent || "white",
77
- ok: "green",
78
- warn: "yellow",
79
- bad: "red",
80
- dim: "gray"
81
- };
82
- function decisionColor(decision) {
83
- const d = (decision || "").toUpperCase();
84
- if (d === "ALLOW") return theme.ok;
85
- if (d === "DENY" || d === "DENIED") return theme.bad;
86
- return theme.dim;
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 modeColor(m) {
89
- if (m === "block" || m === "active" || m === "on") return theme.ok;
90
- if (m === "detect" || m === "idle") return theme.warn;
91
- return theme.dim;
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 prettyJson(s) {
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
- return JSON.stringify(JSON.parse(s), null, 2);
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 wrapLines(s, width) {
101
- const w = Math.max(8, width);
102
- const out = [];
103
- for (const raw of (s ?? "").split("\n")) {
104
- if (raw.length <= w) {
105
- out.push(raw);
106
- continue;
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
- return out;
111
- }
112
- function truncate(s, n) {
113
- if (!s) return "";
114
- return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
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 ago(ts) {
117
- const t = typeof ts === "number" ? ts : Date.parse(ts);
118
- if (Number.isNaN(t)) return String(ts ?? "");
119
- const s = Math.max(0, (Date.now() - t) / 1e3);
120
- if (s < 60) return `${Math.floor(s)}s`;
121
- if (s < 3600) return `${Math.floor(s / 60)}m`;
122
- if (s < 86400) return `${Math.floor(s / 3600)}h`;
123
- return `${Math.floor(s / 86400)}d`;
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 lines = readFileSync2(LOCAL_LOG, "utf-8").split("\n");
237
- let removed = 0;
238
- const kept = lines.filter((line) => {
239
- if (!line.trim()) return false;
240
- if (removed) return true;
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
- const j = JSON.parse(line);
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
- return true;
251
- });
252
- if (removed) writeFileSync(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
253
- return removed;
254
- } catch {
255
- return 0;
256
- }
257
- }
258
- function clearLocalLog() {
259
- try {
260
- const n = readFileSync2(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
261
- writeFileSync(LOCAL_LOG, "");
262
- return n;
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
- const j = JSON.parse(line);
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 raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
304
- if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
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 saveAccount(acc) {
236
+ function guardHookOutdated() {
314
237
  try {
315
- const list5 = (() => {
316
- try {
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 removeAccount(apiKey) {
244
+ function isGuardInstalled() {
331
245
  try {
332
- const list5 = (() => {
333
- try {
334
- const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
335
- return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
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
- var viewOverride = null;
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 dir = join3(homedir3(), ".solongate");
355
- mkdirSync(dir, { recursive: true });
356
- const p = join3(dir, ["cloud", "guard.json"].join("-"));
357
- let existing = {};
256
+ const p = globalPaths();
257
+ unlockProtected();
258
+ removeClaudeShim();
358
259
  try {
359
- existing = JSON.parse(readFileSync3(p, "utf-8"));
260
+ removeAntigravityGuard(p);
360
261
  } catch {
361
262
  }
362
- writeFileSync2(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2));
363
- cached2 = null;
364
- return true;
365
- } catch {
366
- return false;
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 clearActiveCredential() {
370
- try {
371
- const p = join3(homedir3(), ".solongate", ["cloud", "guard.json"].join("-"));
372
- if (!existsSync(p)) {
373
- cached2 = null;
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
- existing = JSON.parse(readFileSync3(p, "utf-8"));
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
- var ApiError = class extends Error {
392
- status;
393
- code;
394
- constructor(status, code, message) {
395
- super(message);
396
- this.name = "ApiError";
397
- this.status = status;
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
- var NotAuthenticatedError = class extends Error {
402
- constructor() {
403
- super("Not logged in. Run `solongate` and log in from the Accounts panel.");
404
- this.name = "NotAuthenticatedError";
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
- function loginCredentialFile() {
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 p = join3(homedir3(), ".solongate", "cloud-guard.json");
410
- if (!existsSync(p)) return {};
411
- const c2 = JSON.parse(readFileSync3(p, "utf-8"));
412
- return c2 && typeof c2 === "object" ? c2 : {};
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
- return {};
377
+ cached = defaults;
415
378
  }
379
+ return cached;
416
380
  }
417
- function dotenvApiKey() {
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
- const envPath = resolve(".env");
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
- var cached2 = null;
435
- function resolveCredentials(apiUrlOverride) {
436
- if (viewOverride && !apiUrlOverride) return viewOverride;
437
- if (cached2 && !apiUrlOverride) return cached2;
438
- const file = loginCredentialFile();
439
- const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
440
- if (!apiKey) throw new NotAuthenticatedError();
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 url.toString();
420
+ return out;
455
421
  }
456
- async function request(method, path, opts = {}) {
457
- const creds = resolveCredentials(opts.apiUrl);
458
- const url = buildUrl(creds.apiUrl, path, opts.query);
459
- const headers = {
460
- Authorization: `Bearer ${creds.apiKey}`
461
- };
462
- let bodyInit;
463
- if (opts.body !== void 0) {
464
- headers["Content-Type"] = "application/json";
465
- bodyInit = JSON.stringify(opts.body);
466
- }
467
- const attempt = () => fetch(url, {
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/api-client/device-login.ts
514
- import { spawn } from "child_process";
515
- function openBrowser(url) {
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 cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
518
- const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
519
- const child = spawn(cmd, args, { stdio: "ignore", detached: true });
520
- child.on("error", () => {
521
- });
522
- child.unref();
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
- async function startDeviceLogin(apiUrl = DEFAULT_API_URL) {
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 res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
540
- method: "POST",
541
- headers: { "Content-Type": "application/json" },
542
- body: JSON.stringify({ device_code: deviceCode })
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
- const j = await res.json().catch(() => ({}));
545
- if (j.status === "approved" && j.api_key) {
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 { status: "pending" };
565
+ return 0;
552
566
  }
553
567
  }
554
-
555
- // src/api-client/auth.ts
556
- var auth_exports = {};
557
- __export(auth_exports, {
558
- me: () => me
559
- });
560
- function me() {
561
- return request("GET", "/auth/me");
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
- function update(id, policy) {
593
- return request("PUT", `/policies/${encodeURIComponent(id)}`, { body: policy });
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 remove(id) {
596
- return request("DELETE", `/policies/${encodeURIComponent(id)}`);
597
- }
598
- function addRule(id, spec) {
599
- return request("POST", `/policies/${encodeURIComponent(id)}/rules`, { body: spec });
600
- }
601
- function revokeRule(id, ruleId) {
602
- return request("DELETE", `/policies/${encodeURIComponent(id)}/rules/${encodeURIComponent(ruleId)}`);
603
- }
604
- function versions(id, opts = {}) {
605
- return request("GET", `/policies/${encodeURIComponent(id)}/versions`, { query: opts });
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/api-client/settings.ts
624
- var settings_exports = {};
625
- __export(settings_exports, {
626
- clearRateLimitHistory: () => clearRateLimitHistory,
627
- createAlert: () => createAlert,
628
- createWebhook: () => createWebhook,
629
- deleteAlert: () => deleteAlert,
630
- deleteWebhook: () => deleteWebhook,
631
- getAlerts: () => getAlerts,
632
- getGuardStatus: () => getGuardStatus,
633
- getLocalLogs: () => getLocalLogs,
634
- getRateLimitHistory: () => getRateLimitHistory,
635
- getSecurityLayers: () => getSecurityLayers,
636
- getSelfProtection: () => getSelfProtection,
637
- getWebhooks: () => getWebhooks,
638
- sendTestWebhook: () => sendTestWebhook,
639
- setAlertEnabled: () => setAlertEnabled,
640
- setLocalLogs: () => setLocalLogs,
641
- setSecurityLayers: () => setSecurityLayers,
642
- setSelfProtection: () => setSelfProtection,
643
- updateAlert: () => updateAlert,
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 getAlerts() {
674
- return request("GET", "/settings/denial-alerts");
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 createAlert(body) {
677
- return request("POST", "/settings/denial-alerts", { body });
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
- function deleteAlert(id) {
680
- return request("DELETE", "/settings/denial-alerts", { query: { id } });
654
+ var viewOverride = null;
655
+ function setViewCredentials(creds) {
656
+ viewOverride = creds;
657
+ cached2 = null;
681
658
  }
682
- function setAlertEnabled(id, enabled) {
683
- return request("PATCH", "/settings/denial-alerts", { query: { id }, body: { enabled } });
659
+ function isActiveAccount(apiKey) {
660
+ return loginCredentialFile().apiKey === apiKey;
684
661
  }
685
- function updateAlert(id, patch) {
686
- return request("PATCH", "/settings/denial-alerts", { query: { id }, body: patch });
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 getWebhooks() {
689
- return request("GET", "/settings/denial-webhook");
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
- function createWebhook(body) {
692
- return request("POST", "/settings/denial-webhook", { body });
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 deleteWebhook(id) {
695
- return request("DELETE", "/settings/denial-webhook", { query: { id } });
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
- function sendTestWebhook(id) {
698
- return request("POST", "/settings/denial-webhook/send-test", { body: { id } });
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 updateWebhook(id, patch) {
701
- return request("PATCH", "/settings/denial-webhook", { body: { id, ...patch } });
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
- // src/api-client/stats.ts
705
- var stats_exports = {};
706
- __export(stats_exports, {
707
- drift: () => drift,
708
- get: () => get2,
709
- securityInsights: () => securityInsights,
710
- timeseries: () => timeseries
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";