aoaoe 0.191.0 → 0.192.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.
@@ -0,0 +1,11 @@
1
+ export declare function createBackup(outputPath?: string): Promise<string>;
2
+ export declare function restoreBackup(inputPath: string): Promise<{
3
+ restored: string[];
4
+ skipped: string[];
5
+ }>;
6
+ export declare function formatBackupResult(path: string): string;
7
+ export declare function formatRestoreResult(result: {
8
+ restored: string[];
9
+ skipped: string[];
10
+ }): string;
11
+ //# sourceMappingURL=backup.d.ts.map
package/dist/backup.js ADDED
@@ -0,0 +1,121 @@
1
+ // backup.ts — backup and restore aoaoe state + config for portability.
2
+ // backs up ~/.aoaoe/ contents to a timestamped tarball or directory.
3
+ import { existsSync, mkdirSync, readdirSync, copyFileSync, statSync } from "node:fs";
4
+ import { join, basename } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { exec } from "./shell.js";
7
+ import { DIM, GREEN, YELLOW, RESET } from "./colors.js";
8
+ const AOAOE_DIR = join(homedir(), ".aoaoe");
9
+ // files to include in backup (relative to ~/.aoaoe/)
10
+ const BACKUP_FILES = [
11
+ "aoaoe.config.json",
12
+ "task-state.json",
13
+ "supervisor-history.jsonl",
14
+ "pin-presets.json",
15
+ "templates.json",
16
+ "prompt-templates.json",
17
+ "tui-prefs.json",
18
+ ];
19
+ export async function createBackup(outputPath) {
20
+ if (!existsSync(AOAOE_DIR)) {
21
+ throw new Error("~/.aoaoe/ does not exist — nothing to back up");
22
+ }
23
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
24
+ const defaultPath = join(process.cwd(), `aoaoe-backup-${timestamp}`);
25
+ const target = outputPath || defaultPath;
26
+ // also include aoaoe.tasks.json from cwd if it exists
27
+ const cwdTasksFile = join(process.cwd(), "aoaoe.tasks.json");
28
+ // try tar first (most portable)
29
+ const tarTarget = target.endsWith(".tar.gz") ? target : `${target}.tar.gz`;
30
+ const filesToBackup = [];
31
+ for (const f of BACKUP_FILES) {
32
+ const full = join(AOAOE_DIR, f);
33
+ if (existsSync(full))
34
+ filesToBackup.push(full);
35
+ }
36
+ if (existsSync(cwdTasksFile))
37
+ filesToBackup.push(cwdTasksFile);
38
+ if (filesToBackup.length === 0) {
39
+ throw new Error("no files to back up");
40
+ }
41
+ const result = await exec("tar", [
42
+ "czf", tarTarget,
43
+ ...filesToBackup.map((f) => f),
44
+ ]);
45
+ if (result.exitCode === 0) {
46
+ const size = statSync(tarTarget).size;
47
+ const sizeStr = size < 1024 ? `${size}B` : `${(size / 1024).toFixed(1)}KB`;
48
+ return `${tarTarget} (${filesToBackup.length} files, ${sizeStr})`;
49
+ }
50
+ // tar failed — fall back to directory copy
51
+ mkdirSync(target, { recursive: true });
52
+ let count = 0;
53
+ for (const full of filesToBackup) {
54
+ const name = basename(full);
55
+ copyFileSync(full, join(target, name));
56
+ count++;
57
+ }
58
+ return `${target}/ (${count} files copied)`;
59
+ }
60
+ export async function restoreBackup(inputPath) {
61
+ const restored = [];
62
+ const skipped = [];
63
+ if (!existsSync(inputPath)) {
64
+ throw new Error(`backup not found: ${inputPath}`);
65
+ }
66
+ if (!existsSync(AOAOE_DIR)) {
67
+ mkdirSync(AOAOE_DIR, { recursive: true });
68
+ }
69
+ // try tar extraction
70
+ if (inputPath.endsWith(".tar.gz") || inputPath.endsWith(".tgz")) {
71
+ const result = await exec("tar", ["xzf", inputPath, "-C", "/"]);
72
+ if (result.exitCode === 0) {
73
+ // check what was restored
74
+ for (const f of BACKUP_FILES) {
75
+ if (existsSync(join(AOAOE_DIR, f)))
76
+ restored.push(f);
77
+ }
78
+ return { restored, skipped };
79
+ }
80
+ throw new Error(`tar extraction failed: ${result.stderr}`);
81
+ }
82
+ // directory restore
83
+ if (!statSync(inputPath).isDirectory()) {
84
+ throw new Error(`${inputPath} is not a directory or tar.gz archive`);
85
+ }
86
+ const files = readdirSync(inputPath);
87
+ for (const f of files) {
88
+ const src = join(inputPath, f);
89
+ if (f === "aoaoe.tasks.json") {
90
+ // restore to cwd
91
+ copyFileSync(src, join(process.cwd(), f));
92
+ restored.push(f);
93
+ }
94
+ else if (BACKUP_FILES.includes(f)) {
95
+ copyFileSync(src, join(AOAOE_DIR, f));
96
+ restored.push(f);
97
+ }
98
+ else {
99
+ skipped.push(f);
100
+ }
101
+ }
102
+ return { restored, skipped };
103
+ }
104
+ export function formatBackupResult(path) {
105
+ return ` ${GREEN}✓${RESET} backup created: ${path}`;
106
+ }
107
+ export function formatRestoreResult(result) {
108
+ const lines = [];
109
+ if (result.restored.length > 0) {
110
+ lines.push(` ${GREEN}✓${RESET} restored ${result.restored.length} file(s):`);
111
+ for (const f of result.restored)
112
+ lines.push(` ${DIM}${f}${RESET}`);
113
+ }
114
+ if (result.skipped.length > 0) {
115
+ lines.push(` ${YELLOW}!${RESET} skipped ${result.skipped.length} file(s):`);
116
+ for (const f of result.skipped)
117
+ lines.push(` ${DIM}${f}${RESET}`);
118
+ }
119
+ return lines.join("\n");
120
+ }
121
+ //# sourceMappingURL=backup.js.map
package/dist/config.d.ts CHANGED
@@ -61,6 +61,10 @@ export declare function parseCliArgs(argv: string[]): {
61
61
  configDiff: boolean;
62
62
  notifyTest: boolean;
63
63
  runDoctor: boolean;
64
+ runBackup: boolean;
65
+ backupOutput?: string;
66
+ runRestore: boolean;
67
+ restoreInput?: string;
64
68
  runLogs: boolean;
65
69
  logsActions: boolean;
66
70
  logsGrep?: string;
@@ -69,6 +73,7 @@ export declare function parseCliArgs(argv: string[]): {
69
73
  exportFormat?: string;
70
74
  exportOutput?: string;
71
75
  exportLast?: string;
76
+ exportTasks: boolean;
72
77
  runInit: boolean;
73
78
  initForce: boolean;
74
79
  runTaskCli: boolean;
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, 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 };
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, 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 };
@@ -531,6 +531,14 @@ export function parseCliArgs(argv) {
531
531
  if (argv[2] === "doctor") {
532
532
  return { ...defaults, runDoctor: true };
533
533
  }
534
+ if (argv[2] === "backup") {
535
+ const output = argv[3] && !argv[3].startsWith("-") ? argv[3] : undefined;
536
+ return { ...defaults, runBackup: true, backupOutput: output };
537
+ }
538
+ if (argv[2] === "restore") {
539
+ const input = argv[3] && !argv[3].startsWith("-") ? argv[3] : undefined;
540
+ return { ...defaults, runRestore: true, restoreInput: input };
541
+ }
534
542
  if (argv[2] === "logs") {
535
543
  const actions = argv.includes("--actions") || argv.includes("-a");
536
544
  let grep;
@@ -551,7 +559,10 @@ export function parseCliArgs(argv) {
551
559
  let format;
552
560
  let output;
553
561
  let last;
562
+ const exportTasks = argv.includes("--tasks");
554
563
  for (let i = 3; i < argv.length; i++) {
564
+ if (argv[i] === "--tasks")
565
+ continue;
555
566
  if ((argv[i] === "--format" || argv[i] === "-f") && argv[i + 1]) {
556
567
  format = argv[++i];
557
568
  }
@@ -562,7 +573,7 @@ export function parseCliArgs(argv) {
562
573
  last = argv[++i];
563
574
  }
564
575
  }
565
- return { ...defaults, runExport: true, exportFormat: format, exportOutput: output, exportLast: last };
576
+ return { ...defaults, runExport: true, exportFormat: format, exportOutput: output, exportLast: last, exportTasks };
566
577
  }
567
578
  if (argv[2] === "init") {
568
579
  const force = argv.includes("--force") || argv.includes("-f");
@@ -717,7 +728,7 @@ export function parseCliArgs(argv) {
717
728
  break;
718
729
  }
719
730
  }
720
- 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, 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 };
731
+ 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, 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 };
721
732
  }
