openclaw-aegis 1.1.1 → 1.2.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/README.md CHANGED
@@ -57,6 +57,8 @@ Probes: 10 passed, 0 failed
57
57
  | `aegis check --json` | JSON output for scripting |
58
58
  | `aegis status` | Health dashboard with per-probe details |
59
59
  | `aegis test-alert` | Send a test notification to all configured channels |
60
+ | `aegis incidents` | Browse past incident logs |
61
+ | `aegis incidents <id>` | Show full timeline for a specific incident |
60
62
 
61
63
  ---
62
64
 
@@ -70,6 +72,7 @@ Probes: 10 passed, 0 failed
70
72
  | [Alerts](docs/alerts.md) | Setting up ntfy, Telegram, WhatsApp, Slack, Discord, Email, Pushover, webhooks |
71
73
  | [CLI Reference](docs/cli-reference.md) | Every command with examples and options |
72
74
  | [Contributing](docs/contributing.md) | Development setup, testing, PR process |
75
+ | [Releasing](docs/releasing.md) | Version bumps, npm publish, GitHub releases |
73
76
 
74
77
  ---
75
78
 
@@ -111,4 +114,4 @@ OpenClaw Gateway Aegis Sidecar
111
114
 
112
115
  MIT — see [LICENSE](LICENSE).
113
116
 
