codealmanac 0.2.0 → 0.2.2

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.
@@ -33,10 +33,10 @@ import {
33
33
  titleCase,
34
34
  toKebabCase,
35
35
  writeTopicsFile
36
- } from "./chunk-3C5SY5SE.js";
36
+ } from "./chunk-KQUVMF27.js";
37
37
  import {
38
38
  runDoctor
39
- } from "./chunk-D3B2EEHL.js";
39
+ } from "./chunk-B2AGSRXL.js";
40
40
  import "./chunk-V3QOQSXI.js";
41
41
  import "./chunk-4CODZRHH.js";
42
42
  import {
@@ -1905,6 +1905,7 @@ async function loadPrompt(name) {
1905
1905
  // src/agent/sdk.ts
1906
1906
  import { spawn } from "child_process";
1907
1907
  import { query } from "@anthropic-ai/claude-agent-sdk";
1908
+ var DEFAULT_AGENT_MODEL = "claude-sonnet-4-6";
1908
1909
  async function runAgent(opts) {
1909
1910
  const provider = opts.provider ?? "claude";
1910
1911
  if (provider === "codex") {
@@ -1924,7 +1925,7 @@ async function runClaudeAgent(opts) {
1924
1925
  allowedTools: opts.allowedTools,
1925
1926
  agents: opts.agents ?? {},
1926
1927
  cwd: opts.cwd,
1927
- model: opts.model ?? "claude-sonnet-4-6",
1928
+ model: opts.model ?? DEFAULT_AGENT_MODEL,
1928
1929
  maxTurns: opts.maxTurns ?? 100,
1929
1930
  ...claudeExecutable !== void 0 ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
1930
1931
  env: {
@@ -2043,6 +2044,7 @@ function runJsonlCli(opts) {
2043
2044
  let turns = 0;
2044
2045
  let result = "";
2045
2046
  let sessionId;
2047
+ let usage;
2046
2048
  let success = false;
2047
2049
  let finalSeen = false;
2048
2050
  let error;
@@ -2051,6 +2053,9 @@ function runJsonlCli(opts) {
2051
2053
  if (sessionId === void 0 && typeof msg.session_id === "string" && msg.session_id.length > 0) {
2052
2054
  sessionId = msg.session_id;
2053
2055
  }
2056
+ if (sessionId === void 0 && typeof msg.thread_id === "string" && msg.thread_id.length > 0) {
2057
+ sessionId = msg.thread_id;
2058
+ }
2054
2059
  const final = opts.parseFinal(msg);
2055
2060
  if (final === null) return;
2056
2061
  finalSeen = true;
@@ -2058,6 +2063,7 @@ function runJsonlCli(opts) {
2058
2063
  if (final.turns !== void 0) turns = final.turns;
2059
2064
  if (final.result !== void 0) result = final.result;
2060
2065
  if (final.sessionId !== void 0) sessionId = final.sessionId;
2066
+ if (final.usage !== void 0) usage = final.usage;
2061
2067
  if (final.success !== void 0) success = final.success;
2062
2068
  if (final.error !== void 0) error = final.error;
2063
2069
  };
@@ -2091,6 +2097,7 @@ function runJsonlCli(opts) {
2091
2097
  turns,
2092
2098
  result,
2093
2099
  sessionId,
2100
+ usage,
2094
2101
  error: err.code === "ENOENT" ? `${opts.command} not found on PATH` : err.message
2095
2102
  });
2096
2103
  });
@@ -2103,7 +2110,7 @@ function runJsonlCli(opts) {
2103
2110
  }
2104
2111
  }
2105
2112
  if (code === 0 && finalSeen && success) {
2106
- resolve({ success, cost, turns, result, sessionId });
2113
+ resolve({ success, cost, turns, result, sessionId, usage });
2107
2114
  return;
2108
2115
  }
2109
2116
  const firstStderr = stderr.trim().split("\n")[0];
@@ -2113,6 +2120,7 @@ function runJsonlCli(opts) {
2113
2120
  turns,
2114
2121
  result,
2115
2122
  sessionId,
2123
+ usage,
2116
2124
  error: error ?? (firstStderr !== void 0 && firstStderr.length > 0 ? firstStderr : `${opts.command} exited ${code ?? 1}`)
2117
2125
  });
2118
2126
  });
@@ -2130,7 +2138,7 @@ function parseCodexFinal(msg) {
2130
2138
  return null;
2131
2139
  }
2132
2140
  if (msg.type === "turn.completed") {
2133
- return { success: true };
2141
+ return { success: true, turns: 1, usage: parseUsage(msg.usage) };
2134
2142
  }
2135
2143
  if (msg.type === "turn.failed" || msg.type === "error") {
2136
2144
  return {
@@ -2140,13 +2148,29 @@ function parseCodexFinal(msg) {
2140
2148
  }
2141
2149
  return null;
2142
2150
  }
2151
+ function parseUsage(value) {
2152
+ if (value === null || typeof value !== "object") return void 0;
2153
+ const obj = value;
2154
+ return {
2155
+ inputTokens: numberField(obj, "input_tokens") ?? numberField(obj, "inputTokens"),
2156
+ cachedInputTokens: numberField(obj, "cached_input_tokens") ?? numberField(obj, "cachedInputTokens") ?? numberField(obj, "cacheReadTokens"),
2157
+ outputTokens: numberField(obj, "output_tokens") ?? numberField(obj, "outputTokens"),
2158
+ reasoningOutputTokens: numberField(obj, "reasoning_output_tokens") ?? numberField(obj, "reasoningOutputTokens")
2159
+ };
2160
+ }
2161
+ function numberField(input, key) {
2162
+ const value = input[key];
2163
+ return typeof value === "number" ? value : void 0;
2164
+ }
2143
2165
  function parseCursorFinal(msg) {
2144
2166
  if (msg.type !== "result") return null;
2145
2167
  const isError = msg.is_error === true || msg.subtype !== "success";
2146
2168
  return {
2147
2169
  success: !isError,
2170
+ turns: 1,
2148
2171
  result: typeof msg.result === "string" ? msg.result : "",
2149
2172
  sessionId: typeof msg.session_id === "string" ? msg.session_id : void 0,
2173
+ usage: parseUsage(msg.usage),
2150
2174
  error: isError ? typeof msg.result === "string" ? msg.result : `cursor result: ${String(msg.subtype ?? "error")}` : void 0
2151
2175
  };
2152
2176
  }
@@ -2415,8 +2439,39 @@ async function resolveAgentSelection(args) {
2415
2439
  function formatFinalLine(result, logPath, repoRoot) {
2416
2440
  const status = result.success ? "done" : "failed";
2417
2441
  const rel = relative(repoRoot, logPath);
2418
- const cost = `$${result.cost.toFixed(3)}`;
2419
- return `[${status}] cost: ${cost}, turns: ${result.turns} (transcript: ${rel})`;
2442
+ const usage = formatRunUsage(result);
2443
+ return `[${status}] ${usage} (transcript: ${rel})`;
2444
+ }
2445
+ function formatRunUsage(result) {
2446
+ const parts = [];
2447
+ if (result.cost > 0 || result.usage === void 0) {
2448
+ parts.push(`cost: $${result.cost.toFixed(3)}`);
2449
+ }
2450
+ parts.push(`turns: ${result.turns}`);
2451
+ const usage = result.usage;
2452
+ if (usage !== void 0) {
2453
+ const tokenParts = [];
2454
+ if (usage.inputTokens !== void 0) {
2455
+ tokenParts.push(`${usage.inputTokens.toLocaleString("en-US")} in`);
2456
+ }
2457
+ if (usage.outputTokens !== void 0) {
2458
+ tokenParts.push(`${usage.outputTokens.toLocaleString("en-US")} out`);
2459
+ }
2460
+ if (usage.cachedInputTokens !== void 0) {
2461
+ tokenParts.push(
2462
+ `${usage.cachedInputTokens.toLocaleString("en-US")} cached`
2463
+ );
2464
+ }
2465
+ if (usage.reasoningOutputTokens !== void 0) {
2466
+ tokenParts.push(
2467
+ `${usage.reasoningOutputTokens.toLocaleString("en-US")} reasoning`
2468
+ );
2469
+ }
2470
+ if (tokenParts.length > 0) {
2471
+ parts.push(`tokens: ${tokenParts.join(", ")}`);
2472
+ }
2473
+ }
2474
+ return parts.join(", ");
2420
2475
  }
2421
2476
  async function countMarkdownPages(pagesDir) {
2422
2477
  try {
@@ -2473,14 +2528,16 @@ var StreamingFormatter = class {
2473
2528
  }
2474
2529
  return;
2475
2530
  }
2476
- if (msg.type === "result") {
2477
- const status = msg.subtype === "success" ? "done" : `failed (${msg.subtype})`;
2478
- const cost = typeof msg.total_cost_usd === "number" ? msg.total_cost_usd : 0;
2479
- const turns = typeof msg.num_turns === "number" ? msg.num_turns : 0;
2480
- this.sink.write(
2481
- `[${status}] cost: $${cost.toFixed(3)}, turns: ${turns}
2482
- `
2483
- );
2531
+ if (msg.type === "item.started" && isRecord(msg.item)) {
2532
+ this.handleCodexItemStarted(msg.item);
2533
+ return;
2534
+ }
2535
+ if (msg.type === "item.completed" && isRecord(msg.item)) {
2536
+ this.handleCodexItemCompleted(msg.item);
2537
+ return;
2538
+ }
2539
+ if (msg.type === "tool_call") {
2540
+ this.handleCursorToolCall(msg);
2484
2541
  return;
2485
2542
  }
2486
2543
  }
@@ -2495,6 +2552,33 @@ var StreamingFormatter = class {
2495
2552
  }
2496
2553
  const summary = formatToolSummary(name, input);
2497
2554
  this.sink.write(`[${this.currentAgent}] ${summary}
2555
+ `);
2556
+ }
2557
+ handleCodexItemStarted(item) {
2558
+ if (item.type !== "command_execution") return;
2559
+ const command = stringField(item, "command") ?? "?";
2560
+ this.sink.write(`[${this.currentAgent}] bash ${truncate(command, 80)}
2561
+ `);
2562
+ }
2563
+ handleCodexItemCompleted(item) {
2564
+ if (item.type !== "file_change") return;
2565
+ const changes = Array.isArray(item.changes) ? item.changes : [];
2566
+ for (const change of changes) {
2567
+ if (!isRecord(change)) continue;
2568
+ const rawPath = stringField(change, "path") ?? "?";
2569
+ const kind = stringField(change, "kind") ?? "edit";
2570
+ const verb = kind === "add" ? "writing" : kind === "delete" ? "deleting" : "editing";
2571
+ this.sink.write(
2572
+ `[${this.currentAgent}] ${verb} ${formatCodexPath(rawPath)}
2573
+ `
2574
+ );
2575
+ }
2576
+ }
2577
+ handleCursorToolCall(msg) {
2578
+ if (msg.subtype !== "started" || !isRecord(msg.tool_call)) return;
2579
+ const summary = formatCursorToolSummary(msg.tool_call);
2580
+ if (summary === void 0) return;
2581
+ this.sink.write(`[${this.currentAgent}] ${summary}
2498
2582
  `);
2499
2583
  }
2500
2584
  };
@@ -2538,18 +2622,57 @@ function formatToolSummary(name, input) {
2538
2622
  }
2539
2623
  case "Bash": {
2540
2624
  const command = stringField(input, "command") ?? "?";
2541
- const trimmed = command.length > 80 ? `${command.slice(0, 77)}...` : command;
2542
- return `bash ${trimmed}`;
2625
+ return `bash ${truncate(command, 80)}`;
2543
2626
  }
2544
2627
  default: {
2545
2628
  return name;
2546
2629
  }
2547
2630
  }
2548
2631
  }