722
733
  export function printHelp() {
723
734
  console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
@@ -761,6 +772,8 @@ commands:
761
772
  config --validate validate config + check tool availability
762
773
  config --diff show only fields that differ from defaults
763
774
  notify-test send a test notification to configured webhooks
775
+ backup [path] backup ~/.aoaoe/ state + config to tarball
776
+ restore <path> restore from backup tarball or directory
764
777
  doctor comprehensive health check (config, tools, daemon, disk)
765
778
  logs show recent conversation log entries (last 50)
766
779
  logs --actions show action log entries (from ~/.aoaoe/actions.log)
@@ -770,6 +783,8 @@ commands:
770
783
  export --format <json|markdown> output format (default: json)
771
784
  export --output <file> write to file (default: stdout)
772
785
  export --last <duration> time window: 1h, 6h, 24h, 7d (default: 24h)
786
+ export --tasks export task history instead of timeline
787
+ export --tasks --format md export task history as markdown
773
788
  stats show aggregate daemon statistics (actions, sessions, activity)
774
789
  stats --last <duration> time window: 1h, 6h, 24h, 7d (default: all time)
775
790
  replay play back tui-history.jsonl like a movie with simulated timing
package/dist/export.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { TaskState } from "./types.js";
1
2
  import type { HistoryEntry } from "./tui-history.js";
2
3
  export interface TimelineEntry {
3
4
  ts: number;
@@ -14,4 +15,6 @@ export declare function filterByAge(entries: TimelineEntry[], maxAgeMs: number,
14
15
  export declare function parseDuration(input: string): number | null;
15
16
  export declare function formatTimelineJson(entries: TimelineEntry[]): string;
16
17
  export declare function formatTimelineMarkdown(entries: TimelineEntry[]): string;
18
+ export declare function formatTaskExportJson(tasks: TaskState[]): string;
19
+ export declare function formatTaskExportMarkdown(tasks: TaskState[]): string;
17
20
  //# sourceMappingURL=export.d.ts.map
package/dist/export.js CHANGED
@@ -129,4 +129,61 @@ export function formatTimelineMarkdown(entries) {
129
129
  lines.push("");
130
130
  return lines.join("\n");
131
131
  }
132
+ // ── task export ─────────────────────────────────────────────────────────────
133
+ export function formatTaskExportJson(tasks) {
134
+ const payload = tasks.map((t) => ({
135
+ session: t.sessionTitle,
136
+ repo: t.repo,
137
+ status: t.status,
138
+ goal: t.goal,
139
+ dependsOn: t.dependsOn ?? [],
140
+ createdAt: t.createdAt ?? null,
141
+ completedAt: t.completedAt ?? null,
142
+ lastProgressAt: t.lastProgressAt ?? null,
143
+ progressCount: t.progress.length,
144
+ progress: t.progress.map((p) => ({
145
+ at: p.at,
146
+ time: new Date(p.at).toISOString(),
147
+ summary: p.summary,
148
+ })),
149
+ }));
150
+ return JSON.stringify(payload, null, 2);
151
+ }
152
+ export function formatTaskExportMarkdown(tasks) {
153
+ const lines = [];
154
+ lines.push("# Task History Export");
155
+ lines.push("");
156
+ lines.push(`Generated: ${new Date().toISOString()}`);
157
+ lines.push(`Tasks: ${tasks.length}`);
158
+ lines.push("");
159
+ for (const t of tasks) {
160
+ const icon = t.status === "completed" ? "✓"
161
+ : t.status === "active" ? "●"
162
+ : t.status === "paused" ? "◎"
163
+ : t.status === "failed" ? "✗"
164
+ : "○";
165
+ lines.push(`## ${icon} ${t.sessionTitle} (${t.status})`);
166
+ lines.push("");
167
+ lines.push(`- **Goal**: ${t.goal}`);
168
+ lines.push(`- **Repo**: ${t.repo}`);
169
+ if (t.dependsOn?.length)
170
+ lines.push(`- **Depends on**: ${t.dependsOn.join(", ")}`);
171
+ if (t.createdAt)
172
+ lines.push(`- **Created**: ${new Date(t.createdAt).toISOString()}`);
173
+ if (t.completedAt)
174
+ lines.push(`- **Completed**: ${new Date(t.completedAt).toISOString()}`);
175
+ lines.push(`- **Progress entries**: ${t.progress.length}`);
176
+ lines.push("");
177
+ if (t.progress.length > 0) {
178
+ lines.push("### Progress");
179
+ lines.push("");
180
+ for (const p of t.progress) {
181
+ const time = new Date(p.at).toLocaleString();
182
+ lines.push(`- **${time}**: ${p.summary}`);
183
+ }
184
+ lines.push("");
185
+ }
186
+ }
187
+ return lines.join("\n");
188
+ }
132
189
  //# sourceMappingURL=export.js.map
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import { savePreset, deletePreset, getPreset, formatPresetList } from "./pin-pre
28
28
  import { resolvePromptTemplate, formatPromptTemplateList } from "./reasoner/prompt-templates.js";
29
29
  import { formatHealthReport, computeAllHealth } from "./health-score.js";
30
30
  import { ConfigWatcher, formatConfigChange } from "./config-watcher.js";
31
- import { parseActionLogEntries, parseActivityEntries, mergeTimeline, filterByAge, parseDuration, formatTimelineJson, formatTimelineMarkdown } from "./export.js";
31
+ import { parseActionLogEntries, parseActivityEntries, mergeTimeline, filterByAge, parseDuration, formatTimelineJson, formatTimelineMarkdown, formatTaskExportJson, formatTaskExportMarkdown } from "./export.js";
32
32
  import { actionSession, actionDetail, toActionLogEntry } from "./types.js";
33
33
  import { YELLOW, GREEN, DIM, BOLD, RED, RESET } from "./colors.js";
34
34
  import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync, chmodSync } from "node:fs";
@@ -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, 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, 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);
@@ -71,7 +71,7 @@ async function main() {
71
71
  return;
72
72
  }
73
73
  // suppress noisy [tasks] [config] log lines in one-shot CLI commands
74
- if (showTasks || runProgress || runHealth || runSummary || runAdopt || showStatus || runRunbook || runIncident || runSupervisor) {
74
+ if (showTasks || runProgress || runHealth || runSummary || runAdopt || runBackup || runRestore || runExport || showStatus || runRunbook || runIncident || runSupervisor) {
75
75
  process.env.AOAOE_QUIET = "1";
76
76
  }
77
77
  // `aoaoe tasks` -- show current task state
@@ -147,6 +147,33 @@ async function main() {
147
147
  await runDoctorCheck();
148
148
  return;
149
149
  }
150
+ if (runBackup) {
151
+ try {
152
+ const result = await createBackup(backupOutput);
153
+ console.log(formatBackupResult(result));
154
+ }
155
+ catch (err) {
156
+ console.error(`backup failed: ${err instanceof Error ? err.message : err}`);
157
+ process.exitCode = 1;
158
+ }
159
+ return;
160
+ }
161
+ if (runRestore) {
162
+ if (!restoreInput) {
163
+ console.error("usage: aoaoe restore <backup-path>");
164
+ process.exitCode = 1;
165
+ return;
166
+ }
167
+ try {
168
+ const result = await restoreBackup(restoreInput);
169
+ console.log(formatRestoreResult(result));
170
+ }
171
+ catch (err) {
172
+ console.error(`restore failed: ${err instanceof Error ? err.message : err}`);
173
+ process.exitCode = 1;
174
+ }
175
+ return;
176
+ }
150
177
  // `aoaoe logs` -- show conversation or action log entries
151
178
  if (runLogs) {
152
179
  await showLogs(logsActions, logsGrep, logsCount);
@@ -154,9 +181,42 @@ async function main() {
154
181
  }
155
182
  // `aoaoe export` -- export session timeline as JSON or Markdown
156
183
  if (runExport) {
157
- await runTimelineExport(exportFormat, exportOutput, exportLast);
184
+ const { exportTasks } = parseCliArgs(process.argv);
185
+ if (exportTasks) {
186
+ await runTaskExport(exportFormat, exportOutput);
187
+ }
188
+ else {
189
+ await runTimelineExport(exportFormat, exportOutput, exportLast);
190
+ }
158
191
  return;
159
192
  }
193
+ async function runTaskExport(format, output) {
194
+ const fmt = format ?? "json";
195
+ if (fmt !== "json" && fmt !== "markdown" && fmt !== "md") {
196
+ console.error(`error: --format must be "json" or "markdown", got "${fmt}"`);
197
+ process.exit(1);
198
+ }
199
+ const basePath = process.cwd();
200
+ const defs = loadTaskDefinitions(basePath);
201
+ const taskProfiles = resolveProfiles(loadConfig());
202
+ const tm = defs.length > 0 ? new TaskManager(basePath, defs, taskProfiles) : undefined;
203
+ const tasks = tm?.tasks ?? [...loadTaskState().values()];
204
+ if (tasks.length === 0) {
205
+ console.error("no tasks to export");
206
+ return;
207
+ }
208
+ const isMarkdown = fmt === "markdown" || fmt === "md";
209
+ const content = isMarkdown ? formatTaskExportMarkdown(tasks) : formatTaskExportJson(tasks);
210
+ if (output) {
211
+ writeFileSync(output, content);
212
+ console.log(`exported ${tasks.length} task(s) to ${output}`);
213
+ }
214
+ else {
215
+ process.stdout.write(content);
216
+ if (!content.endsWith("\n"))
217
+ process.stdout.write("\n");
218
+ }
219
+ }
160
220
  // `aoaoe stats` -- show aggregate daemon statistics
161
221
  if (isStats) {
162
222
  await runStatsCommand(statsLast);
@@ -4469,6 +4529,7 @@ async function showProgressDigest(since, asJson = false) {
4469
4529
  console.log(formatProgressDigest(tasks, maxAgeMs));
4470
4530
  }
4471
4531
  import { resolveTemplate } from "./task-templates.js";
4532
+ import { createBackup, restoreBackup, formatBackupResult, formatRestoreResult } from "./backup.js";
4472
4533
  // adopt untracked live AoE sessions as tasks with optional template goal.
4473
4534
  async function adoptUntrackedSessions(templateName) {
4474
4535
  const basePath = process.cwd();
package/dist/task-cli.js CHANGED
@@ -6,7 +6,7 @@ import { resolve, basename } from "node:path";
6
6
  import { buildProfileListArgs } from "./poller.js";
7
7
  import { loadConfig } from "./config.js";
8
8
  import { resolveProfiles } from "./tui.js";
9
- import { loadTaskState, saveTaskState, formatTaskTable, syncTaskDefinitionsFromState, taskStateKey, resolveTaskRepoPath, TaskManager, loadTaskDefinitions, injectGoalToSession } from "./task-manager.js";
9
+ import { loadTaskState, saveTaskState, formatTaskTable, formatTaskHistory, syncTaskDefinitionsFromState, taskStateKey, resolveTaskRepoPath, TaskManager, loadTaskDefinitions, injectGoalToSession } from "./task-manager.js";
10
10
  import { goalToList } from "./types.js";
11
11
  import { resolveTemplate, formatTemplateList } from "./task-templates.js";
12
12
  import { BOLD, DIM, GREEN, YELLOW, RED, RESET } from "./colors.js";
@@ -26,6 +26,7 @@ function getTaskProfiles() {
26
26
  function taskCommandHelp(prefix = "aoaoe task") {
27
27
  return [
28
28
  `${prefix} list show tracked tasks`,
29
+ `${prefix} history [session] show progress history (all or one session)`,
29
30
  `${prefix} templates show available task templates`,
30
31
  `${prefix} reconcile link/create sessions now`,
31
32
  `${prefix} new <title> <path> create task + session`,
@@ -454,6 +455,13 @@ export async function runTaskCli(argv) {
454
455
  console.log(formatTemplateList());
455
456
  return;
456
457
  }
458
+ case "history": {
459
+ const states = loadTaskState();
460
+ const tasks = [...states.values()];
461
+ const sessionFilter = args[0] || undefined;
462
+ console.log(formatTaskHistory(tasks, sessionFilter));
463
+ return;
464
+ }
457
465
  case "reconcile": {
458
466
  const basePath = process.cwd();
459
467
  const defs = loadTaskDefinitions(basePath);
@@ -623,6 +631,12 @@ export async function handleTaskSlashCommand(args) {
623
631
  if (sub === "templates" || sub === "template") {
624
632
  return formatTemplateList();
625
633
  }
626
- return "usage: /task [list|start|stop|edit|new|rm|reconcile|templates|help] [args]";
634
+ if (sub === "history") {
635
+ const states = loadTaskState();
636
+ const tasks = [...states.values()];
637
+ const sessionFilter = rest[0] || undefined;
638
+ return formatTaskHistory(tasks, sessionFilter);
639
+ }
640
+ return "usage: /task [list|start|stop|edit|new|rm|reconcile|templates|history|help] [args]";
627
641
  }
628
642
  //# sourceMappingURL=task-cli.js.map
@@ -43,6 +43,7 @@ export declare function deriveTitle(repo: string): string;
43
43
  */
44
44
  export declare function readNextRoadmapItems(basePath: string, maxItems?: number): string;
45
45
  export declare function formatTaskTable(states: Map<string, TaskState> | TaskState[]): string;
46
+ export declare function formatTaskHistory(tasks: TaskState[], sessionFilter?: string): string;
46
47
  export declare function formatAgo(ms: number): string;
47
48
  export declare function formatProgressDigest(tasks: TaskState[], maxAgeMs?: number): string;
48
49
  //# sourceMappingURL=task-manager.d.ts.map
@@ -629,6 +629,37 @@ export function formatTaskTable(states) {
629
629
  }
630
630
  return lines.join("\n");
631
631
  }
632
+ // format detailed progress history for one or all tasks.
633
+ export function formatTaskHistory(tasks, sessionFilter) {
634
+ const filtered = sessionFilter
635
+ ? tasks.filter((t) => t.sessionTitle.toLowerCase() === sessionFilter.toLowerCase()
636
+ || t.sessionTitle.toLowerCase().includes(sessionFilter.toLowerCase()))
637
+ : tasks;
638
+ if (filtered.length === 0) {
639
+ return sessionFilter ? ` no task found matching "${sessionFilter}"` : " (no tasks)";
640
+ }
641
+ const lines = [];
642
+ for (const t of filtered) {
643
+ const statusIcon = t.status === "completed" ? "✓"
644
+ : t.status === "active" ? "●"
645
+ : t.status === "paused" ? "◎"
646
+ : t.status === "failed" ? "✗"
647
+ : "○";
648
+ lines.push(` ${statusIcon} ${BOLD}${t.sessionTitle}${RESET} ${DIM}(${t.status})${RESET}`);
649
+ lines.push(` ${DIM}goal: ${t.goal.length > 80 ? t.goal.slice(0, 77) + "..." : t.goal}${RESET}`);
650
+ if (t.progress.length === 0) {
651
+ lines.push(` ${DIM}(no progress entries)${RESET}`);
652
+ }
653
+ else {
654
+ for (const p of t.progress) {
655
+ const ts = new Date(p.at).toLocaleString();
656
+ lines.push(` ${DIM}${ts}${RESET} ${p.summary}`);
657
+ }
658
+ }
659
+ lines.push("");
660
+ }
661
+ return lines.join("\n");
662
+ }
632
663
  export function formatAgo(ms) {
633
664
  if (ms < 60_000)
634
665
  return `${Math.round(ms / 1000)}s ago`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.191.0",
3
+ "version": "0.192.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",