@solongate/proxy 0.61.0 → 0.62.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/index.js CHANGED
@@ -7018,18 +7018,102 @@ var init_hooks = __esm({
7018
7018
  }
7019
7019
  });
7020
7020
 
7021
+ // src/tui/panels/Live.tsx
7022
+ import { Box as Box2, Text as Text2 } from "ink";
7023
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7024
+ function Tile({ label, value, color }) {
7025
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, marginRight: 1, minWidth: 14, children: [
7026
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label }),
7027
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color, children: value })
7028
+ ] });
7029
+ }
7030
+ function LivePanel({ active: active2 }) {
7031
+ const stats = useLoader(() => api.stats.get());
7032
+ const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
7033
+ const feed = useLoader(() => api.audit.list({ limit: 12 }));
7034
+ usePoll(() => {
7035
+ stats.reload();
7036
+ ts.reload();
7037
+ feed.reload();
7038
+ }, 2e3, active2);
7039
+ const s = stats.data;
7040
+ const points = ts.data?.timeseries ?? [];
7041
+ const entries = feed.data?.entries ?? [];
7042
+ const denyRate = s && s.total_calls > 0 ? Math.round(s.denied / s.total_calls * 100) : 0;
7043
+ return /* @__PURE__ */ jsx2(DataView, { loading: stats.loading && !s, error: stats.error, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
7044
+ /* @__PURE__ */ jsxs2(Box2, { children: [
7045
+ /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "\u25CF" }),
7046
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " LIVE \xB7 refreshing every 2s" })
7047
+ ] }),
7048
+ s ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
7049
+ /* @__PURE__ */ jsx2(Tile, { label: "total calls", value: String(s.total_calls) }),
7050
+ /* @__PURE__ */ jsx2(Tile, { label: "allowed", value: String(s.allowed), color: theme.ok }),
7051
+ /* @__PURE__ */ jsx2(Tile, { label: "denied", value: String(s.denied), color: theme.bad }),
7052
+ /* @__PURE__ */ jsx2(Tile, { label: "deny rate", value: denyRate + "%", color: denyRate > 10 ? theme.bad : void 0 }),
7053
+ /* @__PURE__ */ jsx2(Tile, { label: "policies", value: String(s.active_policies), color: theme.accent })
7054
+ ] }) : null,
7055
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
7056
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "Activity \xB7 24h" }),
7057
+ /* @__PURE__ */ jsxs2(Box2, { children: [
7058
+ /* @__PURE__ */ jsx2(Text2, { children: "calls " }),
7059
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sparkline(points.map((p) => p.total), 60) })
7060
+ ] }),
7061
+ /* @__PURE__ */ jsxs2(Box2, { children: [
7062
+ /* @__PURE__ */ jsx2(Text2, { children: "denied " }),
7063
+ /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 60) })
7064
+ ] })
7065
+ ] }),
7066
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
7067
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "Live feed" }),
7068
+ entries.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " waiting for tool calls\u2026" }) : entries.slice(0, 10).map((e) => /* @__PURE__ */ jsxs2(Box2, { children: [
7069
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), children: e.decision.padEnd(8) }),
7070
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool_name, 16).padEnd(17) }),
7071
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent_name ?? "\u2014", 14).padEnd(15) }),
7072
+ e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP " }) : /* @__PURE__ */ jsx2(Text2, { children: " " }),
7073
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7074
+ ago(e.created_at),
7075
+ " ago"
7076
+ ] })
7077
+ ] }, e.id))
7078
+ ] })
7079
+ ] }) });
7080
+ }
7081
+ var init_Live = __esm({
7082
+ "src/tui/panels/Live.tsx"() {
7083
+ "use strict";
7084
+ init_api_client();
7085
+ init_components();
7086
+ init_hooks();
7087
+ init_theme();
7088
+ }
7089
+ });
7090
+
7021
7091
  // src/tui/panels/Policies.tsx
7022
- import { Box as Box2, Text as Text2, useInput } from "ink";
7092
+ import { Box as Box3, Text as Text3, useInput } from "ink";
7093
+ import TextInput from "ink-text-input";
7023
7094
  import { useEffect as useEffect2, useState as useState2 } from "react";
7024
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7095
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
7096
+ function ruleSummary(r) {
7097
+ const bits = [];
7098
+ for (const [g, tag] of [["commandConstraints", "cmd"], ["pathConstraints", "path"], ["filenameConstraints", "file"], ["urlConstraints", "url"]]) {
7099
+ const c2 = r[g];
7100
+ const n = (c2?.allowed?.length ?? 0) + (c2?.denied?.length ?? 0);
7101
+ if (n) bits.push(`${tag}:${n}`);
7102
+ }
7103
+ return bits.join(" ");
7104
+ }
7025
7105
  function PoliciesPanel({ focused }) {
7026
7106
  const list4 = useLoader(() => api.policies.list());
7027
7107
  const policies = list4.data?.policies ?? [];
7028
7108
  const [view, setView] = useState2("list");
7029
7109
  const [pi, setPi] = useState2(0);
7030
7110
  const [ri, setRi] = useState2(0);
7031
- const [rules, setRules] = useState2(null);
7111
+ const [fi, setFi] = useState2(0);
7112
+ const [rules, setRules] = useState2([]);
7032
7113
  const [mode, setMode] = useState2("denylist");
7114
+ const [dirty, setDirty] = useState2(false);
7115
+ const [editing, setEditing] = useState2(false);
7116
+ const [editVal, setEditVal] = useState2("");
7033
7117
  const [status, setStatus] = useState2(null);
7034
7118
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
7035
7119
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
@@ -7037,28 +7121,44 @@ function PoliciesPanel({ focused }) {
7037
7121
  if (detail.data) {
7038
7122
  setRules(detail.data.rules);
7039
7123
  setMode(detail.data.mode ?? "denylist");
7124
+ setDirty(false);
7040
7125
  setRi(0);
7041
7126
  }
7042
7127
  }, [detail.data]);
