@solongate/proxy 0.79.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client/settings.d.ts +6 -0
- package/dist/commands/index.js +9 -1
- package/dist/index.js +489 -136
- package/dist/tui/config.d.ts +6 -0
- package/dist/tui/index.js +385 -51
- package/dist/tui/panels/Agents.d.ts +4 -0
- package/dist/tui/theme.d.ts +2 -3
- package/package.json +1 -1
package/dist/tui/index.js
CHANGED
|
@@ -8,8 +8,8 @@ var __export = (target, all) => {
|
|
|
8
8
|
import { render } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/tui/App.tsx
|
|
11
|
-
import { Box as
|
|
12
|
-
import { useState as
|
|
11
|
+
import { Box as Box9, Text as Text9, useApp, useInput as useInput7 } from "ink";
|
|
12
|
+
import { useState as useState8 } from "react";
|
|
13
13
|
|
|
14
14
|
// src/cli-utils.ts
|
|
15
15
|
var c = {
|
|
@@ -44,10 +44,33 @@ var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
|
44
44
|
// src/tui/components.tsx
|
|
45
45
|
import { Box, Text } from "ink";
|
|
46
46
|
|
|
47
|
+
// src/tui/config.ts
|
|
48
|
+
import { readFileSync } from "fs";
|
|
49
|
+
import { homedir } from "os";
|
|
50
|
+
import { join } from "path";
|
|
51
|
+
var cached = null;
|
|
52
|
+
function loadConfig() {
|
|
53
|
+
if (cached) return cached;
|
|
54
|
+
const defaults = { notifications: true };
|
|
55
|
+
try {
|
|
56
|
+
const raw = readFileSync(join(homedir(), ".solongate", "tui-config.json"), "utf-8");
|
|
57
|
+
const j = JSON.parse(raw);
|
|
58
|
+
cached = {
|
|
59
|
+
notifications: j.notifications !== false,
|
|
60
|
+
accent: typeof j.accent === "string" ? j.accent : void 0,
|
|
61
|
+
pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
cached = defaults;
|
|
65
|
+
}
|
|
66
|
+
return cached;
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
// src/tui/theme.ts
|
|
70
|
+
var accent = loadConfig().accent;
|
|
48
71
|
var theme = {
|
|
49
|
-
accent: "cyan",
|
|
50
|
-
accentBright: "#5a8ce6",
|
|
72
|
+
accent: accent || "cyan",
|
|
73
|
+
accentBright: accent || "#5a8ce6",
|
|
51
74
|
ok: "green",
|
|
52
75
|
warn: "yellow",
|
|
53
76
|
bad: "red",
|
|
@@ -128,15 +151,15 @@ function KeyHints({ hints }) {
|
|
|
128
151
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
129
152
|
import TextInput from "ink-text-input";
|
|
130
153
|
import { spawn } from "child_process";
|
|
131
|
-
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
132
|
-
import { homedir as
|
|
133
|
-
import { join as
|
|
154
|
+
import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
|
|
155
|
+
import { homedir as homedir3 } from "os";
|
|
156
|
+
import { join as join3 } from "path";
|
|
134
157
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
135
158
|
|
|
136
159
|
// src/api-client/client.ts
|
|
137
|
-
import { readFileSync, existsSync } from "fs";
|
|
138
|
-
import { resolve, join } from "path";
|
|
139
|
-
import { homedir } from "os";
|
|
160
|
+
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
161
|
+
import { resolve, join as join2 } from "path";
|
|
162
|
+
import { homedir as homedir2 } from "os";
|
|
140
163
|
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
141
164
|
var ApiError = class extends Error {
|
|
142
165
|
status;
|
|
@@ -156,9 +179,9 @@ var NotAuthenticatedError = class extends Error {
|
|
|
156
179
|
};
|
|
157
180
|
function loginCredentialFile() {
|
|
158
181
|
try {
|
|
159
|
-
const p =
|
|
182
|
+
const p = join2(homedir2(), ".solongate", "cloud-guard.json");
|
|
160
183
|
if (!existsSync(p)) return {};
|
|
161
|
-
const c2 = JSON.parse(
|
|
184
|
+
const c2 = JSON.parse(readFileSync2(p, "utf-8"));
|
|
162
185
|
return c2 && typeof c2 === "object" ? c2 : {};
|
|
163
186
|
} catch {
|
|
164
187
|
return {};
|
|
@@ -168,7 +191,7 @@ function dotenvApiKey() {
|
|
|
168
191
|
try {
|
|
169
192
|
const envPath = resolve(".env");
|
|
170
193
|
if (!existsSync(envPath)) return void 0;
|
|
171
|
-
for (const line of
|
|
194
|
+
for (const line of readFileSync2(envPath, "utf-8").split("\n")) {
|
|
172
195
|
const trimmed = line.trim();
|
|
173
196
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
174
197
|
const eq = trimmed.indexOf("=");
|
|
@@ -181,7 +204,7 @@ function dotenvApiKey() {
|
|
|
181
204
|
}
|
|
182
205
|
return void 0;
|
|
183
206
|
}
|
|
184
|
-
var
|
|
207
|
+
var cached2 = null;
|
|
185
208
|
function isAuthenticated() {
|
|
186
209
|
try {
|
|
187
210
|
resolveCredentials();
|
|
@@ -191,13 +214,13 @@ function isAuthenticated() {
|
|
|
191
214
|
}
|
|
192
215
|
}
|
|
193
216
|
function resolveCredentials(apiUrlOverride) {
|
|
194
|
-
if (
|
|
217
|
+
if (cached2 && !apiUrlOverride) return cached2;
|
|
195
218
|
const file = loginCredentialFile();
|
|
196
219
|
const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
|
|
197
220
|
if (!apiKey) throw new NotAuthenticatedError();
|
|
198
221
|
const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL;
|
|
199
222
|
const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
|
|
200
|
-
if (!apiUrlOverride)
|
|
223
|
+
if (!apiUrlOverride) cached2 = creds;
|
|
201
224
|
return creds;
|
|
202
225
|
}
|
|
203
226
|
function buildUrl(base, path, query) {
|
|
@@ -333,8 +356,10 @@ __export(settings_exports, {
|
|
|
333
356
|
getGuardStatus: () => getGuardStatus,
|
|
334
357
|
getRateLimitHistory: () => getRateLimitHistory,
|
|
335
358
|
getSecurityLayers: () => getSecurityLayers,
|
|
359
|
+
getSelfProtection: () => getSelfProtection,
|
|
336
360
|
getWebhooks: () => getWebhooks,
|
|
337
|
-
setSecurityLayers: () => setSecurityLayers
|
|
361
|
+
setSecurityLayers: () => setSecurityLayers,
|
|
362
|
+
setSelfProtection: () => setSelfProtection
|
|
338
363
|
});
|
|
339
364
|
function getSecurityLayers() {
|
|
340
365
|
return request("GET", "/settings/security-layers");
|
|
@@ -351,6 +376,12 @@ function clearRateLimitHistory() {
|
|
|
351
376
|
function getGuardStatus() {
|
|
352
377
|
return request("GET", "/settings/guard-status");
|
|
353
378
|
}
|
|
379
|
+
function getSelfProtection() {
|
|
380
|
+
return request("GET", "/settings/self-protection");
|
|
381
|
+
}
|
|
382
|
+
function setSelfProtection(enabled) {
|
|
383
|
+
return request("PUT", "/settings/self-protection", { body: { enabled } });
|
|
384
|
+
}
|
|
354
385
|
function getAlerts() {
|
|
355
386
|
return request("GET", "/settings/denial-alerts");
|
|
356
387
|
}
|
|
@@ -500,11 +531,12 @@ function usePoll(reload, intervalMs, enabled = true) {
|
|
|
500
531
|
|
|
501
532
|
// src/tui/panels/Live.tsx
|
|
502
533
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
534
|
+
var CONFIG = loadConfig();
|
|
503
535
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
504
536
|
var BG = "#12234f";
|
|
505
537
|
var DIM_FLOOR = "#233457";
|
|
506
|
-
var LOCAL_LOG =
|
|
507
|
-
var RING =
|
|
538
|
+
var LOCAL_LOG = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
539
|
+
var RING = join3(process.cwd(), ".solongate", ".eval-ring.jsonl");
|
|
508
540
|
var hhmmss = (ts) => {
|
|
509
541
|
const d = new Date(ts);
|
|
510
542
|
return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
|
|
@@ -828,6 +860,7 @@ function LivePanel({ active: active2 }) {
|
|
|
828
860
|
(id, title, msg, level = "warn") => {
|
|
829
861
|
pushLog(`${level === "bad" ? "\u26D4" : "\u23F8"} ${msg}`, level);
|
|
830
862
|
setAlerts((a) => [...a.filter((x) => x.id !== id), { id, msg, level, until: Date.now() + 15e3 }].slice(-4));
|
|
863
|
+
if (!CONFIG.notifications) return;
|
|
831
864
|
try {
|
|
832
865
|
process.stdout.write("\x07");
|
|
833
866
|
} catch {
|
|
@@ -1055,7 +1088,16 @@ function LivePanel({ active: active2 }) {
|
|
|
1055
1088
|
else if (input === "d") toggleSignal("deny");
|
|
1056
1089
|
else if (input === "x") toggleSignal("dlp");
|
|
1057
1090
|
else if (input === "r") toggleSignal("ratelimit");
|
|
1058
|
-
else if (
|
|
1091
|
+
else if (input === "e") {
|
|
1092
|
+
const file = join3(homedir3(), ".solongate", "live-export.jsonl");
|
|
1093
|
+
try {
|
|
1094
|
+
mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
|
|
1095
|
+
writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
1096
|
+
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
1097
|
+
} catch (err) {
|
|
1098
|
+
setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
|
|
1099
|
+
}
|
|
1100
|
+
} else if (key.downArrow) setSel((n) => Math.min(visibleDesc.length - 1, n + 1));
|
|
1059
1101
|
else if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
1060
1102
|
else if (key.pageDown) setSel((n) => Math.min(visibleDesc.length - 1, n + streamBodyRows));
|
|
1061
1103
|
else if (key.pageUp) setSel((n) => Math.max(0, n - streamBodyRows));
|
|
@@ -1309,7 +1351,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1309
1351
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
1310
1352
|
import TextInput2 from "ink-text-input";
|
|
1311
1353
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
1312
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1354
|
+
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1313
1355
|
var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
|
|
1314
1356
|
var setConstr = (r, g, side, val) => {
|
|
1315
1357
|
const arr = val.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -1347,6 +1389,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1347
1389
|
const [pi, setPi] = useState3(0);
|
|
1348
1390
|
const [ri, setRi] = useState3(0);
|
|
1349
1391
|
const [fi, setFi] = useState3(0);
|
|
1392
|
+
const [vi, setVi] = useState3(0);
|
|
1350
1393
|
const [rules, setRules] = useState3([]);
|
|
1351
1394
|
const [mode, setMode] = useState3("denylist");
|
|
1352
1395
|
const [dirty, setDirty] = useState3(false);
|
|
@@ -1355,6 +1398,15 @@ function PoliciesPanel({ focused }) {
|
|
|
1355
1398
|
const [status, setStatus] = useState3(null);
|
|
1356
1399
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
1357
1400
|
const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
|
|
1401
|
+
const versionsQ = useLoader(
|
|
1402
|
+
() => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
|
|
1403
|
+
[view, selected?.id]
|
|
1404
|
+
);
|
|
1405
|
+
const diffVer = versionsQ.data?.versions[vi]?.version;
|
|
1406
|
+
const diffQ = useLoader(
|
|
1407
|
+
() => view === "versions" && selected && diffVer !== void 0 ? api.policies.get(selected.id, diffVer) : Promise.resolve(null),
|
|
1408
|
+
[view, selected?.id, diffVer]
|
|
1409
|
+
);
|
|
1358
1410
|
useEffect3(() => {
|
|
1359
1411
|
if (detail.data) {
|
|
1360
1412
|
setRules(detail.data.rules);
|
|
@@ -1429,10 +1481,41 @@ function PoliciesPanel({ focused }) {
|
|
|
1429
1481
|
}
|
|
1430
1482
|
} else if (input === "m") {
|
|
1431
1483
|
mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
|
|
1484
|
+
} else if (input === "n") {
|
|
1485
|
+
const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true };
|
|
1486
|
+
mutate([nr, ...rules]);
|
|
1487
|
+
setRi(0);
|
|
1488
|
+
setFi(0);
|
|
1489
|
+
setView("rule");
|
|
1490
|
+
} else if (input === "D") {
|
|
1491
|
+
setStatus("Dry-running\u2026");
|
|
1492
|
+
void api.policies.dryRun({ rules, mode }).then((res) => setStatus(`dry-run ${res.evaluated} calls \xB7 allow ${res.would_allow} / deny ${res.would_deny} \xB7 newly blocked ${res.newly_blocked} \xB7 newly allowed ${res.newly_allowed}`)).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1493
|
+
} else if (input === "v") {
|
|
1494
|
+
setVi(0);
|
|
1495
|
+
setView("versions");
|
|
1432
1496
|
} else if (input === "s") void save();
|
|
1433
1497
|
else if (input === "x") discard();
|
|
1434
1498
|
return;
|
|
1435
1499
|
}
|
|
1500
|
+
if (view === "versions") {
|
|
1501
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
1502
|
+
if (key.leftArrow) return void setView("rules");
|
|
1503
|
+
if (key.upArrow) setVi((n) => Math.max(0, n - 1));
|
|
1504
|
+
else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
|
|
1505
|
+
else if (key.return) {
|
|
1506
|
+
const v = vers[vi];
|
|
1507
|
+
if (v && selected) {
|
|
1508
|
+
setStatus("Rolling back\u2026");
|
|
1509
|
+
void api.policies.rollback(selected.id, v.version).then((r) => {
|
|
1510
|
+
setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
|
|
1511
|
+
detail.reload();
|
|
1512
|
+
list5.reload();
|
|
1513
|
+
setView("rules");
|
|
1514
|
+
}).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1436
1519
|
const rule2 = rules[ri];
|
|
1437
1520
|
if (!rule2) return setView("rules");
|
|
1438
1521
|
const field = FIELDS[fi];
|
|
@@ -1477,7 +1560,7 @@ function PoliciesPanel({ focused }) {
|
|
|
1477
1560
|
/* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
|
|
1478
1561
|
dirtyTag
|
|
1479
1562
|
] }),
|
|
1480
|
-
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193
|
|
1563
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7 v versions \xB7 s save \xB7 \u2190 back" }),
|
|
1481
1564
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1482
1565
|
Table,
|
|
1483
1566
|
{
|
|
@@ -1503,6 +1586,50 @@ function PoliciesPanel({ focused }) {
|
|
|
1503
1586
|
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1504
1587
|
] }) });
|
|
1505
1588
|
}
|
|
1589
|
+
if (view === "versions") {
|
|
1590
|
+
const vers = versionsQ.data?.versions ?? [];
|
|
1591
|
+
return /* @__PURE__ */ jsx3(DataView, { loading: versionsQ.loading && !versionsQ.data, error: versionsQ.error, empty: !!versionsQ.data && vers.length === 0, emptyText: "No versions.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1592
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
|
|
1593
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
|
|
1594
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
|
|
1595
|
+
Table,
|
|
1596
|
+
{
|
|
1597
|
+
columns: [
|
|
1598
|
+
{ header: "", width: 2 },
|
|
1599
|
+
{ header: "VER", width: 5 },
|
|
1600
|
+
{ header: "RULES", width: 5 },
|
|
1601
|
+
{ header: "REASON", width: 34 },
|
|
1602
|
+
{ header: "WHEN", width: 16 }
|
|
1603
|
+
],
|
|
1604
|
+
rows: vers.map((v, i) => [
|
|
1605
|
+
{ value: i === vi ? "\u25B8" : "", color: theme.accentBright },
|
|
1606
|
+
{ value: `v${v.version}`, bold: i === vi },
|
|
1607
|
+
{ value: String(v.rules_count), dim: true },
|
|
1608
|
+
{ value: truncate(v.reason || "\u2014", 34) },
|
|
1609
|
+
{ value: truncate(v.created_at, 16), dim: true }
|
|
1610
|
+
])
|
|
1611
|
+
}
|
|
1612
|
+
) }),
|
|
1613
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1614
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: `Diff v${diffVer ?? "?"} vs current draft` }),
|
|
1615
|
+
(() => {
|
|
1616
|
+
const old = diffQ.data?.rules ?? [];
|
|
1617
|
+
const cur = rules;
|
|
1618
|
+
const oldIds = new Set(old.map((r) => r.id));
|
|
1619
|
+
const curIds = new Set(cur.map((r) => r.id));
|
|
1620
|
+
const added = cur.filter((r) => !oldIds.has(r.id));
|
|
1621
|
+
const removed = old.filter((r) => !curIds.has(r.id));
|
|
1622
|
+
if (diffQ.loading && !diffQ.data) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " loading\u2026" });
|
|
1623
|
+
if (!added.length && !removed.length) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " identical rule set" });
|
|
1624
|
+
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1625
|
+
added.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.ok, children: ` + ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "a" + r.id)),
|
|
1626
|
+
removed.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.bad, children: ` - ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "r" + r.id))
|
|
1627
|
+
] });
|
|
1628
|
+
})()
|
|
1629
|
+
] }),
|
|
1630
|
+
status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
1631
|
+
] }) });
|
|
1632
|
+
}
|
|
1506
1633
|
const rule = rules[ri];
|
|
1507
1634
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1508
1635
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
@@ -1616,6 +1743,20 @@ function RateLimitPanel({ focused }) {
|
|
|
1616
1743
|
/* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
|
|
1617
1744
|
/* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
|
|
1618
1745
|
] }),
|
|
1746
|
+
draft.perMinute > 0 ? (() => {
|
|
1747
|
+
const now = minuteSeries.slice(-1)[0] ?? 0;
|
|
1748
|
+
const pct = Math.min(100, Math.round(now / draft.perMinute * 100));
|
|
1749
|
+
const width = 30;
|
|
1750
|
+
const filled = Math.min(width, Math.round(now / draft.perMinute * width));
|
|
1751
|
+
const col = pct >= 100 ? theme.bad : pct >= 80 ? theme.warn : theme.ok;
|
|
1752
|
+
return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
1753
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "load " }),
|
|
1754
|
+
/* @__PURE__ */ jsx4(Text4, { color: col, children: "\u2588".repeat(filled) }),
|
|
1755
|
+
/* @__PURE__ */ jsx4(Text4, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
|
|
1756
|
+
/* @__PURE__ */ jsx4(Text4, { color: col, children: ` ${now}/${draft.perMinute} ` }),
|
|
1757
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: `(${pct}% this min)` })
|
|
1758
|
+
] });
|
|
1759
|
+
})() : null,
|
|
1619
1760
|
dirty || status ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
1620
1761
|
dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
|
|
1621
1762
|
status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
|
|
@@ -1686,12 +1827,19 @@ function DlpPanel({ focused }) {
|
|
|
1686
1827
|
const [adding, setAdding] = useState5(null);
|
|
1687
1828
|
const [newName, setNewName] = useState5("");
|
|
1688
1829
|
const [newRe, setNewRe] = useState5("");
|
|
1830
|
+
const [ghostOn, setGhostOn] = useState5(false);
|
|
1831
|
+
const spQ = useLoader(() => api.settings.getSelfProtection());
|
|
1832
|
+
const [selfProt, setSelfProt] = useState5(null);
|
|
1689
1833
|
useEffect5(() => {
|
|
1690
1834
|
if (q.data && !dlp) {
|
|
1691
1835
|
setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
1692
1836
|
setAvailable(q.data.availablePatterns);
|
|
1837
|
+
setGhostOn(q.data.layers.ghost.mode === "on");
|
|
1693
1838
|
}
|
|
1694
1839
|
}, [q.data, dlp]);
|
|
1840
|
+
useEffect5(() => {
|
|
1841
|
+
if (spQ.data && selfProt === null) setSelfProt(spQ.data.enabled);
|
|
1842
|
+
}, [spQ.data, selfProt]);
|
|
1695
1843
|
const mutate = (next) => {
|
|
1696
1844
|
setDlp(next);
|
|
1697
1845
|
setDirty(true);
|
|
@@ -1701,14 +1849,27 @@ function DlpPanel({ focused }) {
|
|
|
1701
1849
|
if (!dlp || !q.data) return;
|
|
1702
1850
|
setStatus("Saving\u2026");
|
|
1703
1851
|
try {
|
|
1704
|
-
const
|
|
1852
|
+
const ghost = { ...q.data.layers.ghost, mode: ghostOn ? "on" : "off" };
|
|
1853
|
+
const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp, ghost });
|
|
1705
1854
|
setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
|
|
1855
|
+
setGhostOn(res.layers.ghost.mode === "on");
|
|
1706
1856
|
setDirty(false);
|
|
1707
1857
|
setStatus("\u2713 Saved");
|
|
1708
1858
|
} catch (e) {
|
|
1709
1859
|
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
1710
1860
|
}
|
|
1711
1861
|
};
|
|
1862
|
+
const toggleSelfProt = async () => {
|
|
1863
|
+
const next = !(selfProt ?? false);
|
|
1864
|
+
setSelfProt(next);
|
|
1865
|
+
try {
|
|
1866
|
+
await api.settings.setSelfProtection(next);
|
|
1867
|
+
setStatus(`\u2713 self-protection ${next ? "on" : "off"}`);
|
|
1868
|
+
} catch (e) {
|
|
1869
|
+
setSelfProt(!next);
|
|
1870
|
+
setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1712
1873
|
const discard = () => {
|
|
1713
1874
|
if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
|
|
1714
1875
|
setDirty(false);
|
|
@@ -1738,7 +1899,12 @@ function DlpPanel({ focused }) {
|
|
|
1738
1899
|
const ci = sel - available.length;
|
|
1739
1900
|
mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
|
|
1740
1901
|
setSel((n) => Math.max(0, n - 1));
|
|
1741
|
-
} else if (input === "
|
|
1902
|
+
} else if (input === "g") {
|
|
1903
|
+
setGhostOn((v) => !v);
|
|
1904
|
+
setDirty(true);
|
|
1905
|
+
setStatus(null);
|
|
1906
|
+
} else if (input === "P") void toggleSelfProt();
|
|
1907
|
+
else if (input === "s") void save();
|
|
1742
1908
|
else if (input === "x") discard();
|
|
1743
1909
|
},
|
|
1744
1910
|
{ isActive: focused && !adding }
|
|
@@ -1750,7 +1916,13 @@ function DlpPanel({ focused }) {
|
|
|
1750
1916
|
/* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
|
|
1751
1917
|
dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
|
|
1752
1918
|
] }),
|
|
1753
|
-
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193
|
|
1919
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 g ghost \xB7 P self-protect \xB7 s save" : "press \u2192 to edit" }),
|
|
1920
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1921
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "ghost: " }),
|
|
1922
|
+
/* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.ok : theme.dim, children: ghostOn ? "on" : "off" }),
|
|
1923
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " self-protection: " }),
|
|
1924
|
+
/* @__PURE__ */ jsx5(Text5, { color: selfProt ? theme.ok : theme.dim, children: selfProt === null ? "\u2026" : selfProt ? "on" : "off" })
|
|
1925
|
+
] }),
|
|
1754
1926
|
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
|
|
1755
1927
|
const on = enabled.has(p);
|
|
1756
1928
|
const active2 = focused && sel === i;
|
|
@@ -1797,9 +1969,27 @@ function DlpPanel({ focused }) {
|
|
|
1797
1969
|
}
|
|
1798
1970
|
)
|
|
1799
1971
|
] }) : null,
|
|
1972
|
+
adding === "re" ? /* @__PURE__ */ jsx5(RegexTest, { re: newRe }) : null,
|
|
1800
1973
|
status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
|
|
1801
1974
|
] }) : null });
|
|
1802
1975
|
}
|
|
1976
|
+
var SAMPLES = ["AKIA1234567890ABCD00", "sk-ant-api03-xxxxxxxx", "ghp_16chars0000000000000000000000000000", "password=hunter2", "Bearer eyJhbGciOi"];
|
|
1977
|
+
function RegexTest({ re }) {
|
|
1978
|
+
if (!re.trim()) return /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " regex: type a pattern\u2026" });
|
|
1979
|
+
let rx = null;
|
|
1980
|
+
let err = "";
|
|
1981
|
+
try {
|
|
1982
|
+
rx = new RegExp(re);
|
|
1983
|
+
} catch (e) {
|
|
1984
|
+
err = e instanceof Error ? e.message : String(e);
|
|
1985
|
+
}
|
|
1986
|
+
if (!rx) return /* @__PURE__ */ jsx5(Text5, { color: theme.bad, children: ` \u2717 invalid: ${err.slice(0, 50)}` });
|
|
1987
|
+
const hits = SAMPLES.filter((s) => rx.test(s));
|
|
1988
|
+
return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
|
|
1989
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.ok, children: " \u2713 valid" }),
|
|
1990
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: hits.length ? ` matches ${hits.length}/${SAMPLES.length} samples: ${hits.map((h) => h.slice(0, 8)).join(", ")}` : " (no sample match \u2014 will only catch your custom secrets)" })
|
|
1991
|
+
] });
|
|
1992
|
+
}
|
|
1803
1993
|
|
|
1804
1994
|
// src/tui/panels/Stats.tsx
|
|
1805
1995
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
@@ -1807,12 +1997,15 @@ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
|
1807
1997
|
function StatsPanel({ active: active2 }) {
|
|
1808
1998
|
const stats = useLoader(() => api.stats.get());
|
|
1809
1999
|
const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
|
|
2000
|
+
const drift2 = useLoader(() => api.stats.drift(7));
|
|
1810
2001
|
usePoll(() => {
|
|
1811
2002
|
stats.reload();
|
|
1812
2003
|
ts.reload();
|
|
1813
2004
|
}, 5e3, active2);
|
|
2005
|
+
usePoll(drift2.reload, 3e4, active2);
|
|
1814
2006
|
const s = stats.data;
|
|
1815
2007
|
const points = ts.data?.timeseries ?? [];
|
|
2008
|
+
const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
|
|
1816
2009
|
return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
1817
2010
|
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1818
2011
|
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
@@ -1865,7 +2058,7 @@ function StatsPanel({ active: active2 }) {
|
|
|
1865
2058
|
{ header: "MS", width: 5 },
|
|
1866
2059
|
{ header: "WHEN", width: 6 }
|
|
1867
2060
|
],
|
|
1868
|
-
rows: (s.recent_activity ?? []).slice(0,
|
|
2061
|
+
rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
|
|
1869
2062
|
{ value: a.decision, color: decisionColor(a.decision) },
|
|
1870
2063
|
{ value: truncate(a.tool_name, 20), color: theme.accent },
|
|
1871
2064
|
{ value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
|
|
@@ -1873,6 +2066,31 @@ function StatsPanel({ active: active2 }) {
|
|
|
1873
2066
|
])
|
|
1874
2067
|
}
|
|
1875
2068
|
)
|
|
2069
|
+
] }),
|
|
2070
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
2071
|
+
/* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
|
|
2072
|
+
"Denial drift ",
|
|
2073
|
+
drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
|
|
2074
|
+
] }),
|
|
2075
|
+
driftRules.length ? /* @__PURE__ */ jsx6(
|
|
2076
|
+
Table,
|
|
2077
|
+
{
|
|
2078
|
+
columns: [
|
|
2079
|
+
{ header: "NOW", width: 5 },
|
|
2080
|
+
{ header: "PREV", width: 5 },
|
|
2081
|
+
{ header: "\u0394", width: 6 },
|
|
2082
|
+
{ header: "RULE", width: 22 },
|
|
2083
|
+
{ header: "REASON", width: 26 }
|
|
2084
|
+
],
|
|
2085
|
+
rows: driftRules.map((r) => [
|
|
2086
|
+
{ value: String(r.current), bold: true },
|
|
2087
|
+
{ value: String(r.previous), dim: true },
|
|
2088
|
+
{ value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
|
|
2089
|
+
{ value: truncate(r.rule_id ?? "\u2014", 22), color: theme.accent },
|
|
2090
|
+
{ value: truncate(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
|
|
2091
|
+
])
|
|
2092
|
+
}
|
|
2093
|
+
) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
|
|
1876
2094
|
] })
|
|
1877
2095
|
] }) : null });
|
|
1878
2096
|
}
|
|
@@ -2046,22 +2264,109 @@ function Detail({ label, value, color }) {
|
|
|
2046
2264
|
] });
|
|
2047
2265
|
}
|
|
2048
2266
|
|
|
2267
|
+
// src/tui/panels/Agents.tsx
|
|
2268
|
+
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
2269
|
+
import { useState as useState7 } from "react";
|
|
2270
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2271
|
+
var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
|
|
2272
|
+
var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
|
|
2273
|
+
function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
|
|
2274
|
+
const filled = max > 0 ? Math.round(Math.min(value, max) / max * width) : 0;
|
|
2275
|
+
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2276
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(9) }),
|
|
2277
|
+
/* @__PURE__ */ jsx8(Text8, { color, children: "\u2588".repeat(filled) }),
|
|
2278
|
+
/* @__PURE__ */ jsx8(Text8, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
|
|
2279
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + value })
|
|
2280
|
+
] });
|
|
2281
|
+
}
|
|
2282
|
+
function AgentsPanel({ focused }) {
|
|
2283
|
+
const live2 = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
2284
|
+
usePoll(live2.reload, 6e3, focused ? false : true);
|
|
2285
|
+
const [sel, setSel] = useState7(0);
|
|
2286
|
+
const agents = (live2.data?.agents ?? []).filter((a) => a.agent_id);
|
|
2287
|
+
const selected = agents[Math.min(sel, Math.max(0, agents.length - 1))];
|
|
2288
|
+
const detail = useLoader(() => selected?.agent_id ? api.agents.get(selected.agent_id) : Promise.resolve(null), [selected?.agent_id]);
|
|
2289
|
+
useInput6(
|
|
2290
|
+
(_input, key) => {
|
|
2291
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2292
|
+
else if (key.downArrow) setSel((n) => Math.min(agents.length - 1, n + 1));
|
|
2293
|
+
},
|
|
2294
|
+
{ isActive: focused }
|
|
2295
|
+
);
|
|
2296
|
+
const d = detail.data;
|
|
2297
|
+
const base = d?.baseline;
|
|
2298
|
+
const radar = base?.radar;
|
|
2299
|
+
const anomalies2 = d?.anomalies ?? [];
|
|
2300
|
+
const tmap = d?.trust_map;
|
|
2301
|
+
const tools = tmap?.tools ?? [];
|
|
2302
|
+
const resources = tmap?.resources ?? [];
|
|
2303
|
+
const listW = 30;
|
|
2304
|
+
return /* @__PURE__ */ jsx8(DataView, { loading: live2.loading && !live2.data, error: live2.error, empty: !!live2.data && agents.length === 0, emptyText: "No identified agents yet.", children: /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2305
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
|
|
2306
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
|
|
2307
|
+
agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
|
|
2308
|
+
/* @__PURE__ */ jsxs8(Text8, { color: statusColor(a.status), children: [
|
|
2309
|
+
a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
|
|
2310
|
+
" "
|
|
2311
|
+
] }),
|
|
2312
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
|
|
2313
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
|
|
2314
|
+
] }, a.session_id))
|
|
2315
|
+
] }),
|
|
2316
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
2317
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2318
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
|
|
2319
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
|
|
2320
|
+
] }),
|
|
2321
|
+
base?.characterBlurb ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
|
|
2322
|
+
radar ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2323
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
|
|
2324
|
+
/* @__PURE__ */ jsx8(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
|
|
2325
|
+
/* @__PURE__ */ jsx8(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
|
|
2326
|
+
/* @__PURE__ */ jsx8(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
|
|
2327
|
+
/* @__PURE__ */ jsx8(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
|
|
2328
|
+
/* @__PURE__ */ jsx8(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
|
|
2329
|
+
] }) : null,
|
|
2330
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2331
|
+
/* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
|
|
2332
|
+
"Anomalies ",
|
|
2333
|
+
anomalies2.length ? `(${anomalies2.length})` : ""
|
|
2334
|
+
] }),
|
|
2335
|
+
anomalies2.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2336
|
+
/* @__PURE__ */ jsx8(Text8, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
|
|
2337
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: String(an.kind).padEnd(12) }),
|
|
2338
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
|
|
2339
|
+
] }, i))
|
|
2340
|
+
] }),
|
|
2341
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2342
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Trust map \xB7 top tools" }),
|
|
2343
|
+
tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
2344
|
+
/* @__PURE__ */ jsx8(Text8, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
|
|
2345
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
|
|
2346
|
+
] }, i)),
|
|
2347
|
+
resources.slice(0, 3).map((r, i) => /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: r.anomalous ? theme.bad : theme.dim, children: ` ${r.type === "domain" ? "\u{1F310}" : "\u{1F4C1}"} ${truncate(String(r.value), 40)} ${r.count}\xD7` }, "r" + i))
|
|
2348
|
+
] })
|
|
2349
|
+
] }) })
|
|
2350
|
+
] }) });
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2049
2353
|
// src/tui/App.tsx
|
|
2050
|
-
import { jsx as
|
|
2354
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2051
2355
|
function LiveHint() {
|
|
2052
|
-
return /* @__PURE__ */
|
|
2053
|
-
/* @__PURE__ */
|
|
2054
|
-
/* @__PURE__ */
|
|
2055
|
-
/* @__PURE__ */
|
|
2056
|
-
/* @__PURE__ */
|
|
2356
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2357
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
|
|
2358
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
|
|
2359
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
|
|
2360
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2057
2361
|
"press ",
|
|
2058
|
-
/* @__PURE__ */
|
|
2362
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
|
|
2059
2363
|
" to go live"
|
|
2060
2364
|
] }) })
|
|
2061
2365
|
] });
|
|
2062
2366
|
}
|
|
2063
2367
|
var SECTIONS = [
|
|
2064
2368
|
{ label: "Live", Panel: LiveHint },
|
|
2369
|
+
{ label: "Agents", Panel: AgentsPanel },
|
|
2065
2370
|
{ label: "Policies", Panel: PoliciesPanel },
|
|
2066
2371
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
2067
2372
|
{ label: "DLP", Panel: DlpPanel },
|
|
@@ -2072,21 +2377,30 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
|
|
|
2072
2377
|
function Banner() {
|
|
2073
2378
|
const wide = (process.stdout.columns ?? 80) >= 82;
|
|
2074
2379
|
if (!wide) {
|
|
2075
|
-
return /* @__PURE__ */
|
|
2076
|
-
/* @__PURE__ */
|
|
2077
|
-
/* @__PURE__ */
|
|
2380
|
+
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2381
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
2382
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
|
|
2078
2383
|
] });
|
|
2079
2384
|
}
|
|
2080
|
-
return /* @__PURE__ */
|
|
2081
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
2082
|
-
/* @__PURE__ */
|
|
2385
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2386
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
2387
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
2083
2388
|
] });
|
|
2084
2389
|
}
|
|
2085
2390
|
function App() {
|
|
2086
2391
|
const { exit } = useApp();
|
|
2087
|
-
const [section, setSection] =
|
|
2088
|
-
const [focus, setFocus] =
|
|
2089
|
-
|
|
2392
|
+
const [section, setSection] = useState8(0);
|
|
2393
|
+
const [focus, setFocus] = useState8("nav");
|
|
2394
|
+
const [help, setHelp] = useState8(false);
|
|
2395
|
+
useInput7((input, key) => {
|
|
2396
|
+
if (help) {
|
|
2397
|
+
setHelp(false);
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
if (input === "?" && focus === "nav") {
|
|
2401
|
+
setHelp(true);
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2090
2404
|
if (focus === "nav") {
|
|
2091
2405
|
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
2092
2406
|
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
@@ -2100,22 +2414,42 @@ function App() {
|
|
|
2100
2414
|
const cols = process.stdout.columns ?? 100;
|
|
2101
2415
|
const rows = process.stdout.rows ?? 30;
|
|
2102
2416
|
const current = SECTIONS[section];
|
|
2417
|
+
if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
|
|
2103
2418
|
if (current.label === "Live" && focus === "panel") {
|
|
2104
|
-
return /* @__PURE__ */
|
|
2419
|
+
return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }) });
|
|
2105
2420
|
}
|
|
2106
2421
|
const Panel = current.Panel;
|
|
2107
|
-
return /* @__PURE__ */
|
|
2108
|
-
/* @__PURE__ */
|
|
2109
|
-
/* @__PURE__ */
|
|
2110
|
-
/* @__PURE__ */
|
|
2111
|
-
/* @__PURE__ */
|
|
2422
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
2423
|
+
/* @__PURE__ */ jsx9(Banner, {}),
|
|
2424
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
|
|
2425
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
|
|
2426
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
|
|
2112
2427
|
] }),
|
|
2113
|
-
/* @__PURE__ */
|
|
2428
|
+
/* @__PURE__ */ jsx9(Box9, { children: focus === "nav" ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
2429
|
+
] });
|
|
2430
|
+
}
|
|
2431
|
+
var HELP = [
|
|
2432
|
+
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
2433
|
+
["Live", [["\u2191\u2193", "select a stream row"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
|
|
2434
|
+
["Policies", [["\u2191\u2193", "browse / select"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["v", "versions / rollback"], ["s", "save"], ["x", "discard"]]],
|
|
2435
|
+
["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]]
|
|
2436
|
+
];
|
|
2437
|
+
function HelpOverlay({ cols, rows }) {
|
|
2438
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
2439
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
2440
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
|
|
2441
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
|
|
2442
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
2443
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
2444
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
|
|
2445
|
+
] }, k))
|
|
2446
|
+
] }, group)) }),
|
|
2447
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
|
|
2114
2448
|
] });
|
|
2115
2449
|
}
|
|
2116
2450
|
|
|
2117
2451
|
// src/tui/index.tsx
|
|
2118
|
-
import { jsx as
|
|
2452
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2119
2453
|
async function launchTui() {
|
|
2120
2454
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2121
2455
|
process.stderr.write(
|
|
@@ -2129,7 +2463,7 @@ async function launchTui() {
|
|
|
2129
2463
|
}
|
|
2130
2464
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
2131
2465
|
try {
|
|
2132
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
2466
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
|
|
2133
2467
|
await waitUntilExit();
|
|
2134
2468
|
} finally {
|
|
2135
2469
|
process.stdout.write("\x1B[?1049l");
|