@solongate/proxy 0.78.0 → 0.80.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.
@@ -9,10 +9,14 @@ import * as settings from './settings.js';
9
9
  import * as stats from './stats.js';
10
10
  import * as audit from './audit.js';
11
11
  import * as agents from './agents.js';
12
+ import * as keys from './keys.js';
13
+ import * as mcp from './mcp.js';
12
14
  export declare const api: {
13
15
  policies: typeof policies;
14
16
  settings: typeof settings;
15
17
  stats: typeof stats;
16
18
  audit: typeof audit;
17
19
  agents: typeof agents;
20
+ keys: typeof keys;
21
+ mcp: typeof mcp;
18
22
  };
@@ -0,0 +1,17 @@
1
+ export interface ApiKey {
2
+ id: string;
3
+ name: string;
4
+ key_prefix: string;
5
+ is_live?: boolean;
6
+ created_at: string;
7
+ }
8
+ export declare function list(): Promise<{
9
+ keys: ApiKey[];
10
+ }>;
11
+ export declare function create(name: string, isLive?: boolean): Promise<{
12
+ id: string;
13
+ name: string;
14
+ key: string;
15
+ key_prefix: string;
16
+ }>;
17
+ export declare function revoke(id: string): Promise<unknown>;
@@ -0,0 +1,10 @@
1
+ export interface McpServer {
2
+ id: string;
3
+ name: string;
4
+ url?: string;
5
+ command?: string;
6
+ status: string;
7
+ }
8
+ export declare function list(): Promise<{
9
+ servers: McpServer[];
10
+ }>;
@@ -20,3 +20,55 @@ export interface GuardStatus {
20
20
  outdated_count: number;
21
21
  }
22
22
  export declare function getGuardStatus(): Promise<GuardStatus>;
23
+ export declare function getSelfProtection(): Promise<{
24
+ enabled: boolean;
25
+ }>;
26
+ export declare function setSelfProtection(enabled: boolean): Promise<{
27
+ enabled: boolean;
28
+ }>;
29
+ export interface AlertRule {
30
+ id: string;
31
+ name: string;
32
+ enabled: boolean;
33
+ threshold: number;
34
+ windowSeconds: number;
35
+ signal: 'any' | 'deny' | 'dlp' | 'ratelimit';
36
+ slackUrls?: string[];
37
+ emails?: string[];
38
+ telegram?: string[];
39
+ createdAt: string;
40
+ }
41
+ export declare function getAlerts(): Promise<{
42
+ rules: AlertRule[];
43
+ }>;
44
+ export declare function createAlert(body: Partial<AlertRule> & {
45
+ emails?: string[];
46
+ telegram?: string[];
47
+ slackUrl?: string;
48
+ }): Promise<{
49
+ rule: AlertRule;
50
+ }>;
51
+ export declare function deleteAlert(id: string): Promise<{
52
+ ok: true;
53
+ }>;
54
+ export interface DenialWebhook {
55
+ id: string;
56
+ url: string;
57
+ enabled: boolean;
58
+ events: 'denials' | 'allowed' | 'all';
59
+ headers?: Record<string, string>;
60
+ createdAt: string;
61
+ }
62
+ export declare function getWebhooks(): Promise<{
63
+ webhooks: DenialWebhook[];
64
+ }>;
65
+ export declare function createWebhook(body: {
66
+ url: string;
67
+ events?: 'denials' | 'allowed' | 'all';
68
+ headers?: Record<string, string>;
69
+ }): Promise<{
70
+ webhook: DenialWebhook;
71
+ }>;
72
+ export declare function deleteWebhook(id: string): Promise<{
73
+ ok: true;
74
+ }>;
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;
@@ -4,4 +4,4 @@
4
4
  */
5
5
  export declare function runCommand(command: string, argv: string[]): Promise<number>;
6
6
  /** The subcommand names this router owns (used by src/index.ts to route). */