7043
- const persist = async (nextRules, nextMode) => {
7044
- if (!detail.data || !selected) return;
7128
+ const mutate = (nextRules, nextMode = mode) => {
7129
+ setRules(nextRules);
7130
+ setMode(nextMode);
7131
+ setDirty(true);
7132
+ setStatus(null);
7133
+ };
7134
+ const save = async () => {
7135
+ if (!selected || !detail.data) return;
7045
7136
  setStatus("Saving\u2026");
7046
7137
  try {
7047
7138
  const res = await api.policies.update(selected.id, {
7048
7139
  id: selected.id,
7049
7140
  name: detail.data.name,
7050
7141
  description: detail.data.description,
7051
- mode: nextMode,
7142
+ mode,
7052
7143
  agents: detail.data.agents,
7053
- rules: nextRules
7144
+ rules
7054
7145
  });
7055
7146
  setRules(res.rules);
7147
+ setDirty(false);
7056
7148
  setStatus("\u2713 Saved v" + res._version);
7057
7149
  list4.reload();
7058
7150
  } catch (e) {
7059
7151
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
7060
7152
  }
7061
7153
  };
7154
+ const discard = () => {
7155
+ if (detail.data) {
7156
+ setRules(detail.data.rules);
7157
+ setMode(detail.data.mode ?? "denylist");
7158
+ }
7159
+ setDirty(false);
7160
+ setStatus("discarded");
7161
+ };
7062
7162
  useInput(
7063
7163
  (input, key) => {
7064
7164
  if (view === "list") {
@@ -7066,44 +7166,61 @@ function PoliciesPanel({ focused }) {
7066
7166
  else if (key.downArrow) setPi((n) => Math.min(policies.length - 1, n + 1));
7067
7167
  else if (key.return || key.rightArrow) {
7068
7168
  setStatus(null);
7069
- setView("detail");
7169
+ setView("rules");
7070
7170
  }
7071
7171
  return;
7072
7172
  }
7073
- const rs2 = rules ?? [];
7074
- if (key.leftArrow) {
7075
- setView("list");
7076
- setStatus(null);
7077
- } else if (key.upArrow) setRi((n) => Math.max(0, n - 1));
7078
- else if (key.downArrow) setRi((n) => Math.min(rs2.length - 1, n + 1));
7079
- else if (input === " " || input === "e") {
7080
- if (!rs2[ri]) return;
7081
- const next = rs2.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r);
7082
- setRules(next);
7083
- void persist(next, mode);
7084
- } else if (input === "d") {
7085
- const target = rs2[ri];
7086
- if (!target || !selected) return;
7087
- setStatus("Deleting\u2026");
7088
- void api.policies.revokeRule(selected.id, target.id).then(() => {
7089
- setStatus("\u2713 Deleted");
7090
- detail.reload();
7091
- list4.reload();
7092
- }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
7093
- } else if (input === "m") {
7094
- const nm = mode === "denylist" ? "whitelist" : "denylist";
7095
- setMode(nm);
7096
- void persist(rs2, nm);
7173
+ if (view === "rules") {
7174
+ if (key.leftArrow) return setView("list"), void setStatus(null);
7175
+ if (key.upArrow) setRi((n) => Math.max(0, n - 1));
7176
+ else if (key.downArrow) setRi((n) => Math.min(rules.length - 1, n + 1));
7177
+ else if (key.return || key.rightArrow) {
7178
+ if (rules[ri]) {
7179
+ setFi(0);
7180
+ setView("rule");
7181
+ }
7182
+ } else if (input === " ") {
7183
+ if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
7184
+ } else if (input === "e") {
7185
+ if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
7186
+ } else if (input === "d") {
7187
+ if (rules[ri]) {
7188
+ const next = rules.filter((_, i) => i !== ri);
7189
+ setRi((n) => Math.max(0, Math.min(n, next.length - 1)));
7190
+ mutate(next);
7191
+ }
7192
+ } else if (input === "m") {
7193
+ mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
7194
+ } else if (input === "s") void save();
7195
+ else if (input === "x") discard();
7196
+ return;
7097
7197
  }
7198
+ const rule2 = rules[ri];
7199
+ if (!rule2) return setView("rules");
7200
+ const field = FIELDS[fi];
7201
+ if (key.leftArrow) setView("rules");
7202
+ else if (key.upArrow) setFi((n) => Math.max(0, n - 1));
7203
+ else if (key.downArrow) setFi((n) => Math.min(FIELDS.length - 1, n + 1));
7204
+ else if (field.kind === "effect" && (input === " " || key.rightArrow)) {
7205
+ mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
7206
+ } else if (field.kind === "enabled" && (input === " " || key.rightArrow)) {
7207
+ mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
7208
+ } else if (field.kind === "priority" && (key.rightArrow || key.leftArrow)) {
7209
+ const d = key.rightArrow ? 1 : -1;
7210
+ mutate(rules.map((r, i) => i === ri ? { ...r, priority: Math.max(0, r.priority + d) } : r));
7211
+ } else if (field.kind === "text" && key.return) {
7212
+ setEditVal(field.get(rule2));
7213
+ setEditing(true);
7214
+ } else if (input === "s") void save();
7098
7215
  },
7099
- { isActive: focused }
7216
+ { isActive: focused && !editing }
7100
7217
  );
7101
7218
  if (view === "list") {
7102
- return /* @__PURE__ */ jsx2(DataView, { loading: list4.loading && !list4.data, error: list4.error, empty: !!list4.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
7103
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
7104
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
7219
+ return /* @__PURE__ */ jsx3(DataView, { loading: list4.loading && !list4.data, error: list4.error, empty: !!list4.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
7220
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
7221
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs3(Text3, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
7105
7222
  (i === pi ? "\u25B8 " : " ") + truncate2(p.name, 26).padEnd(27),
7106
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7223
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
7107
7224
  p.mode.padEnd(10),
7108
7225
  " ",
7109
7226
  p.rules.length,
@@ -7113,37 +7230,78 @@ function PoliciesPanel({ focused }) {
7113
7230
  ] }, p.id)) })
7114
7231
  ] }) });
7115
7232
  }
