copilot-agent 0.11.0 → 0.12.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 CHANGED
@@ -21,6 +21,8 @@ Autonomous AI agent manager — auto-resume sessions, discover tasks, run overni
21
21
  | **`compact`** | Generate context summary for session handoff/resume |
22
22
  | **`hooks`** | Event-driven automation (on_task_complete, on_error, etc.) |
23
23
  | **`pr`** | Auto-create GitHub Pull Request from session changes |
24
+ | **`log`** | Search, timeline, and export session history |
25
+ | **`template`** | Manage custom task templates (add/list/remove/import/export) |
24
26
 
25
27
  All commands support `--agent copilot` or `--agent claude` (auto-detects if omitted).
26
28
 
@@ -107,11 +109,11 @@ copilot-agent diff abc12345-... --stat
107
109
  ### Dashboards
108
110
 
109
111
  ```bash
110
- # htop-style TUI (blessed — scrollable, keyboard nav)
112
+ # htop-style TUI (blessed — scrollable, keyboard nav, cached rendering)
111
113
  copilot-agent dashboard
112
114
 
113
- # Simple ANSI fallback (no dependencies)
114
- copilot-agent dashboard --simple
115
+ # Custom refresh interval
116
+ copilot-agent dashboard --refresh 3
115
117
 
116
118
  # Web UI (Hono + htmx, opens browser)
117
119
  copilot-agent web
@@ -204,6 +206,37 @@ copilot-agent pr --dry-run
204
206
  copilot-agent pr --no-draft
205
207
  ```
206
208
 
209
+ ### Session log (search & export)
210
+
211
+ ```bash
212
+ # Search sessions by keyword
213
+ copilot-agent log search "auth" --limit 10
214
+
215
+ # View session timeline
216
+ copilot-agent log timeline <session-id>
217
+
218
+ # Export history as JSON or CSV
219
+ copilot-agent log export --format json --output sessions.json
220
+ copilot-agent log export --format csv --limit 50
221
+ ```
222
+
223
+ ### Task templates
224
+
225
+ ```bash
226
+ # List custom templates
227
+ copilot-agent template list
228
+
229
+ # Add a reusable task template
230
+ copilot-agent template add security-audit --prompt "Run a full security audit"
231
+
232
+ # Remove a template
233
+ copilot-agent template remove security-audit
234
+
235
+ # Export/import templates (YAML)
236
+ copilot-agent template export > my-templates.yaml
237
+ copilot-agent template import team-templates.yaml
238
+ ```
239
+
207
240
  ## How it works
208
241
 
209
242
  1. **Agent abstraction** — Unified interface for both Copilot CLI and Claude Code
package/dist/index.js CHANGED
@@ -1779,16 +1779,53 @@ async function loadBlessed() {
1779
1779
  blessed = mod.default || mod;
1780
1780
  }
