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