7116
- const rs = rules ?? [];
7117
- return /* @__PURE__ */ jsx2(DataView, { loading: detail.loading && !rules, error: detail.error, children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
7118
- /* @__PURE__ */ jsxs2(Box2, { children: [
7119
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: truncate2(selected?.name ?? "", 30) }),
7120
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " mode: " }),
7121
- /* @__PURE__ */ jsx2(Text2, { color: mode === "whitelist" ? theme.ok : void 0, children: mode })
7233
+ const dirtyTag = dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
7234
+ if (view === "rules") {
7235
+ return /* @__PURE__ */ jsx3(DataView, { loading: detail.loading && !detail.data, error: detail.error, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
7236
+ /* @__PURE__ */ jsxs3(Box3, { children: [
7237
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate2(selected?.name ?? "", 28) }),
7238
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " mode: " }),
7239
+ /* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
7240
+ dirtyTag
7241
+ ] }),
7242
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 rule \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 d delete \xB7 m mode \xB7 s save \xB7 \u2190 back" }),
7243
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
7244
+ Table,
7245
+ {
7246
+ columns: [
7247
+ { header: "", width: 2 },
7248
+ { header: "ON", width: 2 },
7249
+ { header: "EFFECT", width: 7 },
7250
+ { header: "PRIO", width: 4 },
7251
+ { header: "TOOL", width: 20 },
7252
+ { header: "CONSTRAINTS", width: 22 }
7253
+ ],
7254
+ rows: rules.map((r, i) => [
7255
+ { value: i === ri ? "\u25B8" : "", color: theme.accentBright },
7256
+ { value: r.enabled ? "\u25CF" : "\u25CB", color: r.enabled ? theme.ok : theme.dim },
7257
+ { value: r.effect, color: r.effect === "ALLOW" ? theme.ok : theme.bad, dim: !r.enabled },
7258
+ { value: String(r.priority), dim: true },
7259
+ { value: truncate2(r.toolPattern, 20), color: theme.accent, dim: !r.enabled },
7260
+ { value: ruleSummary(r) || truncate2(r.description || "\u2014", 22), dim: true }
7261
+ ])
7262
+ }
7263
+ ) }),
7264
+ rules.length === 0 ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "(no rules)" }) : null,
7265
+ status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
7266
+ ] }) });
7267
+ }
7268
+ const rule = rules[ri];
7269
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
7270
+ /* @__PURE__ */ jsxs3(Box3, { children: [
7271
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Rule " }),
7272
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: truncate2(rule?.id ?? "", 26) }),
7273
+ dirtyTag
7122
7274
  ] }),
7123
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2191\u2193 rule \xB7 space toggle \xB7 d delete \xB7 m mode \xB7 \u2190 back" }),
7124
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(
7125
- Table,
7126
- {
7127
- columns: [
7128
- { header: "", width: 2 },
7129
- { header: "EFFECT", width: 7 },
7130
- { header: "PRIO", width: 4 },
7131
- { header: "TOOL", width: 20 },
7132
- { header: "DESCRIPTION", width: 34 }
7133
- ],
7134
- rows: rs.map((r, i) => [
7135
- { value: i === ri ? "\u25B8" : "", color: theme.accentBright },
7136
- { value: r.effect, color: r.effect === "ALLOW" ? theme.ok : theme.bad, dim: !r.enabled },
7137
- { value: String(r.priority), dim: true },
7138
- { value: (r.enabled ? "" : "\u2298 ") + truncate2(r.toolPattern, 18), color: theme.accent, dim: !r.enabled },
7139
- { value: truncate2(r.description || "\u2014", 34), dim: !r.enabled }
7140
- ])
7141
- }
7142
- ) }),
7143
- rs.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "(no rules)" }) : null,
7144
- status ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
7145
- ] }) });
7275
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit text \xB7 space/\u2192 toggle \xB7 s save \xB7 \u2190 back" }),
7276
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
7277
+ const val = rule ? f.get(rule) : "";
7278
+ const active2 = i === fi;
7279
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
7280
+ /* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
7281
+ active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
7282
+ TextInput,
7283
+ {
7284
+ value: editVal,
7285
+ onChange: setEditVal,
7286
+ onSubmit: (v) => {
7287
+ if (rule && f.set) mutate(rules.map((r, idx) => idx === ri ? f.set(r, v) : r));
7288
+ setEditing(false);
7289
+ }
7290
+ }
7291
+ ) : /* @__PURE__ */ jsx3(
7292
+ Text3,
7293
+ {
7294
+ color: f.kind === "effect" ? val === "ALLOW" ? theme.ok : theme.bad : void 0,
7295
+ dimColor: !val,
7296
+ children: val || "\u2014"
7297
+ }
7298
+ )
7299
+ ] }, f.label);
7300
+ }) }),
7301
+ status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
7302
+ ] });
7146
7303
  }
7304
+ var constr, setConstr, FIELDS;
7147
7305
  var init_Policies = __esm({
7148
7306
  "src/tui/panels/Policies.tsx"() {
7149
7307
  "use strict";
@@ -7151,13 +7309,43 @@ var init_Policies = __esm({
7151
7309
  init_components();
7152
7310
  init_hooks();
7153
7311
  init_theme();
7312
+ constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
7313
+ setConstr = (r, g, side, val) => {
7314
+ const arr = val.split(",").map((s) => s.trim()).filter(Boolean);
7315
+ const prev = r[g] ?? {};
7316
+ return { ...r, [g]: { ...prev, [side]: arr } };
7317
+ };
7318
+ FIELDS = [
7319
+ { label: "Effect", kind: "effect", get: (r) => r.effect },
7320
+ { label: "Enabled", kind: "enabled", get: (r) => r.enabled ? "yes" : "no" },
7321
+ { label: "Priority", kind: "priority", get: (r) => String(r.priority) },
7322
+ { label: "Tool pattern", kind: "text", get: (r) => r.toolPattern, set: (r, v) => ({ ...r, toolPattern: v || "*" }) },
7323
+ { label: "Description", kind: "text", get: (r) => r.description ?? "", set: (r, v) => ({ ...r, description: v }) },
7324
+ { label: "Cmd allow", kind: "text", get: (r) => constr(r, "commandConstraints", "allowed"), set: (r, v) => setConstr(r, "commandConstraints", "allowed", v) },
7325
+ { label: "Cmd deny", kind: "text", get: (r) => constr(r, "commandConstraints", "denied"), set: (r, v) => setConstr(r, "commandConstraints", "denied", v) },
7326
+ { label: "Path allow", kind: "text", get: (r) => constr(r, "pathConstraints", "allowed"), set: (r, v) => setConstr(r, "pathConstraints", "allowed", v) },
7327
+ { label: "Path deny", kind: "text", get: (r) => constr(r, "pathConstraints", "denied"), set: (r, v) => setConstr(r, "pathConstraints", "denied", v) },
7328
+ { label: "File allow", kind: "text", get: (r) => constr(r, "filenameConstraints", "allowed"), set: (r, v) => setConstr(r, "filenameConstraints", "allowed", v) },
7329
+ { label: "File deny", kind: "text", get: (r) => constr(r, "filenameConstraints", "denied"), set: (r, v) => setConstr(r, "filenameConstraints", "denied", v) },
7330
+ { label: "URL allow", kind: "text", get: (r) => constr(r, "urlConstraints", "allowed"), set: (r, v) => setConstr(r, "urlConstraints", "allowed", v) },
7331
+ { label: "URL deny", kind: "text", get: (r) => constr(r, "urlConstraints", "denied"), set: (r, v) => setConstr(r, "urlConstraints", "denied", v) }
7332
+ ];
7154
7333
  }
7155
7334
  });
7156
7335
 
7157
7336
  // src/tui/panels/RateLimit.tsx
7158
- import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
7337
+ import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
7159
7338
  import { useEffect as useEffect3, useState as useState3 } from "react";
7160
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
7339
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
7340
+ function ColoredSpark({ values, limit, width: width2 = 60 }) {
7341
+ const v = values.length > width2 ? values.slice(values.length - width2) : values;
7342
+ const max = Math.max(1, ...v);
7343
+ return /* @__PURE__ */ jsx4(Text4, { children: v.map((n, i) => {
7344
+ const ch = BLOCKS2[Math.min(BLOCKS2.length - 1, Math.round(n / max * (BLOCKS2.length - 1)))];
7345
+ const over = limit > 0 && n > limit;
7346
+ return /* @__PURE__ */ jsx4(Text4, { color: over ? theme.bad : theme.accent, children: ch }, i);
7347
+ }) });
7348
+ }
7161
7349
  function RateLimitPanel({ focused }) {
7162
7350
  const layersQ = useLoader(() => api.settings.getSecurityLayers());
7163
7351
  const historyQ = useLoader(() => api.settings.getRateLimitHistory());
@@ -7171,7 +7359,7 @@ function RateLimitPanel({ focused }) {
7171
7359
  }, [layersQ.data, draft]);
7172
7360
  const adjust = (dir, step) => {
7173
7361
  if (!draft) return;
7174
- const field = FIELDS[fi];
7362
+ const field = FIELDS2[fi];
7175
7363
  if (field === "mode") {
7176
7364
  const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
7177
7365
  setDraft({ ...draft, mode: MODES[idx] });
@@ -7185,8 +7373,7 @@ function RateLimitPanel({ focused }) {
7185
7373
  if (!draft || !layersQ.data) return;
7186
7374
  setStatus("Saving\u2026");
7187
7375
  try {
7188
- const next = { ...layersQ.data.layers, rateLimit: draft };
7189
- const res = await api.settings.setSecurityLayers(next);
7376
+ const res = await api.settings.setSecurityLayers({ ...layersQ.data.layers, rateLimit: draft });
7190
7377
  setDraft({ ...res.layers.rateLimit });
7191
7378
  setDirty(false);
7192
7379
  setStatus("\u2713 Saved");
@@ -7198,8 +7385,8 @@ function RateLimitPanel({ focused }) {
7198
7385
  useInput2(
7199
7386
  (input, key) => {
7200
7387
  const step = key.shift ? 10 : 1;
7201
- if (key.upArrow) setFi((n) => (n - 1 + FIELDS.length) % FIELDS.length);
7202
- else if (key.downArrow) setFi((n) => (n + 1) % FIELDS.length);
7388
+ if (key.upArrow) setFi((n) => (n - 1 + FIELDS2.length) % FIELDS2.length);
7389
+ else if (key.downArrow) setFi((n) => (n + 1) % FIELDS2.length);
7203
7390
  else if (key.leftArrow) adjust(-1, step);
7204
7391
  else if (key.rightArrow) adjust(1, step);
7205
7392
  else if (input === "s") void save();
@@ -7207,77 +7394,72 @@ function RateLimitPanel({ focused }) {
7207
7394
  { isActive: focused }
7208
7395
  );
7209
7396
  const history = (historyQ.data?.history ?? []).slice().sort((a, b) => b.ts - a.ts);
7210
- const bursts = (insightsQ.data?.["anomalies"] ?? []).slice(0, 6);
7211
- return /* @__PURE__ */ jsx3(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
7212
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
7213
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
7214
- /* @__PURE__ */ jsx3(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx3(Text3, { color: modeColor(draft.mode), children: draft.mode }) }),
7215
- /* @__PURE__ */ jsx3(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perMinute }) }),
7216
- /* @__PURE__ */ jsx3(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
7217
- /* @__PURE__ */ jsx3(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
7397
+ const activity = insightsQ.data?.["activity"] ?? {};
7398
+ const minuteSeries = (activity.minute ?? []).map((b) => b.count);
7399
+ const hourSeries = (activity.hour ?? []).map((b) => b.count);
7400
+ const bursts = minuteSeries.filter((c2) => draft && draft.perMinute > 0 && c2 > draft.perMinute).length;
7401
+ return /* @__PURE__ */ jsx4(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
7402
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
7403
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
7404
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), children: draft.mode }) }),
7405
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perMinute }) }),
7406
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
7407
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
7218
7408
  ] }),