114
- Built by [Canary Builds](https://github.com/Canary-Builds).
117
+ Built by [Canary Builds](https://canarybuilds.com).
package/dist/cli/index.js CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli/index.ts
27
27
  var import_node_fs = require("fs");
28
28
  var import_node_path = require("path");
29
- var import_commander5 = require("commander");
29
+ var import_commander6 = require("commander");
30
30
 
31
31
  // src/cli/commands/status.ts
32
32
  var import_commander = require("commander");
@@ -254,9 +254,15 @@ var DegradedConfirmation = class {
254
254
 
255
255
  // src/health/probes/resolve-pid.ts
256
256
  var fs2 = __toESM(require("fs"));
257
+ var os2 = __toESM(require("os"));
257
258
  var import_node_child_process = require("child_process");
258
259
  function resolvePid(pidSource) {
259
- if (pidSource.endsWith(".service") || !pidSource.includes("/")) {
260
+ const isPath = pidSource.includes("/");
261
+ if (!isPath) {
262
+ if (os2.platform() === "darwin") {
263
+ const pid2 = resolveFromLaunchd(pidSource);
264
+ if (pid2 !== null) return pid2;
265
+ }
260
266
  const pid = resolveFromSystemd(pidSource);
261
267
  if (pid !== null) return pid;
262
268
  }
@@ -264,11 +270,11 @@ function resolvePid(pidSource) {
264
270
  }
265
271
  function resolveFromSystemd(unit) {
266
272
  try {
267
- const output = (0, import_node_child_process.execFileSync)(
268
- "systemctl",
269
- ["--user", "show", "-p", "MainPID", "--value", unit],
270
- { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }
271
- ).trim();
273
+ const output = (0, import_node_child_process.execFileSync)("systemctl", ["--user", "show", "-p", "MainPID", "--value", unit], {
274
+ encoding: "utf-8",
275
+ timeout: 3e3,
276
+ stdio: ["pipe", "pipe", "pipe"]
277
+ }).trim();
272
278
  const pid = parseInt(output, 10);
273
279
  if (!isNaN(pid) && pid > 0) return pid;
274
280
  return null;
@@ -276,6 +282,31 @@ function resolveFromSystemd(unit) {
276
282
  return null;
277
283
  }
278
284
  }
285
+ function resolveFromLaunchd(label) {
286
+ try {
287
+ const output = (0, import_node_child_process.execFileSync)("launchctl", ["list", label], {
288
+ encoding: "utf-8",
289
+ timeout: 3e3,
290
+ stdio: ["pipe", "pipe", "pipe"]
291
+ });
292
+ const pidMatch = output.match(/"PID"\s*=\s*(\d+)/);
293
+ if (pidMatch) {
294
+ const pid = parseInt(pidMatch[1], 10);
295
+ if (!isNaN(pid) && pid > 0) return pid;
296
+ }
297
+ const lines = output.trim().split("\n");
298
+ if (lines.length >= 2) {
299
+ const parts = lines[1].trim().split(/\s+/);
300
+ if (parts[0]) {
301
+ const pid = parseInt(parts[0], 10);
302
+ if (!isNaN(pid) && pid > 0) return pid;
303
+ }
304
+ }
305
+ return null;
306
+ } catch {
307
+ return null;
308
+ }
309
+ }
279
310
  function resolveFromFile(pidFile) {
280
311
  try {
281
312
  if (!fs2.existsSync(pidFile)) return null;
@@ -462,9 +493,9 @@ async function configProbe(_target, configPath) {
462
493
  latencyMs: Date.now() - start
463
494
  };
464
495
  }
465
- const missingPaths = REQUIRED_CONFIG_PATHS.filter(({ path: path3 }) => {
496
+ const missingPaths = REQUIRED_CONFIG_PATHS.filter(({ path: path4 }) => {
466
497
  let obj = parsed;
467
- for (const key of path3) {
498
+ for (const key of path4) {
468
499
  if (obj === null || typeof obj !== "object" || !(key in obj)) return true;
469
500
  obj = obj[key];
470
501
  }
@@ -901,10 +932,7 @@ var HealthMonitor = class extends import_node_events.EventEmitter {
901
932
  "disk"
902
933
  ),
903
934
  withTimeout(() => logTailProbe(target, this.config.gateway.logPath), "logTail"),
904
- withTimeout(
905
- () => websocketProbe(target, this.config.gateway.port, timeout),
906
- "websocket"
907
- )
935
+ withTimeout(() => websocketProbe(target, this.config.gateway.port, timeout), "websocket")
908
936
  ]);
909
937
  const probeResults = results.map((r, i) => {
910
938
  if (r.status === "fulfilled") return r.value;
@@ -968,10 +996,12 @@ var statusCommand = new import_commander.Command("status").description("Show cur
968
996
  };
969
997
  const reset = "\x1B[0m";
970
998
  const color = bandColors[score.band] ?? "";
971
- process.stdout.write(`
999
+ process.stdout.write(
1000
+ `
972
1001
  Health: ${color}${score.band.toUpperCase()}${reset} (score: ${score.total})
973
1002
 
974
- `);
1003
+ `
1004
+ );
975
1005
  for (const probe of score.probeResults) {
976
1006
  const icon = probe.healthy ? "\x1B[32m+\x1B[0m" : "\x1B[31m-\x1B[0m";
977
1007
  const msg = probe.message ? ` \u2014 ${probe.message}` : "";
@@ -1000,8 +1030,10 @@ var checkCommand = new import_commander2.Command("check").description("Run all h
1000
1030
  const failed = score.probeResults.filter((p) => !p.healthy);
1001
1031
  process.stdout.write(`Health: ${score.band.toUpperCase()} (score: ${score.total})
1002
1032
  `);
1003
- process.stdout.write(`Probes: ${score.probeResults.length - failed.length} passed, ${failed.length} failed
1004
- `);
1033
+ process.stdout.write(
1034
+ `Probes: ${score.probeResults.length - failed.length} passed, ${failed.length} failed
1035
+ `
1036
+ );
1005
1037
  if (failed.length > 0) {
1006
1038
  process.stdout.write("\nFailed probes:\n");
1007
1039
  for (const probe of failed) {
@@ -1016,6 +1048,7 @@ var checkCommand = new import_commander2.Command("check").description("Run all h
1016
1048
  // src/cli/commands/init.ts
1017
1049
  var import_commander3 = require("commander");
1018
1050
  var fs8 = __toESM(require("fs"));
1051
+ var os3 = __toESM(require("os"));
1019
1052
  var readline = __toESM(require("readline"));
1020
1053
  function ask(rl, question, defaultValue) {
1021
1054
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
@@ -1030,19 +1063,27 @@ function detectPort() {
1030
1063
  const configPath = expandHome("~/.openclaw/openclaw.json");
1031
1064
  if (fs8.existsSync(configPath)) {
1032
1065
  const raw = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
1033
- if (raw?.gateway?.port) return raw.gateway.port;
1066
+ const gateway = raw?.gateway;
1067
+ if (gateway?.port && typeof gateway.port === "number") return gateway.port;
1034
1068
  }
1035
1069
  } catch {
1036
1070
  }
1037
1071
  return 3e3;
1038
1072
  }
1073
+ function detectPlatform() {
1074
+ if (os3.platform() === "darwin") {
1075
+ return { type: "launchd", pidFile: "ai.openclaw.gateway" };
1076
+ }
1077
+ return { type: "systemd", pidFile: "openclaw-gateway.service" };
1078
+ }
1039
1079
  function generateToml(opts) {
1080
+ const platform3 = detectPlatform();
1040
1081
  let toml = `# OpenClaw Aegis Configuration
1041
1082
  # Generated by 'aegis init'
1042
1083
 
1043
1084
  [gateway]
1044
1085
  configPath = "~/.openclaw/openclaw.json"
1045
- pidFile = "openclaw-gateway.service"
1086
+ pidFile = "${platform3.pidFile}"
1046
1087
  port = ${opts.port}
1047
1088
  logPath = "~/.openclaw/logs/gateway.log"
1048
1089
  healthEndpoint = "/health"
@@ -1069,7 +1110,7 @@ enabled = true
1069
1110
  countdownMs = 30000
1070
1111
 
1071
1112
  [platform]
1072
- type = "systemd"
1113
+ type = "${platform3.type}"
1073
1114
  `;
1074
1115
  if (opts.channels.length > 0) {
1075
1116
  toml += "\n[alerts]\n";
@@ -1089,7 +1130,6 @@ type = "systemd"
1089
1130
  return toml;
1090
1131
  }
1091
1132
  var initCommand = new import_commander3.Command("init").description("Interactive setup wizard \u2014 configure Aegis for your gateway").option("--auto", "Auto-detect everything, no prompts").action(async (opts) => {
1092
- const configDir = getConfigDir();
1093
1133
  const configFile = expandHome(DEFAULT_CONFIG_PATH);
1094
1134
  if (fs8.existsSync(configFile) && !opts.auto) {
1095
1135
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -1105,8 +1145,9 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1105
1145
  const channels = [];
1106
1146
  if (opts.auto) {
1107
1147
  console.log("Auto-detecting configuration...");
1148
+ const platform3 = detectPlatform();
1108
1149
  console.log(` Gateway port: ${detectedPort}`);
1109
- console.log(` PID source: openclaw-gateway.service (systemd)`);
1150
+ console.log(` PID source: ${platform3.pidFile} (${platform3.type})`);
1110
1151
  console.log(` Memory threshold: 768MB`);
1111
1152
  } else {
1112
1153
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -1119,10 +1160,16 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1119
1160
  const memStr = await ask(rl, "Memory threshold (MB)", "768");
1120
1161
  const memThreshold = parseInt(memStr, 10) || 768;
1121
1162
  console.log("\n Alert Channels (out-of-band \u2014 never through the gateway)\n");
1122
- console.log(" Supported: ntfy, telegram, whatsapp, slack, discord, email, pushover, webhook\n");
1163
+ console.log(
1164
+ " Supported: ntfy, telegram, whatsapp, slack, discord, email, pushover, webhook\n"
1165
+ );
1123
1166
  let addMore = true;
1124
1167
  while (addMore) {
1125
- const channelType = await ask(rl, "Add alert channel? (ntfy/telegram/whatsapp/slack/discord/email/pushover/webhook/skip)", "skip");
1168
+ const channelType = await ask(
1169
+ rl,
1170
+ "Add alert channel? (ntfy/telegram/whatsapp/slack/discord/email/pushover/webhook/skip)",
1171
+ "skip"
1172
+ );
1126
1173
  if (channelType === "skip" || channelType === "") {
1127
1174
  addMore = false;
1128
1175
  continue;
@@ -1150,7 +1197,10 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1150
1197
  case "whatsapp": {
1151
1198
  const phoneNumberId = await ask(rl, " WhatsApp Business phone number ID");
1152
1199
  const accessToken = await ask(rl, " WhatsApp Cloud API access token");
1153
- const recipientNumber = await ask(rl, " Recipient phone number (with country code, e.g. 61412345678)");
1200
+ const recipientNumber = await ask(
1201
+ rl,
1202
+ " Recipient phone number (with country code, e.g. 61412345678)"
1203
+ );
1154
1204
  if (phoneNumberId && accessToken && recipientNumber) {
1155
1205
  channels.push({ type: "whatsapp", phoneNumberId, accessToken, recipientNumber });
1156
1206
  console.log(" Added WhatsApp channel\n");
@@ -1163,7 +1213,10 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1163
1213
  const webhookUrl = await ask(rl, " Slack Incoming Webhook URL");
1164
1214
  if (webhookUrl) {
1165
1215
  const channel = await ask(rl, " Slack channel override (optional, e.g. #alerts)");
1166
- const ch = { type: "slack", webhookUrl };
1216
+ const ch = {
1217
+ type: "slack",
1218
+ webhookUrl
1219
+ };
1167
1220
  if (channel) ch.channel = channel;
1168
1221
  channels.push(ch);
1169
1222
  console.log(" Added Slack channel\n");
@@ -1176,7 +1229,10 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1176
1229
  const webhookUrl = await ask(rl, " Discord Webhook URL");
1177
1230
  if (webhookUrl) {
1178
1231
  const username = await ask(rl, " Bot display name (optional)", "Aegis");
1179
- const ch = { type: "discord", webhookUrl };
1232
+ const ch = {
1233
+ type: "discord",
1234
+ webhookUrl
1235
+ };
1180
1236
  if (username && username !== "Aegis") ch.username = username;
1181
1237
  channels.push(ch);
1182
1238
  console.log(" Added Discord channel\n");
@@ -1214,7 +1270,11 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1214
1270
  const userKey = await ask(rl, " Pushover user key");
1215
1271
  if (apiToken && userKey) {
1216
1272
  const device = await ask(rl, " Device name (optional)");
1217
- const ch = { type: "pushover", apiToken, userKey };
1273
+ const ch = {
1274
+ type: "pushover",
1275
+ apiToken,
1276
+ userKey
1277
+ };
1218
1278
  if (device) ch.device = device;
1219
1279
  channels.push(ch);
1220
1280
  console.log(" Added Pushover channel\n");
@@ -1254,7 +1314,9 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1254
1314
  const score2 = await monitor2.runAllProbes();
1255
1315
  const passed2 = score2.probeResults.filter((p) => p.healthy).length;
1256
1316
  const failed = score2.probeResults.filter((p) => !p.healthy);
1257
- console.log(` Health: ${score2.band.toUpperCase()} (${passed2}/${score2.probeResults.length} probes passed)`);
1317
+ console.log(
1318
+ ` Health: ${score2.band.toUpperCase()} (${passed2}/${score2.probeResults.length} probes passed)`
1319
+ );
1258
1320
  if (failed.length > 0) {
1259
1321
  for (const probe of failed) {
1260
1322
  console.log(` - ${probe.name}: ${probe.message ?? "failed"}`);
@@ -1275,7 +1337,9 @@ var initCommand = new import_commander3.Command("init").description("Interactive
1275
1337
  const monitor = new HealthMonitor(config);
1276
1338
  const score = await monitor.runAllProbes();
1277
1339
  const passed = score.probeResults.filter((p) => p.healthy).length;
1278
- console.log(`Health: ${score.band.toUpperCase()} (${passed}/${score.probeResults.length} probes passed)`);
1340
+ console.log(
1341
+ `Health: ${score.band.toUpperCase()} (${passed}/${score.probeResults.length} probes passed)`
1342
+ );
1279
1343
  console.log("\nSetup complete.");
1280
1344
  });
1281
1345
 
@@ -1380,9 +1444,9 @@ var NtfyProvider = class {
1380
1444
  const response = await fetch(url, {
1381
1445
  method: "POST",
1382
1446
  headers: {
1383
- "Title": alert.title,
1384
- "Priority": String(this.config.priority),
1385
- "Tags": alertSeverityToTag(alert.severity)
1447
+ Title: alert.title,
1448
+ Priority: String(this.config.priority),
1449
+ Tags: alertSeverityToTag(alert.severity)
1386
1450
  },
1387
1451
  body: alert.body
1388
1452
  });
@@ -1502,7 +1566,7 @@ ${alert.body}`;
1502
1566
  const response = await fetch(url, {
1503
1567
  method: "POST",
1504
1568
  headers: {
1505
- "Authorization": `Bearer ${this.config.accessToken}`,
1569
+ Authorization: `Bearer ${this.config.accessToken}`,
1506
1570
  "Content-Type": "application/json"
1507
1571
  },
1508
1572
  body: JSON.stringify({
@@ -1662,13 +1726,15 @@ var DiscordProvider = class {
1662
1726
  const start = Date.now();
1663
1727
  const color = alert.severity === "critical" ? 15548997 : alert.severity === "warning" ? 16705372 : 5763719;
1664
1728
  const payload = {
1665
- embeds: [{
1666
- title: alert.title,
1667
- description: alert.body,
1668
- color,
1669
- timestamp: alert.timestamp,
1670
- footer: { text: "OpenClaw Aegis" }
1671
- }]
1729
+ embeds: [
1730
+ {
1731
+ title: alert.title,
1732
+ description: alert.body,
1733
+ color,
1734
+ timestamp: alert.timestamp,
1735
+ footer: { text: "OpenClaw Aegis" }
1736
+ }
1737
+ ]
1672
1738
  };
1673
1739
  if (this.config.username) {
1674
1740
  payload.username = this.config.username;
@@ -1860,29 +1926,31 @@ ${body}\r
1860
1926
  plain.write("STARTTLS\r\n");
1861
1927
  startTlsSent = true;
1862
1928
  } else if (code === 220 && startTlsSent) {
1863
- socket = tls.connect({
1864
- socket: plain,
1865
- host: this.config.host,
1866
- rejectUnauthorized: true
1867
- }, () => {
1868
- let step2 = 0;
1869
- let buffer2 = "";
1870
- const cmds = [
1871
- `EHLO aegis\r
1929
+ socket = tls.connect(
1930
+ {
1931
+ socket: plain,
1932
+ host: this.config.host,
1933
+ rejectUnauthorized: true
1934
+ },
1935
+ () => {
1936
+ let step2 = 0;
1937
+ let buffer2 = "";
1938
+ const cmds = [
1939
+ `EHLO aegis\r
1872
1940
  `,
1873
- `AUTH LOGIN\r
1941
+ `AUTH LOGIN\r
1874
1942
  `,
1875
- `${Buffer.from(this.config.username).toString("base64")}\r
1943
+ `${Buffer.from(this.config.username).toString("base64")}\r
1876
1944
  `,
1877
- `${Buffer.from(this.config.password).toString("base64")}\r
1945
+ `${Buffer.from(this.config.password).toString("base64")}\r
1878
1946
  `,
1879
- `MAIL FROM:<${this.config.from}>\r
1947
+ `MAIL FROM:<${this.config.from}>\r
1880
1948
  `,
1881
- `RCPT TO:<${this.config.to}>\r
1949
+ `RCPT TO:<${this.config.to}>\r
1882
1950
  `,
1883
- `DATA\r
1951
+ `DATA\r
1884
1952
  `,
1885
- `From: ${this.config.from}\r
1953
+ `From: ${this.config.from}\r
1886
1954
  To: ${this.config.to}\r
1887
1955
  Subject: ${subject}\r
1888
1956
  Content-Type: text/plain; charset=utf-8\r
@@ -1891,36 +1959,37 @@ X-Mailer: OpenClaw-Aegis\r
1891
1959
  ${body}\r
1892
1960
  .\r
1893
1961
  `,
1894
- `QUIT\r
1962
+ `QUIT\r
1895
1963
  `
1896
- ];
1897
- socket.write(cmds[0]);
1898
- step2 = 1;
1899
- socket.on("data", (d) => {
1900
- buffer2 += d.toString();
1901
- const ls = buffer2.split("\r\n");
1902
- buffer2 = ls.pop() ?? "";
1903
- for (const l of ls) {
1904
- if (!l) continue;
1905
- const c = parseInt(l.substring(0, 3), 10);
1906
- if (l.charAt(3) === "-") continue;
1907
- if (c >= 400) {
1908
- socket.destroy();
1909
- reject(new Error(`SMTP error: ${l}`));
1910
- return;
1911
- }
1912
- if (step2 < cmds.length) {
1913
- socket.write(cmds[step2]);
1914
- step2++;
1964
+ ];
1965
+ socket.write(cmds[0]);
1966
+ step2 = 1;
1967
+ socket.on("data", (d) => {
1968
+ buffer2 += d.toString();
1969
+ const ls = buffer2.split("\r\n");
1970
+ buffer2 = ls.pop() ?? "";
1971
+ for (const l of ls) {
1972
+ if (!l) continue;
1973
+ const c = parseInt(l.substring(0, 3), 10);
1974
+ if (l.charAt(3) === "-") continue;
1975
+ if (c >= 400) {
1976
+ socket.destroy();
1977
+ reject(new Error(`SMTP error: ${l}`));
1978
+ return;
1979
+ }
1980
+ if (step2 < cmds.length) {
1981
+ socket.write(cmds[step2]);
1982
+ step2++;
1983
+ }
1984
+ if (step2 >= cmds.length && c === 221) {
1985
+ socket.end();
1986
+ resolve();
1987
+ return;
1988
+ }
1915
1989
  }
1916
- if (step2 >= cmds.length && c === 221) {
1917
- socket.end();
1918
- resolve();
1919
- return;
1920
- }
1921
- }
1922
- });
1923
- });
1990
+ });
1991
+ }
1992
+ );
1924
1993
  return;
1925
1994
  }
1926
1995
  }
@@ -2054,6 +2123,135 @@ var testAlertCommand = new import_commander4.Command("test-alert").description("
2054
2123
  process.exit(result.sent ? 0 : 1);
2055
2124
  });
2056
2125
 
2126
+ // src/cli/commands/incidents.ts
2127
+ var import_commander5 = require("commander");
2128
+ var fs9 = __toESM(require("fs"));
2129
+ var path3 = __toESM(require("path"));
2130
+ function parseIncident(filePath) {
2131
+ const id = path3.basename(filePath, ".jsonl");
2132
+ const lines = fs9.readFileSync(filePath, "utf-8").trim().split("\n").filter(Boolean);
2133
+ const events = [];
2134
+ for (const line of lines) {
2135
+ try {
2136
+ events.push(JSON.parse(line));
2137
+ } catch {
2138
+ }
2139
+ }
2140
+ const started = events[0]?.timestamp ?? "unknown";
2141
+ const resolvedEvent = events.find((e) => e.type === "INCIDENT_RESOLVED");
2142
+ const unresolvedEvent = events.find((e) => e.type === "INCIDENT_UNRESOLVED");
2143
+ const resolved = !!resolvedEvent;
2144
+ let durationMs = null;
2145
+ const endEvent = resolvedEvent ?? unresolvedEvent;
2146
+ if (endEvent && events[0]) {
2147
+ durationMs = new Date(endEvent.timestamp).getTime() - new Date(events[0].timestamp).getTime();
2148
+ }
2149
+ return { id, started, resolved, events, durationMs };
2150
+ }
2151
+ function formatDuration(ms) {
2152
+ if (ms < 1e3) return `${ms}ms`;
2153
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2154
+ return `${(ms / 6e4).toFixed(1)}m`;
2155
+ }
2156
+ var incidentsCommand = new import_commander5.Command("incidents").description("Browse past incident logs").option("--json", "Output as JSON").option("--last <n>", "Show last N incidents", "10").argument("[incident-id]", "Show details for a specific incident").action((incidentId, opts) => {
2157
+ const incidentsDir = expandHome("~/.openclaw/aegis/incidents");
2158
+ if (!fs9.existsSync(incidentsDir)) {
2159
+ console.log("No incidents recorded yet.");
2160
+ return;
2161
+ }
2162
+ const files = fs9.readdirSync(incidentsDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
2163
+ if (files.length === 0) {
2164
+ console.log("No incidents recorded yet.");
2165
+ return;
2166
+ }
2167
+ if (incidentId) {
2168
+ const file = files.find((f) => f.startsWith(incidentId));
2169
+ if (!file) {
2170
+ console.log(`Incident "${incidentId}" not found.`);
2171
+ return;
2172
+ }
2173
+ const incident = parseIncident(path3.join(incidentsDir, file));
2174
+ if (opts.json) {
2175
+ process.stdout.write(JSON.stringify(incident, null, 2) + "\n");
2176
+ return;
2177
+ }
2178
+ const status = incident.resolved ? "\x1B[32mRESOLVED\x1B[0m" : "\x1B[31mUNRESOLVED\x1B[0m";
2179
+ const duration = incident.durationMs !== null ? formatDuration(incident.durationMs) : "ongoing";
2180
+ console.log(`
2181
+ Incident: ${incident.id}`);
2182
+ console.log(`Status: ${status}`);
2183
+ console.log(`Started: ${incident.started}`);
2184
+ console.log(`Duration: ${duration}`);
2185
+ console.log(`
2186
+ Timeline:
2187
+ `);
2188
+ for (const event of incident.events) {
2189
+ const time = new Date(event.timestamp).toLocaleTimeString();
2190
+ const icon = eventIcon(event.type);
2191
+ const detail = formatEventDetail(event);
2192
+ console.log(` ${time} ${icon} ${event.type}${detail}`);
2193
+ }
2194
+ console.log();
2195
+ return;
2196
+ }
2197
+ const limit = parseInt(opts.last, 10) || 10;
2198
+ const incidents = files.slice(0, limit).map((f) => parseIncident(path3.join(incidentsDir, f)));
2199
+ if (opts.json) {
2200
+ process.stdout.write(JSON.stringify(incidents, null, 2) + "\n");
2201
+ return;
2202
+ }
2203
+ const resolved = incidents.filter((i) => i.resolved).length;
2204
+ const unresolved = incidents.length - resolved;
2205
+ console.log(
2206
+ `
2207
+ ${incidents.length} incident(s) \u2014 ${resolved} resolved, ${unresolved} unresolved
2208
+ `
2209
+ );
2210
+ for (const inc of incidents) {
2211
+ const icon = inc.resolved ? "\x1B[32m+\x1B[0m" : "\x1B[31m-\x1B[0m";
2212
+ const duration = inc.durationMs !== null ? formatDuration(inc.durationMs) : "ongoing";
2213
+ const eventCount = inc.events.length;
2214
+ const date = new Date(inc.started).toLocaleString();
2215
+ console.log(` ${icon} ${inc.id} ${date} ${duration} (${eventCount} events)`);
2216
+ }
2217
+ console.log(`
2218
+ Run 'aegis incidents <id>' for details.
2219
+ `);
2220
+ });
2221
+ function eventIcon(type) {
2222
+ switch (type) {
2223
+ case "INCIDENT_START":
2224
+ return "\x1B[31m>\x1B[0m";
2225
+ case "L1_ATTEMPT":
2226
+ return "\x1B[33m~\x1B[0m";
2227
+ case "L1_SUCCESS":
2228
+ return "\x1B[32m+\x1B[0m";
2229
+ case "L2_ATTEMPT":
2230
+ return "\x1B[33m~\x1B[0m";
2231
+ case "L2_SUCCESS":
2232
+ return "\x1B[32m+\x1B[0m";
2233
+ case "L4_ALERT":
2234
+ return "\x1B[31m!\x1B[0m";
2235
+ case "INCIDENT_RESOLVED":
2236
+ return "\x1B[32m+\x1B[0m";
2237
+ case "INCIDENT_UNRESOLVED":
2238
+ return "\x1B[31mx\x1B[0m";
2239
+ case "DEAD_MAN_SWITCH_ROLLBACK":
2240
+ return "\x1B[33m<\x1B[0m";
2241
+ case "CIRCUIT_BREAKER_TRIPPED":
2242
+ return "\x1B[31m#\x1B[0m";
2243
+ default:
2244
+ return " ";
2245
+ }
2246
+ }
2247
+ function formatEventDetail(event) {
2248
+ const d = event.data;
2249
+ if (d.attempt) return ` (attempt ${String(d.attempt)})`;
2250
+ if (d.pattern) return ` \u2014 ${String(d.pattern)}`;
2251
+ if (d.reason) return ` \u2014 ${String(d.reason)}`;
2252
+ return "";
2253
+ }
2254
+
2057
2255
  // src/cli/index.ts
2058
2256
  function getVersion() {
2059
2257
  try {
@@ -2063,11 +2261,12 @@ function getVersion() {
2063
2261
  return "unknown";
2064
2262
  }
2065
2263
  }
2066
- var program = new import_commander5.Command();
2264
+ var program = new import_commander6.Command();
2067
2265
  program.name("aegis").description("OpenClaw Aegis \u2014 self-healing sidecar for the OpenClaw gateway").version(getVersion());
2068
2266
  program.addCommand(initCommand);
2069
2267
  program.addCommand(statusCommand);
2070
2268
  program.addCommand(checkCommand);
2071
2269
  program.addCommand(testAlertCommand);
2270
+ program.addCommand(incidentsCommand);
2072
2271
  program.parse(process.argv);
2073
2272
  //# sourceMappingURL=index.js.map