aoaoe 0.63.0 → 0.64.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 +4 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +23 -2
- package/dist/export.d.ts +17 -0
- package/dist/export.js +132 -0
- package/dist/index.js +48 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -248,6 +248,10 @@ commands:
|
|
|
248
248
|
logs --actions show action log entries (from ~/.aoaoe/actions.log)
|
|
249
249
|
logs --grep <pattern> filter log entries by substring or regex
|
|
250
250
|
logs -n <count> number of entries to show (default: 50)
|
|
251
|
+
export export session timeline as JSON or Markdown for post-mortems
|
|
252
|
+
export --format <json|markdown> output format (default: json)
|
|
253
|
+
export --output <file> write to file (default: stdout)
|
|
254
|
+
export --last <duration> time window: 1h, 6h, 24h, 7d (default: 24h)
|
|
251
255
|
task manage tasks and sessions (list, start, stop, new, rm, edit)
|
|
252
256
|
tasks show task progress (from aoaoe.tasks.json)
|
|
253
257
|
history review recent actions (from ~/.aoaoe/actions.log)
|
package/dist/config.d.ts
CHANGED
|
@@ -34,6 +34,10 @@ export declare function parseCliArgs(argv: string[]): {
|
|
|
34
34
|
logsActions: boolean;
|
|
35
35
|
logsGrep?: string;
|
|
36
36
|
logsCount?: number;
|
|
37
|
+
runExport: boolean;
|
|
38
|
+
exportFormat?: string;
|
|
39
|
+
exportOutput?: string;
|
|
40
|
+
exportLast?: string;
|
|
37
41
|
runInit: boolean;
|
|
38
42
|
initForce: boolean;
|
|
39
43
|
runTaskCli: boolean;
|
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, 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 };
|
|
331
331
|
// check for subcommand as first non-flag arg
|
|
332
332
|
if (argv[2] === "test-context") {
|
|
333
333
|
return { ...defaults, testContext: true };
|
|
@@ -374,6 +374,23 @@ export function parseCliArgs(argv) {
|
|
|
374
374
|
}
|
|
375
375
|
return { ...defaults, runLogs: true, logsActions: actions, logsGrep: grep, logsCount: count };
|
|
376
376
|
}
|
|
377
|
+
if (argv[2] === "export") {
|
|
378
|
+
let format;
|
|
379
|
+
let output;
|
|
380
|
+
let last;
|
|
381
|
+
for (let i = 3; i < argv.length; i++) {
|
|
382
|
+
if ((argv[i] === "--format" || argv[i] === "-f") && argv[i + 1]) {
|
|
383
|
+
format = argv[++i];
|
|
384
|
+
}
|
|
385
|
+
else if ((argv[i] === "--output" || argv[i] === "-o") && argv[i + 1]) {
|
|
386
|
+
output = argv[++i];
|
|
387
|
+
}
|
|
388
|
+
else if ((argv[i] === "--last" || argv[i] === "-l") && argv[i + 1]) {
|
|
389
|
+
last = argv[++i];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return { ...defaults, runExport: true, exportFormat: format, exportOutput: output, exportLast: last };
|
|
393
|
+
}
|
|
377
394
|
if (argv[2] === "init") {
|
|
378
395
|
const force = argv.includes("--force") || argv.includes("-f");
|
|
379
396
|
return { ...defaults, runInit: true, initForce: force };
|
|
@@ -471,7 +488,7 @@ export function parseCliArgs(argv) {
|
|
|
471
488
|
break;
|
|
472
489
|
}
|
|
473
490
|
}
|
|
474
|
-
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 };
|
|
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 };
|
|
475
492
|
}
|
|
476
493
|
export function printHelp() {
|
|
477
494
|
console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
|
|
@@ -497,6 +514,10 @@ commands:
|
|
|
497
514
|
logs --actions show action log entries (from ~/.aoaoe/actions.log)
|
|
498
515
|
logs --grep <pattern> filter log entries by substring or regex
|
|
499
516
|
logs -n <count> number of entries to show (default: 50)
|
|
517
|
+
export export session timeline as JSON or Markdown for post-mortems
|
|
518
|
+
export --format <json|markdown> output format (default: json)
|
|
519
|
+
export --output <file> write to file (default: stdout)
|
|
520
|
+
export --last <duration> time window: 1h, 6h, 24h, 7d (default: 24h)
|
|
500
521
|
task manage tasks and sessions (list, start, stop, new, rm, edit)
|
|
501
522
|
tasks show task progress (from aoaoe.tasks.json)
|
|
502
523
|
history review recent actions (from ~/.aoaoe/actions.log)
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HistoryEntry } from "./tui-history.js";
|
|
2
|
+
export interface TimelineEntry {
|
|
3
|
+
ts: number;
|
|
4
|
+
source: "action" | "activity";
|
|
5
|
+
tag: string;
|
|
6
|
+
text: string;
|
|
7
|
+
success?: boolean;
|
|
8
|
+
session?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseActionLogEntries(lines: string[]): TimelineEntry[];
|
|
11
|
+
export declare function parseActivityEntries(entries: HistoryEntry[]): TimelineEntry[];
|
|
12
|
+
export declare function mergeTimeline(...sources: TimelineEntry[][]): TimelineEntry[];
|
|
13
|
+
export declare function filterByAge(entries: TimelineEntry[], maxAgeMs: number, now?: number): TimelineEntry[];
|
|
14
|
+
export declare function parseDuration(input: string): number | null;
|
|
15
|
+
export declare function formatTimelineJson(entries: TimelineEntry[]): string;
|
|
16
|
+
export declare function formatTimelineMarkdown(entries: TimelineEntry[]): string;
|
|
17
|
+
//# sourceMappingURL=export.d.ts.map
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// export.ts — pure functions for exporting session timelines as JSON or Markdown
|
|
2
|
+
// reads actions.log (JSONL) and tui-history.jsonl, merges into a unified timeline
|
|
3
|
+
import { toActionLogEntry } from "./types.js";
|
|
4
|
+
// ── parsers ─────────────────────────────────────────────────────────────────
|
|
5
|
+
// parse actions.log JSONL lines into timeline entries
|
|
6
|
+
export function parseActionLogEntries(lines) {
|
|
7
|
+
const entries = [];
|
|
8
|
+
for (const line of lines) {
|
|
9
|
+
if (!line.trim())
|
|
10
|
+
continue;
|
|
11
|
+
try {
|
|
12
|
+
const entry = toActionLogEntry(JSON.parse(line));
|
|
13
|
+
if (!entry)
|
|
14
|
+
continue;
|
|
15
|
+
// skip wait actions (noise in post-mortems)
|
|
16
|
+
if (entry.action.action === "wait")
|
|
17
|
+
continue;
|
|
18
|
+
entries.push({
|
|
19
|
+
ts: entry.timestamp,
|
|
20
|
+
source: "action",
|
|
21
|
+
tag: entry.action.action,
|
|
22
|
+
text: entry.detail || `${entry.action.action} on ${entry.action.session ?? "unknown"}`,
|
|
23
|
+
success: entry.success,
|
|
24
|
+
session: entry.action.session ?? entry.action.title,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// skip malformed lines
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return entries;
|
|
32
|
+
}
|
|
33
|
+
// convert tui-history entries into timeline entries
|
|
34
|
+
export function parseActivityEntries(entries) {
|
|
35
|
+
return entries.map((e) => ({
|
|
36
|
+
ts: e.ts,
|
|
37
|
+
source: "activity",
|
|
38
|
+
tag: e.tag,
|
|
39
|
+
text: e.text,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
// ── merge + filter ──────────────────────────────────────────────────────────
|
|
43
|
+
// merge multiple sources into a single chronological timeline
|
|
44
|
+
export function mergeTimeline(...sources) {
|
|
45
|
+
const all = sources.flat();
|
|
46
|
+
all.sort((a, b) => a.ts - b.ts);
|
|
47
|
+
return all;
|
|
48
|
+
}
|
|
49
|
+
// filter entries by time window (keep entries newer than cutoff)
|
|
50
|
+
export function filterByAge(entries, maxAgeMs, now = Date.now()) {
|
|
51
|
+
const cutoff = now - maxAgeMs;
|
|
52
|
+
return entries.filter((e) => e.ts >= cutoff);
|
|
53
|
+
}
|
|
54
|
+
// ── duration parser ─────────────────────────────────────────────────────────
|
|
55
|
+
// parse human-friendly duration strings: "1h", "6h", "24h", "2d", "7d", "30d"
|
|
56
|
+
export function parseDuration(input) {
|
|
57
|
+
const match = input.match(/^(\d+)(h|d)$/);
|
|
58
|
+
if (!match)
|
|
59
|
+
return null;
|
|
60
|
+
const val = parseInt(match[1], 10);
|
|
61
|
+
if (val <= 0)
|
|
62
|
+
return null;
|
|
63
|
+
const unit = match[2];
|
|
64
|
+
if (unit === "h")
|
|
65
|
+
return val * 60 * 60 * 1000;
|
|
66
|
+
if (unit === "d")
|
|
67
|
+
return val * 24 * 60 * 60 * 1000;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// ── formatters ──────────────────────────────────────────────────────────────
|
|
71
|
+
// format timeline as pretty-printed JSON
|
|
72
|
+
export function formatTimelineJson(entries) {
|
|
73
|
+
const output = entries.map((e) => {
|
|
74
|
+
const obj = {
|
|
75
|
+
time: new Date(e.ts).toISOString(),
|
|
76
|
+
source: e.source,
|
|
77
|
+
tag: e.tag,
|
|
78
|
+
text: e.text,
|
|
79
|
+
};
|
|
80
|
+
if (e.success !== undefined)
|
|
81
|
+
obj.success = e.success;
|
|
82
|
+
if (e.session)
|
|
83
|
+
obj.session = e.session;
|
|
84
|
+
return obj;
|
|
85
|
+
});
|
|
86
|
+
return JSON.stringify(output, null, 2) + "\n";
|
|
87
|
+
}
|
|
88
|
+
// format timeline as a readable Markdown post-mortem document
|
|
89
|
+
export function formatTimelineMarkdown(entries) {
|
|
90
|
+
if (entries.length === 0) {
|
|
91
|
+
return "# aoaoe Session Export\n\n_No entries in the selected time range._\n";
|
|
92
|
+
}
|
|
93
|
+
const lines = [];
|
|
94
|
+
const now = new Date();
|
|
95
|
+
const first = new Date(entries[0].ts);
|
|
96
|
+
const last = new Date(entries[entries.length - 1].ts);
|
|
97
|
+
lines.push("# aoaoe Session Export");
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push(`**Generated**: ${now.toISOString()}`);
|
|
100
|
+
lines.push(`**Range**: ${first.toISOString()} — ${last.toISOString()}`);
|
|
101
|
+
lines.push(`**Entries**: ${entries.length}`);
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push("## Timeline");
|
|
104
|
+
lines.push("");
|
|
105
|
+
// group entries by hour
|
|
106
|
+
let currentHour = "";
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const d = new Date(entry.ts);
|
|
109
|
+
const hour = d.toLocaleString("en-US", {
|
|
110
|
+
month: "short", day: "numeric", hour: "numeric", minute: undefined, hour12: true,
|
|
111
|
+
}).replace(/:00/, "");
|
|
112
|
+
// simpler: just use HH:00 block headers
|
|
113
|
+
const hourKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:00`;
|
|
114
|
+
if (hourKey !== currentHour) {
|
|
115
|
+
if (currentHour)
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push(`### ${hourKey}`);
|
|
118
|
+
lines.push("");
|
|
119
|
+
currentHour = hourKey;
|
|
120
|
+
}
|
|
121
|
+
const time = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
|
|
122
|
+
const icon = entry.source === "action"
|
|
123
|
+
? (entry.success ? "+" : "!")
|
|
124
|
+
: "·";
|
|
125
|
+
const sessionPart = entry.session ? ` → ${entry.session}` : "";
|
|
126
|
+
const text = entry.text.length > 120 ? entry.text.slice(0, 117) + "..." : entry.text;
|
|
127
|
+
lines.push(`- \`${time}\` ${icon} **${entry.tag}**${sessionPart}: ${text}`);
|
|
128
|
+
}
|
|
129
|
+
lines.push("");
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=export.js.map
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { isDaemonRunningFromState } from "./chat.js";
|
|
|
20
20
|
import { sendNotification, sendTestNotification } from "./notify.js";
|
|
21
21
|
import { startHealthServer } from "./health.js";
|
|
22
22
|
import { loadTuiHistory } from "./tui-history.js";
|
|
23
|
+
import { parseActionLogEntries, parseActivityEntries, mergeTimeline, filterByAge, parseDuration, formatTimelineJson, formatTimelineMarkdown } from "./export.js";
|
|
23
24
|
import { actionSession, actionDetail, toActionLogEntry } from "./types.js";
|
|
24
25
|
import { YELLOW, GREEN, DIM, BOLD, RED, RESET } from "./colors.js";
|
|
25
26
|
import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync, chmodSync } from "node:fs";
|
|
@@ -30,7 +31,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
30
31
|
const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
|
|
31
32
|
const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
|
|
32
33
|
async function main() {
|
|
33
|
-
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);
|
|
34
|
+
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);
|
|
34
35
|
if (help) {
|
|
35
36
|
printHelp();
|
|
36
37
|
process.exit(0);
|
|
@@ -103,6 +104,11 @@ async function main() {
|
|
|
103
104
|
await showLogs(logsActions, logsGrep, logsCount);
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
107
|
+
// `aoaoe export` -- export session timeline as JSON or Markdown
|
|
108
|
+
if (runExport) {
|
|
109
|
+
await runTimelineExport(exportFormat, exportOutput, exportLast);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
106
112
|
// `aoaoe task` -- task management CLI
|
|
107
113
|
if (isTaskCli) {
|
|
108
114
|
await runTaskCli(process.argv);
|
|
@@ -1311,6 +1317,47 @@ async function showLogs(actions, grep, count) {
|
|
|
1311
1317
|
console.log("");
|
|
1312
1318
|
}
|
|
1313
1319
|
}
|
|
1320
|
+
// `aoaoe export` -- export session timeline as JSON or Markdown for post-mortems
|
|
1321
|
+
async function runTimelineExport(format, output, last) {
|
|
1322
|
+
const fmt = format ?? "json";
|
|
1323
|
+
if (fmt !== "json" && fmt !== "markdown" && fmt !== "md") {
|
|
1324
|
+
console.error(`error: --format must be "json" or "markdown", got "${fmt}"`);
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
// parse time window (default 24h)
|
|
1328
|
+
const durationMs = last ? parseDuration(last) : 24 * 60 * 60 * 1000;
|
|
1329
|
+
if (durationMs === null) {
|
|
1330
|
+
console.error(`error: --last must be like "1h", "6h", "24h", "7d", got "${last}"`);
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
// read actions.log
|
|
1334
|
+
const actionsFile = join(homedir(), ".aoaoe", "actions.log");
|
|
1335
|
+
let actionEntries = [];
|
|
1336
|
+
try {
|
|
1337
|
+
const lines = readFileSync(actionsFile, "utf-8").trim().split("\n").filter((l) => l.trim());
|
|
1338
|
+
actionEntries = parseActionLogEntries(lines);
|
|
1339
|
+
}
|
|
1340
|
+
catch {
|
|
1341
|
+
// no actions.log — that's fine
|
|
1342
|
+
}
|
|
1343
|
+
// read tui-history.jsonl
|
|
1344
|
+
const historyEntries = loadTuiHistory(10_000, undefined, durationMs);
|
|
1345
|
+
const activityEntries = parseActivityEntries(historyEntries);
|
|
1346
|
+
// merge and filter
|
|
1347
|
+
let timeline = mergeTimeline(actionEntries, activityEntries);
|
|
1348
|
+
timeline = filterByAge(timeline, durationMs);
|
|
1349
|
+
// format
|
|
1350
|
+
const isMarkdown = fmt === "markdown" || fmt === "md";
|
|
1351
|
+
const content = isMarkdown ? formatTimelineMarkdown(timeline) : formatTimelineJson(timeline);
|
|
1352
|
+
// output
|
|
1353
|
+
if (output) {
|
|
1354
|
+
writeFileSync(output, content);
|
|
1355
|
+
console.log(`exported ${timeline.length} entries to ${output}`);
|
|
1356
|
+
}
|
|
1357
|
+
else {
|
|
1358
|
+
process.stdout.write(content);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1314
1361
|
// `aoaoe test` -- dynamically import and run the integration test
|
|
1315
1362
|
async function runIntegrationTest() {
|
|
1316
1363
|
const testModule = resolve(__dirname, "integration-test.js");
|