7219
- dirty || status ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
7220
- dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
7221
- status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
7409
+ dirty || status ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
7410
+ dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
7411
+ status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
7222
7412
  ] }) : null,
7223
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
7224
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
7413
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
7414
+ /* @__PURE__ */ jsxs4(Box4, { children: [
7415
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Activity " }),
7416
+ bursts > 0 ? /* @__PURE__ */ jsxs4(Text4, { color: theme.bad, children: [
7417
+ bursts,
7418
+ " burst min"
7419
+ ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no bursts" }),
7420
+ insightsQ.loading && !insightsQ.data ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " \u2026" }) : null
7421
+ ] }),
7422
+ /* @__PURE__ */ jsxs4(Box4, { children: [
7423
+ /* @__PURE__ */ jsx4(Text4, { children: "/min " }),
7424
+ minuteSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: minuteSeries, limit: draft.perMinute }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
7425
+ ] }),
7426
+ /* @__PURE__ */ jsxs4(Box4, { children: [
7427
+ /* @__PURE__ */ jsx4(Text4, { children: "/hour " }),
7428
+ hourSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: hourSeries, limit: draft.perHour, width: 24 }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
7429
+ ] })
7430
+ ] }),
7431
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
7432
+ /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
7225
7433
  "Change history ",
7226
7434
  history.length ? `(${history.length})` : ""
7227
7435
  ] }),
7228
- history.length ? /* @__PURE__ */ jsx3(
7436
+ history.length ? /* @__PURE__ */ jsx4(
7229
7437
  Table,
7230
7438
  {
7231
7439
  columns: [
7232
- { header: "WHEN", width: 8 },
7440
+ { header: "WHEN", width: 10 },
7233
7441
  { header: "/MIN", width: 6 },
7234
7442
  { header: "/HOUR", width: 7 },
7235
7443
  { header: "/DAY", width: 6 }
7236
7444
  ],
7237
- rows: history.slice(0, 5).map((h, i) => [
7445
+ rows: history.slice(0, 4).map((h, i) => [
7238
7446
  { value: ago(h.ts) + " ago", dim: true, bold: i === 0 },
7239
7447
  { value: String(h.minute), bold: i === 0 },
7240
7448
  { value: h.hour === 0 ? "off" : String(h.hour), dim: h.hour === 0 },
7241
7449
  { value: h.day === 0 ? "off" : String(h.day), dim: h.day === 0 }
7242
7450
  ])
7243
7451
  }
7244
- ) : /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " no changes" })
7245
- ] }),
7246
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
7247
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
7248
- "Recent bursts ",
7249
- insightsQ.loading && !insightsQ.data ? "\u2026" : `(${bursts.length})`
7250
- ] }),
7251
- bursts.length ? /* @__PURE__ */ jsx3(
7252
- Table,
7253
- {
7254
- columns: [
7255
- { header: "RESULT", width: 9 },
7256
- { header: "AGENT", width: 16 },
7257
- { header: "CALLS/LIMIT", width: 12 },
7258
- { header: "WHEN", width: 8 }
7259
- ],
7260
- rows: bursts.map((b) => [
7261
- { value: b.blocked ? "blocked" : "BYPASSED", color: b.blocked ? theme.accent : theme.bad, bold: !b.blocked },
7262
- { value: (b.agent || "\u2014").slice(0, 16), dim: true },
7263
- { value: `${b.count}/${b.limit}` },
7264
- { value: ago(b.minute) + " ago", dim: true }
7265
- ])
7266
- }
7267
- ) : /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
7268
- " ",
7269
- insightsQ.error ? insightsQ.error : "none in 7d"
7270
- ] })
7452
+ ) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " no changes" })
7271
7453
  ] })