2632
+ function formatCursorToolSummary(toolCall) {
2633
+ if (isRecord(toolCall.readToolCall)) {
2634
+ const args = recordField(toolCall.readToolCall, "args");
2635
+ return `reading ${stringField(args, "path") ?? "?"}`;
2636
+ }
2637
+ if (isRecord(toolCall.editToolCall)) {
2638
+ const args = recordField(toolCall.editToolCall, "args");
2639
+ return `editing ${formatCodexPath(stringField(args, "path") ?? "?")}`;
2640
+ }
2641
+ if (isRecord(toolCall.globToolCall)) {
2642
+ const args = recordField(toolCall.globToolCall, "args");
2643
+ return `glob ${stringField(args, "globPattern") ?? "?"}`;
2644
+ }
2645
+ if (isRecord(toolCall.grepToolCall)) {
2646
+ const args = recordField(toolCall.grepToolCall, "args");
2647
+ return `grep ${stringField(args, "pattern") ?? "?"}`;
2648
+ }
2649
+ if (isRecord(toolCall.shellToolCall)) {
2650
+ const args = recordField(toolCall.shellToolCall, "args");
2651
+ const description = stringField(toolCall.shellToolCall, "description");
2652
+ const command = stringField(args, "command") ?? description ?? "?";
2653
+ return `bash ${truncate(command, 80)}`;
2654
+ }
2655
+ return void 0;
2656
+ }
2657
+ function truncate(value, max) {
2658
+ return value.length > max ? `${value.slice(0, max - 3)}...` : value;
2659
+ }
2660
+ function formatCodexPath(value) {
2661
+ const marker = "/.almanac/";
2662
+ const idx = value.indexOf(marker);
2663
+ if (idx !== -1) {
2664
+ return `.almanac/${value.slice(idx + marker.length)}`;
2665
+ }
2666
+ return value;
2667
+ }
2549
2668
  function stringField(input, key) {
2550
2669
  const value = input[key];
2551
2670
  return typeof value === "string" ? value : void 0;
2552
2671
  }
