aoaoe 0.193.0 → 0.194.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/dist/config.d.ts CHANGED
@@ -68,6 +68,8 @@ export declare function parseCliArgs(argv: string[]): {
68
68
  runSync: boolean;
69
69
  syncAction?: string;
70
70
  syncRemote?: string;
71
+ runWeb: boolean;
72
+ webPort?: number;
71
73
  runLogs: boolean;
72
74
  logsActions: boolean;
73
75
  logsGrep?: string;
package/dist/config.js CHANGED
@@ -333,7 +333,7 @@ export function parseCliArgs(argv) {
333
333
  let initForce = false;
334
334
  let runTaskCli = false;
335
335
  let registerTitle;
336
- const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, runSummary: false, runAdopt: false, adoptTemplate: undefined, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
336
+ const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, runSummary: false, runAdopt: false, adoptTemplate: undefined, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runWeb: false, webPort: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
337
337
  // check for subcommand as first non-flag arg
338
338
  if (argv[2] === "test-context") {
339
339
  return { ...defaults, testContext: true };
@@ -528,6 +528,17 @@ export function parseCliArgs(argv) {
528
528
  if (argv[2] === "notify-test") {
529
529
  return { ...defaults, notifyTest: true };
530
530
  }
531
+ if (argv[2] === "web") {
532
+ let port;
533
+ for (let i = 3; i < argv.length; i++) {
534
+ if ((argv[i] === "--port" || argv[i] === "-p") && argv[i + 1]) {
535
+ const val = parseInt(argv[++i], 10);
536
+ if (!isNaN(val) && val > 0)
537
+ port = val;
538
+ }
539
+ }
540
+ return { ...defaults, runWeb: true, webPort: port };
541
+ }
531
542
  if (argv[2] === "sync") {
532
543
  const action = argv[3]; // init, push, pull, status
533
544
  const remote = argv[4]; // only for init
@@ -733,7 +744,7 @@ export function parseCliArgs(argv) {
733
744
  break;
734
745
  }
735
746
  }
736
- return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, runSummary: false, runAdopt: false, adoptTemplate: undefined, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
747
+ return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, runSummary: false, runAdopt: false, adoptTemplate: undefined, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runWeb: false, webPort: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
737
748
  }
738
749
  export function printHelp() {
739
750
  console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
@@ -779,6 +790,8 @@ commands:
779
790
  notify-test send a test notification to configured webhooks
780
791
  backup [path] backup ~/.aoaoe/ state + config to tarball
781
792
  restore <path> restore from backup tarball or directory
793
+ web start browser dashboard (default: http://127.0.0.1:4099)
794
+ web --port N custom port for dashboard
782
795
  sync init <url> set up git-based state sharing with remote repo
783
796
  sync push push local state to sync remote
784
797
  sync pull pull remote state and restore locally
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
40
40
  const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
41
41
  const TASK_RECONCILE_EVERY_POLLS = 6;
42
42
  async function main() {
43
- const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, runHealth, healthJson, runSummary, runAdopt, adoptTemplate, showHistory, showStatus, runRunbook, runbookJson, runbookSection, runIncident, incidentSince, incidentLimit, incidentJson, incidentNdjson, incidentWatch, incidentChangesOnly, incidentHeartbeatSec, incidentIntervalMs, runSupervisor, supervisorAll, supervisorSince, supervisorLimit, supervisorJson, supervisorNdjson, supervisorWatch, supervisorChangesOnly, supervisorHeartbeatSec, supervisorIntervalMs, showConfig, configValidate, configDiff, notifyTest, runDoctor, runBackup, backupOutput, runRestore, restoreInput, runSync, syncAction, syncRemote, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, runReplay: isReplay, replaySpeed, replayLast, registerTitle } = parseCliArgs(process.argv);
43
+ const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, runHealth, healthJson, runSummary, runAdopt, adoptTemplate, showHistory, showStatus, runRunbook, runbookJson, runbookSection, runIncident, incidentSince, incidentLimit, incidentJson, incidentNdjson, incidentWatch, incidentChangesOnly, incidentHeartbeatSec, incidentIntervalMs, runSupervisor, supervisorAll, supervisorSince, supervisorLimit, supervisorJson, supervisorNdjson, supervisorWatch, supervisorChangesOnly, supervisorHeartbeatSec, supervisorIntervalMs, showConfig, configValidate, configDiff, notifyTest, runDoctor, runBackup, backupOutput, runRestore, restoreInput, runSync, syncAction, syncRemote, runWeb, webPort, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, runReplay: isReplay, replaySpeed, replayLast, registerTitle } = parseCliArgs(process.argv);
44
44
  if (help) {
45
45
  printHelp();
46
46
  process.exit(0);
@@ -143,6 +143,14 @@ async function main() {
143
143
  return;
144
144
  }
145
145
  // `aoaoe doctor` -- comprehensive health check
146
+ if (runWeb) {
147
+ setWebResolveProfiles(resolveProfiles);
148
+ const port = webPort ?? 4099;
149
+ startWebServer(port);
150
+ // keep process alive until Ctrl+C
151
+ process.on("SIGINT", () => process.exit(0));
152
+ return;
153
+ }
146
154
  if (runSync) {
147
155
  process.env.AOAOE_QUIET = "1";
148
156
  try {
@@ -4561,6 +4569,7 @@ async function showProgressDigest(since, asJson = false) {
4561
4569
  import { resolveTemplate } from "./task-templates.js";
4562
4570
  import { createBackup, restoreBackup, formatBackupResult, formatRestoreResult } from "./backup.js";
4563
4571
  import { syncInit, syncPush, syncPull, syncDiff, syncStatus } from "./sync.js";
4572
+ import { startWebServer, setResolveProfiles as setWebResolveProfiles } from "./web.js";
4564
4573
  // adopt untracked live AoE sessions as tasks with optional template goal.
4565
4574
  async function adoptUntrackedSessions(templateName) {
4566
4575
  const basePath = process.cwd();
package/dist/web.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { loadConfig } from "./config.js";
2
+ export declare function setResolveProfiles(fn: (config: ReturnType<typeof loadConfig>) => string[]): void;
3
+ export declare function startWebServer(port: number): {
4
+ close: () => void;
5
+ };
6
+ //# sourceMappingURL=web.d.ts.map
package/dist/web.js ADDED
@@ -0,0 +1,268 @@
1
+ // web.ts — minimal browser dashboard served from aoaoe daemon.
2
+ // serves a single HTML page + JSON API endpoints using Node stdlib http.
3
+ // zero dependencies. auto-refreshes every 5 seconds.
4
+ import { createServer } from "node:http";
5
+ import { loadTaskState, loadTaskDefinitions, formatAgo, TaskManager } from "./task-manager.js";
6
+ import { computeAllHealth } from "./health-score.js";
7
+ import { loadSupervisorEvents } from "./supervisor-history.js";
8
+ import { loadConfig } from "./config.js";
9
+ let resolveProfilesFn = null;
10
+ export function setResolveProfiles(fn) {
11
+ resolveProfilesFn = fn;
12
+ }
13
+ function getTasks() {
14
+ const basePath = process.cwd();
15
+ const defs = loadTaskDefinitions(basePath);
16
+ if (defs.length === 0)
17
+ return [...loadTaskState().values()];
18
+ const config = loadConfig();
19
+ const profiles = resolveProfilesFn ? resolveProfilesFn(config) : ["default"];
20
+ return new TaskManager(basePath, defs, profiles).tasks;
21
+ }
22
+ // ── JSON API ────────────────────────────────────────────────────────────────
23
+ function apiTasks() {
24
+ const tasks = getTasks();
25
+ const now = Date.now();
26
+ return tasks.map((t) => ({
27
+ session: t.sessionTitle,
28
+ repo: t.repo,
29
+ status: t.status,
30
+ goal: t.goal,
31
+ dependsOn: t.dependsOn ?? [],
32
+ lastProgressAt: t.lastProgressAt ?? null,
33
+ lastProgressAgo: t.lastProgressAt ? formatAgo(now - t.lastProgressAt) : null,
34
+ progressCount: t.progress.length,
35
+ lastProgress: t.progress.length > 0 ? t.progress[t.progress.length - 1].summary : null,
36
+ stuckNudgeCount: t.stuckNudgeCount ?? 0,
37
+ }));
38
+ }
39
+ function apiHealth() {
40
+ return computeAllHealth(getTasks());
41
+ }
42
+ function apiProgress(sinceMs = 24 * 60 * 60 * 1000) {
43
+ const tasks = getTasks();
44
+ const now = Date.now();
45
+ const cutoff = now - sinceMs;
46
+ return tasks.map((t) => ({
47
+ session: t.sessionTitle,
48
+ status: t.status,
49
+ recentProgress: t.progress.filter((p) => p.at >= cutoff).map((p) => ({
50
+ at: p.at,
51
+ ago: formatAgo(now - p.at),
52
+ summary: p.summary,
53
+ })),
54
+ }));
55
+ }
56
+ function apiSupervisor(limit = 20) {
57
+ const tasks = getTasks();
58
+ const events = loadSupervisorEvents(limit).reverse();
59
+ const active = tasks.filter((t) => t.status === "active").length;
60
+ const pending = tasks.filter((t) => t.status === "pending").length;
61
+ const paused = tasks.filter((t) => t.status === "paused").length;
62
+ const completed = tasks.filter((t) => t.status === "completed").length;
63
+ return {
64
+ summary: { total: tasks.length, active, pending, paused, completed },
65
+ recentEvents: events.map((e) => ({ at: e.at, detail: e.detail })),
66
+ };
67
+ }
68
+ // ── HTML dashboard ──────────────────────────────────────────────────────────
69
+ function dashboardHtml() {
70
+ return `<!DOCTYPE html>
71
+ <html lang="en">
72
+ <head>
73
+ <meta charset="utf-8">
74
+ <meta name="viewport" content="width=device-width, initial-scale=1">
75
+ <title>aoaoe dashboard</title>
76
+ <style>
77
+ * { box-sizing: border-box; margin: 0; padding: 0; }
78
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace; background: #0d1117; color: #c9d1d9; padding: 20px; }
79
+ h1 { font-size: 1.4em; margin-bottom: 16px; color: #58a6ff; }
80
+ h2 { font-size: 1.1em; margin: 20px 0 8px; color: #8b949e; border-bottom: 1px solid #21262d; padding-bottom: 4px; }
81
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 12px; }
82
+ .card { background: #161b22; border: 1px solid #21262d; border-radius: 8px; padding: 14px; }
83
+ .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
84
+ .session-name { font-weight: 600; font-size: 1em; }
85
+ .status { font-size: 0.8em; padding: 2px 8px; border-radius: 12px; }
86
+ .status-active { background: #1f6f2b; color: #3fb950; }
87
+ .status-pending { background: #2d333b; color: #8b949e; }
88
+ .status-paused { background: #4a3200; color: #d29922; }
89
+ .status-completed { background: #1a3a4a; color: #58a6ff; }
90
+ .status-failed { background: #4a1a1a; color: #f85149; }
91
+ .health-bar { height: 6px; background: #21262d; border-radius: 3px; margin: 6px 0; overflow: hidden; }
92
+ .health-fill { height: 100%; border-radius: 3px; transition: width 0.3s; }
93
+ .health-healthy { background: #3fb950; }
94
+ .health-ok { background: #58a6ff; }
95
+ .health-degraded { background: #d29922; }
96
+ .health-critical { background: #f85149; }
97
+ .health-inactive { background: #484f58; }
98
+ .goal { font-size: 0.85em; color: #8b949e; margin: 4px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
99
+ .progress-entry { font-size: 0.8em; color: #8b949e; padding: 2px 0; border-top: 1px solid #21262d; }
100
+ .progress-time { color: #484f58; }
101
+ .meta { font-size: 0.75em; color: #484f58; margin-top: 6px; }
102
+ .summary-bar { display: flex; gap: 16px; margin-bottom: 12px; font-size: 0.9em; }
103
+ .summary-item { padding: 6px 12px; background: #161b22; border: 1px solid #21262d; border-radius: 6px; }
104
+ .events { max-height: 200px; overflow-y: auto; }
105
+ .event { font-size: 0.8em; padding: 3px 0; color: #8b949e; }
106
+ .event-time { color: #484f58; }
107
+ .refresh { font-size: 0.75em; color: #484f58; text-align: right; margin-top: 12px; }
108
+ #error { color: #f85149; font-size: 0.85em; display: none; margin-bottom: 8px; }
109
+ </style>
110
+ </head>
111
+ <body>
112
+ <h1>aoaoe dashboard</h1>
113
+ <div id="error"></div>
114
+ <div class="summary-bar" id="summary"></div>
115
+ <h2>sessions</h2>
116
+ <div class="grid" id="tasks"></div>
117
+ <h2>supervisor events</h2>
118
+ <div class="events" id="events"></div>
119
+ <div class="refresh" id="refresh"></div>
120
+
121
+ <script>
122
+ const API = window.location.origin;
123
+ const REFRESH_MS = 5000;
124
+
125
+ async function fetchJson(path) {
126
+ const res = await fetch(API + path);
127
+ if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
128
+ return res.json();
129
+ }
130
+
131
+ function statusClass(status) {
132
+ return 'status status-' + (status || 'pending');
133
+ }
134
+
135
+ function healthClass(grade) {
136
+ return 'health-fill health-' + (grade || 'inactive');
137
+ }
138
+
139
+ function escHtml(s) {
140
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
141
+ }
142
+
143
+ async function refresh() {
144
+ try {
145
+ const [tasks, healths, progress, supervisor] = await Promise.all([
146
+ fetchJson('/api/tasks'),
147
+ fetchJson('/api/health'),
148
+ fetchJson('/api/progress?since=8h'),
149
+ fetchJson('/api/supervisor'),
150
+ ]);
151
+
152
+ const healthMap = {};
153
+ healths.forEach(h => healthMap[h.session] = h);
154
+
155
+ // summary
156
+ const s = supervisor.summary;
157
+ document.getElementById('summary').innerHTML =
158
+ '<div class="summary-item">' + s.total + ' tasks</div>' +
159
+ '<div class="summary-item" style="color:#3fb950">' + s.active + ' active</div>' +
160
+ (s.pending > 0 ? '<div class="summary-item">' + s.pending + ' pending</div>' : '') +
161
+ (s.paused > 0 ? '<div class="summary-item" style="color:#d29922">' + s.paused + ' paused</div>' : '') +
162
+ (s.completed > 0 ? '<div class="summary-item" style="color:#58a6ff">' + s.completed + ' done</div>' : '') +
163
+ '<div class="summary-item">avg health: ' + Math.round(healths.reduce((a,h) => a + h.score, 0) / (healths.length || 1)) + '</div>';
164
+
165
+ // task cards
166
+ const progressMap = {};
167
+ progress.forEach(p => progressMap[p.session] = p.recentProgress || []);
168
+
169
+ let html = '';
170
+ tasks.forEach(t => {
171
+ const h = healthMap[t.session] || { score: 0, grade: 'inactive', factors: [] };
172
+ const recent = (progressMap[t.session] || []).slice(-3);
173
+ html += '<div class="card">';
174
+ html += '<div class="card-header"><span class="session-name">' + escHtml(t.session) + '</span><span class="' + statusClass(t.status) + '">' + t.status + '</span></div>';
175
+ html += '<div class="health-bar"><div class="' + healthClass(h.grade) + '" style="width:' + h.score + '%"></div></div>';
176
+ html += '<div class="meta">health: ' + h.score + '/100 (' + h.grade + ') · ' + h.factors.join(' · ') + '</div>';
177
+ html += '<div class="goal" title="' + escHtml(t.goal) + '">' + escHtml(t.goal) + '</div>';
178
+ if (t.dependsOn && t.dependsOn.length) html += '<div class="meta">depends on: ' + t.dependsOn.join(', ') + '</div>';
179
+ if (recent.length > 0) {
180
+ recent.forEach(p => {
181
+ html += '<div class="progress-entry"><span class="progress-time">' + p.ago + '</span> ' + escHtml(p.summary) + '</div>';
182
+ });
183
+ } else {
184
+ html += '<div class="progress-entry">' + (t.lastProgressAgo ? 'last progress: ' + t.lastProgressAgo : 'no progress yet') + '</div>';
185
+ }
186
+ if (t.stuckNudgeCount > 0) html += '<div class="meta" style="color:#d29922">stuck nudges: ' + t.stuckNudgeCount + '</div>';
187
+ html += '</div>';
188
+ });
189
+ document.getElementById('tasks').innerHTML = html;
190
+
191
+ // events
192
+ let evHtml = '';
193
+ supervisor.recentEvents.forEach(e => {
194
+ const d = new Date(e.at);
195
+ evHtml += '<div class="event"><span class="event-time">' + d.toLocaleTimeString() + '</span> ' + escHtml(e.detail) + '</div>';
196
+ });
197
+ document.getElementById('events').innerHTML = evHtml || '<div class="event">no recent events</div>';
198
+
199
+ document.getElementById('error').style.display = 'none';
200
+ document.getElementById('refresh').textContent = 'last refresh: ' + new Date().toLocaleTimeString() + ' (every ' + (REFRESH_MS/1000) + 's)';
201
+ } catch (err) {
202
+ document.getElementById('error').textContent = 'fetch error: ' + err.message;
203
+ document.getElementById('error').style.display = 'block';
204
+ }
205
+ }
206
+
207
+ refresh();
208
+ setInterval(refresh, REFRESH_MS);
209
+ </script>
210
+ </body>
211
+ </html>`;
212
+ }
213
+ // ── HTTP server ─────────────────────────────────────────────────────────────
214
+ function parseSince(url) {
215
+ const raw = url.searchParams.get("since");
216
+ if (!raw)
217
+ return 24 * 60 * 60 * 1000;
218
+ const match = raw.match(/^(\d+)(h|m|d)$/);
219
+ if (!match)
220
+ return 24 * 60 * 60 * 1000;
221
+ const [, n, unit] = match;
222
+ const ms = unit === "h" ? 3_600_000 : unit === "m" ? 60_000 : 86_400_000;
223
+ return parseInt(n, 10) * ms;
224
+ }
225
+ function handleRequest(req, res) {
226
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
227
+ const path = url.pathname;
228
+ // CORS for local dev
229
+ res.setHeader("Access-Control-Allow-Origin", "*");
230
+ if (path === "/" || path === "/index.html") {
231
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
232
+ res.end(dashboardHtml());
233
+ return;
234
+ }
235
+ if (path === "/api/tasks") {
236
+ res.writeHead(200, { "Content-Type": "application/json" });
237
+ res.end(JSON.stringify(apiTasks()));
238
+ return;
239
+ }
240
+ if (path === "/api/health") {
241
+ res.writeHead(200, { "Content-Type": "application/json" });
242
+ res.end(JSON.stringify(apiHealth()));
243
+ return;
244
+ }
245
+ if (path === "/api/progress") {
246
+ res.writeHead(200, { "Content-Type": "application/json" });
247
+ res.end(JSON.stringify(apiProgress(parseSince(url))));
248
+ return;
249
+ }
250
+ if (path === "/api/supervisor") {
251
+ const limit = parseInt(url.searchParams.get("limit") ?? "20", 10);
252
+ res.writeHead(200, { "Content-Type": "application/json" });
253
+ res.end(JSON.stringify(apiSupervisor(limit)));
254
+ return;
255
+ }
256
+ res.writeHead(404, { "Content-Type": "text/plain" });
257
+ res.end("not found");
258
+ }
259
+ export function startWebServer(port) {
260
+ // suppress noisy log lines when loading config/tasks for API responses
261
+ process.env.AOAOE_QUIET = "1";
262
+ const server = createServer(handleRequest);
263
+ server.listen(port, "127.0.0.1", () => {
264
+ console.log(`aoaoe dashboard: http://127.0.0.1:${port}`);
265
+ });
266
+ return { close: () => server.close() };
267
+ }
268
+ //# sourceMappingURL=web.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.193.0",
3
+ "version": "0.194.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",