7272
7454
  ] }) : null });
7273
7455
  }
7274
7456
  function FieldRow({ label, active: active2, children }) {
7275
- return /* @__PURE__ */ jsxs3(Box3, { children: [
7276
- /* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
7457
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
7458
+ /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
7277
7459
  children
7278
7460
  ] });
7279
7461
  }
7280
- var MODES, FIELDS;
7462
+ var MODES, FIELDS2, BLOCKS2;
7281
7463
  var init_RateLimit = __esm({
7282
7464
  "src/tui/panels/RateLimit.tsx"() {
7283
7465
  "use strict";
@@ -7286,81 +7468,138 @@ var init_RateLimit = __esm({
7286
7468
  init_hooks();
7287
7469
  init_theme();
7288
7470
  MODES = ["off", "detect", "block"];
7289
- FIELDS = ["mode", "perMinute", "perHour", "perDay"];
7471
+ FIELDS2 = ["mode", "perMinute", "perHour", "perDay"];
7472
+ BLOCKS2 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
7290
7473
  }
7291
7474
  });
7292
7475
 
7293
7476
  // src/tui/panels/Dlp.tsx
7294
- import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
7477
+ import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
7478
+ import TextInput2 from "ink-text-input";
7295
7479
  import { useEffect as useEffect4, useState as useState4 } from "react";
7296
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
7480
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
7297
7481
  function DlpPanel({ focused }) {
7298
7482
  const q = useLoader(() => api.settings.getSecurityLayers());
7299
7483
  const [dlp, setDlp] = useState4(null);
7300
7484
  const [available, setAvailable] = useState4([]);
7301
7485
  const [sel, setSel] = useState4(0);
7486
+ const [dirty, setDirty] = useState4(false);
7302
7487
  const [status, setStatus] = useState4(null);
7488
+ const [adding, setAdding] = useState4(null);
7489
+ const [newName, setNewName] = useState4("");
7490
+ const [newRe, setNewRe] = useState4("");
7303
7491
  useEffect4(() => {
7304
7492
  if (q.data && !dlp) {
7305
- setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns] });
7493
+ setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
7306
7494
  setAvailable(q.data.availablePatterns);
7307
7495
  }
7308
7496
  }, [q.data, dlp]);
7309
- const persist = async (nextDlp) => {
7310
- if (!q.data) return;
7311
- setDlp(nextDlp);
7497
+ const mutate = (next) => {
7498
+ setDlp(next);
7499
+ setDirty(true);
7500
+ setStatus(null);
7501
+ };
7502
+ const save = async () => {
7503
+ if (!dlp || !q.data) return;
7312
7504
  setStatus("Saving\u2026");
7313
7505
  try {
7314
- const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp: nextDlp });
7315
- setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns] });
7506
+ const res = await api.settings.setSecurityLayers({ ...q.data.layers, dlp });
7507
+ setDlp({ ...res.layers.dlp, patterns: [...res.layers.dlp.patterns], custom: [...res.layers.dlp.custom] });
7508
+ setDirty(false);
7316
7509
  setStatus("\u2713 Saved");
7317
7510
  } catch (e) {
7318
7511
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
7319
7512
  }
7320
7513
  };
7514
+ const discard = () => {
7515
+ if (q.data) setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
7516
+ setDirty(false);
7517
+ setStatus("discarded");
7518
+ };
7519
+ const customCount = dlp?.custom.length ?? 0;
7520
+ const total = available.length + customCount;
7321
7521
  useInput3(
7322
7522
  (input, key) => {
7323
7523
  if (!dlp) return;
7324
7524
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
7325
- else if (key.downArrow) setSel((n) => Math.min(available.length - 1, n + 1));
7326
- else if (input === " " || key.return) {
7525
+ else if (key.downArrow) setSel((n) => Math.min(total - 1, n + 1));
7526
+ else if (input === "m") {
7527
+ const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
7528
+ mutate({ ...dlp, mode: MODES2[idx] });
7529
+ } else if (input === "a") {
7530
+ setNewName("");
7531
+ setNewRe("");
7532
+ setAdding("name");
7533
+ } else if (input === " " && sel < available.length) {
7327
7534
  const p = available[sel];
7328
- if (!p) return;
7329
7535
  const set = new Set(dlp.patterns);
7330
7536
  if (set.has(p)) set.delete(p);
7331
7537
  else set.add(p);
7332
- void persist({ ...dlp, patterns: [...set] });
7333
- } else if (input === "m") {
7334
- const idx = (MODES2.indexOf(dlp.mode) + 1) % MODES2.length;
7335
- void persist({ ...dlp, mode: MODES2[idx] });
7336
- }
7538
+ mutate({ ...dlp, patterns: [...set] });
7539
+ } else if (input === "d" && sel >= available.length) {
7540
+ const ci = sel - available.length;
7541
+ mutate({ ...dlp, custom: dlp.custom.filter((_, i) => i !== ci) });
7542
+ setSel((n) => Math.max(0, n - 1));
7543
+ } else if (input === "s") void save();
7544
+ else if (input === "x") discard();
7337
7545
  },
7338
- { isActive: focused }
7546
+ { isActive: focused && !adding }
7339
7547
  );
7340
7548
  const enabled = new Set(dlp?.patterns ?? []);
7341
- return /* @__PURE__ */ jsx4(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
7342
- /* @__PURE__ */ jsxs4(Box4, { children: [
7343
- /* @__PURE__ */ jsx4(Text4, { children: "mode: " }),
7344
- /* @__PURE__ */ jsx4(Text4, { color: modeColor(dlp.mode), children: dlp.mode }),
7345
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: focused ? " \u2191\u2193 move \xB7 space toggle \xB7 m mode" : " press \u2192 to edit" })
7549
+ return /* @__PURE__ */ jsx5(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
7550
+ /* @__PURE__ */ jsxs5(Box5, { children: [
7551
+ /* @__PURE__ */ jsx5(Text5, { children: "mode: " }),
7552
+ /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), children: dlp.mode }),
7553
+ dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
7346
7554
  ] }),
7347
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
7555
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 space toggle \xB7 m mode \xB7 a add \xB7 d remove \xB7 s save" : "press \u2192 to edit" }),
7556
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: available.map((p, i) => {
7348
7557
  const on = enabled.has(p);
7349
- return /* @__PURE__ */ jsxs4(Text4, { color: focused && i === sel ? theme.accentBright : void 0, bold: focused && i === sel, children: [
7350
- (focused && i === sel ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
7351
- /* @__PURE__ */ jsx4(Text4, { color: on ? theme.ok : theme.dim, children: p })
7558
+ const active2 = focused && sel === i;
7559
+ return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
7560
+ (active2 ? "\u25B8 " : " ") + (on ? "\u25CF " : "\u25CB "),
7561
+ /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: p })
7352
7562
  ] }, p);
7353
7563
  }) }),
7354
- dlp.custom.length ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
7355
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Custom:" }),
7356
- dlp.custom.map((c2) => /* @__PURE__ */ jsxs4(Text4, { children: [
7357
- " ",
7358
- /* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: c2.name }),
7359
- " ",
7360
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: c2.re })
7361
- ] }, c2.name))
7564
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
7565
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Custom patterns" }),
7566
+ dlp.custom.length === 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (none \u2014 press a to add)" }) : null,
7567
+ dlp.custom.map((c2, i) => {
7568
+ const active2 = focused && sel === available.length + i;
7569
+ return /* @__PURE__ */ jsxs5(Text5, { color: active2 ? theme.accentBright : void 0, bold: active2, children: [
7570
+ active2 ? "\u25B8 " : " ",
7571
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
7572
+ " ",
7573
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: c2.re })
7574
+ ] }, c2.name + i);
7575
+ })
7576
+ ] }),
7577
+ adding ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
7578
+ /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "New pattern name: " : `Regex for "${newName}": ` }),
7579
+ adding === "name" ? /* @__PURE__ */ jsx5(
7580
+ TextInput2,
7581
+ {
7582
+ value: newName,
7583
+ onChange: setNewName,
7584
+ onSubmit: (v) => {
7585
+ if (!v.trim()) return setAdding(null);
7586
+ setNewName(v.trim());
7587
+ setAdding("re");
7588
+ }
7589
+ }
7590
+ ) : /* @__PURE__ */ jsx5(
7591
+ TextInput2,
7592
+ {
7593
+ value: newRe,
7594
+ onChange: setNewRe,
7595
+ onSubmit: (v) => {
7596
+ if (v.trim() && dlp) mutate({ ...dlp, custom: [...dlp.custom, { name: newName, re: v.trim() }] });
7597
+ setAdding(null);
7598
+ }
7599
+ }
7600
+ )
7362
7601
  ] }) : null,
