aoaoe 0.55.0 → 0.57.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
@@ -243,6 +243,11 @@ commands:
243
243
  config --validate validate config + check tool availability
244
244
  config --diff show only fields that differ from defaults
245
245
  notify-test send a test notification to configured webhooks
246
+ doctor comprehensive health check (config, tools, daemon, disk)
247
+ logs show recent conversation log entries
248
+ logs --actions show action log entries (from ~/.aoaoe/actions.log)
249
+ logs --grep <pattern> filter log entries by substring or regex
250
+ logs -n <count> number of entries to show (default: 50)
246
251
  task manage tasks and sessions (list, start, stop, new, rm, edit)
247
252
  tasks show task progress (from aoaoe.tasks.json)
248
253
  history review recent actions (from ~/.aoaoe/actions.log)
package/dist/config.d.ts CHANGED
@@ -29,6 +29,11 @@ export declare function parseCliArgs(argv: string[]): {
29
29
  configValidate: boolean;
30
30
  configDiff: boolean;
31
31
  notifyTest: boolean;
32
+ runDoctor: boolean;
33
+ runLogs: boolean;
34
+ logsActions: boolean;
35
+ logsGrep?: string;
36
+ logsCount?: number;
32
37
  runInit: boolean;
33
38
  initForce: boolean;
34
39
  runTaskCli: boolean;
package/dist/config.js CHANGED
@@ -299,7 +299,7 @@ export function parseCliArgs(argv) {
299
299
  let initForce = false;
300
300
  let runTaskCli = false;
301
301
  let registerTitle;
302
- const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, showStatus: false, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runInit: false, initForce: false, runTaskCli: false };
302
+ const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, showStatus: false, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runInit: false, initForce: false, runTaskCli: false };
303
303
  // check for subcommand as first non-flag arg
304
304
  if (argv[2] === "test-context") {
305
305
  return { ...defaults, testContext: true };
@@ -327,6 +327,25 @@ export function parseCliArgs(argv) {
327
327
  if (argv[2] === "notify-test") {
328
328
  return { ...defaults, notifyTest: true };
329
329
  }
330
+ if (argv[2] === "doctor") {
331
+ return { ...defaults, runDoctor: true };
332
+ }
333
+ if (argv[2] === "logs") {
334
+ const actions = argv.includes("--actions") || argv.includes("-a");
335
+ let grep;
336
+ let count;
337
+ for (let i = 3; i < argv.length; i++) {
338
+ if ((argv[i] === "--grep" || argv[i] === "-g") && argv[i + 1]) {
339
+ grep = argv[++i];
340
+ }
341
+ else if ((argv[i] === "-n" || argv[i] === "--count") && argv[i + 1]) {
342
+ const val = parseInt(argv[++i], 10);
343
+ if (!isNaN(val) && val > 0)
344
+ count = val;
345
+ }
346
+ }
347
+ return { ...defaults, runLogs: true, logsActions: actions, logsGrep: grep, logsCount: count };
348
+ }
330
349
  if (argv[2] === "init") {
331
350
  const force = argv.includes("--force") || argv.includes("-f");
332
351
  return { ...defaults, runInit: true, initForce: force };
@@ -416,7 +435,7 @@ export function parseCliArgs(argv) {
416
435
  break;
417
436
  }
418
437
  }
419
- return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, showStatus: false, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runInit: false, initForce: false, runTaskCli: false };
438
+ return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, showStatus: false, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runInit: false, initForce: false, runTaskCli: false };
420
439
  }
