aoaoe 0.70.0 → 0.71.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
@@ -46,6 +46,9 @@ export declare function parseCliArgs(argv: string[]): {
46
46
  tailCount?: number;
47
47
  runStats: boolean;
48
48
  statsLast?: string;
49
+ runReplay: boolean;
50
+ replaySpeed?: number;
51
+ replayLast?: string;
49
52
  registerTitle?: string;
50
53
  };
51
54
  export declare function printHelp(): void;
package/dist/config.js CHANGED
@@ -327,7 +327,7 @@ export function parseCliArgs(argv) {
327
327
  let initForce = false;
328
328
  let runTaskCli = false;
329
329
  let registerTitle;
330
- 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, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined };
330
+ 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, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
331
331
  // check for subcommand as first non-flag arg
332
332
  if (argv[2] === "test-context") {
333
333
  return { ...defaults, testContext: true };
@@ -419,6 +419,24 @@ export function parseCliArgs(argv) {
419
419
  }
420
420
  return { ...defaults, runStats: true, statsLast: last };
421
421
  }
422
+ if (argv[2] === "replay") {
423
+ let speed;
424
+ let last;
425
+ for (let i = 3; i < argv.length; i++) {
426
+ if ((argv[i] === "--speed" || argv[i] === "-s") && argv[i + 1]) {
427
+ const val = parseFloat(argv[++i]);
428
+ if (!isNaN(val) && val >= 0)
429
+ speed = val;
430
+ }
431
+ else if ((argv[i] === "--last" || argv[i] === "-l") && argv[i + 1]) {
432
+ last = argv[++i];
433
+ }
434
+ else if (argv[i] === "--instant") {
435
+ speed = 0;
436
+ }
437
+ }
438
+ return { ...defaults, runReplay: true, replaySpeed: speed, replayLast: last };
439
+ }
422
440
  if (argv[2] === "register") {
423
441
  register = true;
424
442
  // parse --title from remaining args
@@ -512,7 +530,7 @@ export function parseCliArgs(argv) {
512
530
  break;
513
531
  }
514
532
  }
515
- 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, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined };
533
+ 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, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
516
534
  }