7363
- status ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
7602
+ status ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
7364
7603
  ] }) : null });
7365
7604
  }
7366
7605
  var MODES2;
@@ -7376,8 +7615,8 @@ var init_Dlp = __esm({
7376
7615
  });
7377
7616
 
7378
7617
  // src/tui/panels/Stats.tsx
7379
- import { Box as Box5, Text as Text5 } from "ink";
7380
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
7618
+ import { Box as Box6, Text as Text6 } from "ink";
7619
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
7381
7620
  function StatsPanel({ active: active2 }) {
7382
7621
  const stats = useLoader(() => api.stats.get());
7383
7622
  const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
@@ -7387,50 +7626,50 @@ function StatsPanel({ active: active2 }) {
7387
7626
  }, 5e3, active2);
7388
7627
  const s = stats.data;
7389
7628
  const points = ts.data?.timeseries ?? [];
7390
- return /* @__PURE__ */ jsx5(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
7391
- /* @__PURE__ */ jsxs5(Box5, { children: [
7392
- /* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
7629
+ return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
7630
+ /* @__PURE__ */ jsxs6(Box6, { children: [
7631
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
7393
7632
  s.total_calls,
7394
7633
  " "
7395
7634
  ] }),
7396
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "calls " }),
7397
- /* @__PURE__ */ jsxs5(Text5, { color: theme.ok, children: [
7635
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "calls " }),
7636
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
7398
7637
  s.allowed,
7399
7638
  " allowed"
7400
7639
  ] }),
7401
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " }),
7402
- /* @__PURE__ */ jsxs5(Text5, { color: theme.bad, children: [
7640
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
7641
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
7403
7642
  s.denied,
7404
7643
  " denied"
7405
7644
  ] })
7406
7645
  ] }),
7407
- /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
7646
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
7408
7647
  s.active_policies,
7409
7648
  " policies \xB7 ",
7410
7649
  s.registered_tools,
7411
7650
  " tools"
7412
7651
  ] }),
7413
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
7414
- /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
7652
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
7653
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
7415
7654
  "Last 24h ",
7416
7655
  ts.data ? `(per ${ts.data.granularity})` : ""
7417
7656
  ] }),