421
440
  export function printHelp() {
422
441
  console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
@@ -437,6 +456,11 @@ commands:
437
456
  config --validate validate config + check tool availability
438
457
  config --diff show only fields that differ from defaults
439
458
  notify-test send a test notification to configured webhooks
459
+ doctor comprehensive health check (config, tools, daemon, disk)
460
+ logs show recent conversation log entries (last 50)
461
+ logs --actions show action log entries (from ~/.aoaoe/actions.log)
462
+ logs --grep <pattern> filter log entries by substring or regex
463
+ logs -n <count> number of entries to show (default: 50)
440
464
  task manage tasks and sessions (list, start, stop, new, rm, edit)
441
465
  tasks show task progress (from aoaoe.tasks.json)
442
466
  history review recent actions (from ~/.aoaoe/actions.log)
@@ -463,6 +487,11 @@ options:
463
487
  init options:
464
488
  --force, -f overwrite existing config
465
489
 
490
+ logs options:
491
+ --actions, -a show action log instead of conversation log
492
+ --grep, -g <pattern> filter entries by substring or regex
493
+ -n, --count <number> number of entries to show (default: 50)
494
+
466
495
  register options:
467
496
  --title, -t <name> session title in AoE (default: aoaoe)
468
497
 
package/dist/console.d.ts CHANGED
@@ -65,5 +65,11 @@ export declare function summarizeRecentActions(logLines: string[], windowMs?: nu
65
65
  * Pure function: takes stderr text, returns a plain-English explanation.
66
66
  */
67
67
  export declare function friendlyError(stderr: string): string;
68
+ /**
69
+ * Filter log lines by a grep pattern (substring or regex).
70
+ * Tries the pattern as a regex first; falls back to plain case-insensitive substring match
71
+ * if the pattern is not valid regex syntax.
72
+ */
73
+ export declare function filterLogLines(lines: string[], pattern: string): string[];
68
74
  export declare function colorizeConsoleLine(line: string): string;
69
75
  //# sourceMappingURL=console.d.ts.map
package/dist/console.js CHANGED
@@ -365,6 +365,25 @@ export function friendlyError(stderr) {
365
365
  const firstLine = s.split("\n")[0].trim();
366
366
  return firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine;
367
367
  }
368
+ /**
369
+ * Filter log lines by a grep pattern (substring or regex).
370
+ * Tries the pattern as a regex first; falls back to plain case-insensitive substring match
371
+ * if the pattern is not valid regex syntax.
372
+ */
373
+ export function filterLogLines(lines, pattern) {
374
+ let re = null;
375
+ try {
376
+ re = new RegExp(pattern, "i");
377
+ }
378
+ catch {
379
+ // invalid regex — use substring
380
+ }
381
+ return lines.filter((line) => {
382
+ if (re)
383
+ return re.test(line);
384
+ return line.toLowerCase().includes(pattern.toLowerCase());
385
+ });
386
+ }
368
387
  // colorize a single console line for inline terminal output
369
388
  // applied to each line as it's written (not batch like chat.ts colorize)
370
389
  export function colorizeConsoleLine(line) {
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { printDashboard } from "./dashboard.js";
7
7
  import { InputReader } from "./input.js";
8
8
  import { ReasonerConsole } from "./console.js";
9
9
  import { writeState, buildSessionStates, checkInterrupt, clearInterrupt, cleanupState, acquireLock, readState } from "./daemon-state.js";
10
- import { formatSessionSummaries, formatActionDetail, formatPlainEnglishAction, narrateObservation, summarizeRecentActions, friendlyError } from "./console.js";
10
+ import { formatSessionSummaries, formatActionDetail, formatPlainEnglishAction, narrateObservation, summarizeRecentActions, friendlyError, colorizeConsoleLine, filterLogLines } from "./console.js";
11
11
  import { loadGlobalContext, resolveProjectDirWithSource, discoverContextFiles, loadSessionContext } from "./context.js";
12
12
  import { tick as loopTick } from "./loop.js";
13
13
  import { exec as shellExec } from "./shell.js";
@@ -28,7 +28,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
28
28
  const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
29
29
  const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
30
30
  async function main() {
31
- const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showHistory, showStatus, showConfig, configValidate, configDiff, notifyTest, runInit, initForce, runTaskCli: isTaskCli, registerTitle } = parseCliArgs(process.argv);
31
+ const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showHistory, showStatus, showConfig, configValidate, configDiff, notifyTest, runDoctor, runLogs, logsActions, logsGrep, logsCount, runInit, initForce, runTaskCli: isTaskCli, registerTitle } = parseCliArgs(process.argv);
32
32
  if (help) {
33
33
  printHelp();
34
34
  process.exit(0);
@@ -91,6 +91,16 @@ async function main() {
91
91
  await runNotifyTest();
92
92
  return;
93
93
  }
94
+ // `aoaoe doctor` -- comprehensive health check
95
+ if (runDoctor) {
96
+ await runDoctorCheck();
97
+ return;
98
+ }
99
+ // `aoaoe logs` -- show conversation or action log entries
100
+ if (runLogs) {
101
+ await showLogs(logsActions, logsGrep, logsCount);
102
+ return;
103
+ }
94
104
  // `aoaoe task` -- task management CLI
95
105
  if (isTaskCli) {
96
106
  await runTaskCli(process.argv);
@@ -1183,6 +1193,104 @@ async function showActionHistory() {
1183
1193
  console.log(` breakdown: ${breakdown}`);
1184
1194
  console.log("");
1185
1195
  }
1196
+ // `aoaoe logs` -- show conversation or action log entries
1197
+ async function showLogs(actions, grep, count) {
1198
+ const n = count ?? 50;
1199
+ if (actions) {
1200
+ // show action log entries (JSONL from ~/.aoaoe/actions.log)
1201
+ const logFile = join(homedir(), ".aoaoe", "actions.log");
1202
+ if (!existsSync(logFile)) {
1203
+ console.log("no action log found (no actions have been taken yet)");
1204
+ return;
1205
+ }
1206
+ let lines;
1207
+ try {
1208
+ lines = readFileSync(logFile, "utf-8").trim().split("\n").filter((l) => l.trim());
1209
+ }
1210
+ catch {
1211
+ console.error("failed to read action log");
1212
+ return;
1213
+ }
1214
+ if (lines.length === 0) {
1215
+ console.log("action log is empty");
1216
+ return;
1217
+ }
1218
+ // apply grep filter before slicing
1219
+ if (grep) {
1220
+ lines = filterLogLines(lines, grep);
1221
+ if (lines.length === 0) {
1222
+ console.log(`no action log entries matching '${grep}'`);
1223
+ return;
1224
+ }
1225
+ }
1226
+ const recent = lines.slice(-n);
1227
+ console.log("");
1228
+ console.log(` action log (last ${recent.length} of ${lines.length}${grep ? ` matching '${grep}'` : ""})`);
1229
+ console.log(` ${"─".repeat(70)}`);
1230
+ for (const line of recent) {
1231
+ try {
1232
+ const entry = toActionLogEntry(JSON.parse(line));
1233
+ if (!entry)
1234
+ continue;
1235
+ const time = new Date(entry.timestamp).toLocaleTimeString();
1236
+ const date = new Date(entry.timestamp).toLocaleDateString();
1237
+ const icon = entry.success ? `${GREEN}+${RESET}` : `${RED}!${RESET}`;
1238
+ const actionName = entry.action.action;
1239
+ const session = entry.action.session?.slice(0, 8) ?? entry.action.title ?? "";
1240
+ const detail = entry.detail.length > 50 ? entry.detail.slice(0, 47) + "..." : entry.detail;
1241
+ console.log(` ${icon} ${DIM}${date} ${time}${RESET} ${YELLOW}${actionName.padEnd(16)}${RESET} ${session.padEnd(10)} ${detail}`);
1242
+ }
1243
+ catch {
1244
+ // skip unparseable lines
1245
+ }
1246
+ }
1247
+ console.log(` ${"─".repeat(70)}`);
1248
+ console.log("");
1249
+ }
1250
+ else {
1251
+ // show conversation log entries (text from ~/.aoaoe/conversation.log)
1252
+ const logFile = join(homedir(), ".aoaoe", "conversation.log");
1253
+ if (!existsSync(logFile)) {
1254
+ console.log("no conversation log found (daemon hasn't run yet)");
1255
+ return;
1256
+ }
1257
+ let lines;
1258
+ try {
1259
+ const content = readFileSync(logFile, "utf-8");
1260
+ lines = content.split("\n");
1261
+ }
1262
+ catch {
1263
+ console.error("failed to read conversation log");
1264
+ return;
1265
+ }
1266
+ // remove trailing empty line from split
1267
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
1268
+ lines.pop();
1269
+ }
1270
+ if (lines.length === 0) {
1271
+ console.log("conversation log is empty");
1272
+ return;
1273
+ }
1274
+ // apply grep filter before slicing
1275
+ if (grep) {
1276
+ lines = filterLogLines(lines, grep);
1277
+ if (lines.length === 0) {
1278
+ console.log(`no conversation log entries matching '${grep}'`);
1279
+ return;
1280
+ }
1281
+ }
1282
+ const recent = lines.slice(-n);
1283
+ console.log("");
1284
+ console.log(` conversation log (last ${recent.length} of ${lines.length}${grep ? ` matching '${grep}'` : ""})`);
1285
+ console.log(` ${"─".repeat(70)}`);
1286
+ // colorize using the same function as the inline console output
1287
+ for (const line of recent) {
1288
+ console.log(` ${colorizeConsoleLine(line)}`);
1289
+ }
1290
+ console.log(` ${"─".repeat(70)}`);
1291
+ console.log("");
1292
+ }
1293
+ }
1186
1294
  // `aoaoe test` -- dynamically import and run the integration test
1187
1295
  async function runIntegrationTest() {
1188
1296
  const testModule = resolve(__dirname, "integration-test.js");
@@ -1402,6 +1510,204 @@ async function runConfigValidation() {
1402
1510
  if (failed > 0)
1403
1511
  process.exit(1);
1404
1512
  }
1513
+ // `aoaoe doctor` -- comprehensive health check: config, tools, daemon, disk, sessions
1514
+ async function runDoctorCheck() {
1515
+ const pkg = readPkgVersion();
1516
+ let checks = 0;
1517
+ let passed = 0;
1518
+ let warnings = 0;
1519
+ console.log("");
1520
+ console.log(` aoaoe${pkg ? ` v${pkg}` : ""} — doctor`);
1521
+ console.log(` ${"─".repeat(50)}`);
1522
+ // ── 1. config ──────────────────────────────────────────────────────────
1523
+ console.log(`\n ${BOLD}config${RESET}`);
1524
+ const configPath = findConfigFile();
1525
+ checks++;
1526
+ if (configPath) {
1527
+ console.log(` ${GREEN}✓${RESET} config file: ${configPath}`);
1528
+ passed++;
1529
+ }
1530
+ else {
1531
+ console.log(` ${YELLOW}!${RESET} no config file (using defaults)`);
1532
+ warnings++;
1533
+ }
1534
+ let config;
1535
+ checks++;
1536
+ try {
1537
+ config = loadConfig();
1538
+ console.log(` ${GREEN}✓${RESET} config validates OK`);
1539
+ passed++;
1540
+ }
1541
+ catch (err) {
1542
+ const msg = err instanceof Error ? err.message : String(err);
1543
+ console.log(` ${RED}✗${RESET} config invalid: ${msg.split("\n")[0]}`);
1544
+ // use defaults to continue checking other things
1545
+ config = loadConfig({});
1546
+ }
1547
+ // ── 2. tools ───────────────────────────────────────────────────────────
1548
+ console.log(`\n ${BOLD}tools${RESET}`);
1549
+ const toolChecks = [
1550
+ { cmd: "node", label: "Node.js", versionArg: ["--version"], required: true },
1551
+ { cmd: "aoe", label: "agent-of-empires", versionArg: ["--version"], required: true },
1552
+ { cmd: "tmux", label: "terminal multiplexer", versionArg: ["-V"], required: true },
1553
+ ];
1554
+ if (config.reasoner === "opencode") {
1555
+ toolChecks.push({ cmd: "opencode", label: "OpenCode CLI", versionArg: ["version"], required: true });
1556
+ }
1557
+ else {
1558
+ toolChecks.push({ cmd: "claude", label: "Claude Code CLI", versionArg: ["--version"], required: true });
1559
+ }
1560
+ for (const tool of toolChecks) {
1561
+ checks++;
1562
+ try {
1563
+ const result = await shellExec(tool.cmd, tool.versionArg);
1564
+ const ver = result.stdout.trim().split("\n")[0].slice(0, 60) || result.stderr.trim().split("\n")[0].slice(0, 60);
1565
+ console.log(` ${GREEN}✓${RESET} ${tool.cmd} — ${ver}`);
1566
+ passed++;
1567
+ }
1568
+ catch {
1569
+ if (tool.required) {
1570
+ console.log(` ${RED}✗${RESET} ${tool.cmd} not found (${tool.label})`);
1571
+ }
1572
+ else {
1573
+ console.log(` ${YELLOW}!${RESET} ${tool.cmd} not found (${tool.label}, optional)`);
1574
+ warnings++;
1575
+ }
1576
+ }
1577
+ }
1578
+ // ── 3. reasoner server ─────────────────────────────────────────────────
1579
+ if (config.reasoner === "opencode") {
1580
+ console.log(`\n ${BOLD}reasoner${RESET}`);
1581
+ checks++;
1582
+ try {
1583
+ const resp = await fetch(`http://127.0.0.1:${config.opencode.port}/health`, {
1584
+ signal: AbortSignal.timeout(3000),
1585
+ });
1586
+ if (resp.ok) {
1587
+ console.log(` ${GREEN}✓${RESET} opencode serve responding on port ${config.opencode.port}`);
1588
+ passed++;
1589
+ }
1590
+ else {
1591
+ console.log(` ${YELLOW}!${RESET} opencode serve on port ${config.opencode.port} returned ${resp.status}`);
1592
+ warnings++;
1593
+ }
1594
+ }
1595
+ catch {
1596
+ console.log(` ${RED}✗${RESET} opencode serve not responding on port ${config.opencode.port}`);
1597
+ console.log(` ${DIM}start with: opencode serve --port ${config.opencode.port}${RESET}`);
1598
+ }
1599
+ }
1600
+ // ── 4. daemon ──────────────────────────────────────────────────────────
1601
+ console.log(`\n ${BOLD}daemon${RESET}`);
1602
+ const state = readState();
1603
+ const daemonRunning = isDaemonRunningFromState(state);
1604
+ checks++;
1605
+ if (daemonRunning && state) {
1606
+ console.log(` ${GREEN}✓${RESET} daemon running (poll #${state.pollCount}, phase: ${state.phase})`);
1607
+ console.log(` ${state.sessions.length} session(s) monitored`);
1608
+ passed++;
1609
+ }
1610
+ else {
1611
+ console.log(` ${DIM}○${RESET} daemon not running`);
1612
+ passed++; // not running is fine for doctor — just informational
1613
+ }
1614
+ // lock file check
1615
+ const lockPath = join(homedir(), ".aoaoe", "daemon.lock");
1616
+ if (existsSync(lockPath) && !daemonRunning) {
1617
+ checks++;
1618
+ console.log(` ${YELLOW}!${RESET} stale lock file found: ${lockPath}`);
1619
+ console.log(` ${DIM}remove with: rm ${lockPath}${RESET}`);
1620
+ warnings++;
1621
+ }
1622
+ // ── 5. disk / data ─────────────────────────────────────────────────────
1623
+ console.log(`\n ${BOLD}data${RESET}`);
1624
+ const aoaoeDir = join(homedir(), ".aoaoe");
1625
+ if (existsSync(aoaoeDir)) {
1626
+ checks++;
1627
+ try {
1628
+ const files = await import("node:fs").then(fs => fs.readdirSync(aoaoeDir));
1629
+ let totalSize = 0;
1630
+ for (const f of files) {
1631
+ try {
1632
+ totalSize += statSync(join(aoaoeDir, f)).size;
1633
+ }
1634
+ catch { /* skip unreadable */ }
1635
+ }
1636
+ const sizeStr = totalSize < 1024 ? `${totalSize}B` :
1637
+ totalSize < 1_048_576 ? `${(totalSize / 1024).toFixed(1)}KB` :
1638
+ `${(totalSize / 1_048_576).toFixed(1)}MB`;
1639
+ console.log(` ${GREEN}✓${RESET} ~/.aoaoe/ — ${files.length} files, ${sizeStr}`);
1640
+ passed++;
1641
+ }
1642
+ catch {
1643
+ console.log(` ${YELLOW}!${RESET} could not read ~/.aoaoe/`);
1644
+ warnings++;
1645
+ }
1646
+ // actions log stats
1647
+ const actionsPath = join(aoaoeDir, "actions.log");
1648
+ if (existsSync(actionsPath)) {
1649
+ checks++;
1650
+ try {
1651
+ const content = readFileSync(actionsPath, "utf-8").trim();
1652
+ const lineCount = content ? content.split("\n").length : 0;
1653
+ const size = statSync(actionsPath).size;
1654
+ const sizeStr = size < 1024 ? `${size}B` : `${(size / 1024).toFixed(1)}KB`;
1655
+ console.log(` ${GREEN}✓${RESET} actions.log — ${lineCount} entries, ${sizeStr}`);
1656
+ passed++;
1657
+ }
1658
+ catch {
1659
+ console.log(` ${YELLOW}!${RESET} actions.log unreadable`);
1660
+ warnings++;
1661
+ }
1662
+ }
1663
+ }
1664
+ else {
1665
+ console.log(` ${DIM}○${RESET} ~/.aoaoe/ does not exist yet (run 'aoaoe init')`);
1666
+ }
1667
+ // ── 6. aoe sessions ───────────────────────────────────────────────────
1668
+ console.log(`\n ${BOLD}sessions${RESET}`);
1669
+ checks++;
1670
+ try {
1671
+ const listResult = await shellExec("aoe", ["list", "--json"]);
1672
+ if (listResult.exitCode === 0 && listResult.stdout.trim()) {
1673
+ const sessions = JSON.parse(listResult.stdout);
1674
+ if (Array.isArray(sessions) && sessions.length > 0) {
1675
+ console.log(` ${GREEN}✓${RESET} ${sessions.length} aoe session(s) found`);
1676
+ for (const s of sessions.slice(0, 5)) {
1677
+ console.log(` ${DIM}${s.title ?? s.id} (${s.tool ?? "?"})${RESET}`);
1678
+ }
1679
+ if (sessions.length > 5)
1680
+ console.log(` ${DIM}...and ${sessions.length - 5} more${RESET}`);
1681
+ passed++;
1682
+ }
1683
+ else {
1684
+ console.log(` ${DIM}○${RESET} no aoe sessions (start some with 'aoe add')`);
1685
+ passed++;
1686
+ }
1687
+ }
1688
+ else {
1689
+ console.log(` ${YELLOW}!${RESET} aoe list returned non-zero (is aoe running?)`);
1690
+ warnings++;
1691
+ }
1692
+ }
1693
+ catch {
1694
+ console.log(` ${RED}✗${RESET} could not run 'aoe list --json'`);
1695
+ }
1696
+ // ── summary ────────────────────────────────────────────────────────────
1697
+ const failed = checks - passed - warnings;
1698
+ console.log("");
1699
+ console.log(` ${"─".repeat(50)}`);
1700
+ if (failed === 0 && warnings === 0) {
1701
+ console.log(` ${GREEN}${BOLD}all ${checks} checks passed${RESET} — looking healthy`);
1702
+ }
1703
+ else if (failed === 0) {
1704
+ console.log(` ${passed}/${checks} passed, ${YELLOW}${warnings} warning(s)${RESET}`);
1705
+ }
1706
+ else {
1707
+ console.log(` ${passed}/${checks} passed, ${RED}${failed} failed${RESET}${warnings > 0 ? `, ${YELLOW}${warnings} warning(s)${RESET}` : ""}`);
1708
+ }
1709
+ console.log("");
1710
+ }
1405
1711
  // `aoaoe config --diff` -- show only fields that differ from defaults
1406
1712
  function showConfigDiff() {
1407
1713
  const configPath = findConfigFile();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.55.0",
3
+ "version": "0.57.0",
4
4
  "description": "Autonomous supervisor for agent-of-empires sessions using OpenCode or Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",