2672
+ function recordField(input, key) {
2673
+ const value = input[key];
2674
+ return isRecord(value) ? value : {};
2675
+ }
2553
2676
  function isRecord(value) {
2554
2677
  return value !== null && typeof value === "object";
2555
2678
  }
@@ -2558,12 +2681,219 @@ function isRecord(value) {
2558
2681
  import { createHash } from "crypto";
2559
2682
  import {
2560
2683
  createWriteStream as createWriteStream2,
2561
- existsSync as existsSync6,
2684
+ existsSync as existsSync7,
2562
2685
  statSync
2563
2686
  } from "fs";
2564
- import { mkdir as mkdir3, readFile as readFile6, readdir as readdir2, stat } from "fs/promises";
2687
+ import { mkdir as mkdir4, readFile as readFile7, readdir as readdir3, stat } from "fs/promises";
2565
2688
  import { homedir } from "os";
2566
- import { basename as basename3, join as join7, relative as relative2 } from "path";
2689
+ import { basename as basename3, join as join8, relative as relative3 } from "path";
2690
+
2691
+ // src/commands/captureStatus.ts
2692
+ import { existsSync as existsSync6 } from "fs";
2693
+ import { mkdir as mkdir3, readFile as readFile6, readdir as readdir2, rename as rename2, writeFile as writeFile3 } from "fs/promises";
2694
+ import { dirname, join as join7, relative as relative2 } from "path";
2695
+ function captureStatePath(dir, stem) {
2696
+ return join7(dir, `.capture-${stem}.state.json`);
2697
+ }
2698
+ async function writeCaptureRunRecord(path2, record) {
2699
+ await mkdir3(dirname(path2), { recursive: true });
2700
+ const tmp = `${path2}.tmp-${process.pid}`;
2701
+ await writeFile3(tmp, `${JSON.stringify(record, null, 2)}
2702
+ `, "utf8");
2703
+ await rename2(tmp, path2);
2704
+ }
2705
+ function buildStartedCaptureRecord(args) {
2706
+ return {
2707
+ version: 1,
2708
+ kind: "capture",
2709
+ status: "running",
2710
+ sessionId: args.sessionId ?? args.stem,
2711
+ repoRoot: args.repoRoot,
2712
+ pid: process.pid,
2713
+ model: args.model ?? DEFAULT_AGENT_MODEL,
2714
+ transcriptPath: args.transcriptPath,
2715
+ startedAt: args.startedAt.toISOString(),
2716
+ logPath: join7(args.almanacDir, `.capture-${args.stem}.log`),
2717
+ jsonlPath: join7(args.almanacDir, `.capture-${args.stem}.jsonl`)
2718
+ };
2719
+ }
2720
+ function finishCaptureRecord(args) {
2721
+ const started = Date.parse(args.record.startedAt);
2722
+ const finished = args.finishedAt.getTime();
2723
+ return {
2724
+ ...args.record,
2725
+ status: args.status,
2726
+ finishedAt: args.finishedAt.toISOString(),
2727
+ durationMs: Number.isFinite(started) ? Math.max(0, finished - started) : void 0,
2728
+ summary: args.summary,
2729
+ error: args.error
2730
+ };
2731
+ }
2732
+ async function runCaptureStatus(options) {
2733
+ const repoRoot = findNearestAlmanacDir(options.cwd);
2734
+ if (repoRoot === null) {
2735
+ return {
2736
+ stdout: "",
2737
+ stderr: "almanac: no .almanac/ found in this directory or any parent. Run 'almanac bootstrap' first.\n",
2738
+ exitCode: 1
2739
+ };
2740
+ }
2741
+ const almanacDir = getRepoAlmanacDir(repoRoot);
2742
+ const records = await readCaptureRecords(almanacDir);
2743
+ const now = options.now?.() ?? /* @__PURE__ */ new Date();
2744
+ const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
2745
+ const views = records.map((record) => toView(record, repoRoot, now, isPidAlive)).sort((a, b) => b.sortTime - a.sortTime);
2746
+ if (options.json === true) {
2747
+ return {
2748
+ stdout: `${JSON.stringify(
2749
+ {
2750
+ repo: repoRoot,
2751
+ captures: views.map(({ sortTime: _sortTime, ...v }) => v)
2752
+ },
2753
+ null,
2754
+ 2
2755
+ )}
2756
+ `,
2757
+ stderr: "",
2758
+ exitCode: 0
2759
+ };
2760
+ }
2761
+ return {
2762
+ stdout: formatCaptureStatus(views),
2763
+ stderr: "",
2764
+ exitCode: 0
2765
+ };
2766
+ }
2767
+ async function readCaptureRecords(almanacDir) {
2768
+ if (!existsSync6(almanacDir)) return [];
2769
+ const out = [];
2770
+ const dirs = [join7(almanacDir, "logs"), almanacDir];
2771
+ for (const dir of dirs) {
2772
+ let entries;
2773
+ try {
2774
+ entries = await readdir2(dir);
2775
+ } catch {
2776
+ continue;
2777
+ }
2778
+ for (const entry of entries) {
2779
+ if (!entry.startsWith(".capture-") || !entry.endsWith(".state.json")) {
2780
+ continue;
2781
+ }
2782
+ try {
2783
+ const parsed = JSON.parse(await readFile6(join7(dir, entry), "utf8"));
2784
+ if (isCaptureRunRecord(parsed)) out.push(parsed);
2785
+ } catch {
2786
+ continue;
2787
+ }
2788
+ }
2789
+ }
2790
+ return out;
2791
+ }
2792
+ function isCaptureRunRecord(value) {
2793
+ if (value === null || typeof value !== "object") return false;
2794
+ const v = value;
2795
+ return v.version === 1 && v.kind === "capture" && (v.status === "running" || v.status === "done" || v.status === "failed") && typeof v.sessionId === "string" && typeof v.repoRoot === "string" && typeof v.pid === "number" && typeof v.model === "string" && typeof v.transcriptPath === "string" && typeof v.startedAt === "string" && typeof v.logPath === "string" && typeof v.jsonlPath === "string";
2796
+ }
2797
+ function toView(record, repoRoot, now, isPidAlive) {
2798
+ const started = Date.parse(record.startedAt);
2799
+ const finished = record.finishedAt !== void 0 ? Date.parse(record.finishedAt) : void 0;
2800
+ const elapsedMs = record.durationMs ?? (Number.isFinite(started) ? Math.max(0, (finished ?? now.getTime()) - started) : 0);
2801
+ const status = record.status === "running" && !isPidAlive(record.pid) ? "stale" : record.status;
2802
+ return {
2803
+ status,
2804
+ sessionId: record.sessionId,
2805
+ model: record.model,
2806
+ elapsedMs,
2807
+ startedAt: record.startedAt,
2808
+ finishedAt: record.finishedAt,
2809
+ pid: record.pid,
2810
+ logPath: relative2(repoRoot, record.logPath),
2811
+ jsonlPath: relative2(repoRoot, record.jsonlPath),
2812
+ summary: record.summary,
2813
+ error: status === "stale" ? "process ended without a final status" : record.error,
2814
+ sortTime: finished ?? (Number.isFinite(started) ? started : 0)
2815
+ };
2816
+ }
2817
+ function formatCaptureStatus(views) {
2818
+ const lines = ["Capture jobs", ""];
2819
+ if (views.length === 0) {
2820
+ lines.push("No capture jobs found.");
2821
+ return `${lines.join("\n")}
2822
+ `;
2823
+ }
2824
+ const active = views.filter((v) => v.status === "running" || v.status === "stale");
2825
+ const finished = views.filter((v) => v.status === "done" || v.status === "failed");
2826
+ if (active.length === 0) {
2827
+ lines.push("No active captures.", "");
2828
+ } else {
2829
+ for (const view of active) {
2830
+ lines.push(formatRow(view));
2831
+ lines.push(` log: ${view.logPath}`);
2832
+ if (view.error !== void 0) lines.push(` error: ${view.error}`);
2833
+ lines.push("");
2834
+ }
2835
+ }
2836
+ if (finished.length > 0) {
2837
+ lines.push(active.length === 0 ? "Last capture:" : "Last finished:");
2838
+ for (const view of finished.slice(0, 3)) {
2839
+ lines.push(formatRow(view));
2840
+ if (view.status === "failed") {
2841
+ lines.push(` log: ${view.logPath}`);
2842
+ if (view.error !== void 0) lines.push(` error: ${view.error}`);
2843
+ }
2844
+ }
2845
+ }
2846
+ return `${trimTrailingBlank(lines).join("\n")}
2847
+ `;
2848
+ }
2849
+ function formatRow(view) {
2850
+ const status = view.status.padEnd(7, " ");
2851
+ const session = view.sessionId.padEnd(12, " ");
2852
+ const model = view.model.padEnd(17, " ");
2853
+ const elapsed = formatDuration(view.elapsedMs);
2854
+ const summary = formatSummary(view);
2855
+ return `${status} ${session} ${model} ${elapsed}${summary.length > 0 ? ` ${summary}` : ""}`;
2856
+ }
2857
+ function formatSummary(view) {
2858
+ if (view.status === "failed") return "failed; see log";
2859
+ if (view.summary === void 0) return "";
2860
+ const parts = [];
2861
+ if (view.summary.updated > 0) {
2862
+ parts.push(`${view.summary.updated} updated`);
2863
+ }
2864
+ if (view.summary.created > 0) {
2865
+ parts.push(`${view.summary.created} created`);
2866
+ }
2867
+ if (view.summary.archived > 0) {
2868
+ parts.push(`${view.summary.archived} archived`);
2869
+ }
2870
+ return parts.length > 0 ? parts.join(", ") : "0 pages written";
2871
+ }
2872
+ function formatDuration(ms) {
2873
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
2874
+ const minutes = Math.floor(totalSeconds / 60);
2875
+ const seconds = totalSeconds % 60;
2876
+ if (minutes < 60) return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
2877
+ const hours = Math.floor(minutes / 60);
2878
+ const restMinutes = minutes % 60;
2879
+ return `${hours}h${restMinutes.toString().padStart(2, "0")}m`;
2880
+ }
2881
+ function trimTrailingBlank(lines) {
2882
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
2883
+ lines.pop();
2884
+ }
2885
+ return lines;
2886
+ }
2887
+ function defaultIsPidAlive(pid) {
2888
+ try {
2889
+ process.kill(pid, 0);
2890
+ return true;
2891
+ } catch {
2892
+ return false;
2893
+ }
2894
+ }
2895
+
2896
+ // src/commands/capture.ts
2567
2897
  var WRITER_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Agent"];
