aoaoe 0.68.0 → 0.69.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
@@ -41,6 +41,9 @@ export declare function parseCliArgs(argv: string[]): {
41
41
  runInit: boolean;
42
42
  initForce: boolean;
43
43
  runTaskCli: boolean;
44
+ runTail: boolean;
45
+ tailFollow: boolean;
46
+ tailCount?: number;
44
47
  registerTitle?: string;
45
48
  };
46
49
  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 };
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 };
331
331
  // check for subcommand as first non-flag arg
332
332
  if (argv[2] === "test-context") {
333
333
  return { ...defaults, testContext: true };
@@ -395,6 +395,21 @@ export function parseCliArgs(argv) {
395
395
  const force = argv.includes("--force") || argv.includes("-f");
396
396
  return { ...defaults, runInit: true, initForce: force };
397
397
  }
398
+ if (argv[2] === "tail") {
399
+ let follow = false;
400
+ let count;
401
+ for (let i = 3; i < argv.length; i++) {
402
+ if (argv[i] === "-f" || argv[i] === "--follow") {
403
+ follow = true;
404
+ }
405
+ else if ((argv[i] === "-n" || argv[i] === "--count") && argv[i + 1]) {
406
+ const val = parseInt(argv[++i], 10);
407
+ if (!isNaN(val) && val > 0)
408
+ count = val;
409
+ }
410
+ }
411
+ return { ...defaults, runTail: true, tailFollow: follow, tailCount: count };
412
+ }
398
413
  if (argv[2] === "register") {
399
414
  register = true;
400
415
  // parse --title from remaining args
@@ -488,7 +503,7 @@ export function parseCliArgs(argv) {
488
503
  break;
489
504
  }
490
505
  }
491
- 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 };
506
+ 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 };
492
507
  }
