@solongate/proxy 0.77.2 → 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.
@@ -20,3 +20,11 @@ export declare function whitelist(id: string, scope?: 'exact' | 'tool'): Promise
20
20
  policy_version?: number;
21
21
  message?: string;
22
22
  }>;
23
+ export declare function block(id: string, scope?: 'exact' | 'tool'): Promise<{
24
+ ok: true;
25
+ deduped: boolean;
26
+ scope: string;
27
+ policy_id: string;
28
+ policy_version?: number;
29
+ message?: string;
30
+ }>;
@@ -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
+ }>;
@@ -13,6 +13,7 @@ export interface RuleSpec {
13
13
  toolPattern?: string;
14
14
  kind?: 'command' | 'path' | 'url' | 'tool';
15
15
  value?: string;
16
+ effect?: 'ALLOW' | 'DENY';
16
17
  }
17
18
  export declare function addRule(id: string, spec: RuleSpec): Promise<{
18
19
  ok: true;
@@ -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 = {};
@@ -233,6 +265,7 @@ function securityInsights(days) {
233
265
  // src/api-client/audit.ts
234
266
  var audit_exports = {};
235
267
  __export(audit_exports, {
268
+ block: () => block,
236
269
  list: () => list2,
237
270
  whitelist: () => whitelist
238
271
  });
@@ -242,6 +275,9 @@ function list2(query = {}) {
242
275
  function whitelist(id, scope = "exact") {
243
276
  return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
244
277
  }
278
+ function block(id, scope = "exact") {
279
+ return request("POST", `/audit-logs/${encodeURIComponent(id)}/block`, { body: { scope } });
280
+ }
245
281
 
246
282
  // src/api-client/agents.ts
247
283
  var agents_exports = {};
@@ -264,8 +300,34 @@ function anomalies(id, limit) {
264
300
  });
265
301
  }
266
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
+
267
329
  // src/api-client/index.ts
268
- 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 };
269
331
 
270
332
  // src/cli-utils.ts
271
333
  var c = {
@@ -821,6 +883,16 @@ async function run5(argv) {
821
883
  else err(green(` \u2713 Whitelisted (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
822
884
  return 0;
823
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
+ }
824
896
  const query = {
825
897
  filter: flagStr(flags, "filter"),
826
898
  tool: flagStr(flags, "tool"),
@@ -902,6 +974,341 @@ async function runAgent(argv) {
902
974
  return 0;
903
975
  }
904
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
+
905
1312
  // src/commands/index.ts
906
1313
  async function dispatch(command, argv) {
907
1314
  switch (command) {
@@ -919,6 +1326,18 @@ async function dispatch(command, argv) {
919
1326
  return runAgents(argv);
920
1327
  case "agent":
921
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);
922
1341
  default:
923
1342
  err(` Unknown command: ${command}`);
924
1343
  return 1;
@@ -941,7 +1360,7 @@ async function runCommand(command, argv) {
941
1360
  return 1;
942
1361
  }
943
1362
  }
944
- 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"];
945
1364
  export {
946
1365
  COMMAND_NAMES,
947
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>;