517
535
  export function printHelp() {
518
536
  console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
@@ -544,6 +562,10 @@ commands:
544
562
  export --last <duration> time window: 1h, 6h, 24h, 7d (default: 24h)
545
563
  stats show aggregate daemon statistics (actions, sessions, activity)
546
564
  stats --last <duration> time window: 1h, 6h, 24h, 7d (default: all time)
565
+ replay play back tui-history.jsonl like a movie with simulated timing
566
+ replay --speed <N> playback speed: 1=realtime, 5=5x (default), 10=fast, 0=instant
567
+ replay --instant same as --speed 0 (no delays, dump all entries immediately)
568
+ replay --last <duration> only replay entries from the last 1h, 6h, 24h, 7d
547
569
  tail live-stream daemon activity to a separate terminal
548
570
  tail -f follow mode — keep watching for new entries (Ctrl+C to stop)
549
571
  tail -n <N> number of entries to show (default: 50)
@@ -579,6 +601,11 @@ logs options:
579
601
  --grep, -g <pattern> filter entries by substring or regex
580
602
  -n, --count <number> number of entries to show (default: 50)
581
603
 
604
+ replay options:
605
+ --speed, -s <number> playback speed multiplier (default: 5)
606
+ --instant no delays, dump all entries immediately
607
+ --last, -l <duration> time window: 1h, 6h, 24h, 7d
608
+
582
609
  tail options:
583
610
  -f, --follow keep watching for new entries (Ctrl+C to stop)
584
611
  -n, --count <number> number of entries to show (default: 50)
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
32
32
  const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
33
33
  const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
34
34
  async function main() {
35
- const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showHistory, showStatus, showConfig, configValidate, configDiff, notifyTest, runDoctor, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, registerTitle } = parseCliArgs(process.argv);
35
+ const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showHistory, showStatus, showConfig, configValidate, configDiff, notifyTest, runDoctor, 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);
36
36
  if (help) {
37
37
  printHelp();
38
38
  process.exit(0);
@@ -115,6 +115,12 @@ async function main() {
115
115
  await runStatsCommand(statsLast);
116
116
  return;
117
117
  }
118
+ // `aoaoe replay` -- play back tui-history.jsonl like a movie
119
+ if (isReplay) {
120
+ const { runReplay: doReplay } = await import("./replay.js");
121
+ await doReplay({ speed: replaySpeed ?? 5, last: replayLast });
122
+ return;
123
+ }
118
124
  // `aoaoe task` -- task management CLI
119
125
  if (isTaskCli) {
120
126
  await runTaskCli(process.argv);
@@ -0,0 +1,43 @@
1
+ import { type HistoryEntry } from "./tui-history.js";
2
+ /**
3
+ * Compute the delay between two entries, scaled by speed multiplier.
4
+ * Caps individual delays to prevent long waits on idle periods.
5
+ */
6
+ export declare function computeDelay(prevTs: number, currTs: number, speed: number, maxDelayMs?: number): number;
7
+ /**
8
+ * Format a speed multiplier for display.
9
+ */
10
+ export declare function formatSpeed(speed: number): string;
11
+ /**
12
+ * Parse a speed string ("2x", "10x", "0.5x", "instant") into a number.
13
+ * Returns 0 for instant, null for invalid.
14
+ */
15
+ export declare function parseSpeed(input: string): number | null;
16
+ /**
17
+ * Filter entries by time window.
18
+ */
19
+ export declare function filterByWindow(entries: HistoryEntry[], maxAgeMs?: number, now?: number): HistoryEntry[];
20
+ /**
21
+ * Build the replay header.
22
+ */
23
+ export declare function formatReplayHeader(entries: HistoryEntry[], speed: number, windowLabel?: string): string;
24
+ /**
25
+ * Build the replay footer.
26
+ */
27
+ export declare function formatReplayFooter(entries: HistoryEntry[]): string;
28
+ /**
29
+ * Load entries from the history file, optionally filtering by time window.
30
+ */
31
+ export declare function loadReplayEntries(maxAgeMs?: number, filePath?: string): HistoryEntry[];
32
+ /**
33
+ * Run the replay.
34
+ * Prints entries with simulated delays between them.
35
+ * Speed 0 = instant (no delays).
36
+ * Ctrl+C stops playback.
37
+ */
38
+ export declare function runReplay(opts: {
39
+ speed: number;
40
+ last?: string;
41
+ filePath?: string;
42
+ }): Promise<void>;
43
+ //# sourceMappingURL=replay.d.ts.map
package/dist/replay.js ADDED
@@ -0,0 +1,174 @@
1
+ // replay.ts — play back tui-history.jsonl like a movie.
2
+ // shows what the daemon did with simulated timing or instant output.
3
+ // reuses formatTailEntry from tail.ts for consistent rendering.
4
+ // pure exported functions for testability.
5
+ import { readFileSync, existsSync } from "node:fs";
6
+ import { TUI_HISTORY_FILE } from "./tui-history.js";
7
+ import { formatTailEntry, formatTailDate } from "./tail.js";
8
+ import { parseDuration } from "./export.js";
9
+ import { DIM, RESET, AMBER } from "./colors.js";
10
+ /**
11
+ * Compute the delay between two entries, scaled by speed multiplier.
12
+ * Caps individual delays to prevent long waits on idle periods.
13
+ */
14
+ export function computeDelay(prevTs, currTs, speed, maxDelayMs = 3000) {
15
+ if (speed <= 0)
16
+ return 0;
17
+ const raw = currTs - prevTs;
18
+ if (raw <= 0)
19
+ return 0;
20
+ return Math.min(Math.round(raw / speed), maxDelayMs);
21
+ }
22
+ /**
23
+ * Format a speed multiplier for display.
24
+ */
25
+ export function formatSpeed(speed) {
26
+ if (speed <= 0)
27
+ return "instant";
28
+ if (speed === 1)
29
+ return "1x (realtime)";
30
+ if (Number.isInteger(speed))
31
+ return `${speed}x`;
32
+ return `${speed.toFixed(1)}x`;
33
+ }
34
+ /**
35
+ * Parse a speed string ("2x", "10x", "0.5x", "instant") into a number.
36
+ * Returns 0 for instant, null for invalid.
37
+ */
38
+ export function parseSpeed(input) {
39
+ if (input === "instant" || input === "0")
40
+ return 0;
41
+ const match = input.match(/^(\d+(?:\.\d+)?)x?$/);
42
+ if (!match)
43
+ return null;
44
+ const val = parseFloat(match[1]);
45
+ if (!isFinite(val) || val < 0)
46
+ return null;
47
+ return val;
48
+ }
49
+ /**
50
+ * Filter entries by time window.
51
+ */
52
+ export function filterByWindow(entries, maxAgeMs, now) {
53
+ if (!maxAgeMs)
54
+ return entries;
55
+ const cutoff = (now ?? Date.now()) - maxAgeMs;
56
+ return entries.filter((e) => e.ts >= cutoff);
57
+ }
58
+ /**
59
+ * Build the replay header.
60
+ */
61
+ export function formatReplayHeader(entries, speed, windowLabel) {
62
+ if (entries.length === 0)
63
+ return `${DIM}no entries to replay${RESET}`;
64
+ const first = entries[0];
65
+ const last = entries[entries.length - 1];
66
+ const dateRange = formatTailDate(first.ts) === formatTailDate(last.ts)
67
+ ? formatTailDate(first.ts)
68
+ : `${formatTailDate(first.ts)} → ${formatTailDate(last.ts)}`;
69
+ const span = last.ts - first.ts;
70
+ const spanStr = span < 60_000 ? `${Math.floor(span / 1000)}s`
71
+ : span < 3_600_000 ? `${Math.floor(span / 60_000)}m`
72
+ : `${Math.floor(span / 3_600_000)}h ${Math.floor((span % 3_600_000) / 60_000)}m`;
73
+ const speedStr = formatSpeed(speed);
74
+ const windowStr = windowLabel ? ` (${windowLabel})` : "";
75
+ return `${DIM}── replay: ${entries.length} entries, ${dateRange}, span ${spanStr}, ${AMBER}${speedStr}${RESET}${DIM}${windowStr} ──${RESET}`;
76
+ }
77
+ /**
78
+ * Build the replay footer.
79
+ */
80
+ export function formatReplayFooter(entries) {
81
+ if (entries.length === 0)
82
+ return "";
83
+ return `${DIM}── replay complete: ${entries.length} entries ──${RESET}`;
84
+ }
85
+ /**
86
+ * Sleep for a given number of milliseconds.
87
+ */
88
+ function sleep(ms) {
89
+ return new Promise((resolve) => setTimeout(resolve, ms));
90
+ }
91
+ /**
92
+ * Load entries from the history file, optionally filtering by time window.
93
+ */
94
+ export function loadReplayEntries(maxAgeMs, filePath = TUI_HISTORY_FILE) {
95
+ try {
96
+ if (!existsSync(filePath))
97
+ return [];
98
+ const content = readFileSync(filePath, "utf-8");
99
+ const lines = content.split("\n").filter((l) => l.trim());
100
+ const entries = [];
101
+ for (const line of lines) {
102
+ try {
103
+ const parsed = JSON.parse(line);
104
+ if (isValidEntry(parsed))
105
+ entries.push(parsed);
106
+ }
107
+ catch {
108
+ // skip malformed
109
+ }
110
+ }
111
+ return filterByWindow(entries, maxAgeMs);
112
+ }
113
+ catch {
114
+ return [];
115
+ }
116
+ }
117
+ /**
118
+ * Run the replay.
119
+ * Prints entries with simulated delays between them.
120
+ * Speed 0 = instant (no delays).
121
+ * Ctrl+C stops playback.
122
+ */
123
+ export async function runReplay(opts) {
124
+ const filePath = opts.filePath ?? TUI_HISTORY_FILE;
125
+ const maxAgeMs = opts.last ? parseDuration(opts.last) ?? undefined : undefined;
126
+ if (opts.last && maxAgeMs === undefined) {
127
+ process.stderr.write(`error: --last must be like "1h", "6h", "24h", "7d", got "${opts.last}"\n`);
128
+ process.exit(1);
129
+ }
130
+ const entries = loadReplayEntries(maxAgeMs, filePath);
131
+ const windowLabel = opts.last ?? undefined;
132
+ // header
133
+ process.stderr.write(formatReplayHeader(entries, opts.speed, windowLabel) + "\n");
134
+ if (entries.length === 0)
135
+ return;
136
+ // Ctrl+C cleanup
137
+ let stopped = false;
138
+ const onSignal = () => { stopped = true; };
139
+ process.on("SIGINT", onSignal);
140
+ process.on("SIGTERM", onSignal);
141
+ try {
142
+ for (let i = 0; i < entries.length; i++) {
143
+ if (stopped)
144
+ break;
145
+ // delay between entries
146
+ if (i > 0 && opts.speed > 0) {
147
+ const delay = computeDelay(entries[i - 1].ts, entries[i].ts, opts.speed);
148
+ if (delay > 0)
149
+ await sleep(delay);
150
+ }
151
+ if (stopped)
152
+ break;
153
+ process.stderr.write(formatTailEntry(entries[i]) + "\n");
154
+ }
155
+ }
156
+ finally {
157
+ process.removeListener("SIGINT", onSignal);
158
+ process.removeListener("SIGTERM", onSignal);
159
+ }
160
+ // footer
161
+ if (!stopped) {
162
+ process.stderr.write(formatReplayFooter(entries) + "\n");
163
+ }
164
+ }
165
+ function isValidEntry(val) {
166
+ if (typeof val !== "object" || val === null)
167
+ return false;
168
+ const obj = val;
169
+ return (typeof obj.ts === "number" &&
170
+ typeof obj.time === "string" &&
171
+ typeof obj.tag === "string" &&
172
+ typeof obj.text === "string");
173
+ }
174
+ //# sourceMappingURL=replay.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.70.0",
3
+ "version": "0.71.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",