7418
- /* @__PURE__ */ jsxs5(Box5, { children: [
7419
- /* @__PURE__ */ jsx5(Text5, { children: "total " }),
7420
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
7657
+ /* @__PURE__ */ jsxs6(Box6, { children: [
7658
+ /* @__PURE__ */ jsx6(Text6, { children: "total " }),
7659
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
7421
7660
  ] }),
7422
- /* @__PURE__ */ jsxs5(Box5, { children: [
7423
- /* @__PURE__ */ jsx5(Text5, { children: "allowed " }),
7424
- /* @__PURE__ */ jsx5(Text5, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
7661
+ /* @__PURE__ */ jsxs6(Box6, { children: [
7662
+ /* @__PURE__ */ jsx6(Text6, { children: "allowed " }),
7663
+ /* @__PURE__ */ jsx6(Text6, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
7425
7664
  ] }),
7426
- /* @__PURE__ */ jsxs5(Box5, { children: [
7427
- /* @__PURE__ */ jsx5(Text5, { children: "denied " }),
7428
- /* @__PURE__ */ jsx5(Text5, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
7665
+ /* @__PURE__ */ jsxs6(Box6, { children: [
7666
+ /* @__PURE__ */ jsx6(Text6, { children: "denied " }),
7667
+ /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
7429
7668
  ] })
7430
7669
  ] }),
7431
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
7432
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "Recent" }),
7433
- /* @__PURE__ */ jsx5(
7670
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
7671
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Recent" }),
7672
+ /* @__PURE__ */ jsx6(
7434
7673
  Table,
7435
7674
  {
7436
7675
  columns: [
@@ -7461,52 +7700,172 @@ var init_Stats = __esm({
7461
7700
  });
7462
7701
 
7463
7702
  // src/tui/panels/Audit.tsx
7464
- import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
7703
+ import { Box as Box7, Text as Text7, useInput as useInput4 } from "ink";
7704
+ import TextInput3 from "ink-text-input";
7465
7705
  import { useState as useState5 } from "react";
7466
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
7706
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
7467
7707
  function AuditPanel({ active: active2, focused }) {
7468
- const [fi, setFi] = useState5(0);
7469
- const filter = FILTERS[fi];
7470
- const audit = useLoader(() => api.audit.list({ filter, limit: 14 }), [fi]);
7471
- usePoll(audit.reload, 5e3, active2);
7708
+ const [di, setDi] = useState5(0);
7709
+ const [gi, setGi] = useState5(0);
7710
+ const [tool, setTool] = useState5("");
7711
+ const [agent, setAgent] = useState5("");
7712
+ const [search, setSearch] = useState5("");
7713
+ const [sel, setSel] = useState5(0);
7714
+ const [view, setView] = useState5("list");
7715
+ const [editing, setEditing] = useState5(null);
7716
+ const query = {
7717
+ filter: DECISIONS[di],
7718
+ signal: SIGNALS[gi],
7719
+ tool: tool || void 0,
7720
+ agent_name: agent || void 0,
7721
+ search: search || void 0,
7722
+ limit: 100
7723
+ };
7724
+ const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
7725
+ usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
7726
+ const entries = auditQ.data?.entries ?? [];
7727
+ const current = entries[Math.min(sel, Math.max(0, entries.length - 1))];
7728
+ const sessionQ = useLoader(
7729
+ () => view === "detail" && current?.session_id ? api.audit.list({ session_id: current.session_id, limit: 40 }) : Promise.resolve(null),
7730
+ [view, current?.session_id]
7731
+ );
7472
7732
  useInput4(
7473
- (input) => {
7474
- if (input === "f") setFi((n) => (n + 1) % FILTERS.length);
7733
+ (input, key) => {
7734
+ if (view === "detail") {
7735
+ if (key.leftArrow || key.escape) setView("list");
7736
+ return;
7737
+ }
7738
+ if (key.upArrow) setSel((n) => Math.max(0, n - 1));
7739
+ else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
7740
+ else if (key.return || key.rightArrow) {
7741
+ if (current) setView("detail");
7742
+ } else if (input === "f") setDi((n) => (n + 1) % DECISIONS.length);
7743
+ else if (input === "g") setGi((n) => (n + 1) % SIGNALS.length);
7744
+ else if (input === "t") setEditing("tool");
7745
+ else if (input === "n") setEditing("agent");
7746
+ else if (input === "/") setEditing("search");
7747
+ else if (input === "c") {
7748
+ setDi(0);
7749
+ setGi(0);
7750
+ setTool("");
7751
+ setAgent("");
7752
+ setSearch("");
7753
+ }
7475
7754
  },
7476
- { isActive: focused }
7755
+ { isActive: focused && !editing }
7477
7756
  );
7478
- const entries = audit.data?.entries ?? [];
7479
- return /* @__PURE__ */ jsx6(DataView, { loading: audit.loading && !audit.data, error: audit.error, empty: !!audit.data && entries.length === 0, emptyText: "No audit entries.", children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
7480
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
7481
- audit.data?.total ?? 0,
7482
- " entries \xB7 filter: ",
7483
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: filter ?? "all" }),
7484
- " (press f)"
7757
+ if (view === "detail" && current) {
7758
+ const sessionCalls = sessionQ.data?.entries ?? [];
7759
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
7760
+ /* @__PURE__ */ jsxs7(Box7, { children: [
7761
+ /* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
7762
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
7763
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
7764
+ ] }),
7765
+ /* @__PURE__ */ jsx7(Detail, { label: "reason", value: current.reason ?? "\u2014" }),
7766
+ /* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
7767
+ /* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
7768
+ /* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
7769
+ /* @__PURE__ */ jsx7(Detail, { label: "dlp", value: current.dlp_matches?.length ? current.dlp_matches.join(", ") : "none", color: current.dlp_matches?.length ? theme.bad : void 0 }),
7770
+ /* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() }),
7771
+ current.arguments_summary ? /* @__PURE__ */ jsx7(Detail, { label: "args", value: truncate2(JSON.stringify(current.arguments_summary), 60) }) : null,
7772
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
7773
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
7774
+ "Session ",
7775
+ current.session_id ? `\xB7 ${sessionCalls.length} calls` : "(no session id)"
7776
+ ] }),
7777
+ current.session_id ? /* @__PURE__ */ jsx7(
7778
+ Table,
7779
+ {
7780
+ columns: [
7781
+ { header: "DECISION", width: 9 },
7782
+ { header: "TOOL", width: 20 },
7783
+ { header: "REASON", width: 26 },
7784
+ { header: "WHEN", width: 6 }
7785
+ ],
7786
+ rows: sessionCalls.slice(0, 12).map((e) => [
7787
+ { value: e.decision, color: decisionColor(e.decision) },
7788
+ { value: truncate2(e.tool_name, 20), color: theme.accent },
7789
+ { value: truncate2(e.reason ?? "\u2014", 26) },
7790
+ { value: ago(e.created_at), dim: true }
7791
+ ])
7792
+ }
7793
+ ) : null
7794
+ ] }),
7795
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2190 back to list" })
7796
+ ] });
7797
+ }
7798
+ const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
7799
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
7800
+ label,
7801
+ ":"
7485
7802
  ] }),
7486
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
7803
+ /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
7804
+ /* @__PURE__ */ jsx7(Text7, { children: " " })
7805
+ ] });
7806
+ return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
7807
+ /* @__PURE__ */ jsxs7(Box7, { children: [
7808
+ chip("dec", DECISIONS[di] ?? "all", di !== 0),
7809
+ chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
7810
+ chip("tool", tool || "\xB7", !!tool),
7811
+ chip("agent", agent || "\xB7", !!agent),
7812
+ chip("search", search || "\xB7", !!search)
7813
+ ] }),
7814
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 enter detail \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 c clear" : "press \u2192 to browse" }),
7815
+ editing ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
7816
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
7817
+ editing,
7818
+ ": "
7819
+ ] }),
7820
+ /* @__PURE__ */ jsx7(
7821
+ TextInput3,
7822
+ {
7823
+ value: editing === "tool" ? tool : editing === "agent" ? agent : search,
7824
+ onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
7825
+ onSubmit: () => setEditing(null)
7826
+ }
7827
+ )
7828
+ ] }) : null,
7829
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
7830
+ auditQ.data?.total ?? 0,
7831
+ " matched \xB7 showing ",
7832
+ Math.min(entries.length, 12)
7833
+ ] }) }),
7834
+ /* @__PURE__ */ jsx7(
7487
7835
  Table,
7488
7836
  {
7489
7837
  columns: [
7838
+ { header: "", width: 2 },
7490
7839
  { header: "DECISION", width: 9 },
7491
7840
  { header: "TOOL", width: 18 },
7492
7841
  { header: "AGENT", width: 14 },
7493
- { header: "REASON", width: 26 },
7842
+ { header: "REASON", width: 24 },
7494
7843
  { header: "DLP", width: 4 },
7495
7844
  { header: "WHEN", width: 6 }
7496
7845
  ],
7497
- rows: entries.map((e) => [
7498
- { value: e.decision, color: decisionColor(e.decision) },
7499
- { value: truncate2(e.tool_name, 18), color: theme.accent },
7500
- { value: truncate2(e.agent_name ?? "\u2014", 14), dim: true },
7501
- { value: truncate2(e.reason ?? "\u2014", 26) },
7502
- { value: e.dlp_matches?.length ? String(e.dlp_matches.length) : "0", color: e.dlp_matches?.length ? theme.bad : void 0, dim: !e.dlp_matches?.length },
7503
- { value: ago(e.created_at), dim: true }
7504
- ])
7846
+ rows: entries.slice(0, 12).map((e, i) => rowFor(e, i === sel && focused))
7505
7847
  }
7506
- ) })
7848
+ )
7507
7849
  ] }) });
