aoaoe 0.55.0 → 0.56.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,7 @@ 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)
246
247
  task manage tasks and sessions (list, start, stop, new, rm, edit)
247
248
  tasks show task progress (from aoaoe.tasks.json)
248
249
  history review recent actions (from ~/.aoaoe/actions.log)
package/dist/config.d.ts CHANGED
@@ -29,6 +29,7 @@ export declare function parseCliArgs(argv: string[]): {
29
29
  configValidate: boolean;
30
30
  configDiff: boolean;
31
31
  notifyTest: boolean;
32
+ runDoctor: boolean;
32
33
  runInit: boolean;
33
34
  initForce: boolean;
34
35
  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, 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,9 @@ 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
+ }
330
333
  if (argv[2] === "init") {
331
334
  const force = argv.includes("--force") || argv.includes("-f");
332
335
  return { ...defaults, runInit: true, initForce: force };
@@ -416,7 +419,7 @@ export function parseCliArgs(argv) {
416
419
  break;
417
420
  }
418
421
  }
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 };
422
+ 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, runInit: false, initForce: false, runTaskCli: false };
420
423
  }
421
424
  export function printHelp() {
422
425
  console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
@@ -437,6 +440,7 @@ commands:
437
440
  config --validate validate config + check tool availability
438
441
  config --diff show only fields that differ from defaults
439
442
  notify-test send a test notification to configured webhooks
443
+ doctor comprehensive health check (config, tools, daemon, disk)
440
444
  task manage tasks and sessions (list, start, stop, new, rm, edit)
441
445
  tasks show task progress (from aoaoe.tasks.json)
442
446
  history review recent actions (from ~/.aoaoe/actions.log)
package/dist/index.js CHANGED
@@ -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, runInit, initForce, runTaskCli: isTaskCli, registerTitle } = parseCliArgs(process.argv);
32
32
  if (help) {
33
33
  printHelp();
34
34
  process.exit(0);
@@ -91,6 +91,11 @@ 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
+ }
94
99
  // `aoaoe task` -- task management CLI
