@solongate/proxy 0.81.24 → 0.81.26
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/audit.d.ts +13 -0
- package/dist/commands/index.js +8 -0
- package/dist/index.js +253 -256
- package/dist/tui/hooks.d.ts +3 -0
- package/dist/tui/index.js +209 -201
- package/dist/tui/local-log.d.ts +4 -0
- package/package.json +1 -1
- package/dist/tui/panels/Agents.d.ts +0 -4
- package/dist/tui/panels/Stats.d.ts +0 -3
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 Box8, Text as Text8, 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 = {
|
|
@@ -142,7 +142,7 @@ function DataView({
|
|
|
142
142
|
"\u2717 ",
|
|
143
143
|
error
|
|
144
144
|
] });
|
|
145
|
-
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.
|
|
145
|
+
if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
|
|
146
146
|
if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
|
|
147
147
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
148
148
|
}
|
|
@@ -169,12 +169,12 @@ function KeyHints({ hints }) {
|
|
|
169
169
|
// src/tui/panels/Live.tsx
|
|
170
170
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
171
171
|
import TextInput from "ink-text-input";
|
|
172
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
172
|
+
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
173
173
|
import { homedir as homedir4 } from "os";
|
|
174
174
|
import { join as join4 } from "path";
|
|
175
175
|
|
|
176
176
|
// src/tui/local-log.ts
|
|
177
|
-
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
177
|
+
import { closeSync, openSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
|
|
178
178
|
import { homedir as homedir2 } from "os";
|
|
179
179
|
import { join as join2 } from "path";
|
|
180
180
|
var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
|
|
@@ -193,6 +193,39 @@ function tailLines(file, maxBytes = 131072) {
|
|
|
193
193
|
return [];
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
+
function deleteLocalEntry(at, tool, session) {
|
|
197
|
+
try {
|
|
198
|
+
const lines = readFileSync2(LOCAL_LOG, "utf-8").split("\n");
|
|
199
|
+
let removed = 0;
|
|
200
|
+
const kept = lines.filter((line) => {
|
|
201
|
+
if (!line.trim()) return false;
|
|
202
|
+
if (removed) return true;
|
|
203
|
+
try {
|
|
204
|
+
const j = JSON.parse(line);
|
|
205
|
+
const hit = Date.parse(j.ts ?? "") === at && (j.tool ?? "?") === tool && (!session || j.session_id === session);
|
|
206
|
+
if (hit) {
|
|
207
|
+
removed++;
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
});
|
|
214
|
+
if (removed) writeFileSync(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
|
|
215
|
+
return removed;
|
|
216
|
+
} catch {
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function clearLocalLog() {
|
|
221
|
+
try {
|
|
222
|
+
const n = readFileSync2(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
|
|
223
|
+
writeFileSync(LOCAL_LOG, "");
|
|
224
|
+
return n;
|
|
225
|
+
} catch {
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
196
229
|
function parseLocalLines(lines) {
|
|
197
230
|
const out = [];
|
|
198
231
|
for (const line of lines) {
|
|
@@ -210,7 +243,7 @@ function parseLocalLines(lines) {
|
|
|
210
243
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
211
244
|
|
|
212
245
|
// src/api-client/client.ts
|
|
213
|
-
import { readFileSync as
|
|
246
|
+
import { readFileSync as readFileSync3, existsSync } from "fs";
|
|
214
247
|
import { resolve, join as join3 } from "path";
|
|
215
248
|
import { homedir as homedir3 } from "os";
|
|
216
249
|
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
@@ -234,7 +267,7 @@ function loginCredentialFile() {
|
|
|
234
267
|
try {
|
|
235
268
|
const p = join3(homedir3(), ".solongate", "cloud-guard.json");
|
|
236
269
|
if (!existsSync(p)) return {};
|
|
237
|
-
const c2 = JSON.parse(
|
|
270
|
+
const c2 = JSON.parse(readFileSync3(p, "utf-8"));
|
|
238
271
|
return c2 && typeof c2 === "object" ? c2 : {};
|
|
239
272
|
} catch {
|
|
240
273
|
return {};
|
|
@@ -244,7 +277,7 @@ function dotenvApiKey() {
|
|
|
244
277
|
try {
|
|
245
278
|
const envPath = resolve(".env");
|
|
246
279
|
if (!existsSync(envPath)) return void 0;
|
|
247
|
-
for (const line of
|
|
280
|
+
for (const line of readFileSync3(envPath, "utf-8").split("\n")) {
|
|
248
281
|
const trimmed = line.trim();
|
|
249
282
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
250
283
|
const eq = trimmed.indexOf("=");
|
|
@@ -496,11 +529,19 @@ var audit_exports = {};
|
|
|
496
529
|
__export(audit_exports, {
|
|
497
530
|
block: () => block,
|
|
498
531
|
list: () => list2,
|
|
532
|
+
remove: () => remove2,
|
|
533
|
+
removeAll: () => removeAll,
|
|
499
534
|
whitelist: () => whitelist
|
|
500
535
|
});
|
|
501
536
|
function list2(query = {}) {
|
|
502
537
|
return request("GET", "/audit-logs", { query });
|
|
503
538
|
}
|
|
539
|
+
function remove2(ids) {
|
|
540
|
+
return request("DELETE", "/audit-logs", { body: { ids } });
|
|
541
|
+
}
|
|
542
|
+
function removeAll() {
|
|
543
|
+
return request("DELETE", "/audit-logs", { body: { scope: "logs" } });
|
|
544
|
+
}
|
|
504
545
|
function whitelist(id, scope = "exact") {
|
|
505
546
|
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
506
547
|
}
|
|
@@ -593,6 +634,7 @@ function useLoader(fn, deps = []) {
|
|
|
593
634
|
const [loading, setLoading] = useState(true);
|
|
594
635
|
const [nonce, setNonce] = useState(0);
|
|
595
636
|
const alive = useRef(true);
|
|
637
|
+
const quiet = useRef(false);
|
|
596
638
|
const fnRef = useRef(fn);
|
|
597
639
|
fnRef.current = fn;
|
|
598
640
|
useEffect(() => {
|
|
@@ -602,7 +644,8 @@ function useLoader(fn, deps = []) {
|
|
|
602
644
|
};
|
|
603
645
|
}, []);
|
|
604
646
|
useEffect(() => {
|
|
605
|
-
setLoading(true);
|
|
647
|
+
if (!quiet.current) setLoading(true);
|
|
648
|
+
quiet.current = false;
|
|
606
649
|
fnRef.current().then((d) => {
|
|
607
650
|
if (!alive.current) return;
|
|
608
651
|
setData(d);
|
|
@@ -615,7 +658,11 @@ function useLoader(fn, deps = []) {
|
|
|
615
658
|
});
|
|
616
659
|
}, [nonce, ...deps]);
|
|
617
660
|
const reload = useCallback(() => setNonce((n) => n + 1), []);
|
|
618
|
-
|
|
661
|
+
const reloadQuiet = useCallback(() => {
|
|
662
|
+
quiet.current = true;
|
|
663
|
+
setNonce((n) => n + 1);
|
|
664
|
+
}, []);
|
|
665
|
+
return { data, error, loading, reload, reloadQuiet };
|
|
619
666
|
}
|
|
620
667
|
function usePoll(reload, intervalMs, enabled = true) {
|
|
621
668
|
useEffect(() => {
|
|
@@ -968,13 +1015,13 @@ function LivePanel({ active: active2 }) {
|
|
|
968
1015
|
const insights = useLoader(() => api.stats.securityInsights(7));
|
|
969
1016
|
const guard = useLoader(() => api.settings.getGuardStatus());
|
|
970
1017
|
usePoll(() => {
|
|
971
|
-
if (!paused()) sessions.
|
|
1018
|
+
if (!paused()) sessions.reloadQuiet();
|
|
972
1019
|
}, 8e3, active2);
|
|
973
1020
|
usePoll(() => {
|
|
974
|
-
if (!paused()) insights.
|
|
1021
|
+
if (!paused()) insights.reloadQuiet();
|
|
975
1022
|
}, 2e4, active2);
|
|
976
1023
|
usePoll(() => {
|
|
977
|
-
if (!paused()) guard.
|
|
1024
|
+
if (!paused()) guard.reloadQuiet();
|
|
978
1025
|
}, 6e4, active2);
|
|
979
1026
|
useEffect2(() => {
|
|
980
1027
|
if (!detail) return;
|
|
@@ -1251,7 +1298,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1251
1298
|
const file = join4(homedir4(), ".solongate", "live-export.jsonl");
|
|
1252
1299
|
try {
|
|
1253
1300
|
mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
|
|
1254
|
-
|
|
1301
|
+
writeFileSync2(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
1255
1302
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
1256
1303
|
} catch (err) {
|
|
1257
1304
|
setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
|
|
@@ -2231,12 +2278,14 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2231
2278
|
const [si, setSi] = useState6(0);
|
|
2232
2279
|
const [sessSearch, setSessSearch] = useState6("");
|
|
2233
2280
|
const [sessSel, setSessSel] = useState6(0);
|
|
2281
|
+
const [confirm, setConfirm] = useState6(null);
|
|
2282
|
+
const [msg, setMsg] = useState6(null);
|
|
2234
2283
|
const toTop = () => setSel(0);
|
|
2235
2284
|
const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
|
|
2236
2285
|
const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
|
|
2237
2286
|
usePoll(() => {
|
|
2238
|
-
statsQ.
|
|
2239
|
-
tsQ.
|
|
2287
|
+
statsQ.reloadQuiet();
|
|
2288
|
+
tsQ.reloadQuiet();
|
|
2240
2289
|
}, 15e3, active2 && source === "cloud");
|
|
2241
2290
|
const query = {
|
|
2242
2291
|
filter: DECISIONS[di],
|
|
@@ -2252,9 +2301,9 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2252
2301
|
() => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
|
|
2253
2302
|
[source, di, gi, tool, agent, search, sessFilter, page]
|
|
2254
2303
|
);
|
|
2255
|
-
usePoll(cloudQ.
|
|
2304
|
+
usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
|
|
2256
2305
|
const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
|
|
2257
|
-
usePoll(localQ.
|
|
2306
|
+
usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
|
|
2258
2307
|
let pageRows = [];
|
|
2259
2308
|
let total = 0;
|
|
2260
2309
|
if (source === "cloud") {
|
|
@@ -2282,7 +2331,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2282
2331
|
() => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
|
|
2283
2332
|
[source]
|
|
2284
2333
|
);
|
|
2285
|
-
usePoll(agentsQ.
|
|
2334
|
+
usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions");
|
|
2286
2335
|
let sessions = [];
|
|
2287
2336
|
if (source === "cloud") {
|
|
2288
2337
|
sessions = (agentsQ.data?.agents ?? []).map((a) => ({
|
|
@@ -2316,6 +2365,31 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2316
2365
|
() => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
|
|
2317
2366
|
[view, current?.session, source]
|
|
2318
2367
|
);
|
|
2368
|
+
const logsLoading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
|
|
2369
|
+
const sessLoading = source === "cloud" ? agentsQ.loading : localQ.loading;
|
|
2370
|
+
const doDelete = (kind) => {
|
|
2371
|
+
setMsg({ text: "deleting\u2026", level: "ok" });
|
|
2372
|
+
const run = async () => {
|
|
2373
|
+
if (source === "cloud") {
|
|
2374
|
+
if (kind === "one") {
|
|
2375
|
+
if (!current) throw new Error("nothing selected");
|
|
2376
|
+
await api.audit.remove([current.id]);
|
|
2377
|
+
} else {
|
|
2378
|
+
await api.audit.removeAll();
|
|
2379
|
+
}
|
|
2380
|
+
cloudQ.reload();
|
|
2381
|
+
statsQ.reloadQuiet();
|
|
2382
|
+
} else {
|
|
2383
|
+
const n = kind === "one" && current ? deleteLocalEntry(current.at, current.tool, current.session) : kind === "all" ? clearLocalLog() : 0;
|
|
2384
|
+
if (!n) throw new Error("entry not found in the local file");
|
|
2385
|
+
localQ.reload();
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
run().then(() => {
|
|
2389
|
+
setMsg({ text: kind === "one" ? "\u2713 entry deleted" : `\u2713 ALL ${source} logs deleted`, level: "ok" });
|
|
2390
|
+
toTop();
|
|
2391
|
+
}).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
|
|
2392
|
+
};
|
|
2319
2393
|
useInput5(
|
|
2320
2394
|
(input, key) => {
|
|
2321
2395
|
if (view === "detail") {
|
|
@@ -2326,6 +2400,11 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2326
2400
|
else if (key.pageDown) setDetailScroll((n) => n + 10);
|
|
2327
2401
|
return;
|
|
2328
2402
|
}
|
|
2403
|
+
if (view === "logs" ? logsLoading : sessLoading) return;
|
|
2404
|
+
if (confirm && input !== "x" && input !== "X") {
|
|
2405
|
+
setConfirm(null);
|
|
2406
|
+
setMsg(null);
|
|
2407
|
+
}
|
|
2329
2408
|
if (input === "s") {
|
|
2330
2409
|
setSource((s) => s === "cloud" ? "local" : "cloud");
|
|
2331
2410
|
setPage(0);
|
|
@@ -2342,7 +2421,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2342
2421
|
else if (key.downArrow) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 1));
|
|
2343
2422
|
else if (key.pageUp) setSessSel((n) => Math.max(0, n - 10));
|
|
2344
2423
|
else if (key.pageDown) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 10));
|
|
2345
|
-
else if (key.return
|
|
2424
|
+
else if (key.return) {
|
|
2346
2425
|
if (currentSess) {
|
|
2347
2426
|
setSessFilter(currentSess.id);
|
|
2348
2427
|
setPage(0);
|
|
@@ -2364,17 +2443,17 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2364
2443
|
else if (key.downArrow) setSel((n) => Math.min(pageRows.length - 1, n + 1));
|
|
2365
2444
|
else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
|
|
2366
2445
|
else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
|
|
2367
|
-
else if (key.return
|
|
2446
|
+
else if (key.return) {
|
|
2368
2447
|
if (current) {
|
|
2369
2448
|
setDetailScroll(0);
|
|
2370
2449
|
setView("detail");
|
|
2371
2450
|
}
|
|
2372
|
-
} else if (
|
|
2451
|
+
} else if (key.rightArrow) {
|
|
2373
2452
|
if (page < pages - 1) {
|
|
2374
2453
|
setPage(page + 1);
|
|
2375
2454
|
toTop();
|
|
2376
2455
|
}
|
|
2377
|
-
} else if (
|
|
2456
|
+
} else if (key.leftArrow) {
|
|
2378
2457
|
if (page > 0) {
|
|
2379
2458
|
setPage(page - 1);
|
|
2380
2459
|
toTop();
|
|
@@ -2387,6 +2466,23 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2387
2466
|
setGi((n) => (n + 1) % SIGNALS.length);
|
|
2388
2467
|
setPage(0);
|
|
2389
2468
|
toTop();
|
|
2469
|
+
} else if (input === "x") {
|
|
2470
|
+
if (!current) return;
|
|
2471
|
+
if (confirm?.kind !== "one" || confirm.key !== current.id) {
|
|
2472
|
+
setConfirm({ kind: "one", key: current.id });
|
|
2473
|
+
setMsg({ text: `delete ${current.decision} ${current.tool} (${ago(current.at)} ago)? press x again`, level: "bad" });
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
setConfirm(null);
|
|
2477
|
+
doDelete("one");
|
|
2478
|
+
} else if (input === "X") {
|
|
2479
|
+
if (confirm?.kind !== "all") {
|
|
2480
|
+
setConfirm({ kind: "all", key: "all" });
|
|
2481
|
+
setMsg({ text: `DELETE ALL ${source} logs (${total} entries)? press X again`, level: "bad" });
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
setConfirm(null);
|
|
2485
|
+
doDelete("all");
|
|
2390
2486
|
} else if (input === "t") setEditing("tool");
|
|
2391
2487
|
else if (input === "n") setEditing("agent");
|
|
2392
2488
|
else if (input === "/") setEditing("search");
|
|
@@ -2513,8 +2609,7 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2513
2609
|
const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
|
|
2514
2610
|
const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
|
|
2515
2611
|
const win = sessionsFiltered.slice(start2, start2 + listRows2);
|
|
2516
|
-
|
|
2517
|
-
return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2612
|
+
return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2518
2613
|
strip,
|
|
2519
2614
|
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2520
2615
|
srcChip,
|
|
@@ -2577,15 +2672,14 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2577
2672
|
] }) : null
|
|
2578
2673
|
] }) });
|
|
2579
2674
|
}
|
|
2580
|
-
const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
|
|
2675
|
+
const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0) + (msg ? 1 : 0);
|
|
2581
2676
|
const listRows = Math.max(4, rows - headerRows);
|
|
2582
2677
|
const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
|
|
2583
2678
|
const maxStart = Math.max(0, pageRows.length - listRows);
|
|
2584
2679
|
const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
|
|
2585
2680
|
const windowed = pageRows.slice(start, start + listRows);
|
|
2586
2681
|
const reasonW = Math.min(60, Math.max(12, cols - 67));
|
|
2587
|
-
|
|
2588
|
-
return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2682
|
+
return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
2589
2683
|
strip,
|
|
2590
2684
|
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2591
2685
|
srcChip,
|
|
@@ -2598,7 +2692,8 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2598
2692
|
chip("search", search || "\xB7", !!search),
|
|
2599
2693
|
sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
|
|
2600
2694
|
] }),
|
|
2601
|
-
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7
|
|
2695
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 x del \xB7 X del ALL \xB7 s source \xB7 v sessions \xB7 c clear" : "press \u2192 to browse" }),
|
|
2696
|
+
msg ? /* @__PURE__ */ jsx6(Text6, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : null,
|
|
2602
2697
|
editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
2603
2698
|
/* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
|
|
2604
2699
|
editing,
|
|
@@ -2658,97 +2753,11 @@ function Detail({ label, value, color }) {
|
|
|
2658
2753
|
] });
|
|
2659
2754
|
}
|
|
2660
2755
|
|
|
2661
|
-
// src/tui/panels/Agents.tsx
|
|
2662
|
-
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
2663
|
-
import { useState as useState7 } from "react";
|
|
2664
|
-
import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2665
|
-
var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
|
|
2666
|
-
var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
|
|
2667
|
-
function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
|
|
2668
|
-
const filled = max > 0 ? Math.round(Math.min(value, max) / max * width) : 0;
|
|
2669
|
-
return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2670
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
|
|
2671
|
-
/* @__PURE__ */ jsx7(Text7, { color, children: "\u2588".repeat(filled) }),
|
|
2672
|
-
/* @__PURE__ */ jsx7(Text7, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
|
|
2673
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + value })
|
|
2674
|
-
] });
|
|
2675
|
-
}
|
|
2676
|
-
function AgentsPanel({ focused }) {
|
|
2677
|
-
const live2 = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
|
|
2678
|
-
usePoll(live2.reload, 6e3, focused ? false : true);
|
|
2679
|
-
const [sel, setSel] = useState7(0);
|
|
2680
|
-
const agents = (live2.data?.agents ?? []).filter((a) => a.agent_id);
|
|
2681
|
-
const selected = agents[Math.min(sel, Math.max(0, agents.length - 1))];
|
|
2682
|
-
const detail = useLoader(() => selected?.agent_id ? api.agents.get(selected.agent_id) : Promise.resolve(null), [selected?.agent_id]);
|
|
2683
|
-
useInput6(
|
|
2684
|
-
(_input, key) => {
|
|
2685
|
-
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
2686
|
-
else if (key.downArrow) setSel((n) => Math.min(agents.length - 1, n + 1));
|
|
2687
|
-
},
|
|
2688
|
-
{ isActive: focused }
|
|
2689
|
-
);
|
|
2690
|
-
const d = detail.data;
|
|
2691
|
-
const base = d?.baseline;
|
|
2692
|
-
const radar = base?.radar;
|
|
2693
|
-
const anomalies2 = d?.anomalies ?? [];
|
|
2694
|
-
const tmap = d?.trust_map;
|
|
2695
|
-
const tools = tmap?.tools ?? [];
|
|
2696
|
-
const resources = tmap?.resources ?? [];
|
|
2697
|
-
const listW = 30;
|
|
2698
|
-
return /* @__PURE__ */ jsx7(DataView, { loading: live2.loading && !live2.data, error: live2.error, empty: !!live2.data && agents.length === 0, emptyText: "No identified agents yet.", children: /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2699
|
-
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
|
|
2700
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
|
|
2701
|
-
agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
|
|
2702
|
-
/* @__PURE__ */ jsxs7(Text7, { color: statusColor(a.status), children: [
|
|
2703
|
-
a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
|
|
2704
|
-
" "
|
|
2705
|
-
] }),
|
|
2706
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
|
|
2707
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `t${a.trust_score}` })
|
|
2708
|
-
] }, a.session_id))
|
|
2709
|
-
] }),
|
|
2710
|
-
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2711
|
-
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2712
|
-
/* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
|
|
2713
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
|
|
2714
|
-
] }),
|
|
2715
|
-
base?.characterBlurb ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
|
|
2716
|
-
radar ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2717
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
|
|
2718
|
-
/* @__PURE__ */ jsx7(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
|
|
2719
|
-
/* @__PURE__ */ jsx7(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
|
|
2720
|
-
/* @__PURE__ */ jsx7(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
|
|
2721
|
-
/* @__PURE__ */ jsx7(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
|
|
2722
|
-
/* @__PURE__ */ jsx7(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
|
|
2723
|
-
] }) : null,
|
|
2724
|
-
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2725
|
-
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2726
|
-
"Anomalies ",
|
|
2727
|
-
anomalies2.length ? `(${anomalies2.length})` : ""
|
|
2728
|
-
] }),
|
|
2729
|
-
anomalies2.length === 0 ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2730
|
-
/* @__PURE__ */ jsx7(Text7, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
|
|
2731
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: String(an.kind).padEnd(12) }),
|
|
2732
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
|
|
2733
|
-
] }, i))
|
|
2734
|
-
] }),
|
|
2735
|
-
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2736
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Trust map \xB7 top tools" }),
|
|
2737
|
-
tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2738
|
-
/* @__PURE__ */ jsx7(Text7, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
|
|
2739
|
-
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
|
|
2740
|
-
] }, i)),
|
|
2741
|
-
resources.slice(0, 3).map((r, i) => /* @__PURE__ */ jsx7(Text7, { 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))
|
|
2742
|
-
] })
|
|
2743
|
-
] }) })
|
|
2744
|
-
] }) });
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
2756
|
// src/tui/panels/Settings.tsx
|
|
2748
|
-
import { Box as
|
|
2757
|
+
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
2749
2758
|
import TextInput5 from "ink-text-input";
|
|
2750
|
-
import { useState as
|
|
2751
|
-
import { jsx as
|
|
2759
|
+
import { useState as useState7 } from "react";
|
|
2760
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2752
2761
|
var EVENTS = ["denials", "allowed", "all"];
|
|
2753
2762
|
function alertBodyFor(channel) {
|
|
2754
2763
|
const c2 = channel.trim();
|
|
@@ -2760,12 +2769,12 @@ function alertBodyFor(channel) {
|
|
|
2760
2769
|
function SettingsPanel({ active: active2, focused }) {
|
|
2761
2770
|
void active2;
|
|
2762
2771
|
const { cols, rows } = usePanelSize();
|
|
2763
|
-
const [sel, setSel] =
|
|
2764
|
-
const [editing, setEditing] =
|
|
2765
|
-
const [input, setInput] =
|
|
2766
|
-
const [confirmDel, setConfirmDel] =
|
|
2767
|
-
const [msg, setMsg] =
|
|
2768
|
-
const [busy, setBusy] =
|
|
2772
|
+
const [sel, setSel] = useState7(0);
|
|
2773
|
+
const [editing, setEditing] = useState7(null);
|
|
2774
|
+
const [input, setInput] = useState7("");
|
|
2775
|
+
const [confirmDel, setConfirmDel] = useState7(null);
|
|
2776
|
+
const [msg, setMsg] = useState7(null);
|
|
2777
|
+
const [busy, setBusy] = useState7(false);
|
|
2769
2778
|
const localQ = useLoader(() => api.settings.getLocalLogs());
|
|
2770
2779
|
const whQ = useLoader(() => api.settings.getWebhooks());
|
|
2771
2780
|
const alertQ = useLoader(() => api.settings.getAlerts());
|
|
@@ -2835,7 +2844,7 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2835
2844
|
run("alert rule added", () => api.settings.createAlert(alertBodyFor(v)), alertQ.reload);
|
|
2836
2845
|
}
|
|
2837
2846
|
};
|
|
2838
|
-
|
|
2847
|
+
useInput6(
|
|
2839
2848
|
(inp, key) => {
|
|
2840
2849
|
setMsg(null);
|
|
2841
2850
|
if (key.upArrow) {
|
|
@@ -2874,7 +2883,7 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2874
2883
|
const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
|
|
2875
2884
|
const error = localQ.error ?? whQ.error ?? alertQ.error;
|
|
2876
2885
|
const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
|
|
2877
|
-
const onOff = (on) => on ? /* @__PURE__ */
|
|
2886
|
+
const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
|
|
2878
2887
|
const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
|
|
2879
2888
|
const listBudget = Math.max(4, rows - 8);
|
|
2880
2889
|
const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
|
|
@@ -2882,90 +2891,89 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
2882
2891
|
const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
|
|
2883
2892
|
return i >= start && i < start + listBudget;
|
|
2884
2893
|
};
|
|
2885
|
-
return /* @__PURE__ */
|
|
2886
|
-
/* @__PURE__ */
|
|
2887
|
-
editing ? /* @__PURE__ */
|
|
2888
|
-
/* @__PURE__ */
|
|
2889
|
-
/* @__PURE__ */
|
|
2890
|
-
] }) : msg ? /* @__PURE__ */
|
|
2891
|
-
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */
|
|
2892
|
-
/* @__PURE__ */
|
|
2894
|
+
return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2895
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter/space toggle-edit-add \xB7 e events \xB7 d delete \xB7 r refresh" : "press \u2192 to configure" }),
|
|
2896
|
+
editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
2897
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
|
|
2898
|
+
/* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
|
|
2899
|
+
] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
|
|
2900
|
+
inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2901
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
|
|
2893
2902
|
"LOCAL LOGS ",
|
|
2894
|
-
/* @__PURE__ */
|
|
2903
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
|
|
2895
2904
|
] }),
|
|
2896
|
-
/* @__PURE__ */
|
|
2897
|
-
/* @__PURE__ */
|
|
2898
|
-
/* @__PURE__ */
|
|
2905
|
+
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2906
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
|
|
2907
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
|
|
2899
2908
|
onOff(!!local?.enabled),
|
|
2900
|
-
/* @__PURE__ */
|
|
2909
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
|
|
2901
2910
|
] }),
|
|
2902
|
-
/* @__PURE__ */
|
|
2903
|
-
/* @__PURE__ */
|
|
2904
|
-
/* @__PURE__ */
|
|
2905
|
-
local?.path ? /* @__PURE__ */
|
|
2911
|
+
/* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2912
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
|
|
2913
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
|
|
2914
|
+
local?.path ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "not set \u2014 enter to edit" })
|
|
2906
2915
|
] })
|
|
2907
2916
|
] }) : null,
|
|
2908
|
-
/* @__PURE__ */
|
|
2909
|
-
/* @__PURE__ */
|
|
2917
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2918
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
|
|
2910
2919
|
"WEBHOOKS ",
|
|
2911
|
-
/* @__PURE__ */
|
|
2920
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2912
2921
|
"\u2014 POST every matching event to your endpoint (",
|
|
2913
2922
|
webhooks.length,
|
|
2914
2923
|
")"
|
|
2915
2924
|
] })
|
|
2916
2925
|
] }),
|
|
2917
|
-
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */
|
|
2918
|
-
/* @__PURE__ */
|
|
2926
|
+
webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2927
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
|
|
2919
2928
|
onOff(wh.enabled),
|
|
2920
|
-
/* @__PURE__ */
|
|
2921
|
-
/* @__PURE__ */
|
|
2929
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
|
|
2930
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(wh.url, cols - 16) })
|
|
2922
2931
|
] }, wh.id)),
|
|
2923
|
-
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */
|
|
2924
|
-
/* @__PURE__ */
|
|
2925
|
-
/* @__PURE__ */
|
|
2932
|
+
inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
2933
|
+
/* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
|
|
2934
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
|
|
2926
2935
|
] }) : null
|
|
2927
2936
|
] }),
|
|
2928
|
-
/* @__PURE__ */
|
|
2929
|
-
/* @__PURE__ */
|
|
2937
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2938
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
|
|
2930
2939
|
"ALERTS ",
|
|
2931
|
-
/* @__PURE__ */
|
|
2940
|
+
/* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
|
|
2932
2941
|
"\u2014 notify on a signal spike (",
|
|
2933
2942
|
alerts.length,
|
|
2934
2943
|
")"
|
|
2935
2944
|
] })
|
|
2936
2945
|
] }),
|
|
2937
|
-
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */
|
|
2938
|
-
/* @__PURE__ */
|
|
2946
|
+
alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
|
|
2947
|
+
/* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
|
|
2939
2948
|
onOff(rule.enabled),
|
|
2940
|
-
/* @__PURE__ */
|
|
2941
|
-
/* @__PURE__ */
|
|
2942
|
-
/* @__PURE__ */
|
|
2949
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
|
|
2950
|
+
/* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
|
|
2951
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
|
|
2943
2952
|
] }, rule.id)),
|
|
2944
|
-
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */
|
|
2945
|
-
/* @__PURE__ */
|
|
2946
|
-
/* @__PURE__ */
|
|
2953
|
+
inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
2954
|
+
/* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
|
|
2955
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
|
|
2947
2956
|
] }) : null
|
|
2948
2957
|
] })
|
|
2949
2958
|
] }) });
|
|
2950
2959
|
}
|
|
2951
2960
|
|
|
2952
2961
|
// src/tui/App.tsx
|
|
2953
|
-
import { jsx as
|
|
2962
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2954
2963
|
function LiveHint() {
|
|
2955
|
-
return /* @__PURE__ */
|
|
2956
|
-
/* @__PURE__ */
|
|
2957
|
-
/* @__PURE__ */
|
|
2958
|
-
/* @__PURE__ */
|
|
2959
|
-
/* @__PURE__ */
|
|
2964
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2965
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
|
|
2966
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
|
|
2967
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
|
|
2968
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2960
2969
|
"press ",
|
|
2961
|
-
/* @__PURE__ */
|
|
2970
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
|
|
2962
2971
|
" to go live"
|
|
2963
2972
|
] }) })
|
|
2964
2973
|
] });
|
|
2965
2974
|
}
|
|
2966
2975
|
var SECTIONS = [
|
|
2967
2976
|
{ label: "Live", Panel: LiveHint },
|
|
2968
|
-
{ label: "Agents", Panel: AgentsPanel },
|
|
2969
2977
|
{ label: "Policies", Panel: PoliciesPanel },
|
|
2970
2978
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
2971
2979
|
{ label: "DLP", Panel: DlpPanel },
|
|
@@ -2976,22 +2984,22 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
|
|
|
2976
2984
|
function Banner({ cols }) {
|
|
2977
2985
|
const wide = cols >= 82;
|
|
2978
2986
|
if (!wide) {
|
|
2979
|
-
return /* @__PURE__ */
|
|
2980
|
-
/* @__PURE__ */
|
|
2981
|
-
/* @__PURE__ */
|
|
2987
|
+
return /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2988
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
2989
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
|
|
2982
2990
|
] });
|
|
2983
2991
|
}
|
|
2984
|
-
return /* @__PURE__ */
|
|
2985
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
2986
|
-
/* @__PURE__ */
|
|
2992
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2993
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
2994
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
2987
2995
|
] });
|
|
2988
2996
|
}
|
|
2989
2997
|
function App() {
|
|
2990
2998
|
const { exit } = useApp();
|
|
2991
|
-
const [section, setSection] =
|
|
2992
|
-
const [focus, setFocus] =
|
|
2993
|
-
const [help, setHelp] =
|
|
2994
|
-
|
|
2999
|
+
const [section, setSection] = useState8(0);
|
|
3000
|
+
const [focus, setFocus] = useState8("nav");
|
|
3001
|
+
const [help, setHelp] = useState8(false);
|
|
3002
|
+
useInput7((input, key) => {
|
|
2995
3003
|
if (help) {
|
|
2996
3004
|
setHelp(false);
|
|
2997
3005
|
return;
|
|
@@ -3012,18 +3020,18 @@ function App() {
|
|
|
3012
3020
|
});
|
|
3013
3021
|
const { cols, rows } = useTermSize();
|
|
3014
3022
|
const current = SECTIONS[section];
|
|
3015
|
-
if (help) return /* @__PURE__ */
|
|
3023
|
+
if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
|
|
3016
3024
|
if (current.label === "Live" && focus === "panel") {
|
|
3017
|
-
return /* @__PURE__ */
|
|
3025
|
+
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
|
|
3018
3026
|
}
|
|
3019
3027
|
const Panel = current.Panel;
|
|
3020
|
-
return /* @__PURE__ */
|
|
3021
|
-
/* @__PURE__ */
|
|
3022
|
-
/* @__PURE__ */
|
|
3023
|
-
/* @__PURE__ */
|
|
3024
|
-
/* @__PURE__ */
|
|
3028
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
3029
|
+
/* @__PURE__ */ jsx8(Banner, { cols }),
|
|
3030
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
|
|
3031
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
|
|
3032
|
+
/* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
|
|
3025
3033
|
] }),
|
|
3026
|
-
/* @__PURE__ */
|
|
3034
|
+
/* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
3027
3035
|
] });
|
|
3028
3036
|
}
|
|
3029
3037
|
var HELP = [
|
|
@@ -3031,25 +3039,25 @@ var HELP = [
|
|
|
3031
3039
|
["Live", [["\u2191\u2193", "select a stream row"], ["enter", "full entry content"], ["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)"]]],
|
|
3032
3040
|
["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["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"], ["s", "save"], ["x", "discard"]]],
|
|
3033
3041
|
["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"]]],
|
|
3034
|
-
["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["
|
|
3042
|
+
["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["x / X", "delete entry / ALL (press twice)"], ["c", "clear filters"]]],
|
|
3035
3043
|
["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
|
|
3036
3044
|
];
|
|
3037
3045
|
function HelpOverlay({ cols, rows }) {
|
|
3038
|
-
return /* @__PURE__ */
|
|
3039
|
-
/* @__PURE__ */
|
|
3040
|
-
/* @__PURE__ */
|
|
3041
|
-
/* @__PURE__ */
|
|
3042
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
3043
|
-
/* @__PURE__ */
|
|
3044
|
-
/* @__PURE__ */
|
|
3046
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
3047
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
3048
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
|
|
3049
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
|
|
3050
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
3051
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
3052
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
|
|
3045
3053
|
] }, k))
|
|
3046
3054
|
] }, group)) }),
|
|
3047
|
-
/* @__PURE__ */
|
|
3055
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
|
|
3048
3056
|
] });
|
|
3049
3057
|
}
|
|
3050
3058
|
|
|
3051
3059
|
// src/tui/index.tsx
|
|
3052
|
-
import { jsx as
|
|
3060
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
3053
3061
|
async function launchTui() {
|
|
3054
3062
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
3055
3063
|
process.stderr.write(
|
|
@@ -3063,7 +3071,7 @@ async function launchTui() {
|
|
|
3063
3071
|
}
|
|
3064
3072
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
3065
3073
|
try {
|
|
3066
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
3074
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
|
|
3067
3075
|
await waitUntilExit();
|
|
3068
3076
|
} finally {
|
|
3069
3077
|
process.stdout.write("\x1B[?1049l");
|