493
508
  export function printHelp() {
494
509
  console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
@@ -518,6 +533,9 @@ commands:
518
533
  export --format <json|markdown> output format (default: json)
519
534
  export --output <file> write to file (default: stdout)
520
535
  export --last <duration> time window: 1h, 6h, 24h, 7d (default: 24h)
536
+ tail live-stream daemon activity to a separate terminal
537
+ tail -f follow mode — keep watching for new entries (Ctrl+C to stop)
538
+ tail -n <N> number of entries to show (default: 50)
521
539
  task manage tasks and sessions (list, start, stop, new, rm, edit)
522
540
  tasks show task progress (from aoaoe.tasks.json)
523
541
  history review recent actions (from ~/.aoaoe/actions.log)
@@ -550,6 +568,10 @@ logs options:
550
568
  --grep, -g <pattern> filter entries by substring or regex
551
569
  -n, --count <number> number of entries to show (default: 50)
552
570
 
571
+ tail options:
572
+ -f, --follow keep watching for new entries (Ctrl+C to stop)
573
+ -n, --count <number> number of entries to show (default: 50)
574
+
553
575
  register options:
554
576
  --title, -t <name> session title in AoE (default: aoaoe)
555
577
 
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, 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, registerTitle } = parseCliArgs(process.argv);
36
36
  if (help) {
37
37
  printHelp();
38
38
  process.exit(0);
@@ -121,6 +121,12 @@ async function main() {
121
121
  await doInit(initForce);
122
122
  return;
123
123
  }
124
+ // `aoaoe tail` -- live-stream daemon activity to a separate terminal
125
+ if (isTail) {
126
+ const { runTail: doTail } = await import("./tail.js");
127
+ await doTail({ count: tailCount ?? 50, follow: tailFollow });
128
+ return;
129
+ }
124
130
  // auto-init: if no config file exists, run init automatically
125
131
  if (!configFileExists()) {
126
132
  console.error("");
package/dist/tail.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { type HistoryEntry } from "./tui-history.js";
2
+ /**
3
+ * Format a HistoryEntry as a colorized terminal line.
4
+ * Matches TUI formatActivity style but for plain scrolling output.
5
+ */
6
+ export declare function formatTailEntry(entry: HistoryEntry): string;
7
+ /**
8
+ * Format a date string for the tail header.
9
+ */
10
+ export declare function formatTailDate(ts: number): string;
11
+ /**
12
+ * Load the last N entries from the history file.
13
+ * Returns entries newest-last (chronological order).
14
+ */
15
+ export declare function loadTailEntries(count: number, filePath?: string): HistoryEntry[];
16
+ /**
17
+ * Get the current file size (for follow mode to detect appends).
18
+ */
19
+ export declare function getFileSize(filePath: string): number;
20
+ /**
21
+ * Read new bytes appended since a given offset, parse into entries.
22
+ */
23
+ export declare function readNewEntries(filePath: string, fromByte: number): {
24
+ entries: HistoryEntry[];
25
+ newSize: number;
26
+ };
27
+ /**
28
+ * Print entries to stderr (colorized).
29
+ */
30
+ export declare function printEntries(entries: HistoryEntry[]): void;
31
+ /**
32
+ * Run the tail command.
33
+ * Without follow: print last N entries, exit.
34
+ * With follow: print last N, then watch for new entries (blocks until Ctrl+C).
35
+ */
36
+ export declare function runTail(opts: {
37
+ count: number;
38
+ follow: boolean;
39
+ filePath?: string;
40
+ }): Promise<void>;
41
+ //# sourceMappingURL=tail.d.ts.map
package/dist/tail.js ADDED
@@ -0,0 +1,211 @@
1
+ // tail.ts — live-stream daemon activity to a separate terminal.
2
+ // reads from tui-history.jsonl, optionally follows for new entries.
3
+ // pure exported functions for testability.
4
+ import { readFileSync, statSync, existsSync, openSync, readSync, closeSync } from "node:fs";
5
+ import { watch } from "node:fs";
6
+ import { TUI_HISTORY_FILE } from "./tui-history.js";
7
+ import { SLATE, RESET, DIM, BOLD, CYAN, AMBER, ROSE, LIME, SKY, } from "./colors.js";
8
+ /**
9
+ * Format a HistoryEntry as a colorized terminal line.
10
+ * Matches TUI formatActivity style but for plain scrolling output.
11
+ */
12
+ export function formatTailEntry(entry) {
13
+ let color = SLATE;
14
+ let prefix = entry.tag;
15
+ switch (entry.tag) {
16
+ case "observation":
17
+ color = SLATE;
18
+ prefix = "obs";
19
+ break;
20
+ case "reasoner":
21
+ color = SKY;
22
+ break;
23
+ case "explain":
24
+ color = `${BOLD}${CYAN}`;
25
+ prefix = "AI";
26
+ break;
27
+ case "+ action":
28
+ case "action":
29
+ color = AMBER;
30
+ prefix = "→ action";
31
+ break;
32
+ case "! action":
33
+ case "error":
34
+ color = ROSE;
35
+ prefix = "✗ error";
36
+ break;
37
+ case "you":
38
+ color = LIME;
39
+ break;
40
+ case "system":
41
+ color = SLATE;
42
+ break;
43
+ case "status":
44
+ color = SLATE;
45
+ break;
46
+ default:
47
+ color = SLATE;
48
+ break;
49
+ }
50
+ return `${SLATE}${entry.time}${RESET} ${color}${prefix}${RESET} ${DIM}│${RESET} ${entry.text}`;
51
+ }
52
+ /**
53
+ * Format a date string for the tail header.
54
+ */
55
+ export function formatTailDate(ts) {
56
+ const d = new Date(ts);
57
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
58
+ }
59
+ /**
60
+ * Load the last N entries from the history file.
61
+ * Returns entries newest-last (chronological order).
62
+ */
63
+ export function loadTailEntries(count, filePath = TUI_HISTORY_FILE) {
64
+ try {
65
+ if (!existsSync(filePath))
66
+ return [];
67
+ const content = readFileSync(filePath, "utf-8");
68
+ const lines = content.split("\n").filter((l) => l.trim());
69
+ const entries = [];
70
+ // read from the end to minimize parsing
71
+ const start = Math.max(0, lines.length - count);
72
+ for (let i = start; i < lines.length; i++) {
73
+ try {
74
+ const parsed = JSON.parse(lines[i]);
75
+ if (isValidEntry(parsed))
76
+ entries.push(parsed);
77
+ }
78
+ catch {
79
+ // skip malformed
80
+ }
81
+ }
82
+ return entries;
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ }
88
+ /**
89
+ * Get the current file size (for follow mode to detect appends).
90
+ */
91
+ export function getFileSize(filePath) {
92
+ try {
93
+ return statSync(filePath).size;
94
+ }
95
+ catch {
96
+ return 0;
97
+ }
98
+ }
99
+ /**
100
+ * Read new bytes appended since a given offset, parse into entries.
101
+ */
102
+ export function readNewEntries(filePath, fromByte) {
103
+ try {
104
+ const size = statSync(filePath).size;
105
+ if (size === 0)
106
+ return { entries: [], newSize: 0 };
107
+ // file was truncated/rotated — read from start
108
+ const start = size < fromByte ? 0 : fromByte;
109
+ if (start === size)
110
+ return { entries: [], newSize: size };
111
+ const buf = Buffer.alloc(size - start);
112
+ const fd = openSync(filePath, "r");
113
+ readSync(fd, buf, 0, buf.length, start);
114
+ closeSync(fd);
115
+ const text = buf.toString("utf-8");
116
+ const lines = text.split("\n").filter((l) => l.trim());
117
+ const entries = [];
118
+ for (const line of lines) {
119
+ try {
120
+ const parsed = JSON.parse(line);
121
+ if (isValidEntry(parsed))
122
+ entries.push(parsed);
123
+ }
124
+ catch {
125
+ // skip
126
+ }
127
+ }
128
+ return { entries, newSize: size };
129
+ }
130
+ catch {
131
+ return { entries: [], newSize: fromByte };
132
+ }
133
+ }
134
+ /**
135
+ * Print entries to stderr (colorized).
136
+ */
137
+ export function printEntries(entries) {
138
+ for (const e of entries) {
139
+ process.stderr.write(formatTailEntry(e) + "\n");
140
+ }
141
+ }
142
+ /**
143
+ * Run the tail command.
144
+ * Without follow: print last N entries, exit.
145
+ * With follow: print last N, then watch for new entries (blocks until Ctrl+C).
146
+ */
147
+ export async function runTail(opts) {
148
+ const filePath = opts.filePath ?? TUI_HISTORY_FILE;
149
+ // print initial entries
150
+ const entries = loadTailEntries(opts.count, filePath);
151
+ if (entries.length === 0) {
152
+ process.stderr.write(`${DIM}no history entries found${RESET}\n`);
153
+ if (!opts.follow)
154
+ return;
155
+ process.stderr.write(`${DIM}waiting for new entries... (Ctrl+C to stop)${RESET}\n`);
156
+ }
157
+ else {
158
+ // date header
159
+ const firstDate = formatTailDate(entries[0].ts);
160
+ const lastDate = formatTailDate(entries[entries.length - 1].ts);
161
+ const dateRange = firstDate === lastDate ? firstDate : `${firstDate} → ${lastDate}`;
162
+ process.stderr.write(`${DIM}── ${entries.length} entries (${dateRange}) ──${RESET}\n`);
163
+ printEntries(entries);
164
+ }
165
+ if (!opts.follow)
166
+ return;
167
+ // follow mode: watch file for changes
168
+ process.stderr.write(`${DIM}── following (Ctrl+C to stop) ──${RESET}\n`);
169
+ let lastSize = getFileSize(filePath);
170
+ return new Promise((_resolve) => {
171
+ let watcher = null;
172
+ try {
173
+ watcher = watch(filePath, { persistent: true }, () => {
174
+ const { entries: newEntries, newSize } = readNewEntries(filePath, lastSize);
175
+ if (newEntries.length > 0) {
176
+ printEntries(newEntries);
177
+ }
178
+ lastSize = newSize;
179
+ });
180
+ watcher.on("error", () => {
181
+ // file may be rotated — try to recover
182
+ lastSize = 0;
183
+ });
184
+ }
185
+ catch {
186
+ process.stderr.write(`${ROSE}failed to watch ${filePath}${RESET}\n`);
187
+ }
188
+ // Ctrl+C cleanup
189
+ const cleanup = () => {
190
+ if (watcher) {
191
+ try {
192
+ watcher.close();
193
+ }
194
+ catch { }
195
+ }
196
+ process.exit(0);
197
+ };
198
+ process.on("SIGINT", cleanup);
199
+ process.on("SIGTERM", cleanup);
200
+ });
201
+ }
202
+ function isValidEntry(val) {
203
+ if (typeof val !== "object" || val === null)
204
+ return false;
205
+ const obj = val;
206
+ return (typeof obj.ts === "number" &&
207
+ typeof obj.time === "string" &&
208
+ typeof obj.tag === "string" &&
209
+ typeof obj.text === "string");
210
+ }
211
+ //# sourceMappingURL=tail.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.68.0",
3
+ "version": "0.69.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",