95
100
  if (isTaskCli) {
96
101
  await runTaskCli(process.argv);
@@ -1402,6 +1407,204 @@ async function runConfigValidation() {
1402
1407
  if (failed > 0)
1403
1408
  process.exit(1);
1404
1409
  }
1410
+ // `aoaoe doctor` -- comprehensive health check: config, tools, daemon, disk, sessions
1411
+ async function runDoctorCheck() {
1412
+ const pkg = readPkgVersion();
1413
+ let checks = 0;
1414
+ let passed = 0;
1415
+ let warnings = 0;
1416
+ console.log("");
1417
+ console.log(` aoaoe${pkg ? ` v${pkg}` : ""} — doctor`);
1418
+ console.log(` ${"─".repeat(50)}`);
1419
+ // ── 1. config ──────────────────────────────────────────────────────────
1420
+ console.log(`\n ${BOLD}config${RESET}`);
1421
+ const configPath = findConfigFile();
1422
+ checks++;
1423
+ if (configPath) {
1424
+ console.log(` ${GREEN}✓${RESET} config file: ${configPath}`);
1425
+ passed++;
1426
+ }
1427
+ else {
1428
+ console.log(` ${YELLOW}!${RESET} no config file (using defaults)`);
1429
+ warnings++;
1430
+ }
1431
+ let config;
1432
+ checks++;
1433
+ try {
1434
+ config = loadConfig();
1435
+ console.log(` ${GREEN}✓${RESET} config validates OK`);
1436
+ passed++;
1437
+ }
1438
+ catch (err) {
1439
+ const msg = err instanceof Error ? err.message : String(err);
1440
+ console.log(` ${RED}✗${RESET} config invalid: ${msg.split("\n")[0]}`);
1441
+ // use defaults to continue checking other things
1442
+ config = loadConfig({});
1443
+ }
1444
+ // ── 2. tools ───────────────────────────────────────────────────────────
1445
+ console.log(`\n ${BOLD}tools${RESET}`);
1446
+ const toolChecks = [
1447
+ { cmd: "node", label: "Node.js", versionArg: ["--version"], required: true },
1448
+ { cmd: "aoe", label: "agent-of-empires", versionArg: ["--version"], required: true },
1449
+ { cmd: "tmux", label: "terminal multiplexer", versionArg: ["-V"], required: true },
1450
+ ];
1451
+ if (config.reasoner === "opencode") {
1452
+ toolChecks.push({ cmd: "opencode", label: "OpenCode CLI", versionArg: ["version"], required: true });
1453
+ }
1454
+ else {
1455
+ toolChecks.push({ cmd: "claude", label: "Claude Code CLI", versionArg: ["--version"], required: true });
1456
+ }
1457
+ for (const tool of toolChecks) {
1458
+ checks++;
1459
+ try {
1460
+ const result = await shellExec(tool.cmd, tool.versionArg);
1461
+ const ver = result.stdout.trim().split("\n")[0].slice(0, 60) || result.stderr.trim().split("\n")[0].slice(0, 60);
1462
+ console.log(` ${GREEN}✓${RESET} ${tool.cmd} — ${ver}`);
1463
+ passed++;
1464
+ }
1465
+ catch {
1466
+ if (tool.required) {
1467
+ console.log(` ${RED}✗${RESET} ${tool.cmd} not found (${tool.label})`);
1468
+ }
1469
+ else {
1470
+ console.log(` ${YELLOW}!${RESET} ${tool.cmd} not found (${tool.label}, optional)`);
1471
+ warnings++;
1472
+ }
1473
+ }
1474
+ }
1475
+ // ── 3. reasoner server ─────────────────────────────────────────────────
1476
+ if (config.reasoner === "opencode") {
1477
+ console.log(`\n ${BOLD}reasoner${RESET}`);
1478
+ checks++;
1479
+ try {
1480
+ const resp = await fetch(`http://127.0.0.1:${config.opencode.port}/health`, {
1481
+ signal: AbortSignal.timeout(3000),
1482
+ });
1483
+ if (resp.ok) {
1484
+ console.log(` ${GREEN}✓${RESET} opencode serve responding on port ${config.opencode.port}`);
1485
+ passed++;
1486
+ }
1487
+ else {
1488
+ console.log(` ${YELLOW}!${RESET} opencode serve on port ${config.opencode.port} returned ${resp.status}`);
1489
+ warnings++;
1490
+ }
1491
+ }
1492
+ catch {
1493
+ console.log(` ${RED}✗${RESET} opencode serve not responding on port ${config.opencode.port}`);
1494
+ console.log(` ${DIM}start with: opencode serve --port ${config.opencode.port}${RESET}`);
1495
+ }
1496
+ }
1497
+ // ── 4. daemon ──────────────────────────────────────────────────────────
1498
+ console.log(`\n ${BOLD}daemon${RESET}`);
1499
+ const state = readState();
1500
+ const daemonRunning = isDaemonRunningFromState(state);
1501
+ checks++;
1502
+ if (daemonRunning && state) {
1503
+ console.log(` ${GREEN}✓${RESET} daemon running (poll #${state.pollCount}, phase: ${state.phase})`);
1504
+ console.log(` ${state.sessions.length} session(s) monitored`);
1505
+ passed++;
1506
+ }
1507
+ else {
1508
+ console.log(` ${DIM}○${RESET} daemon not running`);
1509
+ passed++; // not running is fine for doctor — just informational
1510
+ }
1511
+ // lock file check
1512
+ const lockPath = join(homedir(), ".aoaoe", "daemon.lock");
1513
+ if (existsSync(lockPath) && !daemonRunning) {
1514
+ checks++;
1515
+ console.log(` ${YELLOW}!${RESET} stale lock file found: ${lockPath}`);
1516
+ console.log(` ${DIM}remove with: rm ${lockPath}${RESET}`);
1517
+ warnings++;
1518
+ }
1519
+ // ── 5. disk / data ─────────────────────────────────────────────────────
1520
+ console.log(`\n ${BOLD}data${RESET}`);
1521
+ const aoaoeDir = join(homedir(), ".aoaoe");
1522
+ if (existsSync(aoaoeDir)) {
1523
+ checks++;
1524
+ try {
1525
+ const files = await import("node:fs").then(fs => fs.readdirSync(aoaoeDir));
1526
+ let totalSize = 0;
1527
+ for (const f of files) {
1528
+ try {
1529
+ totalSize += statSync(join(aoaoeDir, f)).size;
1530
+ }
1531
+ catch { /* skip unreadable */ }
1532
+ }
1533
+ const sizeStr = totalSize < 1024 ? `${totalSize}B` :
1534
+ totalSize < 1_048_576 ? `${(totalSize / 1024).toFixed(1)}KB` :
1535
+ `${(totalSize / 1_048_576).toFixed(1)}MB`;
1536
+ console.log(` ${GREEN}✓${RESET} ~/.aoaoe/ — ${files.length} files, ${sizeStr}`);
1537
+ passed++;
1538
+ }
1539
+ catch {
1540
+ console.log(` ${YELLOW}!${RESET} could not read ~/.aoaoe/`);
1541
+ warnings++;
1542
+ }
1543
+ // actions log stats
1544
+ const actionsPath = join(aoaoeDir, "actions.log");
1545
+ if (existsSync(actionsPath)) {
1546
+ checks++;
1547
+ try {
1548
+ const content = readFileSync(actionsPath, "utf-8").trim();
1549
+ const lineCount = content ? content.split("\n").length : 0;
1550
+ const size = statSync(actionsPath).size;
1551
+ const sizeStr = size < 1024 ? `${size}B` : `${(size / 1024).toFixed(1)}KB`;
1552
+ console.log(` ${GREEN}✓${RESET} actions.log — ${lineCount} entries, ${sizeStr}`);
1553
+ passed++;
1554
+ }
1555
+ catch {
1556
+ console.log(` ${YELLOW}!${RESET} actions.log unreadable`);
1557
+ warnings++;
1558
+ }
1559
+ }
1560
+ }
1561
+ else {
1562
+ console.log(` ${DIM}○${RESET} ~/.aoaoe/ does not exist yet (run 'aoaoe init')`);
1563
+ }
1564
+ // ── 6. aoe sessions ───────────────────────────────────────────────────
1565
+ console.log(`\n ${BOLD}sessions${RESET}`);
1566
+ checks++;
1567
+ try {
1568
+ const listResult = await shellExec("aoe", ["list", "--json"]);
1569
+ if (listResult.exitCode === 0 && listResult.stdout.trim()) {
1570
+ const sessions = JSON.parse(listResult.stdout);
1571
+ if (Array.isArray(sessions) && sessions.length > 0) {
1572
+ console.log(` ${GREEN}✓${RESET} ${sessions.length} aoe session(s) found`);
1573
+ for (const s of sessions.slice(0, 5)) {
1574
+ console.log(` ${DIM}${s.title ?? s.id} (${s.tool ?? "?"})${RESET}`);
1575
+ }
1576
+ if (sessions.length > 5)
1577
+ console.log(` ${DIM}...and ${sessions.length - 5} more${RESET}`);
1578
+ passed++;
1579
+ }
1580
+ else {
1581
+ console.log(` ${DIM}○${RESET} no aoe sessions (start some with 'aoe add')`);
1582
+ passed++;
1583
+ }
1584
+ }
1585
+ else {
1586
+ console.log(` ${YELLOW}!${RESET} aoe list returned non-zero (is aoe running?)`);
1587
+ warnings++;
1588
+ }
1589
+ }
1590
+ catch {
1591
+ console.log(` ${RED}✗${RESET} could not run 'aoe list --json'`);
1592
+ }
1593
+ // ── summary ────────────────────────────────────────────────────────────
1594
+ const failed = checks - passed - warnings;
1595
+ console.log("");
1596
+ console.log(` ${"─".repeat(50)}`);
1597
+ if (failed === 0 && warnings === 0) {
1598
+ console.log(` ${GREEN}${BOLD}all ${checks} checks passed${RESET} — looking healthy`);
1599
+ }
1600
+ else if (failed === 0) {
1601
+ console.log(` ${passed}/${checks} passed, ${YELLOW}${warnings} warning(s)${RESET}`);
1602
+ }
1603
+ else {
1604
+ console.log(` ${passed}/${checks} passed, ${RED}${failed} failed${RESET}${warnings > 0 ? `, ${YELLOW}${warnings} warning(s)${RESET}` : ""}`);
1605
+ }
1606
+ console.log("");
1607
+ }
1405
1608
  // `aoaoe config --diff` -- show only fields that differ from defaults
1406
1609
  function showConfigDiff() {
1407
1610
  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.56.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",