7
- export declare const COMMAND_NAMES: readonly ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"];
7
+ export declare const COMMAND_NAMES: readonly ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks"];
@@ -53,6 +53,14 @@ function dotenvApiKey() {
53
53
  return void 0;
54
54
  }
55
55
  var cached = null;
56
+ function isAuthenticated() {
57
+ try {
58
+ resolveCredentials();
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
56
64
  function resolveCredentials(apiUrlOverride) {
57
65
  if (cached && !apiUrlOverride) return cached;
58
66
  const file = loginCredentialFile();
@@ -188,10 +196,18 @@ function dryRun(body) {
188
196
  var settings_exports = {};
189
197
  __export(settings_exports, {
190
198
  clearRateLimitHistory: () => clearRateLimitHistory,
199
+ createAlert: () => createAlert,
200
+ createWebhook: () => createWebhook,
201
+ deleteAlert: () => deleteAlert,
202
+ deleteWebhook: () => deleteWebhook,
203
+ getAlerts: () => getAlerts,
191
204
  getGuardStatus: () => getGuardStatus,
192
205
  getRateLimitHistory: () => getRateLimitHistory,
193
206
  getSecurityLayers: () => getSecurityLayers,
194
- setSecurityLayers: () => setSecurityLayers
207
+ getSelfProtection: () => getSelfProtection,
208
+ getWebhooks: () => getWebhooks,
209
+ setSecurityLayers: () => setSecurityLayers,
210
+ setSelfProtection: () => setSelfProtection
195
211
  });
196
212
  function getSecurityLayers() {
197
213
  return request("GET", "/settings/security-layers");
@@ -208,6 +224,30 @@ function clearRateLimitHistory() {
208
224
  function getGuardStatus() {
209
225
  return request("GET", "/settings/guard-status");
210
226
  }
227
+ function getSelfProtection() {
228
+ return request("GET", "/settings/self-protection");
229
+ }
230
+ function setSelfProtection(enabled) {
231
+ return request("PUT", "/settings/self-protection", { body: { enabled } });
232
+ }
233
+ function getAlerts() {
234
+ return request("GET", "/settings/denial-alerts");
235
+ }
236
+ function createAlert(body) {
237
+ return request("POST", "/settings/denial-alerts", { body });
238
+ }
239
+ function deleteAlert(id) {
240
+ return request("DELETE", "/settings/denial-alerts", { query: { id } });
241
+ }
242
+ function getWebhooks() {
243
+ return request("GET", "/settings/denial-webhook");
244
+ }
245
+ function createWebhook(body) {
246
+ return request("POST", "/settings/denial-webhook", { body });
247
+ }
248
+ function deleteWebhook(id) {
249
+ return request("DELETE", "/settings/denial-webhook", { query: { id } });
250
+ }
211
251
 
212
252
  // src/api-client/stats.ts
213
253
  var stats_exports = {};
@@ -268,8 +308,34 @@ function anomalies(id, limit) {
268
308
  });
269
309
  }
270
310
 
311
+ // src/api-client/keys.ts
312
+ var keys_exports = {};
313
+ __export(keys_exports, {
314
+ create: () => create2,
315
+ list: () => list3,
316
+ revoke: () => revoke
317
+ });
318
+ function list3() {
319
+ return request("GET", "/keys");
320
+ }
321
+ function create2(name, isLive = true) {
322
+ return request("POST", "/keys", { body: { name, is_live: isLive } });
323
+ }
324
+ function revoke(id) {
325
+ return request("DELETE", `/keys/${encodeURIComponent(id)}`);
326
+ }
327
+
328
+ // src/api-client/mcp.ts
329
+ var mcp_exports = {};
330
+ __export(mcp_exports, {
331
+ list: () => list4
332
+ });
333
+ function list4() {
334
+ return request("GET", "/mcp-servers");
335
+ }
336
+
271
337
  // src/api-client/index.ts
272
- var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports };
338
+ var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
273
339
 
274
340
  // src/cli-utils.ts
275
341
  var c = {
@@ -825,6 +891,16 @@ async function run5(argv) {
825
891
  else err(green(` \u2713 Whitelisted (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
826
892
  return 0;
827
893
  }
894
+ if (positionals[0] === "block") {
895
+ const id = positionals[1];
896
+ if (!id) return err(" Usage: audit block <logId> [--scope exact|tool]"), 1;
897
+ const scope = flagStr(flags, "scope") ?? "exact";
898
+ const res2 = await api.audit.block(id, scope);
899
+ if (json) return printJson(res2), 0;
900
+ if (res2.deduped) err(green(" \u2713 ") + dim("Equivalent DENY already present."));
901
+ else err(green(` \u2713 Blocked (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
902
+ return 0;
903
+ }
828
904
  const query = {
829
905
  filter: flagStr(flags, "filter"),
830
906
  tool: flagStr(flags, "tool"),
@@ -906,6 +982,341 @@ async function runAgent(argv) {
906
982
  return 0;
907
983
  }
908
984
 
985
+ // src/commands/doctor.ts
986
+ import { existsSync as existsSync2, statSync } from "fs";
987
+ import { homedir as homedir2 } from "os";
988
+ import { join as join2 } from "path";
989
+ var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
990
+ async function run6(argv) {
991
+ const { flags } = parse(argv);
992
+ const json = flagBool(flags, "json");
993
+ const checks = [];
994
+ if (!isAuthenticated()) {
995
+ checks.push({ name: "login", ok: false, detail: "not logged in \u2014 run `solongate login`" });
996
+ } else {
997
+ const { apiUrl } = resolveCredentials();
998
+ checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
999
+ try {
1000
+ const active2 = await api.policies.active();
1001
+ if (active2.policy) {
1002
+ checks.push({ name: "active policy", ok: true, detail: `${active2.policy.name} v${active2.version} \xB7 ${active2.policy.mode ?? "denylist"} \xB7 matched by ${active2.matched_by}` });
1003
+ } else {
1004
+ checks.push({ name: "active policy", ok: "warn", detail: "no policy resolves \u2014 every call falls back to default" });
1005
+ }
1006
+ const sec = active2.security;
1007
+ checks.push({ name: "rate limit", ok: sec?.rateLimit ? true : "warn", detail: sec?.rateLimit ? `${sec.rateLimit.perMinute}/min` : "off" });
1008
+ checks.push({ name: "dlp", ok: sec?.dlpBlock ? true : "warn", detail: sec?.dlpBlock ? `block \xB7 ${sec.dlpBlock.patterns.length} patterns` : sec?.dlpRedact ? "redact" : "off" });
1009
+ checks.push({ name: "self-protection", ok: active2.self_protection_enabled ? true : "warn", detail: active2.self_protection_enabled ? "on" : "off" });
1010
+ } catch (e) {
1011
+ checks.push({ name: "api", ok: false, detail: "unreachable: " + (e instanceof Error ? e.message : String(e)) });
1012
+ }
1013
+ try {
1014
+ const g = await api.settings.getGuardStatus();
1015
+ checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 run \`solongate login\`` });
1016
+ } catch {
1017
+ }
1018
+ }
1019
+ if (existsSync2(LOCAL_LOG)) {
1020
+ const st = statSync(LOCAL_LOG);
1021
+ const ageMin = (Date.now() - st.mtimeMs) / 6e4;
1022
+ checks.push({ name: "local logs", ok: true, detail: `on \xB7 ${(st.size / 1024).toFixed(0)}KB \xB7 last write ${ageMin < 1 ? "just now" : Math.round(ageMin) + "m ago"}` });
1023
+ } else {
1024
+ checks.push({ name: "local logs", ok: "warn", detail: "off (logs go to cloud) \u2014 enable in dashboard \u2192 Settings" });
1025
+ }
1026
+ if (json) return printJson(checks), checks.some((c2) => c2.ok === false) ? 1 : 0;
1027
+ err("");
1028
+ err(` ${bold("SolonGate doctor")}`);
1029
+ err("");
1030
+ for (const c2 of checks) {
1031
+ const mark = c2.ok === true ? green("\u2713") : c2.ok === "warn" ? yellow("!") : red("\u2717");
1032
+ err(` ${mark} ${c2.name.padEnd(16)} ${dim(c2.detail)}`);
1033
+ }
1034
+ err("");
1035
+ const bad = checks.filter((c2) => c2.ok === false).length;
1036
+ const warn = checks.filter((c2) => c2.ok === "warn").length;
1037
+ if (bad) err(` ${red(`${bad} problem(s)`)}${warn ? dim(` \xB7 ${warn} warning(s)`) : ""}`);
1038
+ else if (warn) err(` ${yellow(`${warn} warning(s)`)} ${dim("\u2014 guard is working")}`);
1039
+ else err(` ${green("all good")}`);
1040
+ return bad ? 1 : 0;
1041
+ }
1042
+
1043
+ // src/commands/watch.ts
1044
+ import { closeSync, existsSync as existsSync3, openSync, readSync, statSync as statSync2 } from "fs";
1045
+ import { homedir as homedir3 } from "os";
1046
+ import { join as join3 } from "path";
1047
+ var LOCAL_LOG2 = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
1048
+ var trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
1049
+ var time = (ms) => new Date(ms).toTimeString().slice(0, 8);
1050
+ function tailLocal(file, maxBytes = 131072) {
1051
+ try {
1052
+ const size = statSync2(file).size;
1053
+ const start = Math.max(0, size - maxBytes);
1054
+ const fd = openSync(file, "r");
1055
+ const buf = Buffer.alloc(size - start);
1056
+ readSync(fd, buf, 0, buf.length, start);
1057
+ closeSync(fd);
1058
+ const lines = buf.toString("utf-8").split("\n").filter(Boolean);
1059
+ if (start > 0) lines.shift();
1060
+ return lines;
1061
+ } catch {
1062
+ return [];
1063
+ }
1064
+ }
1065
+ function print(r, json) {
1066
+ if (json) return out(JSON.stringify(r));
1067
+ const dec = r.decision === "ALLOW" ? c.green : c.red;
1068
+ const src = r.source === "local" ? c.green + "LOC" : c.blue4 + "CLD";
1069
+ out(
1070
+ `${c.dim}[${time(r.at)}]${c.reset} ${src}${c.reset} ${dec}${r.decision.padEnd(6)}${c.reset} ${c.cyan}${trunc(r.tool, 12).padEnd(13)}${c.reset}${c.dim}${r.permission.slice(0, 4).padEnd(5)}${c.reset}${r.dlp ? c.red + "DLP! " + c.reset : ""}${c.dim}${trunc(r.agent || "-", 12).padEnd(13)}${r.detail}${c.reset}`
1071
+ );
1072
+ }
1073
+ async function run7(argv) {
1074
+ const { flags } = parse(argv);
1075
+ const json = flagBool(flags, "json");
1076
+ const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
1077
+ const toolFilter = (flagStr(flags, "tool") || "").toLowerCase();
1078
+ const localOnly = flagBool(flags, "local-only");
1079
+ const cloudOnly = flagBool(flags, "cloud-only");
1080
+ const keep = (r) => {
1081
+ if (decisionFilter && r.decision.toUpperCase() !== decisionFilter && !(decisionFilter === "DENY" && r.decision === "DENIED")) return false;
1082
+ if (toolFilter && !r.tool.toLowerCase().includes(toolFilter)) return false;
1083
+ return true;
1084
+ };
1085
+ const seen = /* @__PURE__ */ new Set();
1086
+ let lastLocalTs = 0;
1087
+ let first = true;
1088
+ const emit = (rows) => {
1089
+ rows.sort((a, b) => a.at - b.at);
1090
+ for (const r of rows) if (keep(r)) print(r, json);
1091
+ };
1092
+ const pollLocal = () => {
1093
+ if (cloudOnly || !existsSync3(LOCAL_LOG2)) return;
1094
+ const rows = [];
1095
+ for (const line of tailLocal(LOCAL_LOG2)) {
1096
+ try {
1097
+ const j = JSON.parse(line);
1098
+ const at = Date.parse(String(j.ts ?? ""));
1099
+ if (!Number.isFinite(at) || at <= lastLocalTs) continue;
1100
+ rows.push({
1101
+ at,
1102
+ tool: String(j.tool ?? "?"),
1103
+ decision: String(j.decision ?? "ALLOW"),
1104
+ permission: String(j.permission ?? ""),
1105
+ detail: (j.arguments ? JSON.stringify(j.arguments) : String(j.reason ?? "")).replace(/\s+/g, " "),
1106
+ agent: String(j.agent_name ?? ""),
1107
+ source: "local",
1108
+ dlp: !!j.dlp
1109
+ });
1110
+ } catch {
1111
+ }
1112
+ }
1113
+ if (rows.length) lastLocalTs = rows[rows.length - 1].at;
1114
+ if (!first) emit(rows);
1115
+ };
1116
+ const pollCloud = async () => {
1117
+ if (localOnly) return;
1118
+ try {
1119
+ const res = await api.audit.list({ limit: 50 });
1120
+ const rows = [];
1121
+ for (const e of res.entries) {
1122
+ if (seen.has(e.id)) continue;
1123
+ seen.add(e.id);
1124
+ rows.push({
1125
+ at: Date.parse(e.created_at),
1126
+ tool: e.tool_name,
1127
+ decision: e.decision,
1128
+ permission: e.permission ?? "",
1129
+ detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
1130
+ agent: e.agent_name ?? "",
1131
+ source: "cloud",
1132
+ dlp: !!e.dlp_matches?.length
1133
+ });
1134
+ }
1135
+ if (!first) emit(rows);
1136
+ } catch {
1137
+ }
1138
+ };
1139
+ if (!json) err(` ${c.dim}watching guard stream \u2014 Ctrl+C to stop${c.reset}`);
1140
+ pollLocal();
1141
+ await pollCloud();
1142
+ first = false;
1143
+ return new Promise(() => {
1144
+ setInterval(pollLocal, 2e3);
1145
+ setInterval(() => void pollCloud(), 4e3);
1146
+ });
1147
+ }
1148
+
1149
+ // src/commands/keys.ts
1150
+ var USAGE6 = `${bold("solongate keys")} \u2014 API keys
1151
+
1152
+ keys list List keys (prefix only)
1153
+ keys create --name <n> Create a key (secret shown once)
1154
+ keys revoke <id> Revoke a key
1155
+
1156
+ Add --json for machine-readable output.`;
1157
+ async function run8(argv) {
1158
+ const { positionals, flags } = parse(argv);
1159
+ const sub = positionals[0] ?? "list";
1160
+ const json = flagBool(flags, "json");
1161
+ switch (sub) {
1162
+ case "help":
1163
+ return err(USAGE6), 0;
1164
+ case "list": {
1165
+ const { keys } = await api.keys.list();
1166
+ if (json) return printJson(keys), 0;
1167
+ if (!keys.length) return err(dim(" No API keys.")), 0;
1168
+ table(
1169
+ ["ID", "NAME", "PREFIX", "CREATED"],
1170
+ keys.map((k) => [dim(truncate(k.id, 10)), truncate(k.name, 24), cyan(k.key_prefix), dim(k.created_at)])
1171
+ );
1172
+ return 0;
1173
+ }
1174
+ case "create": {
1175
+ const name = flagStr(flags, "name");
1176
+ if (!name) return err(" Usage: keys create --name <name>"), 1;
1177
+ const res = await api.keys.create(name, !flagBool(flags, "test"));
1178
+ if (json) return printJson(res), 0;
1179
+ err(green(` \u2713 Created "${res.name}"`));
1180
+ err("");
1181
+ err(" " + bold(res.key));
1182
+ err(dim(" \u2191 shown only once \u2014 copy it now."));
1183
+ return 0;
1184
+ }
1185
+ case "revoke": {
1186
+ const id = positionals[1];
1187
+ if (!id) return err(" Usage: keys revoke <id>"), 1;
1188
+ await api.keys.revoke(id);
1189
+ if (json) return printJson({ ok: true, id }), 0;
1190
+ return err(green(` \u2713 Revoked ${id}`)), 0;
1191
+ }
1192
+ default:
1193
+ return err(USAGE6), 1;
1194
+ }
1195
+ }
1196
+
1197
+ // src/commands/mcp.ts
1198
+ async function run9(argv) {
1199
+ const { flags } = parse(argv);
1200
+ const json = flagBool(flags, "json");
1201
+ const { servers } = await api.mcp.list();
1202
+ if (json) return printJson(servers), 0;
1203
+ if (!servers.length) return err(dim(" No MCP servers registered.")), 0;
1204
+ table(
1205
+ ["STATUS", "NAME", "TARGET"],
1206
+ servers.map((s) => [
1207
+ s.status === "active" ? green(s.status) : yellow(s.status),
1208
+ cyan(truncate(s.name, 24)),
1209
+ dim(truncate(s.url || s.command || "\u2014", 46))
1210
+ ])
1211
+ );
1212
+ return 0;
1213
+ }
1214
+
1215
+ // src/commands/alerts.ts
1216
+ var USAGE7 = `${bold("solongate alerts")} \u2014 spike alerts (Telegram / email)
1217
+
1218
+ alerts list
1219
+ alerts add --signal deny|dlp|ratelimit|any --threshold N --window S
1220
+ (--email <a> | --telegram <chatId> | --slack <url>)
1221
+ alerts remove <id>
1222
+
1223
+ Add --json for machine-readable output.`;
1224
+ async function run10(argv) {
1225
+ const { positionals, flags } = parse(argv);
1226
+ const sub = positionals[0] ?? "list";
1227
+ const json = flagBool(flags, "json");
1228
+ switch (sub) {
1229
+ case "help":
1230
+ return err(USAGE7), 0;
1231
+ case "list": {
1232
+ const { rules } = await api.settings.getAlerts();
1233
+ if (json) return printJson(rules), 0;
1234
+ if (!rules.length) return err(dim(" No alert rules.")), 0;
1235
+ table(
1236
+ ["ID", "ON", "SIGNAL", "THRESH", "WINDOW", "CHANNELS"],
1237
+ rules.map((r) => [
1238
+ dim(truncate(r.id, 10)),
1239
+ r.enabled ? green("\u25CF") : dim("\u25CB"),
1240
+ cyan(r.signal),
1241
+ `${r.threshold}`,
1242
+ `${r.windowSeconds}s`,
1243
+ dim([...r.emails ?? [], ...(r.telegram ?? []).map((t) => "tg:" + t), ...(r.slackUrls ?? []).map(() => "slack")].join(" ") || "\u2014")
1244
+ ])
1245
+ );
1246
+ return 0;
1247
+ }
1248
+ case "add": {
1249
+ const email = flagStr(flags, "email");
1250
+ const telegram = flagStr(flags, "telegram");
1251
+ const slack = flagStr(flags, "slack");
1252
+ if (!email && !telegram && !slack) return err(" Need a channel: --email / --telegram / --slack"), 1;
1253
+ const res = await api.settings.createAlert({
1254
+ signal: flagStr(flags, "signal") ?? "deny",
1255
+ threshold: flagNum(flags, "threshold") ?? 5,
1256
+ windowSeconds: flagNum(flags, "window") ?? 300,
1257
+ emails: email ? [email] : void 0,
1258
+ telegram: telegram ? [telegram] : void 0,
1259
+ slackUrl: slack
1260
+ });
1261
+ if (json) return printJson(res.rule), 0;
1262
+ return err(green(` \u2713 Alert added (${res.rule.signal}, ${res.rule.threshold}/${res.rule.windowSeconds}s)`)), 0;
1263
+ }
1264
+ case "remove": {
1265
+ const id = positionals[1];
1266
+ if (!id) return err(" Usage: alerts remove <id>"), 1;
1267
+ await api.settings.deleteAlert(id);
1268
+ if (json) return printJson({ ok: true, id }), 0;
1269
+ return err(green(` \u2713 Removed ${id}`)), 0;
1270
+ }
1271
+ default:
1272
+ return err(USAGE7), 1;
1273
+ }
1274
+ }
1275
+
1276
+ // src/commands/webhooks.ts
1277
+ var USAGE8 = `${bold("solongate webhooks")} \u2014 event webhooks
1278
+
1279
+ webhooks list
1280
+ webhooks add --url <https://\u2026> [--events denials|allowed|all]
1281
+ webhooks remove <id>
1282
+
1283
+ Add --json for machine-readable output.`;
1284
+ async function run11(argv) {
1285
+ const { positionals, flags } = parse(argv);
1286
+ const sub = positionals[0] ?? "list";
1287
+ const json = flagBool(flags, "json");
1288
+ switch (sub) {
1289
+ case "help":
1290
+ return err(USAGE8), 0;
1291
+ case "list": {
1292
+ const { webhooks } = await api.settings.getWebhooks();
1293
+ if (json) return printJson(webhooks), 0;
1294
+ if (!webhooks.length) return err(dim(" No webhooks.")), 0;
1295
+ table(
1296
+ ["ID", "ON", "EVENTS", "URL"],
1297
+ webhooks.map((w) => [dim(truncate(w.id, 10)), w.enabled ? green("\u25CF") : dim("\u25CB"), cyan(w.events), dim(truncate(w.url, 46))])
1298
+ );
1299
+ return 0;
1300
+ }
1301
+ case "add": {
1302
+ const url = flagStr(flags, "url");
1303
+ if (!url) return err(" Usage: webhooks add --url <https://\u2026> [--events denials|allowed|all]"), 1;
1304
+ const res = await api.settings.createWebhook({ url, events: flagStr(flags, "events") ?? "denials" });
1305
+ if (json) return printJson(res.webhook), 0;
1306
+ return err(green(` \u2713 Webhook added (${res.webhook.events}) \u2192 ${res.webhook.url}`)), 0;
1307
+ }
1308
+ case "remove": {
1309
+ const id = positionals[1];
1310
+ if (!id) return err(" Usage: webhooks remove <id>"), 1;
1311
+ await api.settings.deleteWebhook(id);
1312
+ if (json) return printJson({ ok: true, id }), 0;
1313
+ return err(green(` \u2713 Removed ${id}`)), 0;
1314
+ }
1315
+ default:
1316
+ return err(USAGE8), 1;
1317
+ }
1318
+ }
1319
+
909
1320
  // src/commands/index.ts
910
1321
  async function dispatch(command, argv) {
911
1322
  switch (command) {
@@ -923,6 +1334,18 @@ async function dispatch(command, argv) {
923
1334
  return runAgents(argv);
924
1335
  case "agent":
925
1336
  return runAgent(argv);
1337
+ case "doctor":
1338
+ return run6(argv);
1339
+ case "watch":
1340
+ return run7(argv);
1341
+ case "keys":
1342
+ return run8(argv);
1343
+ case "mcp":
1344
+ return run9(argv);
1345
+ case "alerts":
1346
+ return run10(argv);
1347
+ case "webhooks":
1348
+ return run11(argv);
926
1349
  default:
927
1350
  err(` Unknown command: ${command}`);
928
1351
  return 1;
@@ -945,7 +1368,7 @@ async function runCommand(command, argv) {
945
1368
  return 1;
946
1369
  }
947
1370
  }
948
- var COMMAND_NAMES = ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"];
1371
+ var COMMAND_NAMES = ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent", "doctor", "watch", "keys", "mcp", "alerts", "webhooks"];
949
1372
  export {
950
1373
  COMMAND_NAMES,
951
1374
  runCommand
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;