2568
2898
  var REVIEWER_TOOLS = ["Read", "Grep", "Glob", "Bash"];
2569
2899
  var REVIEWER_DESCRIPTION = "Reviews proposed wiki changes against the full knowledge base for cohesion, duplication, missing links, notability, and writing conventions.";
@@ -2601,7 +2931,7 @@ async function runCapture(options) {
2601
2931
  };
2602
2932
  }
2603
2933
  const almanacDir = getRepoAlmanacDir(repoRoot);
2604
- const pagesDir = join7(almanacDir, "pages");
2934
+ const pagesDir = join8(almanacDir, "pages");
2605
2935
  const transcriptResolution = await resolveTranscript({
2606
2936
  repoRoot,
2607
2937
  explicit: options.transcriptPath,
@@ -2627,12 +2957,24 @@ async function runCapture(options) {
2627
2957
  tools: REVIEWER_TOOLS
2628
2958
  }
2629
2959
  };
2630
- const now = options.now?.() ?? /* @__PURE__ */ new Date();
2631
- const logStem = options.sessionId !== void 0 && options.sessionId.length > 0 ? options.sessionId : formatTimestamp2(now);
2632
- const logsDir = join7(almanacDir, "logs");
2633
- await mkdir3(logsDir, { recursive: true });
2960
+ const startedAt = options.now?.() ?? /* @__PURE__ */ new Date();
2961
+ const logStem = options.sessionId !== void 0 && options.sessionId.length > 0 ? options.sessionId : formatTimestamp2(startedAt);
2962
+ const logsDir = join8(almanacDir, "logs");
2963
+ await mkdir4(logsDir, { recursive: true });
2634
2964
  const logName = `.capture-${logStem}.jsonl`;
2635
- const logPath = join7(logsDir, logName);
2965
+ const logPath = join8(logsDir, logName);
2966
+ const statePath = captureStatePath(logsDir, logStem);
2967
+ const stateRecord = buildStartedCaptureRecord({
2968
+ repoRoot,
2969
+ almanacDir: logsDir,
2970
+ stem: logStem,
2971
+ sessionId: options.sessionId,
2972
+ transcriptPath,
2973
+ model: options.model,
2974
+ startedAt
2975
+ });
2976
+ await writeCaptureRunRecord(statePath, stateRecord).catch(() => {
2977
+ });
2636
2978
  const logStream = createWriteStream2(logPath, { flags: "w" });
2637
2979
  const out = process.stdout;
2638
2980
  const formatter = new StreamingFormatter({
@@ -2674,16 +3016,43 @@ Working directory: ${repoRoot}.`;
2674
3016
  }
2675
3017
  const snapshotAfter = await snapshotPages(pagesDir);
2676
3018
  const delta = diffSnapshots(snapshotBefore, snapshotAfter);
3019
+ const finishedAt = options.now?.() ?? /* @__PURE__ */ new Date();
3020
+ const captureSummary = {
3021
+ ...delta,
3022
+ cost: result.cost,
3023
+ turns: result.turns
3024
+ };
2677
3025
  if (!result.success) {
3026
+ await writeCaptureRunRecord(
3027
+ statePath,
3028
+ finishCaptureRecord({
3029
+ record: stateRecord,
3030
+ status: "failed",
3031
+ finishedAt,
3032
+ summary: captureSummary,
3033
+ error: result.error ?? "unknown error"
3034
+ })
3035
+ ).catch(() => {
3036
+ });
2678
3037
  return {
2679
3038
  stdout: "",
2680
3039
  stderr: `almanac: capture failed: ${result.error ?? "unknown error"}
2681
- (transcript: ${relative2(repoRoot, logPath)})
3040
+ (transcript: ${relative3(repoRoot, logPath)})
2682
3041
  `,
2683
3042
  exitCode: 1
2684
3043
  };
2685
3044
  }
2686
- const summary = formatSummary(result, delta, logPath, repoRoot);
3045
+ await writeCaptureRunRecord(
3046
+ statePath,
3047
+ finishCaptureRecord({
3048
+ record: stateRecord,
3049
+ status: "done",
3050
+ finishedAt,
3051
+ summary: captureSummary
3052
+ })
3053
+ ).catch(() => {
3054
+ });
3055
+ const summary = formatSummary2(result, delta, logPath, repoRoot);
2687
3056
  return {
2688
3057
  stdout: `${summary}
2689
3058
  `,
@@ -2706,7 +3075,7 @@ async function resolveAgentSelection2(args) {
2706
3075
  }
2707
3076
  async function resolveTranscript(args) {
2708
3077
  if (args.explicit !== void 0 && args.explicit.length > 0) {
2709
- if (!existsSync6(args.explicit)) {
3078
+ if (!existsSync7(args.explicit)) {
2710
3079
  return {
2711
3080
  ok: false,
2712
3081
  error: `transcript not found: ${args.explicit}`
@@ -2714,8 +3083,8 @@ async function resolveTranscript(args) {
2714
3083
  }
2715
3084
  return { ok: true, path: args.explicit };
2716
3085
  }
2717
- const projectsDir = args.claudeProjectsDir ?? join7(homedir(), ".claude", "projects");
2718
- if (!existsSync6(projectsDir)) {
3086
+ const projectsDir = args.claudeProjectsDir ?? join8(homedir(), ".claude", "projects");
3087
+ if (!existsSync7(projectsDir)) {
2719
3088
  return {
2720
3089
  ok: false,
2721
3090
  error: `could not auto-resolve transcript; ${projectsDir} does not exist. Pass --session <id> or <transcript-path>.`
@@ -2747,21 +3116,21 @@ async function collectTranscripts(projectsDir) {
2747
3116
  const out = [];
2748
3117
  let topLevel;
2749
3118
  try {
2750
- topLevel = await readdir2(projectsDir);
3119
+ topLevel = await readdir3(projectsDir);
2751
3120
  } catch {
2752
3121
  return out;
2753
3122
  }
2754
3123
  for (const name of topLevel) {
2755
- const projectDir = join7(projectsDir, name);
3124
+ const projectDir = join8(projectsDir, name);
2756
3125
  let entries;
2757
3126
  try {
2758
- entries = await readdir2(projectDir);
3127
+ entries = await readdir3(projectDir);
2759
3128
  } catch {
2760
3129
  continue;
2761
3130
  }
2762
3131
  for (const entry of entries) {
2763
3132
  if (!entry.endsWith(".jsonl")) continue;
2764
- const full = join7(projectDir, entry);
3133
+ const full = join8(projectDir, entry);
2765
3134
  try {
2766
3135
  const st = await stat(full);
2767
3136
  if (st.isFile()) {
@@ -2776,7 +3145,7 @@ async function collectTranscripts(projectsDir) {
2776
3145
  async function filterTranscriptsByCwd(transcripts, repoRoot) {
2777
3146
  const dirHash = `-${repoRoot.replace(/^\/+/, "").replace(/\//g, "-")}`;
2778
3147
  const byDirName = transcripts.filter((t) => {
2779
- const parent = basename3(join7(t.path, ".."));
3148
+ const parent = basename3(join8(t.path, ".."));
2780
3149
  return parent === dirHash || parent.endsWith(dirHash);
2781
3150
  });
2782
3151
  if (byDirName.length > 0) return byDirName;
@@ -2793,26 +3162,26 @@ async function filterTranscriptsByCwd(transcripts, repoRoot) {
2793
3162
  return hits;
2794
3163
  }
2795
3164
  async function readHead(path2, bytes) {
2796
- const content = await readFile6(path2, "utf8");
3165
+ const content = await readFile7(path2, "utf8");
2797
3166
  return content.length > bytes ? content.slice(0, bytes) : content;
2798
3167
  }
2799
3168
  async function snapshotPages(pagesDir) {
2800
3169
  const out = /* @__PURE__ */ new Map();
2801
- if (!existsSync6(pagesDir)) return out;
3170
+ if (!existsSync7(pagesDir)) return out;
2802
3171
  let entries;
2803
3172
  try {
2804
- entries = await readdir2(pagesDir);
3173
+ entries = await readdir3(pagesDir);
2805
3174
  } catch {
2806
3175
  return out;
2807
3176
  }
2808
3177
  for (const entry of entries) {
2809
3178
  if (!entry.endsWith(".md")) continue;
2810
3179
  const slug = entry.slice(0, -3);
2811
- const full = join7(pagesDir, entry);
3180
+ const full = join8(pagesDir, entry);
2812
3181
  try {
2813
3182
  const st = statSync(full);
2814
3183
  if (!st.isFile()) continue;
2815
- const content = await readFile6(full, "utf8");
3184
+ const content = await readFile7(full, "utf8");
2816
3185
  const hash = createHash("sha256").update(content).digest("hex");
2817
3186
  const fm = parseFrontmatter(content);
2818
3187
  out.set(slug, {
@@ -2846,14 +3215,14 @@ function diffSnapshots(before, after) {
2846
3215
  }
2847
3216
  return { created, updated, archived };
2848
3217
  }
2849
- function formatSummary(result, delta, logPath, repoRoot) {
2850
- const rel = relative2(repoRoot, logPath);
2851
- const cost = `$${result.cost.toFixed(3)}`;
3218
+ function formatSummary2(result, delta, logPath, repoRoot) {
3219
+ const rel = relative3(repoRoot, logPath);
3220
+ const usage = formatRunUsage(result);
2852
3221
  const { created, updated, archived } = delta;
2853
3222
  if (created === 0 && updated === 0 && archived === 0) {
2854
- return `[capture] no new knowledge met the notability bar (0 pages written), cost: ${cost}, turns: ${result.turns} (transcript: ${rel})`;
3223
+ return `[capture] no new knowledge met the notability bar (0 pages written), ${usage} (transcript: ${rel})`;
2855
3224
  }
2856
- return `[done] ${updated} page${updated === 1 ? "" : "s"} updated, ${created} created, ${archived} archived, cost: ${cost}, turns: ${result.turns} (transcript: ${rel})`;
3225
+ return `[done] ${updated} page${updated === 1 ? "" : "s"} updated, ${created} created, ${archived} archived, ${usage} (transcript: ${rel})`;
2857
3226
  }
2858
3227
  function formatTimestamp2(d) {
2859
3228
  const pad = (n) => n.toString().padStart(2, "0");
@@ -2894,7 +3263,7 @@ function registerWikiLifecycleCommands(program) {
2894
3263
  emit(result);
2895
3264
  }
2896
3265
  );
2897
- program.command("capture [transcript]").description("run the writer/reviewer pipeline on a session (usually automatic)").option("--session <id>", "target a specific session by ID").option("--quiet", "suppress per-tool streaming; print only the final summary").option("--agent <agent>", "agent provider: claude, codex, or cursor").option("--model <model>", "override the agent model").action(
3266
+ const capture = program.command("capture [transcript]").alias("c").description("run the writer/reviewer pipeline on a session (usually automatic)").option("--session <id>", "target a specific session by ID").option("--quiet", "suppress per-tool streaming; print only the final summary").option("--agent <agent>", "agent provider: claude, codex, or cursor").option("--model <model>", "override the agent model").action(
2898
3267
  async (transcript, opts) => {
2899
3268
  await autoRegisterIfNeeded(process.cwd());
2900
3269
  const result = await runCapture({
@@ -2908,6 +3277,22 @@ function registerWikiLifecycleCommands(program) {
2908
3277
  emit(result);
2909
3278
  }
2910
3279
  );
3280
+ capture.command("status").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
3281
+ await autoRegisterIfNeeded(process.cwd());
3282
+ const result = await runCaptureStatus({
3283
+ cwd: process.cwd(),
3284
+ json: opts.json
3285
+ });
3286
+ emit(result);
3287
+ });
3288
+ program.command("ps").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
3289
+ await autoRegisterIfNeeded(process.cwd());
3290
+ const result = await runCaptureStatus({
3291
+ cwd: process.cwd(),
3292
+ json: opts.json
3293
+ });
3294
+ emit(result);
3295
+ });
2911
3296
  const hook = program.command("hook").description("manage the SessionEnd auto-capture hook");
2912
3297
  hook.command("install").description("add a SessionEnd entry that runs 'almanac capture' on session end").option("--source <source>", "claude, codex, cursor, or all").action(async (opts) => {
2913
3298
  const result = await runHookInstall({
@@ -2950,4 +3335,4 @@ function registerCommands(program) {
2950
3335
  export {
2951
3336
  registerCommands
2952
3337
  };
2953
- //# sourceMappingURL=register-commands-7SKQLQW4.js.map
3338
+ //# sourceMappingURL=register-commands-7QCIENRZ.js.map