1781
1781
  function registerDashboardCommand(program2) {
1782
- program2.command("dashboard").alias("tui").description("Real-time terminal dashboard for copilot sessions (htop-style)").option("-r, --refresh <n>", "Refresh interval in seconds", "5").option("-l, --limit <n>", "Number of sessions to show", "20").option("--simple", "Use simple ANSI dashboard (no blessed)").action(async (opts) => {
1783
- if (opts.simple) {
1784
- runSimpleDashboard(parseInt(opts.refresh, 10), parseInt(opts.limit, 10));
1785
- } else {
1786
- await loadBlessed();
1787
- runBlessedDashboard(parseInt(opts.refresh, 10), parseInt(opts.limit, 10));
1788
- }
1782
+ program2.command("dashboard").alias("tui").description("Real-time terminal dashboard for copilot sessions (htop-style)").option("-r, --refresh <n>", "Refresh interval in seconds", "5").option("-l, --limit <n>", "Number of sessions to show", "20").action(async (opts) => {
1783
+ await loadBlessed();
1784
+ runBlessedDashboard(parseInt(opts.refresh, 10), parseInt(opts.limit, 10));
1789
1785
  });
1790
1786
  }
1787
+ function createCache() {
1788
+ return { sessions: [], sessionsTs: 0, procs: [], procsTs: 0, details: /* @__PURE__ */ new Map() };
1789
+ }
1790
+ function cacheSessions(cache, limit) {
1791
+ const now = Date.now();
1792
+ if (now - cache.sessionsTs > 2e3) {
1793
+ try {
1794
+ cache.sessions = listAllSessions(limit);
1795
+ } catch {
1796
+ }
1797
+ cache.sessionsTs = now;
1798
+ }
1799
+ return cache.sessions;
1800
+ }
1801
+ function cacheProcs(cache) {
1802
+ const now = Date.now();
1803
+ if (now - cache.procsTs > 3e3) {
1804
+ try {
1805
+ cache.procs = findAgentProcesses();
1806
+ } catch {
1807
+ }
1808
+ cache.procsTs = now;
1809
+ }
1810
+ return cache.procs;
1811
+ }
1812
+ function cacheDetail(cache, s) {
1813
+ const entry = cache.details.get(s.id);
1814
+ if (entry && Date.now() - entry.ts < 1e4) return entry.report;
1815
+ let report = null;
1816
+ try {
1817
+ report = getAgentSessionReport(s.id, s.agent);
1818
+ } catch {
1819
+ }
1820
+ cache.details.set(s.id, { report, ts: Date.now() });
1821
+ if (cache.details.size > 15) {
1822
+ const oldest = [...cache.details.entries()].sort((a, b) => a[1].ts - b[1].ts)[0];
1823
+ if (oldest) cache.details.delete(oldest[0]);
1824
+ }
1825
+ return report;
1826
+ }
1791
1827
  function runBlessedDashboard(refreshSec, limit) {
1828
+ const cache = createCache();
1792
1829
  const screen = blessed.screen({
1793
1830
  smartCSR: true,
1794
1831
  title: "copilot-agent dashboard",
@@ -1878,13 +1915,7 @@ function runBlessedDashboard(refreshSec, limit) {
1878
1915
  let procs = [];
1879
1916
  let selectedIdx = 0;
1880
1917
  let focusedPanel = "sessions";
1881
- function refreshData() {
1882
- try {
1883
- procs = findAgentProcesses();
1884
- sessions = listAllSessions(limit);
1885
- } catch {
1886
- }
1887
- }
1918
+ let lastDetailId = "";
1888
1919
  function renderHeader() {
1889
1920
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB");
1890
1921
  const totalPremium = sessions.reduce((s, x) => s + x.premiumRequests, 0);
@@ -1910,37 +1941,44 @@ ${rows.join("\n")}`);
1910
1941
  }
1911
1942
  sessionList.setLabel(` {cyan-fg}{bold}Sessions (${sessions.length}){/} `);
1912
1943
  }
1913
- function renderDetail2() {
1944
+ function renderDetail2(force = false) {
1914
1945
  if (sessions.length === 0 || selectedIdx < 0 || selectedIdx >= sessions.length) {
1915
1946
  detailBox.setContent("{gray-fg}No session selected{/}");
1947
+ lastDetailId = "";
1916
1948
  return;
1917
1949
  }
1918
1950
  const s = sessions[selectedIdx];
1919
- try {
1920
- const report = getAgentSessionReport(s.id, s.agent);
1921
- if (report) {
1922
- detailBox.setContent(detailContent(report));
1923
- detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${s.id.slice(0, 12)}\u2026{/} `);
1924
- } else {
1925
- detailBox.setContent(`{gray-fg}Could not load report for ${s.id.slice(0, 8)}\u2026{/}`);
1926
- }
1927
- } catch {
1928
- detailBox.setContent("{red-fg}Error loading session detail{/}");
1951
+ if (!force && s.id === lastDetailId) return;
1952
+ lastDetailId = s.id;
1953
+ const report = cacheDetail(cache, s);
1954
+ if (report) {
1955
+ detailBox.setContent(detailContent(report));
1956
+ detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${s.id.slice(0, 12)}\u2026{/} `);
1957
+ } else {
1958
+ detailBox.setContent(`{gray-fg}Could not load report for ${s.id.slice(0, 8)}\u2026{/}`);
1929
1959
  }
1930
1960
  }
1931
1961
  function render() {
1932
- refreshData();
1962
+ sessions = cacheSessions(cache, limit);
1963
+ procs = cacheProcs(cache);
1933
1964
  renderHeader();
1934
1965
  renderProcesses2();
1935
1966
  renderSessions();
1936
1967
  renderDetail2();
1937
1968
  screen.render();
1938
1969
  }
1970
+ function forceRefresh() {
1971
+ cache.sessionsTs = 0;
1972
+ cache.procsTs = 0;
1973
+ cache.details.clear();
1974
+ lastDetailId = "";
1975
+ render();
1976
+ }
1939
1977
  screen.key(["q", "C-c"], () => {
1940
1978
  screen.destroy();
1941
1979
  process.exit(0);
1942
1980
  });
1943
- screen.key(["r"], () => render());
1981
+ screen.key(["r"], () => forceRefresh());
1944
1982
  screen.key(["tab"], () => {
1945
1983
  if (focusedPanel === "sessions") {
1946
1984
  focusedPanel = "detail";
@@ -1977,7 +2015,7 @@ ${rows.join("\n")}`);
1977
2015
  }
1978
2016
  });
1979
2017
  sessionList.key(["enter"], () => {
1980
- renderDetail2();
2018
+ renderDetail2(true);
1981
2019
  focusedPanel = "detail";
1982
2020
  detailBox.focus();
1983
2021
  sessionList.style.border.fg = BORDER_COLOR;
@@ -1990,82 +2028,6 @@ ${rows.join("\n")}`);
1990
2028
  const timer = setInterval(render, refreshSec * 1e3);
1991
2029
  screen.on("destroy", () => clearInterval(timer));
1992
2030
  }
1993
- var ESC = "\x1B";
1994
- var CLEAR = `${ESC}[2J${ESC}[H`;
1995
- var HIDE_CURSOR = `${ESC}[?25l`;
1996
- var SHOW_CURSOR = `${ESC}[?25h`;
1997
- function runSimpleDashboard(refreshSec, limit) {
1998
- process.stdout.write(HIDE_CURSOR);
1999
- const cleanup = () => {
2000
- process.stdout.write(SHOW_CURSOR);
2001
- process.stdout.write(CLEAR);
2002
- process.exit(0);
2003
- };
2004
- process.on("SIGINT", cleanup);
2005
- process.on("SIGTERM", cleanup);
2006
- const render = () => {
2007
- try {
2008
- const output = buildSimpleScreen(limit);
2009
- process.stdout.write(CLEAR + output);
2010
- } catch {
2011
- }
2012
- };
2013
- render();
2014
- setInterval(render, refreshSec * 1e3);
2015
- process.stdout.on("resize", render);
2016
- process.stdin.setRawMode?.(true);
2017
- process.stdin.resume();
2018
- process.stdin.on("data", (data) => {
2019
- const key = data.toString();
2020
- if (key === "q" || key === "") cleanup();
2021
- if (key === "r") render();
2022
- });
2023
- }
2024
- function buildSimpleScreen(limit) {
2025
- const cols = process.stdout.columns || 80;
2026
- const lines = [];
2027
- const now = /* @__PURE__ */ new Date();
2028
- const timeStr = now.toLocaleTimeString("en-GB");
2029
- lines.push("");
2030
- lines.push(` ${BOLD}${CYAN}\u250C${"\u2500".repeat(cols - 6)}\u2510${RESET}`);
2031
- lines.push(` ${BOLD}${CYAN}\u2502${RESET} \u26A1 ${BOLD}Copilot Agent Dashboard${RESET}${" ".repeat(Math.max(0, cols - 37 - timeStr.length))}${DIM}${timeStr}${RESET} ${BOLD}${CYAN}\u2502${RESET}`);
2032
- lines.push(` ${BOLD}${CYAN}\u2514${"\u2500".repeat(cols - 6)}\u2518${RESET}`);
2033
- lines.push("");
2034
- const procs = findAgentProcesses();
2035
- lines.push(` ${BOLD}${GREEN}\u25CF Active Processes (${procs.length})${RESET}`);
2036
- lines.push(` ${"\u2500".repeat(Math.min(cols - 4, 70))}`);
2037
- if (procs.length === 0) {
2038
- lines.push(` ${DIM}No agent processes running${RESET}`);
2039
- } else {
2040
- for (const p of procs) {
2041
- const agentTag = p.agent === "claude" ? `${YELLOW}[claude]${RESET}` : `${CYAN}[copilot]${RESET}`;
2042
- const sid = p.sessionId ? p.sessionId.slice(0, 8) + "\u2026" : "\u2014";
2043
- const cwdShort = p.cwd ? "~/" + p.cwd.split("/").slice(-2).join("/") : "\u2014";
2044
- lines.push(` ${GREEN}\u2B24${RESET} ${agentTag} PID ${BOLD}${p.pid}${RESET} ${CYAN}${sid}${RESET} ${DIM}${cwdShort}${RESET}`);
2045
- }
2046
- }
2047
- lines.push("");
2048
- const sessions = listAllSessions(limit);
2049
- lines.push(` ${BOLD}${CYAN}\u25CF Recent Sessions (${sessions.length})${RESET}`);
2050
- lines.push(` ${"\u2500".repeat(Math.min(cols - 4, 70))}`);
2051
- const hdr = [pad("Status", 10), pad("Agent", 8), pad("Premium", 8), pad("Project", 18), pad("Last Activity", 14)];
2052
- lines.push(` ${DIM}${hdr.join(" ")}${RESET}`);
2053
- for (const s of sessions) {
2054
- const icon = s.complete ? `${GREEN}\u2714 done ${RESET}` : `${YELLOW}\u23F8 stop ${RESET}`;
2055
- const agent = s.agent === "claude" ? `${YELLOW}claude ${RESET}` : `${CYAN}copilot${RESET}`;
2056
- const prem = pad(String(s.premiumRequests), 8);
2057
- const proj = pad(s.cwd.split("/").pop() ?? "\u2014", 18);
2058
- const ago = pad(fmtTimeAgo(s.mtime), 14);
2059
- lines.push(` ${icon}${agent} ${prem}${proj}${ago}`);
2060
- }
2061
- lines.push("");
2062
- lines.push(` ${DIM}Press ${BOLD}q${RESET}${DIM} to quit, ${BOLD}r${RESET}${DIM} to refresh${RESET}`);
2063
- return lines.join("\n");
2064
- }
2065
- function pad(s, n) {
2066
- if (s.length >= n) return s.slice(0, n);
2067
- return s + " ".repeat(n - s.length);
2068
- }
2069
2031
 
2070
2032
  // src/commands/web.ts
2071
2033
  import { Hono } from "hono";
@@ -3408,9 +3370,250 @@ function formatDur(ms) {
3408
3370
  return `${h}h ${m}m`;
3409
3371
  }
3410
3372
 
3373
+ // src/commands/template.ts
3374
+ import chalk8 from "chalk";
3375
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
3376
+ import { join as join12 } from "path";
3377
+ import { homedir as homedir10 } from "os";
3378
+ import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
3379
+ var CONFIG_DIR2 = join12(homedir10(), ".copilot-agent");
3380
+ var TEMPLATES_FILE = join12(CONFIG_DIR2, "templates.yaml");
3381
+ function ensureDir3() {
3382
+ if (!existsSync10(CONFIG_DIR2)) mkdirSync7(CONFIG_DIR2, { recursive: true });
3383
+ }
3384
+ function loadTemplates() {
3385
+ if (!existsSync10(TEMPLATES_FILE)) return [];
3386
+ try {
3387
+ const data = parseYaml3(readFileSync7(TEMPLATES_FILE, "utf-8"));
3388
+ return Array.isArray(data) ? data : [];
3389
+ } catch {
3390
+ return [];
3391
+ }
3392
+ }
3393
+ function saveTemplates(templates) {
3394
+ ensureDir3();
3395
+ writeFileSync5(TEMPLATES_FILE, stringifyYaml2(templates), "utf-8");
3396
+ }
3397
+ function registerTemplateCommand(program2) {
3398
+ const cmd = program2.command("template").description("Manage custom task templates");
3399
+ cmd.command("list").description("Show all custom templates").action(() => {
3400
+ const templates = loadTemplates();
3401
+ console.log(chalk8.bold.cyan("\n \u{1F4CB} Task Templates") + chalk8.dim(` (${templates.length})
3402
+ `));
3403
+ if (templates.length === 0) {
3404
+ console.log(chalk8.dim(" No custom templates"));
3405
+ console.log(chalk8.dim('\n Add one: copilot-agent template add <name> --prompt "..."'));
3406
+ console.log();
3407
+ return;
3408
+ }
3409
+ for (const t of templates) {
3410
+ const prio = t.priority !== void 0 ? chalk8.dim(` [priority: ${t.priority}]`) : "";
3411
+ console.log(` ${chalk8.green("\u25CF")} ${chalk8.bold(t.name)}${prio}`);
3412
+ console.log(chalk8.dim(` ${t.prompt.slice(0, 80)}${t.prompt.length > 80 ? "\u2026" : ""}`));
3413
+ }
3414
+ console.log();
3415
+ });
3416
+ cmd.command("add <name>").description("Add a custom template").requiredOption("-p, --prompt <text>", "Task prompt").option("--priority <n>", "Priority (lower = higher priority)").action((name, opts) => {
3417
+ const templates = loadTemplates();
3418
+ const existing = templates.findIndex((t) => t.name === name);
3419
+ const entry = {
3420
+ name,
3421
+ prompt: opts.prompt,
3422
+ priority: opts.priority ? parseInt(opts.priority, 10) : void 0
3423
+ };
3424
+ if (existing >= 0) {
3425
+ templates[existing] = entry;
3426
+ console.log(chalk8.yellow(` \u2714 Updated template: ${chalk8.bold(name)}`));
3427
+ } else {
3428
+ templates.push(entry);
3429
+ console.log(chalk8.green(` \u2714 Added template: ${chalk8.bold(name)}`));
3430
+ }
3431
+ saveTemplates(templates);
3432
+ });
3433
+ cmd.command("remove <name>").description("Remove a custom template").action((name) => {
3434
+ const templates = loadTemplates();
3435
+ const idx = templates.findIndex((t) => t.name === name);
3436
+ if (idx < 0) {
3437
+ console.log(chalk8.red(` \u2717 Template not found: ${name}`));
3438
+ return;
3439
+ }
3440
+ templates.splice(idx, 1);
3441
+ saveTemplates(templates);
3442
+ console.log(chalk8.green(` \u2714 Removed template: ${chalk8.bold(name)}`));
3443
+ });
3444
+ cmd.command("export").description("Export templates to stdout (YAML)").action(() => {
3445
+ const templates = loadTemplates();
3446
+ if (templates.length === 0) {
3447
+ console.log(chalk8.dim(" No templates to export"));
3448
+ return;
3449
+ }
3450
+ console.log(stringifyYaml2(templates));
3451
+ });
3452
+ cmd.command("import <file>").description("Import templates from a YAML file").action((file) => {
3453
+ try {
3454
+ const data = parseYaml3(readFileSync7(file, "utf-8"));
3455
+ if (!Array.isArray(data)) {
3456
+ console.log(chalk8.red(" \u2717 Invalid format: expected YAML array"));
3457
+ return;
3458
+ }
3459
+ const current = loadTemplates();
3460
+ let added = 0;
3461
+ for (const entry of data) {
3462
+ if (!entry.name || !entry.prompt) continue;
3463
+ const idx = current.findIndex((t) => t.name === entry.name);
3464
+ if (idx >= 0) {
3465
+ current[idx] = entry;
3466
+ } else {
3467
+ current.push(entry);
3468
+ added++;
3469
+ }
3470
+ }
3471
+ saveTemplates(current);
3472
+ console.log(chalk8.green(` \u2714 Imported ${added} new templates (${data.length} total processed)`));
3473
+ } catch (err) {
3474
+ const msg = err instanceof Error ? err.message : String(err);
3475
+ console.log(chalk8.red(` \u2717 ${msg}`));
3476
+ }
3477
+ });
3478
+ }
3479
+
3480
+ // src/commands/log.ts
3481
+ import chalk9 from "chalk";
3482
+ import { writeFileSync as writeFileSync6 } from "fs";
3483
+ function registerLogCommand(program2) {
3484
+ const cmd = program2.command("log").description("Search, browse, and export session history");
3485
+ cmd.command("search <query>").description("Search across all sessions").option("-a, --agent <type>", "Filter: copilot | claude").option("-l, --limit <n>", "Max results", "20").option("--after <date>", "After date (YYYY-MM-DD)").option("--before <date>", "Before date (YYYY-MM-DD)").action((query, opts) => {
3486
+ const limit = parseInt(opts.limit, 10);
3487
+ const sessions = listAllSessions(200);
3488
+ const q = query.toLowerCase();
3489
+ let filtered = sessions;
3490
+ if (opts.agent) filtered = filtered.filter((s) => s.agent === opts.agent);
3491
+ if (opts.after) {
3492
+ const after = new Date(opts.after).getTime();
3493
+ filtered = filtered.filter((s) => s.mtime >= after);
3494
+ }
3495
+ if (opts.before) {
3496
+ const before = new Date(opts.before).getTime();
3497
+ filtered = filtered.filter((s) => s.mtime <= before);
3498
+ }
3499
+ const matches = filtered.filter((s) => {
3500
+ if (s.summary?.toLowerCase().includes(q)) return true;
3501
+ if (s.cwd?.toLowerCase().includes(q)) return true;
3502
+ if (s.lastEvent?.toLowerCase().includes(q)) return true;
3503
+ const report = getAgentSessionReport(s.id, s.agent);
3504
+ if (!report) return false;
3505
+ if (report.gitCommits.some((c) => c.toLowerCase().includes(q))) return true;
3506
+ if (report.filesCreated.some((f) => f.toLowerCase().includes(q))) return true;
3507
+ if (report.filesEdited.some((f) => f.toLowerCase().includes(q))) return true;
3508
+ if (report.taskCompletions.some((t) => t.toLowerCase().includes(q))) return true;
3509
+ return false;
3510
+ }).slice(0, limit);
3511
+ console.log(chalk9.bold.cyan(`
3512
+ \u{1F50D} Search: "${query}"`) + chalk9.dim(` (${matches.length} results)
3513
+ `));
3514
+ if (matches.length === 0) {
3515
+ console.log(chalk9.dim(" No matches found"));
3516
+ return;
3517
+ }
3518
+ for (const s of matches) {
3519
+ const agentTag = s.agent === "claude" ? chalk9.yellow("claude ") : chalk9.cyan("copilot");
3520
+ const icon = s.complete ? chalk9.green("\u2714") : chalk9.yellow("\u23F3");
3521
+ const date = new Date(s.mtime).toLocaleDateString("en-CA");
3522
+ const time = new Date(s.mtime).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
3523
+ const project = s.cwd?.split("/").pop() || "\u2014";
3524
+ const summary = (s.summary || "\u2014").slice(0, 50);
3525
+ console.log(` ${icon} ${agentTag} ${chalk9.dim(date + " " + time)} ${chalk9.bold(project.padEnd(16))} ${summary}`);
3526
+ console.log(chalk9.dim(` ${s.id}`));
3527
+ }
3528
+ console.log();
3529
+ });
3530
+ cmd.command("timeline [session-id]").description("Show chronological timeline of a session").option("-a, --agent <type>", "Agent type").action((sessionId, opts) => {
3531
+ if (!sessionId) {
3532
+ const sessions = listAllSessions(1);
3533
+ if (sessions.length === 0) {
3534
+ console.log(chalk9.dim(" No sessions"));
3535
+ return;
3536
+ }
3537
+ sessionId = sessions[0].id;
3538
+ opts.agent = opts.agent || sessions[0].agent;
3539
+ console.log(chalk9.dim(` Using latest: ${sessionId.slice(0, 12)}\u2026
3540
+ `));
3541
+ }
3542
+ const report = getAgentSessionReport(sessionId, opts.agent);
3543
+ if (!report) {
3544
+ console.log(chalk9.red(` \u2717 Session not found`));
3545
+ return;
3546
+ }
3547
+ const project = report.cwd?.split("/").pop() || "unknown";
3548
+ const agentTag = report.agent === "claude" ? chalk9.yellow("[claude]") : chalk9.cyan("[copilot]");
3549
+ console.log(chalk9.bold.cyan(` \u{1F4C5} Timeline \u2014 ${project}`) + ` ${agentTag}
3550
+ `);
3551
+ const entries = [];
3552
+ if (report.startTime) entries.push(`${chalk9.dim(fmtTime2(report.startTime))} ${chalk9.green("\u25B6")} Session started`);
3553
+ for (const f of report.filesCreated) entries.push(`${chalk9.dim(" ")} ${chalk9.green("+")} Created ${f}`);
3554
+ for (const f of report.filesEdited) entries.push(`${chalk9.dim(" ")} ${chalk9.yellow("~")} Edited ${f}`);
3555
+ for (const c of report.gitCommits) entries.push(`${chalk9.dim(" ")} ${chalk9.cyan("\u25CF")} Commit: ${c.split("\n")[0].slice(0, 60)}`);
3556
+ for (const t of report.taskCompletions) entries.push(`${chalk9.dim(" ")} ${chalk9.green("\u2714")} ${t.split("\n")[0].slice(0, 60)}`);
3557
+ for (const e of report.errors.slice(0, 5)) entries.push(`${chalk9.dim(" ")} ${chalk9.red("\u2717")} ${e.split("\n")[0].slice(0, 60)}`);
3558
+ const statusIcon3 = report.complete ? chalk9.green("\u25A0") : chalk9.yellow("\u23F8");
3559
+ if (report.endTime) entries.push(`${chalk9.dim(fmtTime2(report.endTime))} ${statusIcon3} Session ${report.complete ? "completed" : "stopped"}`);
3560
+ for (const e of entries) console.log(` ${e}`);
3561
+ console.log(chalk9.dim(`
3562
+ Duration: ${fmtDur2(report.durationMs)} | Turns: ${report.assistantTurns} | Premium: \u2B21${report.premiumRequests}
3563
+ `));
3564
+ });
3565
+ cmd.command("export").description("Export session history").option("-f, --format <fmt>", "Format: json | csv", "json").option("-l, --limit <n>", "Max sessions", "50").option("-o, --output <file>", "Output file").action((opts) => {
3566
+ const sessions = listAllSessions(parseInt(opts.limit, 10));
3567
+ const data = sessions.map((s) => {
3568
+ const r = getAgentSessionReport(s.id, s.agent);
3569
+ return {
3570
+ id: s.id,
3571
+ agent: s.agent,
3572
+ project: s.cwd?.split("/").pop() || "",
3573
+ complete: s.complete,
3574
+ premium: s.premiumRequests,
3575
+ tokens: r?.outputTokens || 0,
3576
+ turns: r?.assistantTurns || 0,
3577
+ commits: r?.gitCommits.length || 0,
3578
+ filesCreated: r?.filesCreated.length || 0,
3579
+ filesEdited: r?.filesEdited.length || 0,
3580
+ durationMs: r?.durationMs || 0,
3581
+ summary: s.summary || "",
3582
+ date: new Date(s.mtime).toISOString()
3583
+ };
3584
+ });
3585
+ let output;
3586
+ if (opts.format === "csv") {
3587
+ const headers = Object.keys(data[0] || {}).join(",");
3588
+ const rows = data.map((d) => Object.values(d).map((v) => `"${String(v).replace(/"/g, '""')}"`).join(","));
3589
+ output = [headers, ...rows].join("\n");
3590
+ } else {
3591
+ output = JSON.stringify(data, null, 2);
3592
+ }
3593
+ if (opts.output) {
3594
+ writeFileSync6(opts.output, output, "utf-8");
3595
+ console.log(chalk9.green(` \u2714 Exported ${data.length} sessions to ${opts.output}`));
3596
+ } else {
3597
+ console.log(output);
3598
+ }
3599
+ });
3600
+ }
3601
+ function fmtTime2(iso) {
3602
+ try {
3603
+ return new Date(iso).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
3604
+ } catch {
3605
+ return " ";
3606
+ }
3607
+ }
3608
+ function fmtDur2(ms) {
3609
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
3610
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
3611
+ return `${Math.floor(ms / 36e5)}h ${Math.round(ms % 36e5 / 6e4)}m`;
3612
+ }
3613
+
3411
3614
  // src/index.ts
3412
3615
  var program = new Command();
3413
- program.name("copilot-agent").version("0.11.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
3616
+ program.name("copilot-agent").version("0.12.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
3414
3617
  registerStatusCommand(program);
3415
3618
  registerWatchCommand(program);
3416
3619
  registerRunCommand(program);
@@ -3426,5 +3629,7 @@ registerQuotaCommand(program);
3426
3629
  registerCompactCommand(program);
3427
3630
  registerHooksCommand(program);
3428
3631
  registerPrCommand(program);
3632
+ registerLogCommand(program);
3633
+ registerTemplateCommand(program);
3429
3634
  program.parse();
3430
3635
  //# sourceMappingURL=index.js.map