7508
7850
  }
7509
- var FILTERS;
7851
+ function rowFor(e, active2) {
7852
+ return [
7853
+ { value: active2 ? "\u25B8" : "", color: theme.accentBright },
7854
+ { value: e.decision, color: decisionColor(e.decision) },
7855
+ { value: truncate2(e.tool_name, 18), color: theme.accent },
7856
+ { value: truncate2(e.agent_name ?? "\u2014", 14), dim: true },
7857
+ { value: truncate2(e.reason ?? "\u2014", 24) },
7858
+ { value: e.dlp_matches?.length ? String(e.dlp_matches.length) : "0", color: e.dlp_matches?.length ? theme.bad : void 0, dim: !e.dlp_matches?.length },
7859
+ { value: ago(e.created_at), dim: true }
7860
+ ];
7861
+ }
7862
+ function Detail({ label, value, color }) {
7863
+ return /* @__PURE__ */ jsxs7(Box7, { children: [
7864
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
7865
+ /* @__PURE__ */ jsx7(Text7, { color, children: value })
7866
+ ] });
7867
+ }
7868
+ var DECISIONS, SIGNALS;
7510
7869
  var init_Audit = __esm({
7511
7870
  "src/tui/panels/Audit.tsx"() {
7512
7871
  "use strict";
@@ -7514,65 +7873,8 @@ var init_Audit = __esm({
7514
7873
  init_components();
7515
7874
  init_hooks();
7516
7875
  init_theme();
7517
- FILTERS = [void 0, "DENY", "ALLOW"];
7518
- }
7519
- });
7520
-
7521
- // src/tui/panels/Agents.tsx
7522
- import { Box as Box7, Text as Text7 } from "ink";
7523
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
7524
- function AgentsPanel({ active: active2 }) {
7525
- const agents = useLoader(() => api.agents.live({ limit: 14 }));
7526
- usePoll(agents.reload, 4e3, active2);
7527
- const data = agents.data;
7528
- const rows = data?.agents ?? [];
7529
- return /* @__PURE__ */ jsx7(DataView, { loading: agents.loading && !data, error: agents.error, empty: !!data && rows.length === 0, emptyText: "No agent sessions.", children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
7530
- data ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
7531
- /* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
7532
- data.counts.active,
7533
- " active"
7534
- ] }),
7535
- " \xB7 ",
7536
- /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
7537
- data.counts.idle,
7538
- " idle"
7539
- ] }),
7540
- " \xB7 ",
7541
- data.counts.deactivated,
7542
- " off"
7543
- ] }) : null,
7544
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(
7545
- Table,
7546
- {
7547
- columns: [
7548
- { header: "STATUS", width: 7 },
7549
- { header: "AGENT", width: 20 },
7550
- { header: "CALLS", width: 6 },
7551
- { header: "DENY", width: 5 },
7552
- { header: "DLP", width: 4 },
7553
- { header: "TRUST", width: 6 },
7554
- { header: "CHARACTER", width: 20 }
7555
- ],
7556
- rows: rows.map((a) => [
7557
- { value: a.status, color: modeColor(a.status) },
7558
- { value: truncate2(a.agent_name ?? a.agent_id ?? a.session_id, 20), color: theme.accent },
7559
- { value: String(a.total_calls) },
7560
- { value: String(a.denied_calls), color: a.denied_calls ? theme.bad : void 0, dim: !a.denied_calls },
7561
- { value: String(a.dlp_events), color: a.dlp_events ? theme.bad : void 0, dim: !a.dlp_events },
7562
- { value: `${a.trust_score}` },
7563
- { value: truncate2(a.character || "\u2014", 20), dim: true }
7564
- ])
7565
- }
7566
- ) })
7567
- ] }) });
7568
- }
7569
- var init_Agents = __esm({
7570
- "src/tui/panels/Agents.tsx"() {
7571
- "use strict";
7572
- init_api_client();
7573
- init_components();
7574
- init_hooks();
7575
- init_theme();
7876
+ DECISIONS = [void 0, "DENY", "ALLOW"];
7877
+ SIGNALS = [void 0, "dlp", "ratelimit"];
7576
7878
  }
7577
7879
  });
7578
7880
 
@@ -7625,19 +7927,19 @@ var init_App = __esm({
7625
7927
  init_cli_utils();
7626
7928
  init_components();
7627
7929
  init_theme();
7930
+ init_Live();
7628
7931
  init_Policies();
7629
7932
  init_RateLimit();
7630
7933
  init_Dlp();
7631
7934
  init_Stats();
7632
7935
  init_Audit();
7633
- init_Agents();
7634
7936
  SECTIONS = [
7937
+ { label: "Live", Panel: LivePanel },
7635
7938
  { label: "Policies", Panel: PoliciesPanel },
7636
7939
  { label: "Rate Limit", Panel: RateLimitPanel },
7637
7940
  { label: "DLP", Panel: DlpPanel },
7638
7941
  { label: "Stats", Panel: StatsPanel },
7639
- { label: "Audit", Panel: AuditPanel },
7640
- { label: "Agents", Panel: AgentsPanel }
7942
+ { label: "Audit", Panel: AuditPanel }
7641
7943
  ];
7642
7944
  BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
7643
7945
  }
@@ -7702,10 +8004,10 @@ function truncate3(s, n) {
7702
8004
  function sparkline2(values) {
7703
8005
  if (values.length === 0) return "";
7704
8006
  const max = Math.max(...values, 0);
7705
- if (max === 0) return BLOCKS2[0].repeat(values.length);
7706
- return values.map((v) => BLOCKS2[Math.min(BLOCKS2.length - 1, Math.round(v / max * (BLOCKS2.length - 1)))]).join("");
8007
+ if (max === 0) return BLOCKS3[0].repeat(values.length);
8008
+ return values.map((v) => BLOCKS3[Math.min(BLOCKS3.length - 1, Math.round(v / max * (BLOCKS3.length - 1)))]).join("");
7707
8009
  }
7708
- var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS2;
8010
+ var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS3;
7709
8011
  var init_format = __esm({
7710
8012
  "src/commands/format.ts"() {
7711
8013
  "use strict";
@@ -7720,7 +8022,7 @@ var init_format = __esm({
7720
8022
  cyan = (s) => `${c.cyan}${s}${c.reset}`;
7721
8023
  ANSI = /\x1b\[[0-9;]*m/g;
7722
8024
  width = (s) => s.replace(ANSI, "").length;
7723
- BLOCKS2 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
8025
+ BLOCKS3 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
7724
8026
  }
7725
8027
  });
7726
8028