codealmanac 0.2.6 → 0.2.7

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.
Files changed (86) hide show
  1. package/LICENSE +21 -133
  2. package/README.md +20 -15
  3. package/dist/{agents-HYRWRHRX.js → agents-V2ZOIACP.js} +6 -5
  4. package/dist/{chunk-PDFS5VFE.js → chunk-447U3GQJ.js} +5 -17
  5. package/dist/chunk-447U3GQJ.js.map +1 -0
  6. package/dist/{chunk-3E7JNMTZ.js → chunk-5BWUMAOX.js} +4 -29
  7. package/dist/chunk-5BWUMAOX.js.map +1 -0
  8. package/dist/{chunk-KQUVMF27.js → chunk-BFIG2CXM.js} +2 -516
  9. package/dist/chunk-BFIG2CXM.js.map +1 -0
  10. package/dist/{chunk-K2JBCB7R.js → chunk-BQY5L3DL.js} +7 -43
  11. package/dist/chunk-BQY5L3DL.js.map +1 -0
  12. package/dist/{chunk-F53U6JQG.js → chunk-CQJVM34R.js} +2 -2
  13. package/dist/chunk-FUBE6KCO.js +124 -0
  14. package/dist/chunk-FUBE6KCO.js.map +1 -0
  15. package/dist/chunk-IZBXXAVL.js +524 -0
  16. package/dist/chunk-IZBXXAVL.js.map +1 -0
  17. package/dist/{chunk-7JUX4ADQ.js → chunk-IZT6RBHS.js} +1 -1
  18. package/dist/{chunk-DW32TL5W.js → chunk-JLQZELHQ.js} +18 -58
  19. package/dist/chunk-JLQZELHQ.js.map +1 -0
  20. package/dist/{chunk-2BNDNGUR.js → chunk-KZXWPG4P.js} +4 -8
  21. package/dist/{chunk-2BNDNGUR.js.map → chunk-KZXWPG4P.js.map} +1 -1
  22. package/dist/{chunk-GPFVEF6V.js → chunk-QIA22IAM.js} +6 -24
  23. package/dist/chunk-QIA22IAM.js.map +1 -0
  24. package/dist/{chunk-J7DNV2DH.js → chunk-RALBM6HZ.js} +43 -355
  25. package/dist/chunk-RALBM6HZ.js.map +1 -0
  26. package/dist/{chunk-HJ3WREGP.js → chunk-U5DLLWIC.js} +3 -3
  27. package/dist/chunk-WL4UE7Q6.js +1386 -0
  28. package/dist/chunk-WL4UE7Q6.js.map +1 -0
  29. package/dist/{chunk-VXDPUOQ5.js → chunk-ZUQN5Y3K.js} +129 -382
  30. package/dist/chunk-ZUQN5Y3K.js.map +1 -0
  31. package/dist/{chunk-ODJAAJGZ.js → chunk-ZZLLOAI6.js} +3 -3
  32. package/dist/{cli-MKXCNEMW.js → cli-XWPNARA6.js} +37 -20
  33. package/dist/cli-XWPNARA6.js.map +1 -0
  34. package/dist/codealmanac.js +1 -1
  35. package/dist/{config-F7FKEQ7F.js → config-KH3JUMG6.js} +4 -4
  36. package/dist/doctor-ENJT665Z.js +18 -0
  37. package/dist/{hook-4SVX446M.js → hook-2NP3UE7U.js} +2 -4
  38. package/dist/paths-O5CZADP2.js +14 -0
  39. package/dist/process-KFSLENL3.js +61 -0
  40. package/dist/{register-commands-2F6SXLDI.js → register-commands-LULZUSPO.js} +999 -1030
  41. package/dist/register-commands-LULZUSPO.js.map +1 -0
  42. package/dist/uninstall-BD4MMQ7M.js +16 -0
  43. package/dist/uninstall-BD4MMQ7M.js.map +1 -0
  44. package/dist/update-XSKPDFMJ.js +11 -0
  45. package/dist/update-XSKPDFMJ.js.map +1 -0
  46. package/dist/{wiki-IGNRNLUZ.js → wiki-O4RWMAE6.js} +8 -6
  47. package/dist/wiki-O4RWMAE6.js.map +1 -0
  48. package/guides/mini.md +8 -6
  49. package/guides/reference.md +89 -32
  50. package/hooks/almanac-capture.sh +7 -8
  51. package/package.json +3 -4
  52. package/prompts/agents/.gitkeep +1 -0
  53. package/prompts/base/notability.md +139 -0
  54. package/prompts/base/purpose.md +85 -0
  55. package/prompts/base/syntax.md +114 -0
  56. package/prompts/operations/absorb.md +43 -0
  57. package/prompts/operations/build.md +49 -0
  58. package/prompts/operations/garden.md +51 -0
  59. package/COMMERCIAL.md +0 -9
  60. package/dist/chunk-3E7JNMTZ.js.map +0 -1
  61. package/dist/chunk-DW32TL5W.js.map +0 -1
  62. package/dist/chunk-GPFVEF6V.js.map +0 -1
  63. package/dist/chunk-J7DNV2DH.js.map +0 -1
  64. package/dist/chunk-K2JBCB7R.js.map +0 -1
  65. package/dist/chunk-KQUVMF27.js.map +0 -1
  66. package/dist/chunk-PDFS5VFE.js.map +0 -1
  67. package/dist/chunk-VXDPUOQ5.js.map +0 -1
  68. package/dist/cli-MKXCNEMW.js.map +0 -1
  69. package/dist/doctor-37UH3HT5.js +0 -17
  70. package/dist/register-commands-2F6SXLDI.js.map +0 -1
  71. package/dist/uninstall-C62ZOK32.js +0 -17
  72. package/dist/update-2UGOFN5C.js +0 -11
  73. package/dist/wiki-IGNRNLUZ.js.map +0 -1
  74. package/prompts/bootstrap.md +0 -176
  75. package/prompts/reviewer.md +0 -129
  76. package/prompts/writer.md +0 -134
  77. /package/dist/{agents-HYRWRHRX.js.map → agents-V2ZOIACP.js.map} +0 -0
  78. /package/dist/{chunk-F53U6JQG.js.map → chunk-CQJVM34R.js.map} +0 -0
  79. /package/dist/{chunk-7JUX4ADQ.js.map → chunk-IZT6RBHS.js.map} +0 -0
  80. /package/dist/{chunk-HJ3WREGP.js.map → chunk-U5DLLWIC.js.map} +0 -0
  81. /package/dist/{chunk-ODJAAJGZ.js.map → chunk-ZZLLOAI6.js.map} +0 -0
  82. /package/dist/{config-F7FKEQ7F.js.map → config-KH3JUMG6.js.map} +0 -0
  83. /package/dist/{doctor-37UH3HT5.js.map → doctor-ENJT665Z.js.map} +0 -0
  84. /package/dist/{hook-4SVX446M.js.map → hook-2NP3UE7U.js.map} +0 -0
  85. /package/dist/{uninstall-C62ZOK32.js.map → paths-O5CZADP2.js.map} +0 -0
  86. /package/dist/{update-2UGOFN5C.js.map → process-KFSLENL3.js.map} +0 -0
@@ -1,4 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ runUpdate
4
+ } from "./chunk-ZZLLOAI6.js";
5
+ import {
6
+ finishRunRecord,
7
+ listRunRecords,
8
+ markRunCancelled,
9
+ readRunRecord,
10
+ runRecordPath,
11
+ startBackgroundProcess,
12
+ startForegroundProcess,
13
+ toRunView,
14
+ writeRunRecord
15
+ } from "./chunk-WL4UE7Q6.js";
2
16
  import {
3
17
  collectOption,
4
18
  deprecationWarning,
@@ -14,40 +28,42 @@ import {
14
28
  runAgentsUse,
15
29
  runDeprecatedSetAgentModel,
16
30
  runDeprecatedSetDefaultAgent
17
- } from "./chunk-GPFVEF6V.js";
31
+ } from "./chunk-QIA22IAM.js";
18
32
  import {
19
33
  runConfigGet,
20
34
  runConfigList,
21
35
  runConfigSet,
22
36
  runConfigUnset
23
- } from "./chunk-2BNDNGUR.js";
37
+ } from "./chunk-KZXWPG4P.js";
24
38
  import {
25
39
  addEntry,
26
40
  ancestorsInFile,
27
41
  descendantsInDb,
28
42
  dropEntry,
29
- ensureFreshIndex,
30
43
  ensureGlobalDir,
44
+ parseDuration,
45
+ readRegistry,
46
+ resolveWikiRoot,
47
+ runHealth
48
+ } from "./chunk-IZBXXAVL.js";
49
+ import {
50
+ ensureFreshIndex,
31
51
  ensureTopic,
32
52
  findTopic,
33
53
  loadTopicsFile,
34
54
  looksLikeDir,
35
55
  normalizePath,
36
56
  openIndex,
37
- parseDuration,
38
- parseFrontmatter,
39
- readRegistry,
40
- resolveWikiRoot,
41
- runHealth,
42
57
  runIndexer,
43
58
  titleCase,
44
59
  toKebabCase,
45
60
  writeTopicsFile
46
- } from "./chunk-KQUVMF27.js";
61
+ } from "./chunk-BFIG2CXM.js";
47
62
  import {
48
63
  runDoctor
49
- } from "./chunk-DW32TL5W.js";
50
- import "./chunk-HJ3WREGP.js";
64
+ } from "./chunk-JLQZELHQ.js";
65
+ import "./chunk-U5DLLWIC.js";
66
+ import "./chunk-CQJVM34R.js";
51
67
  import "./chunk-4CODZRHH.js";
52
68
  import {
53
69
  BLUE,
@@ -57,37 +73,24 @@ import {
57
73
  } from "./chunk-FM3VRDK7.js";
58
74
  import {
59
75
  runUninstall
60
- } from "./chunk-K2JBCB7R.js";
76
+ } from "./chunk-BQY5L3DL.js";
61
77
  import {
62
78
  runSetup
63
- } from "./chunk-VXDPUOQ5.js";
79
+ } from "./chunk-ZUQN5Y3K.js";
64
80
  import {
65
81
  runHookInstall,
66
82
  runHookStatus,
67
83
  runHookUninstall
68
- } from "./chunk-PDFS5VFE.js";
84
+ } from "./chunk-447U3GQJ.js";
85
+ import "./chunk-RALBM6HZ.js";
86
+ import "./chunk-FUBE6KCO.js";
69
87
  import {
70
- DEFAULT_AGENT_MODEL,
71
- assertAgentAuth,
72
- getAgentProvider,
73
- getProviderDefaultModel,
74
- parseAgentSelection
75
- } from "./chunk-J7DNV2DH.js";
76
- import {
77
- runUpdate
78
- } from "./chunk-ODJAAJGZ.js";
79
- import "./chunk-F53U6JQG.js";
80
- import {
81
- disabledAgentProviderMessage,
82
- formatEnabledAgentProviderList,
83
- isAgentProviderId,
84
- isEnabledAgentProviderId,
85
88
  readConfig
86
- } from "./chunk-3E7JNMTZ.js";
89
+ } from "./chunk-5BWUMAOX.js";
87
90
  import {
88
91
  findNearestAlmanacDir,
89
92
  getRepoAlmanacDir
90
- } from "./chunk-7JUX4ADQ.js";
93
+ } from "./chunk-IZT6RBHS.js";
91
94
 
92
95
  // src/topics/frontmatter-rewrite.ts
93
96
  import { readFile, rename, writeFile } from "fs/promises";
@@ -1186,7 +1189,7 @@ function isReachable(entry) {
1186
1189
  }
1187
1190
  function formatPretty(entries) {
1188
1191
  if (entries.length === 0) {
1189
- return `${DIM}no wikis registered. run \`almanac bootstrap\` in a repo to create one.${RST}
1192
+ return `${DIM}no wikis registered. run \`almanac init\` in a repo to create one.${RST}
1190
1193
  `;
1191
1194
  }
1192
1195
  const nameWidth = Math.min(
@@ -1793,16 +1796,16 @@ function registerQueryCommands(program) {
1793
1796
  // src/cli/register-setup-commands.ts
1794
1797
  function registerSetupCommands(program) {
1795
1798
  const agents = program.command("agents").description("list supported AI agent providers and readiness");
1796
- agents.command("list").description("show Claude and Codex provider status").action(async () => {
1799
+ agents.command("list").description("show Claude, Codex, and Cursor provider status").action(async () => {
1797
1800
  emit(await runAgentsList());
1798
1801
  });
1799
1802
  agents.command("doctor").description("diagnose supported AI agent providers").action(async () => {
1800
1803
  emit(await runAgentsDoctor());
1801
1804
  });
1802
- agents.command("use").description("set the default AI agent provider").argument("<provider>", "claude, codex, or claude/<model>").action(async (provider) => {
1805
+ agents.command("use").description("set the default AI agent provider").argument("<provider>", "claude, codex, cursor, or claude/<model>").action(async (provider) => {
1803
1806
  emit(await runAgentsUse({ provider }));
1804
1807
  });
1805
- agents.command("model").description("set or reset a provider model").argument("<provider>", "claude or codex").argument("[model]", "provider-specific model id").option("--default", "reset to provider default").action(async (provider, model, opts) => {
1808
+ agents.command("model").description("set or reset a provider model").argument("<provider>", "claude, codex, or cursor").argument("[model]", "provider-specific model id").option("--default", "reset to provider default").action(async (provider, model, opts) => {
1806
1809
  emit(await runAgentsModel({
1807
1810
  provider,
1808
1811
  model,
@@ -1840,7 +1843,7 @@ function registerSetupCommands(program) {
1840
1843
  exitCode: 1
1841
1844
  });
1842
1845
  });
1843
- program.command("setup").description("set up codealmanac for Claude and Codex").option("-y, --yes", "skip prompts; install everything").option("--agent <agent>", "default agent: claude or codex").option("--model <model>", "default model for the selected agent").option("--skip-hook", "opt out of the SessionEnd hook").option("--skip-guides", "opt out of the CLAUDE.md guides").action(
1846
+ program.command("setup").description("install the hook + CLAUDE.md guides (bare codealmanac alias)").option("-y, --yes", "skip prompts; install everything").option("--agent <agent>", "default agent: claude, codex, or cursor").option("--model <model>", "default model for the selected agent").option("--skip-hook", "opt out of the SessionEnd hook").option("--skip-guides", "opt out of the CLAUDE.md guides").action(
1844
1847
  async (opts) => {
1845
1848
  const result = await runSetup({
1846
1849
  yes: opts.yes,
@@ -1898,20 +1901,290 @@ function registerSetupCommands(program) {
1898
1901
  );
1899
1902
  }
1900
1903
 
1901
- // src/commands/bootstrap.ts
1902
- import { createWriteStream, existsSync as existsSync5 } from "fs";
1903
- import { mkdir as mkdir2, readdir } from "fs/promises";
1904
- import { join as join6, relative } from "path";
1904
+ // src/commands/jobs.ts
1905
+ import { readFile as readFile4 } from "fs/promises";
1906
+
1907
+ // src/cli/outcome.ts
1908
+ function renderOutcome(outcome, opts = {}) {
1909
+ const exitCode = opts.exitCode ?? defaultExitCode(outcome);
1910
+ if (opts.json === true) {
1911
+ return {
1912
+ stdout: `${JSON.stringify(outcome, null, 2)}
1913
+ `,
1914
+ stderr: "",
1915
+ exitCode
1916
+ };
1917
+ }
1918
+ if (outcome.type === "needs-action") {
1919
+ return {
1920
+ stdout: opts.stdout ?? "",
1921
+ stderr: `almanac: ${outcome.message}
1922
+ ${outcome.fix}
1923
+ `,
1924
+ exitCode
1925
+ };
1926
+ }
1927
+ if (outcome.type === "error") {
1928
+ return {
1929
+ stdout: opts.stdout ?? "",
1930
+ stderr: `almanac: ${outcome.message}
1931
+ `,
1932
+ exitCode
1933
+ };
1934
+ }
1935
+ return {
1936
+ stdout: opts.stdout ?? `${outcome.message}
1937
+ `,
1938
+ stderr: "",
1939
+ exitCode
1940
+ };
1941
+ }
1942
+ function defaultExitCode(outcome) {
1943
+ switch (outcome.type) {
1944
+ case "success":
1945
+ case "noop":
1946
+ return 0;
1947
+ case "needs-action":
1948
+ case "error":
1949
+ return 1;
1950
+ }
1951
+ }
1952
+
1953
+ // src/commands/jobs.ts
1954
+ async function runJobsList(options) {
1955
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
1956
+ if (typeof repoRoot !== "string") return repoRoot;
1957
+ const views = await listRunViews(repoRoot, options);
1958
+ if (options.json === true) {
1959
+ return {
1960
+ stdout: `${JSON.stringify({ runs: views }, null, 2)}
1961
+ `,
1962
+ stderr: "",
1963
+ exitCode: 0
1964
+ };
1965
+ }
1966
+ if (views.length === 0) {
1967
+ return { stdout: "Jobs\n\nNo jobs found.\n", stderr: "", exitCode: 0 };
1968
+ }
1969
+ const lines = ["Jobs", ""];
1970
+ for (const view of views) {
1971
+ lines.push(
1972
+ `${view.id} ${view.operation} ${view.displayStatus} ${formatMs(view.elapsedMs)}`
1973
+ );
1974
+ }
1975
+ return { stdout: `${lines.join("\n")}
1976
+ `, stderr: "", exitCode: 0 };
1977
+ }
1978
+ async function runJobsShow(options) {
1979
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
1980
+ if (typeof repoRoot !== "string") return repoRoot;
1981
+ const view = await readRunView(repoRoot, options);
1982
+ if (view === null) return missingRun(options.runId, options.json);
1983
+ if (options.json === true) {
1984
+ return {
1985
+ stdout: `${JSON.stringify(view, null, 2)}
1986
+ `,
1987
+ stderr: "",
1988
+ exitCode: 0
1989
+ };
1990
+ }
1991
+ return {
1992
+ stdout: [
1993
+ `Run: ${view.id}`,
1994
+ `Operation: ${view.operation}`,
1995
+ `Status: ${view.displayStatus}`,
1996
+ `Provider: ${view.provider}${view.model !== void 0 ? `/${view.model}` : ""}`,
1997
+ `Elapsed: ${formatMs(view.elapsedMs)}`,
1998
+ `Log: ${view.logPath}`,
1999
+ view.failure !== void 0 ? `Reason: ${view.failure.message}` : view.error !== void 0 ? `Error: ${view.error}` : void 0,
2000
+ view.failure?.fix !== void 0 ? `Fix: ${view.failure.fix}` : void 0
2001
+ ].filter((line) => line !== void 0).join("\n") + "\n",
2002
+ stderr: "",
2003
+ exitCode: 0
2004
+ };
2005
+ }
2006
+ async function runJobsLogs(options) {
2007
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
2008
+ if (typeof repoRoot !== "string") return repoRoot;
2009
+ const record = await readRunRecord(runRecordPath(repoRoot, options.runId));
2010
+ if (record === null) return missingRun(options.runId, options.json);
2011
+ try {
2012
+ return {
2013
+ stdout: await readFile4(record.logPath, "utf8"),
2014
+ stderr: "",
2015
+ exitCode: 0
2016
+ };
2017
+ } catch (err) {
2018
+ return renderOutcome(
2019
+ {
2020
+ type: "error",
2021
+ message: err instanceof Error ? err.message : String(err)
2022
+ },
2023
+ { json: options.json }
2024
+ );
2025
+ }
2026
+ }
2027
+ async function streamJobsAttach(options) {
2028
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
2029
+ if (typeof repoRoot !== "string") return repoRoot;
2030
+ const initial = await readRunRecord(runRecordPath(repoRoot, options.runId));
2031
+ if (initial === null) return missingRun(options.runId, options.json);
2032
+ const write = options.write ?? ((chunk) => process.stdout.write(chunk));
2033
+ let offset = 0;
2034
+ while (true) {
2035
+ const record = await readRunRecord(runRecordPath(repoRoot, options.runId));
2036
+ if (record === null) return missingRun(options.runId, options.json);
2037
+ offset = await writeLogChunk(record.logPath, offset, write);
2038
+ const view = toRunView({
2039
+ record,
2040
+ now: options.now?.() ?? /* @__PURE__ */ new Date(),
2041
+ isPidAlive: options.isPidAlive ?? isPidAlive
2042
+ });
2043
+ if (view.displayStatus === "done" || view.displayStatus === "failed" || view.displayStatus === "cancelled" || view.displayStatus === "stale") {
2044
+ const summary = terminalAttachSummary(view);
2045
+ if (summary.length > 0) write(summary);
2046
+ return { stdout: "", stderr: "", exitCode: 0 };
2047
+ }
2048
+ await sleep(options.pollMs ?? 500);
2049
+ }
2050
+ }
2051
+ async function runJobsCancel(options) {
2052
+ const repoRoot = resolveWikiOrResult(options.cwd, options.json);
2053
+ if (typeof repoRoot !== "string") return repoRoot;
2054
+ const path2 = runRecordPath(repoRoot, options.runId);
2055
+ const record = await readRunRecord(path2);
2056
+ if (record === null) return missingRun(options.runId, options.json);
2057
+ if (record.status === "done" || record.status === "failed" || record.status === "cancelled") {
2058
+ return renderOutcome(
2059
+ {
2060
+ type: "noop",
2061
+ message: `job already ${record.status}: ${record.id}`,
2062
+ data: { runId: record.id, status: record.status }
2063
+ },
2064
+ { json: options.json }
2065
+ );
2066
+ }
2067
+ await markRunCancelled(repoRoot, record.id);
2068
+ if (record.pid > 0) {
2069
+ try {
2070
+ process.kill(record.pid, "SIGTERM");
2071
+ } catch {
2072
+ }
2073
+ }
2074
+ const cancelled = finishRunRecord({
2075
+ record,
2076
+ status: "cancelled",
2077
+ finishedAt: options.now?.() ?? /* @__PURE__ */ new Date()
2078
+ });
2079
+ await writeRunRecord(path2, cancelled);
2080
+ return renderOutcome(
2081
+ {
2082
+ type: "success",
2083
+ message: `cancelled job: ${record.id}`,
2084
+ data: { runId: record.id, status: "cancelled" }
2085
+ },
2086
+ { json: options.json }
2087
+ );
2088
+ }
2089
+ async function listRunViews(repoRoot, options) {
2090
+ const records = await listRunRecords(repoRoot);
2091
+ return records.map(
2092
+ (record) => toRunView({
2093
+ record,
2094
+ now: options.now?.() ?? /* @__PURE__ */ new Date(),
2095
+ isPidAlive: options.isPidAlive ?? isPidAlive
2096
+ })
2097
+ );
2098
+ }
2099
+ async function readRunView(repoRoot, options) {
2100
+ const record = await readRunRecord(runRecordPath(repoRoot, options.runId));
2101
+ if (record === null) return null;
2102
+ return toRunView({
2103
+ record,
2104
+ now: options.now?.() ?? /* @__PURE__ */ new Date(),
2105
+ isPidAlive: options.isPidAlive ?? isPidAlive
2106
+ });
2107
+ }
2108
+ function resolveWikiOrResult(cwd, json) {
2109
+ const repoRoot = findNearestAlmanacDir(cwd);
2110
+ if (repoRoot !== null) return repoRoot;
2111
+ return renderOutcome(
2112
+ {
2113
+ type: "needs-action",
2114
+ message: "no .almanac/ found in this directory or any parent",
2115
+ fix: "run: almanac init"
2116
+ },
2117
+ { json }
2118
+ );
2119
+ }
2120
+ function missingRun(runId, json) {
2121
+ return renderOutcome(
2122
+ { type: "error", message: `run not found: ${runId}` },
2123
+ { json }
2124
+ );
2125
+ }
2126
+ async function writeLogChunk(path2, offset, write) {
2127
+ let text = "";
2128
+ try {
2129
+ text = await readFile4(path2, "utf8");
2130
+ } catch {
2131
+ return offset;
2132
+ }
2133
+ if (text.length <= offset) return offset;
2134
+ const chunk = text.slice(offset);
2135
+ write(chunk);
2136
+ return text.length;
2137
+ }
2138
+ function sleep(ms) {
2139
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
2140
+ }
2141
+ function isPidAlive(pid) {
2142
+ if (pid <= 0) return false;
2143
+ try {
2144
+ process.kill(pid, 0);
2145
+ return true;
2146
+ } catch {
2147
+ return false;
2148
+ }
2149
+ }
2150
+ function formatMs(ms) {
2151
+ if (ms < 1e3) return `${ms}ms`;
2152
+ const seconds = Math.round(ms / 1e3);
2153
+ if (seconds < 60) return `${seconds}s`;
2154
+ const minutes = Math.round(seconds / 60);
2155
+ if (minutes < 60) return `${minutes}m`;
2156
+ return `${Math.round(minutes / 60)}h`;
2157
+ }
2158
+ function terminalAttachSummary(view) {
2159
+ if (view.displayStatus !== "failed" && view.displayStatus !== "stale") {
2160
+ return "";
2161
+ }
2162
+ const lines = [`job ${view.displayStatus}: ${view.id}`];
2163
+ if (view.failure !== void 0) {
2164
+ lines.push(`Reason: ${view.failure.message}`);
2165
+ if (view.failure.fix !== void 0) lines.push(`Fix: ${view.failure.fix}`);
2166
+ } else if (view.error !== void 0) {
2167
+ lines.push(`Error: ${view.error}`);
2168
+ }
2169
+ return `${lines.join("\n")}
2170
+ `;
2171
+ }
2172
+
2173
+ // src/commands/operations.ts
2174
+ import { resolve as resolve2 } from "path";
1905
2175
 
1906
2176
  // src/agent/prompts.ts
1907
2177
  import { existsSync as existsSync3 } from "fs";
1908
- import { readFile as readFile4 } from "fs/promises";
2178
+ import { readFile as readFile5 } from "fs/promises";
1909
2179
  import path from "path";
1910
2180
  import { fileURLToPath } from "url";
1911
2181
  var PROMPT_NAMES = [
1912
- "bootstrap",
1913
- "writer",
1914
- "reviewer"
2182
+ "base/purpose",
2183
+ "base/notability",
2184
+ "base/syntax",
2185
+ "operations/build",
2186
+ "operations/absorb",
2187
+ "operations/garden"
1915
2188
  ];
1916
2189
  var overrideDir = null;
1917
2190
  var resolvedDir = null;
@@ -1946,88 +2219,132 @@ function isPromptsDir(dir) {
1946
2219
  }
1947
2220
  async function loadPrompt(name) {
1948
2221
  const dir = resolvePromptsDir();
1949
- return readFile4(path.join(dir, `${name}.md`), "utf8");
2222
+ return readFile5(resolvePromptPath(dir, name), "utf8");
1950
2223
  }
1951
-
1952
- // src/agent/selection.ts
1953
- async function resolveAgentSelection(args) {
1954
- const config = await readConfig({ cwd: args.cwd });
1955
- const rawAgent = args.agent ?? process.env.ALMANAC_AGENT ?? config.agent.default;
1956
- const agentSource = args.agent !== void 0 ? "flag" : process.env.ALMANAC_AGENT !== void 0 ? "env" : "config";
1957
- const parsed = parseAgentSelection(rawAgent);
1958
- if (parsed.provider === null || !isAgentProviderId(parsed.provider)) {
1959
- return {
1960
- ok: false,
1961
- error: `unknown agent '${rawAgent}'. Expected one of: ${formatEnabledAgentProviderList()}.`
1962
- };
2224
+ function resolvePromptPath(dir, name) {
2225
+ if (name.length === 0 || path.isAbsolute(name) || name.includes("\\") || name.split("/").some((part) => part === "" || part === "." || part === "..")) {
2226
+ throw new Error(`invalid prompt name: ${name}`);
1963
2227
  }
1964
- const provider = parsed.provider;
1965
- if (!isEnabledAgentProviderId(provider)) {
1966
- return {
1967
- ok: false,
1968
- error: disabledAgentProviderMessage(provider)
1969
- };
2228
+ const file = path.resolve(dir, `${name}.md`);
2229
+ const relative = path.relative(dir, file);
2230
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
2231
+ throw new Error(`invalid prompt name: ${name}`);
1970
2232
  }
1971
- const configuredModel = config.agent.models[provider] ?? void 0;
1972
- const model = args.model !== void 0 ? args.model : parsed.model !== void 0 && agentSource === "flag" ? parsed.model : process.env.ALMANAC_MODEL !== void 0 ? process.env.ALMANAC_MODEL : parsed.model !== void 0 ? parsed.model : configuredModel === null ? void 0 : configuredModel;
1973
- return { ok: true, provider, model };
2233
+ return file;
2234
+ }
2235
+ function joinPrompts(parts) {
2236
+ return parts.map((part) => part?.trim()).filter((part) => part !== void 0 && part.length > 0).join("\n\n---\n\n");
1974
2237
  }
1975
2238
 
1976
- // src/cli/outcome.ts
1977
- function renderOutcome(outcome, opts = {}) {
1978
- const exitCode = opts.exitCode ?? defaultExitCode(outcome);
1979
- if (opts.json === true) {
1980
- return {
1981
- stdout: `${JSON.stringify(outcome, null, 2)}
1982
- `,
1983
- stderr: "",
1984
- exitCode
1985
- };
1986
- }
1987
- if (outcome.type === "needs-action") {
1988
- return {
1989
- stdout: opts.stdout ?? "",
1990
- stderr: `almanac: ${outcome.message}
1991
- ${outcome.fix}
1992
- `,
1993
- exitCode
1994
- };
1995
- }
1996
- if (outcome.type === "error") {
1997
- return {
1998
- stdout: opts.stdout ?? "",
1999
- stderr: `almanac: ${outcome.message}
2000
- `,
2001
- exitCode
2002
- };
2003
- }
2239
+ // src/operations/run.ts
2240
+ var DEFAULT_MAX_TURNS = 150;
2241
+ var BASE_OPERATION_TOOLS = [
2242
+ { id: "read" },
2243
+ { id: "write" },
2244
+ { id: "edit" },
2245
+ { id: "search" },
2246
+ { id: "shell" }
2247
+ ];
2248
+ var BASE_PROMPTS = [
2249
+ "base/purpose",
2250
+ "base/notability",
2251
+ "base/syntax"
2252
+ ];
2253
+ async function createOperationRunSpec(args) {
2254
+ const basePrompts = await Promise.all(
2255
+ BASE_PROMPTS.map((name) => loadPrompt(name))
2256
+ );
2257
+ const operationPrompt = await loadPrompt(args.promptName);
2258
+ const prompt = joinPrompts([
2259
+ ...basePrompts,
2260
+ operationPrompt,
2261
+ operationRuntimeContext(args.repoRoot),
2262
+ args.context
2263
+ ]);
2004
2264
  return {
2005
- stdout: opts.stdout ?? `${outcome.message}
2006
- `,
2007
- stderr: "",
2008
- exitCode
2265
+ provider: args.provider ?? { id: "claude" },
2266
+ cwd: args.repoRoot,
2267
+ prompt,
2268
+ tools: BASE_OPERATION_TOOLS,
2269
+ limits: {
2270
+ maxTurns: DEFAULT_MAX_TURNS
2271
+ },
2272
+ metadata: {
2273
+ operation: args.operation,
2274
+ targetKind: args.targetKind,
2275
+ targetPaths: args.targetPaths
2276
+ }
2009
2277
  };
2010
2278
  }
2011
- function defaultExitCode(outcome) {
2012
- switch (outcome.type) {
2013
- case "success":
2014
- case "noop":
2015
- return 0;
2016
- case "needs-action":
2017
- case "error":
2018
- return 1;
2279
+ async function runOperationProcess(args) {
2280
+ if (args.background) {
2281
+ const background = await (args.startBackground ?? startBackgroundProcess)({
2282
+ repoRoot: args.repoRoot,
2283
+ spec: args.spec,
2284
+ runId: args.runId
2285
+ });
2286
+ return { mode: "background", runId: background.runId, background };
2019
2287
  }
2288
+ const foreground = await (args.startForeground ?? startForegroundProcess)({
2289
+ repoRoot: args.repoRoot,
2290
+ spec: args.spec,
2291
+ runId: args.runId,
2292
+ onEvent: args.onEvent
2293
+ });
2294
+ return { mode: "foreground", runId: foreground.runId, foreground };
2295
+ }
2296
+ function operationRuntimeContext(repoRoot) {
2297
+ return [
2298
+ "Runtime context:",
2299
+ `- Repository root: ${repoRoot}`,
2300
+ `- Almanac directory: ${repoRoot}/.almanac`,
2301
+ `- Wiki pages directory: ${repoRoot}/.almanac/pages`
2302
+ ].join("\n");
2020
2303
  }
2021
2304
 
2022
- // src/agent/sdk.ts
2023
- async function runAgent(opts) {
2024
- const provider = opts.provider ?? "claude";
2025
- return await getAgentProvider(provider).run(opts);
2305
+ // src/operations/absorb.ts
2306
+ async function createAbsorbRunSpec(args) {
2307
+ return createOperationRunSpec({
2308
+ operation: "absorb",
2309
+ promptName: "operations/absorb",
2310
+ provider: args.provider ?? { id: "claude" },
2311
+ repoRoot: args.repoRoot,
2312
+ context: args.context,
2313
+ targetKind: args.targetKind,
2314
+ targetPaths: args.targetPaths
2315
+ });
2316
+ }
2317
+ async function runAbsorbOperation(options) {
2318
+ const repoRoot = findNearestAlmanacDir(options.cwd);
2319
+ if (repoRoot === null) {
2320
+ throw new Error("no .almanac/ found in this directory or any parent");
2321
+ }
2322
+ const spec = await createAbsorbRunSpec({
2323
+ repoRoot,
2324
+ provider: options.provider,
2325
+ context: options.context,
2326
+ targetKind: options.targetKind,
2327
+ targetPaths: options.targetPaths
2328
+ });
2329
+ return runOperationProcess({
2330
+ repoRoot,
2331
+ spec,
2332
+ background: options.background !== false,
2333
+ runId: options.runId,
2334
+ onEvent: options.onEvent,
2335
+ startForeground: options.startForeground,
2336
+ startBackground: options.startBackground
2337
+ });
2026
2338
  }
2027
2339
 
2340
+ // src/operations/build.ts
2341
+ import { existsSync as existsSync5 } from "fs";
2342
+ import { readdir } from "fs/promises";
2343
+ import { join as join6 } from "path";
2344
+
2028
2345
  // src/commands/init.ts
2029
2346
  import { existsSync as existsSync4 } from "fs";
2030
- import { mkdir, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
2347
+ import { mkdir, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
2031
2348
  import { basename as basename2, join as join5 } from "path";
2032
2349
  async function initWiki(options) {
2033
2350
  const repoRoot = findNearestAlmanacDir(options.cwd) ?? options.cwd;
@@ -2039,7 +2356,7 @@ async function initWiki(options) {
2039
2356
  if (!existsSync4(readmePath)) {
2040
2357
  await writeFile2(readmePath, starterReadme(), "utf8");
2041
2358
  }
2042
- await ensureGitignoreHasIndexDb(repoRoot);
2359
+ await ensureGitignoreHasRuntimeArtifacts(repoRoot);
2043
2360
  const name = toKebabCase(options.name ?? basename2(repoRoot));
2044
2361
  if (name.length === 0) {
2045
2362
  throw new Error(
@@ -2057,23 +2374,17 @@ async function initWiki(options) {
2057
2374
  await addEntry(entry);
2058
2375
  return { entry, almanacDir, created: !alreadyExisted };
2059
2376
  }
2060
- async function ensureGitignoreHasIndexDb(cwd) {
2377
+ async function ensureGitignoreHasRuntimeArtifacts(cwd) {
2061
2378
  const path2 = join5(cwd, ".gitignore");
2062
2379
  const targets = [
2063
2380
  ".almanac/index.db",
2064
2381
  ".almanac/index.db-wal",
2065
2382
  ".almanac/index.db-shm",
2066
- // Runtime logs written by the AI pipeline. These can be multi-megabyte
2067
- // JSONL files and should never be committed. Keep the old root-level
2068
- // globs too so repos initialized by earlier releases stay quiet.
2069
- ".almanac/logs/",
2070
- ".almanac/.capture-*",
2071
- ".almanac/.bootstrap-*",
2072
- ".almanac/.ingest-*"
2383
+ ".almanac/runs/"
2073
2384
  ];
2074
2385
  let existing = "";
2075
2386
  if (existsSync4(path2)) {
2076
- existing = await readFile5(path2, "utf8");
2387
+ existing = await readFile6(path2, "utf8");
2077
2388
  }
2078
2389
  const lines = existing.split(/\r?\n/).map((l) => l.trim());
2079
2390
  const missing = targets.filter((t) => !lines.includes(t));
@@ -2165,882 +2476,224 @@ and optional \`files:\`. The rest is prose.
2165
2476
  `;
2166
2477
  }
2167
2478
 
2168
- // src/commands/bootstrap.ts
2169
- var BOOTSTRAP_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
2170
- async function runBootstrap(options) {
2171
- const repoRoot = findNearestAlmanacDir(options.cwd) ?? options.cwd;
2172
- const providerResolution = await resolveAgentSelection({
2173
- agent: options.agent,
2174
- model: options.model,
2175
- cwd: repoRoot
2479
+ // src/operations/build.ts
2480
+ async function createBuildRunSpec(args) {
2481
+ return createOperationRunSpec({
2482
+ operation: "build",
2483
+ promptName: "operations/build",
2484
+ provider: args.provider ?? { id: "claude" },
2485
+ repoRoot: args.repoRoot,
2486
+ context: args.context,
2487
+ targetKind: "repo",
2488
+ targetPaths: [args.repoRoot]
2176
2489
  });
2177
- if (!providerResolution.ok) {
2178
- return renderOutcome(
2179
- { type: "error", message: providerResolution.error },
2180
- { json: options.json }
2490
+ }
2491
+ async function runBuildOperation(options) {
2492
+ const init = await initWiki({ cwd: options.cwd });
2493
+ const repoRoot = init.entry.path;
2494
+ const pageCount = await countWikiPages(repoRoot);
2495
+ if (pageCount > 0 && options.force !== true) {
2496
+ throw new Error(
2497
+ `.almanac/ already initialized with ${pageCount} page${pageCount === 1 ? "" : "s"}; pass --force to rebuild`
2181
2498
  );
2182
2499
  }
2183
- const { provider, model } = providerResolution;
2184
- try {
2185
- await assertAgentAuth({ provider, spawnCli: options.spawnCli });
2186
- } catch (err) {
2187
- const msg = err instanceof Error ? err.message : String(err);
2188
- return renderOutcome(
2189
- {
2190
- type: "needs-action",
2191
- message: msg,
2192
- fix: authFixFor(provider),
2193
- data: { provider }
2194
- },
2195
- { json: options.json }
2196
- );
2500
+ const spec = await createBuildRunSpec({
2501
+ repoRoot,
2502
+ provider: options.provider,
2503
+ context: options.context
2504
+ });
2505
+ return runOperationProcess({
2506
+ repoRoot,
2507
+ spec,
2508
+ background: options.background === true,
2509
+ runId: options.runId,
2510
+ onEvent: options.onEvent,
2511
+ startForeground: options.startForeground,
2512
+ startBackground: options.startBackground
2513
+ });
2514
+ }
2515
+ async function countWikiPages(repoRoot) {
2516
+ const pagesDir = join6(repoRoot, ".almanac", "pages");
2517
+ if (!existsSync5(pagesDir)) return 0;
2518
+ const entries = await readdir(pagesDir);
2519
+ return entries.filter((entry) => entry.endsWith(".md")).length;
2520
+ }
2521
+
2522
+ // src/operations/garden.ts
2523
+ async function createGardenRunSpec(args) {
2524
+ return createOperationRunSpec({
2525
+ operation: "garden",
2526
+ promptName: "operations/garden",
2527
+ provider: args.provider ?? { id: "claude" },
2528
+ repoRoot: args.repoRoot,
2529
+ context: args.context,
2530
+ targetKind: "wiki",
2531
+ targetPaths: [`${args.repoRoot}/.almanac`]
2532
+ });
2533
+ }
2534
+ async function runGardenOperation(options) {
2535
+ const repoRoot = findNearestAlmanacDir(options.cwd);
2536
+ if (repoRoot === null) {
2537
+ throw new Error("no .almanac/ found in this directory or any parent");
2197
2538
  }
2198
- const almanacDir = getRepoAlmanacDir(repoRoot);
2199
- const pagesDir = join6(almanacDir, "pages");
2200
- if (options.force !== true && existsSync5(pagesDir)) {
2201
- const existing = await countMarkdownPages(pagesDir);
2202
- if (existing > 0) {
2203
- return renderOutcome(
2204
- {
2205
- type: "needs-action",
2206
- message: `.almanac/ already initialized with ${existing} page${existing === 1 ? "" : "s"}.`,
2207
- fix: "run: almanac capture (or pass --force to overwrite)",
2208
- data: { pages: existing }
2209
- },
2210
- { json: options.json }
2211
- );
2212
- }
2213
- }
2214
- if (!existsSync5(almanacDir)) {
2215
- try {
2216
- await initWiki({ cwd: repoRoot });
2217
- } catch (err) {
2218
- const msg = err instanceof Error ? err.message : String(err);
2219
- return renderOutcome(
2220
- { type: "error", message: `init failed during bootstrap: ${msg}` },
2221
- { json: options.json }
2222
- );
2223
- }
2224
- }
2225
- const systemPrompt = await loadPrompt("bootstrap");
2226
- const now = options.now?.() ?? /* @__PURE__ */ new Date();
2227
- const logsDir = join6(almanacDir, "logs");
2228
- await mkdir2(logsDir, { recursive: true });
2229
- const logName = `.bootstrap-${formatTimestamp(now)}.log`;
2230
- const logPath = join6(logsDir, logName);
2231
- const logStream = createWriteStream(logPath, { flags: "w" });
2232
- const out = process.stdout;
2233
- const formatter = new StreamingFormatter({
2234
- write: (line) => {
2235
- if (options.quiet !== true && options.json !== true) out.write(line);
2236
- }
2539
+ const spec = await createGardenRunSpec({
2540
+ repoRoot,
2541
+ provider: options.provider,
2542
+ context: options.context
2237
2543
  });
2238
- const onMessage = (msg) => {
2239
- try {
2240
- logStream.write(`${JSON.stringify(msg)}
2241
- `);
2242
- } catch {
2243
- }
2244
- formatter.handle(msg);
2245
- };
2246
- const runner = options.runAgent ?? runAgent;
2247
- const userPrompt = `Begin the bootstrap now. Working directory: ${repoRoot}.`;
2248
- let result;
2249
- try {
2250
- result = await runner({
2251
- systemPrompt,
2252
- prompt: userPrompt,
2253
- allowedTools: BOOTSTRAP_TOOLS,
2254
- cwd: repoRoot,
2255
- provider,
2256
- model,
2257
- onMessage
2258
- });
2259
- } finally {
2260
- await closeStream(logStream);
2261
- }
2262
- const finalLine = formatFinalLine(result, logPath, repoRoot);
2263
- if (result.success) {
2264
- return renderOutcome(
2265
- {
2266
- type: "success",
2267
- message: finalLine,
2268
- data: runData(result, logPath, repoRoot)
2269
- },
2270
- { json: options.json }
2271
- );
2272
- }
2273
- return renderOutcome(
2274
- {
2275
- type: "error",
2276
- message: `bootstrap failed: ${result.error ?? "unknown error"}`,
2277
- data: runData(result, logPath, repoRoot)
2278
- },
2279
- {
2280
- json: options.json,
2281
- stdout: options.quiet === true || options.json === true ? "" : `${finalLine}
2282
- `
2283
- }
2284
- );
2285
- }
2286
- function authFixFor(provider) {
2287
- switch (provider) {
2288
- case "claude":
2289
- return "run: claude auth login --claudeai (or export ANTHROPIC_API_KEY)";
2290
- case "codex":
2291
- return "run: codex login";
2292
- case "cursor":
2293
- return "run: cursor-agent login";
2294
- default:
2295
- return "run: almanac agents doctor";
2296
- }
2297
- }
2298
- function runData(result, logPath, repoRoot) {
2299
- return {
2300
- transcript: relative(repoRoot, logPath),
2301
- cost: result.cost,
2302
- turns: result.turns,
2303
- sessionId: result.sessionId
2304
- };
2305
- }
2306
- function formatFinalLine(result, logPath, repoRoot) {
2307
- const status = result.success ? "done" : "failed";
2308
- const rel = relative(repoRoot, logPath);
2309
- const usage = formatRunUsage(result);
2310
- return `[${status}] ${usage} (transcript: ${rel})`;
2311
- }
2312
- function formatRunUsage(result) {
2313
- const parts = [];
2314
- if (result.cost > 0 || result.usage === void 0) {
2315
- parts.push(`cost: $${result.cost.toFixed(3)}`);
2316
- }
2317
- parts.push(`turns: ${result.turns}`);
2318
- const usage = result.usage;
2319
- if (usage !== void 0) {
2320
- const tokenParts = [];
2321
- if (usage.inputTokens !== void 0) {
2322
- tokenParts.push(`${usage.inputTokens.toLocaleString("en-US")} in`);
2323
- }
2324
- if (usage.outputTokens !== void 0) {
2325
- tokenParts.push(`${usage.outputTokens.toLocaleString("en-US")} out`);
2326
- }
2327
- if (usage.cachedInputTokens !== void 0) {
2328
- tokenParts.push(
2329
- `${usage.cachedInputTokens.toLocaleString("en-US")} cached`
2330
- );
2331
- }
2332
- if (usage.reasoningOutputTokens !== void 0) {
2333
- tokenParts.push(
2334
- `${usage.reasoningOutputTokens.toLocaleString("en-US")} reasoning`
2335
- );
2336
- }
2337
- if (tokenParts.length > 0) {
2338
- parts.push(`tokens: ${tokenParts.join(", ")}`);
2339
- }
2340
- }
2341
- return parts.join(", ");
2342
- }
2343
- async function countMarkdownPages(pagesDir) {
2344
- try {
2345
- const entries = await readdir(pagesDir, { withFileTypes: true });
2346
- return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).length;
2347
- } catch {
2348
- return 0;
2349
- }
2350
- }
2351
- function closeStream(stream) {
2352
- return new Promise((resolve) => {
2353
- stream.end(() => resolve());
2544
+ return runOperationProcess({
2545
+ repoRoot,
2546
+ spec,
2547
+ background: options.background !== false,
2548
+ runId: options.runId,
2549
+ onEvent: options.onEvent,
2550
+ startForeground: options.startForeground,
2551
+ startBackground: options.startBackground
2354
2552
  });
2355
2553
  }
2356
- function formatTimestamp(d) {
2357
- const pad = (n) => n.toString().padStart(2, "0");
2358
- const y = d.getFullYear();
2359
- const mo = pad(d.getMonth() + 1);
2360
- const da = pad(d.getDate());
2361
- const h = pad(d.getHours());
2362
- const mi = pad(d.getMinutes());
2363
- const s = pad(d.getSeconds());
2364
- return `${y}${mo}${da}-${h}${mi}${s}`;
2365
- }
2366
- var StreamingFormatter = class {
2367
- sink;
2368
- /**
2369
- * Current agent label. Starts as "bootstrap"; switches when we see an
2370
- * `Agent` tool-use (slice 5 will exercise this). We still track it here
2371
- * so the formatter can stay shared between bootstrap and capture.
2372
- */
2373
- currentAgent = "bootstrap";
2374
- constructor(sink) {
2375
- this.sink = sink;
2376
- }
2377
- /**
2378
- * Swap the top-level agent label. `capture` uses this to relabel from
2379
- * the default "bootstrap" to "writer" — otherwise the writer's tool-use
2380
- * output would render as `[bootstrap] …`, which is confusing when you're
2381
- * reading capture logs.
2382
- */
2383
- setAgent(name) {
2384
- this.currentAgent = name;
2385
- }
2386
- handle(msg) {
2387
- if (!isRecord(msg)) return;
2388
- if (msg.type === "assistant" && isRecord(msg.message)) {
2389
- const content = msg.message.content;
2390
- if (!Array.isArray(content)) return;
2391
- for (const block of msg.message.content) {
2392
- if (!isRecord(block) || block.type !== "tool_use") continue;
2393
- if (typeof block.name !== "string") continue;
2394
- this.handleToolUse(block.name, block.input);
2395
- }
2396
- return;
2397
- }
2398
- if (msg.type === "item.started" && isRecord(msg.item)) {
2399
- this.handleCodexItemStarted(msg.item);
2400
- return;
2401
- }
2402
- if (msg.type === "item.completed" && isRecord(msg.item)) {
2403
- this.handleCodexItemCompleted(msg.item);
2404
- return;
2405
- }
2406
- if (msg.type === "tool_call") {
2407
- this.handleCursorToolCall(msg);
2408
- return;
2409
- }
2410
- }
2411
- handleToolUse(name, rawInput) {
2412
- const input = normalizeToolInput(rawInput);
2413
- if (name === "Agent") {
2414
- const sub = typeof input.subagent_type === "string" ? input.subagent_type : "subagent";
2415
- this.currentAgent = sub;
2416
- this.sink.write(`[${sub}] starting
2417
- `);
2418
- return;
2419
- }
2420
- const summary = formatToolSummary(name, input);
2421
- this.sink.write(`[${this.currentAgent}] ${summary}
2422
- `);
2423
- }
2424
- handleCodexItemStarted(item) {
2425
- if (item.type !== "command_execution") return;
2426
- const command = stringField(item, "command") ?? "?";
2427
- this.sink.write(`[${this.currentAgent}] bash ${truncate(command, 80)}
2428
- `);
2429
- }
2430
- handleCodexItemCompleted(item) {
2431
- if (item.type !== "file_change") return;
2432
- const changes = Array.isArray(item.changes) ? item.changes : [];
2433
- for (const change of changes) {
2434
- if (!isRecord(change)) continue;
2435
- const rawPath = stringField(change, "path") ?? "?";
2436
- const kind = stringField(change, "kind") ?? "edit";
2437
- const verb = kind === "add" ? "writing" : kind === "delete" ? "deleting" : "editing";
2438
- this.sink.write(
2439
- `[${this.currentAgent}] ${verb} ${formatCodexPath(rawPath)}
2440
- `
2441
- );
2442
- }
2443
- }
2444
- handleCursorToolCall(msg) {
2445
- if (msg.subtype !== "started" || !isRecord(msg.tool_call)) return;
2446
- const summary = formatCursorToolSummary(msg.tool_call);
2447
- if (summary === void 0) return;
2448
- this.sink.write(`[${this.currentAgent}] ${summary}
2449
- `);
2450
- }
2451
- };
2452
- function normalizeToolInput(raw) {
2453
- if (typeof raw === "string") {
2454
- try {
2455
- const parsed = JSON.parse(raw);
2456
- if (parsed !== null && typeof parsed === "object") {
2457
- return parsed;
2458
- }
2459
- } catch {
2460
- }
2461
- return {};
2462
- }
2463
- if (raw !== null && typeof raw === "object") {
2464
- return raw;
2465
- }
2466
- return {};
2467
- }
2468
- function formatToolSummary(name, input) {
2469
- switch (name) {
2470
- case "Read": {
2471
- const target = stringField(input, "file_path") ?? "?";
2472
- return `reading ${target}`;
2473
- }
2474
- case "Write": {
2475
- const target = stringField(input, "file_path") ?? "?";
2476
- return `writing ${target}`;
2477
- }
2478
- case "Edit": {
2479
- const target = stringField(input, "file_path") ?? "?";
2480
- return `editing ${target}`;
2481
- }
2482
- case "Glob": {
2483
- const pattern = stringField(input, "pattern") ?? "?";
2484
- return `glob ${pattern}`;
2485
- }
2486
- case "Grep": {
2487
- const pattern = stringField(input, "pattern") ?? "?";
2488
- return `grep ${pattern}`;
2489
- }
2490
- case "Bash": {
2491
- const command = stringField(input, "command") ?? "?";
2492
- return `bash ${truncate(command, 80)}`;
2493
- }
2494
- default: {
2495
- return name;
2554
+
2555
+ // src/commands/session-transcripts.ts
2556
+ import { existsSync as existsSync6 } from "fs";
2557
+ import { readFile as readFile7, readdir as readdir2, stat } from "fs/promises";
2558
+ import { homedir } from "os";
2559
+ import { basename as basename3, join as join7, resolve } from "path";
2560
+ async function resolveCaptureTranscripts(options) {
2561
+ const explicit = options.files ?? [];
2562
+ if (explicit.length > 0) {
2563
+ const paths = explicit.map((path2) => resolve(options.cwd, path2));
2564
+ const missing = paths.find((path2) => !existsSync6(path2));
2565
+ if (missing !== void 0) {
2566
+ return {
2567
+ ok: false,
2568
+ error: `transcript not found: ${missing}`,
2569
+ fix: "pass an existing transcript file"
2570
+ };
2496
2571
  }
2572
+ return { ok: true, paths, app: "file" };
2497
2573
  }
2498
- }
2499
- function formatCursorToolSummary(toolCall) {
2500
- if (isRecord(toolCall.readToolCall)) {
2501
- const args = recordField(toolCall.readToolCall, "args");
2502
- return `reading ${stringField(args, "path") ?? "?"}`;
2503
- }
2504
- if (isRecord(toolCall.editToolCall)) {
2505
- const args = recordField(toolCall.editToolCall, "args");
2506
- return `editing ${formatCodexPath(stringField(args, "path") ?? "?")}`;
2507
- }
2508
- if (isRecord(toolCall.globToolCall)) {
2509
- const args = recordField(toolCall.globToolCall, "args");
2510
- return `glob ${stringField(args, "globPattern") ?? "?"}`;
2511
- }
2512
- if (isRecord(toolCall.grepToolCall)) {
2513
- const args = recordField(toolCall.grepToolCall, "args");
2514
- return `grep ${stringField(args, "pattern") ?? "?"}`;
2515
- }
2516
- if (isRecord(toolCall.shellToolCall)) {
2517
- const args = recordField(toolCall.shellToolCall, "args");
2518
- const description = stringField(toolCall.shellToolCall, "description");
2519
- const command = stringField(args, "command") ?? description ?? "?";
2520
- return `bash ${truncate(command, 80)}`;
2521
- }
2522
- return void 0;
2523
- }
2524
- function truncate(value, max) {
2525
- return value.length > max ? `${value.slice(0, max - 3)}...` : value;
2526
- }
2527
- function formatCodexPath(value) {
2528
- const marker = "/.almanac/";
2529
- const idx = value.indexOf(marker);
2530
- if (idx !== -1) {
2531
- return `.almanac/${value.slice(idx + marker.length)}`;
2574
+ const app = options.app ?? "claude";
2575
+ if (options.allApps === true) {
2576
+ return {
2577
+ ok: false,
2578
+ error: "capture --all-apps discovery is not implemented yet",
2579
+ fix: "run capture per app, pass transcript files explicitly, or use almanac ingest <file-or-folder>"
2580
+ };
2532
2581
  }
2533
- return value;
2534
- }
2535
- function stringField(input, key) {
2536
- const value = input[key];
2537
- return typeof value === "string" ? value : void 0;
2538
- }
2539
- function recordField(input, key) {
2540
- const value = input[key];
2541
- return isRecord(value) ? value : {};
2542
- }
2543
- function isRecord(value) {
2544
- return value !== null && typeof value === "object";
2545
- }
2546
-
2547
- // src/commands/capture.ts
2548
- import { createHash } from "crypto";
2549
- import {
2550
- createWriteStream as createWriteStream2,
2551
- existsSync as existsSync7,
2552
- statSync
2553
- } from "fs";
2554
- import { mkdir as mkdir4, readFile as readFile7, readdir as readdir3, stat } from "fs/promises";
2555
- import { homedir } from "os";
2556
- import { basename as basename3, join as join8, relative as relative3 } from "path";
2557
-
2558
- // src/commands/captureStatus.ts
2559
- import { existsSync as existsSync6 } from "fs";
2560
- import { mkdir as mkdir3, readFile as readFile6, readdir as readdir2, rename as rename2, writeFile as writeFile3 } from "fs/promises";
2561
- import { dirname, join as join7, relative as relative2 } from "path";
2562
- function captureStatePath(dir, stem) {
2563
- return join7(dir, `.capture-${stem}.state.json`);
2564
- }
2565
- async function writeCaptureRunRecord(path2, record) {
2566
- await mkdir3(dirname(path2), { recursive: true });
2567
- const tmp = `${path2}.tmp-${process.pid}`;
2568
- await writeFile3(tmp, `${JSON.stringify(record, null, 2)}
2569
- `, "utf8");
2570
- await rename2(tmp, path2);
2571
- }
2572
- function buildStartedCaptureRecord(args) {
2573
- return {
2574
- version: 1,
2575
- kind: "capture",
2576
- status: "running",
2577
- sessionId: args.sessionId ?? args.stem,
2578
- repoRoot: args.repoRoot,
2579
- pid: process.pid,
2580
- model: args.model ?? DEFAULT_AGENT_MODEL,
2581
- transcriptPath: args.transcriptPath,
2582
- startedAt: args.startedAt.toISOString(),
2583
- logPath: join7(args.almanacDir, `.capture-${args.stem}.log`),
2584
- jsonlPath: join7(args.almanacDir, `.capture-${args.stem}.jsonl`)
2585
- };
2586
- }
2587
- function finishCaptureRecord(args) {
2588
- const started = Date.parse(args.record.startedAt);
2589
- const finished = args.finishedAt.getTime();
2590
- return {
2591
- ...args.record,
2592
- status: args.status,
2593
- finishedAt: args.finishedAt.toISOString(),
2594
- durationMs: Number.isFinite(started) ? Math.max(0, finished - started) : void 0,
2595
- summary: args.summary,
2596
- error: args.error
2597
- };
2598
- }
2599
- async function runCaptureStatus(options) {
2600
- const repoRoot = findNearestAlmanacDir(options.cwd);
2601
- if (repoRoot === null) {
2582
+ if (app !== "claude") {
2602
2583
  return {
2603
- stdout: "",
2604
- stderr: "almanac: no .almanac/ found in this directory or any parent. Run 'almanac bootstrap' first.\n",
2605
- exitCode: 1
2584
+ ok: false,
2585
+ error: `capture discovery for ${app} sessions is not implemented yet`,
2586
+ fix: "pass one or more transcript files, or use almanac ingest <file-or-folder>"
2606
2587
  };
2607
2588
  }
2608
- const almanacDir = getRepoAlmanacDir(repoRoot);
2609
- const records = await readCaptureRecords(almanacDir);
2610
- const now = options.now?.() ?? /* @__PURE__ */ new Date();
2611
- const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
2612
- const views = records.map((record) => toView(record, repoRoot, now, isPidAlive)).sort((a, b) => b.sortTime - a.sortTime);
2613
- if (options.json === true) {
2589
+ const projectsDir = options.claudeProjectsDir ?? join7(homedir(), ".claude", "projects");
2590
+ if (!existsSync6(projectsDir)) {
2614
2591
  return {
2615
- stdout: `${JSON.stringify(
2616
- {
2617
- repo: repoRoot,
2618
- captures: views.map(({ sortTime: _sortTime, ...v }) => v)
2619
- },
2620
- null,
2621
- 2
2622
- )}
2623
- `,
2624
- stderr: "",
2625
- exitCode: 0
2592
+ ok: false,
2593
+ error: `could not auto-resolve Claude transcript; ${projectsDir} does not exist`,
2594
+ fix: "pass --session <id> with a Claude transcript available, or pass a transcript file"
2626
2595
  };
2627
2596
  }
2628
- return {
2629
- stdout: formatCaptureStatus(views),
2630
- stderr: "",
2631
- exitCode: 0
2632
- };
2633
- }
2634
- async function readCaptureRecords(almanacDir) {
2635
- if (!existsSync6(almanacDir)) return [];
2636
- const out = [];
2637
- const dirs = [join7(almanacDir, "logs"), almanacDir];
2638
- for (const dir of dirs) {
2639
- let entries;
2640
- try {
2641
- entries = await readdir2(dir);
2642
- } catch {
2643
- continue;
2644
- }
2645
- for (const entry of entries) {
2646
- if (!entry.startsWith(".capture-") || !entry.endsWith(".state.json")) {
2647
- continue;
2648
- }
2649
- try {
2650
- const parsed = JSON.parse(await readFile6(join7(dir, entry), "utf8"));
2651
- if (isCaptureRunRecord(parsed)) out.push(parsed);
2652
- } catch {
2653
- continue;
2654
- }
2597
+ const transcripts = await collectTranscripts(projectsDir);
2598
+ if (options.session !== void 0 && options.session.length > 0) {
2599
+ if (hasBulkScope(options)) {
2600
+ return {
2601
+ ok: false,
2602
+ error: "capture --session cannot be combined with --since, --limit, --all, or --all-apps",
2603
+ fix: "use --session for one transcript, or remove --session to capture a filtered set"
2604
+ };
2655
2605
  }
2656
- }
2657
- return out;
2658
- }
2659
- function isCaptureRunRecord(value) {
2660
- if (value === null || typeof value !== "object") return false;
2661
- const v = value;
2662
- 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";
2663
- }
2664
- function toView(record, repoRoot, now, isPidAlive) {
2665
- const started = Date.parse(record.startedAt);
2666
- const finished = record.finishedAt !== void 0 ? Date.parse(record.finishedAt) : void 0;
2667
- const elapsedMs = record.durationMs ?? (Number.isFinite(started) ? Math.max(0, (finished ?? now.getTime()) - started) : 0);
2668
- const status = record.status === "running" && !isPidAlive(record.pid) ? "stale" : record.status;
2669
- return {
2670
- status,
2671
- sessionId: record.sessionId,
2672
- model: record.model,
2673
- elapsedMs,
2674
- startedAt: record.startedAt,
2675
- finishedAt: record.finishedAt,
2676
- pid: record.pid,
2677
- logPath: relative2(repoRoot, record.logPath),
2678
- jsonlPath: relative2(repoRoot, record.jsonlPath),
2679
- summary: record.summary,
2680
- error: status === "stale" ? "process ended without a final status" : record.error,
2681
- sortTime: finished ?? (Number.isFinite(started) ? started : 0)
2682
- };
2683
- }
2684
- function formatCaptureStatus(views) {
2685
- const lines = ["Capture jobs", ""];
2686
- if (views.length === 0) {
2687
- lines.push("No capture jobs found.");
2688
- return `${lines.join("\n")}
2689
- `;
2690
- }
2691
- const active = views.filter((v) => v.status === "running" || v.status === "stale");
2692
- const finished = views.filter((v) => v.status === "done" || v.status === "failed");
2693
- if (active.length === 0) {
2694
- lines.push("No active captures.", "");
2695
- } else {
2696
- for (const view of active) {
2697
- lines.push(formatRow(view));
2698
- lines.push(` log: ${view.logPath}`);
2699
- if (view.error !== void 0) lines.push(` error: ${view.error}`);
2700
- lines.push("");
2701
- }
2702
- }
2703
- if (finished.length > 0) {
2704
- lines.push(active.length === 0 ? "Last capture:" : "Last finished:");
2705
- for (const view of finished.slice(0, 3)) {
2706
- lines.push(formatRow(view));
2707
- if (view.status === "failed") {
2708
- lines.push(` log: ${view.logPath}`);
2709
- if (view.error !== void 0) lines.push(` error: ${view.error}`);
2710
- }
2606
+ const expected = `${options.session}.jsonl`;
2607
+ const match = transcripts.find((entry) => basename3(entry.path) === expected);
2608
+ if (match === void 0) {
2609
+ return {
2610
+ ok: false,
2611
+ error: `no Claude transcript found for session ${options.session}`,
2612
+ fix: "pass an existing transcript file"
2613
+ };
2711
2614
  }
2615
+ return { ok: true, paths: [match.path], app: "claude" };
2712
2616
  }
2713
- return `${trimTrailingBlank(lines).join("\n")}
2714
- `;
2715
- }
2716
- function formatRow(view) {
2717
- const status = view.status.padEnd(7, " ");
2718
- const session = view.sessionId.padEnd(12, " ");
2719
- const model = view.model.padEnd(17, " ");
2720
- const elapsed = formatDuration(view.elapsedMs);
2721
- const summary = formatSummary(view);
2722
- return `${status} ${session} ${model} ${elapsed}${summary.length > 0 ? ` ${summary}` : ""}`;
2723
- }
2724
- function formatSummary(view) {
2725
- if (view.status === "failed") return "failed; see log";
2726
- if (view.summary === void 0) return "";
2727
- const parts = [];
2728
- if (view.summary.updated > 0) {
2729
- parts.push(`${view.summary.updated} updated`);
2730
- }
2731
- if (view.summary.created > 0) {
2732
- parts.push(`${view.summary.created} created`);
2733
- }
2734
- if (view.summary.archived > 0) {
2735
- parts.push(`${view.summary.archived} archived`);
2736
- }
2737
- return parts.length > 0 ? parts.join(", ") : "0 pages written";
2738
- }
2739
- function formatDuration(ms) {
2740
- const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
2741
- const minutes = Math.floor(totalSeconds / 60);
2742
- const seconds = totalSeconds % 60;
2743
- if (minutes < 60) return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
2744
- const hours = Math.floor(minutes / 60);
2745
- const restMinutes = minutes % 60;
2746
- return `${hours}h${restMinutes.toString().padStart(2, "0")}m`;
2747
- }
2748
- function trimTrailingBlank(lines) {
2749
- while (lines.length > 0 && lines[lines.length - 1] === "") {
2750
- lines.pop();
2751
- }
2752
- return lines;
2753
- }
2754
- function defaultIsPidAlive(pid) {
2755
- try {
2756
- process.kill(pid, 0);
2757
- return true;
2758
- } catch {
2759
- return false;
2617
+ let matches = await filterTranscriptsByCwd(transcripts, options.repoRoot);
2618
+ const cutoff = parseSinceCutoff(options.since, options.now?.() ?? /* @__PURE__ */ new Date());
2619
+ if (!cutoff.ok) return cutoff;
2620
+ const cutoffMtime = cutoff.mtime;
2621
+ if (cutoffMtime !== void 0) {
2622
+ matches = matches.filter((entry) => entry.mtime >= cutoffMtime);
2760
2623
  }
2761
- }
2762
-
2763
- // src/commands/capture.ts
2764
- var WRITER_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Agent"];
2765
- var REVIEWER_TOOLS = ["Read", "Grep", "Glob", "Bash"];
2766
- var REVIEWER_DESCRIPTION = "Reviews proposed wiki changes against the full knowledge base for cohesion, duplication, missing links, notability, and writing conventions.";
2767
- async function runCapture(options) {
2768
- const repoRoot = findNearestAlmanacDir(options.cwd);
2769
- if (repoRoot === null) {
2770
- return renderOutcome(
2771
- {
2772
- type: "needs-action",
2773
- message: "no .almanac/ found in this directory or any parent.",
2774
- fix: "run: almanac bootstrap"
2775
- },
2776
- { json: options.json }
2777
- );
2778
- }
2779
- const providerResolution = await resolveAgentSelection({
2780
- agent: options.agent,
2781
- model: options.model,
2782
- cwd: repoRoot
2783
- });
2784
- if (!providerResolution.ok) {
2785
- return renderOutcome(
2786
- { type: "error", message: providerResolution.error },
2787
- { json: options.json }
2788
- );
2789
- }
2790
- const { provider, model } = providerResolution;
2791
- const statusModel = model ?? getProviderDefaultModel(provider) ?? "provider default";
2792
- try {
2793
- await (options.assertAgentAuthFn ?? assertAgentAuth)({
2794
- provider,
2795
- spawnCli: options.spawnCli
2796
- });
2797
- } catch (err) {
2798
- const msg = err instanceof Error ? err.message : String(err);
2799
- return renderOutcome(
2800
- {
2801
- type: "needs-action",
2802
- message: msg,
2803
- fix: authFixFor2(provider),
2804
- data: { provider }
2805
- },
2806
- { json: options.json }
2807
- );
2808
- }
2809
- const almanacDir = getRepoAlmanacDir(repoRoot);
2810
- const pagesDir = join8(almanacDir, "pages");
2811
- const transcriptResolution = await resolveTranscript({
2812
- repoRoot,
2813
- explicit: options.transcriptPath,
2814
- sessionId: options.sessionId,
2815
- claudeProjectsDir: options.claudeProjectsDir
2816
- });
2817
- if (!transcriptResolution.ok) {
2818
- return renderOutcome(
2819
- {
2820
- type: "needs-action",
2821
- message: transcriptResolution.error,
2822
- fix: transcriptFix(transcriptResolution.error)
2823
- },
2824
- { json: options.json }
2825
- );
2624
+ if (matches.length === 0) {
2625
+ return {
2626
+ ok: false,
2627
+ error: `could not auto-resolve Claude transcript for cwd ${options.repoRoot}`,
2628
+ fix: "pass --session <id> or a transcript file"
2629
+ };
2826
2630
  }
2827
- const transcriptPath = transcriptResolution.path;
2828
- const snapshotBefore = await snapshotPages(pagesDir);
2829
- const systemPrompt = await loadPrompt("writer");
2830
- const reviewerPrompt = await loadPrompt("reviewer");
2831
- const agents = {
2832
- reviewer: {
2833
- description: REVIEWER_DESCRIPTION,
2834
- prompt: reviewerPrompt,
2835
- tools: REVIEWER_TOOLS
2836
- }
2837
- };
2838
- const startedAt = options.now?.() ?? /* @__PURE__ */ new Date();
2839
- const logStem = options.sessionId !== void 0 && options.sessionId.length > 0 ? options.sessionId : formatTimestamp2(startedAt);
2840
- const logsDir = join8(almanacDir, "logs");
2841
- await mkdir4(logsDir, { recursive: true });
2842
- const logName = `.capture-${logStem}.jsonl`;
2843
- const logPath = join8(logsDir, logName);
2844
- const statePath = captureStatePath(logsDir, logStem);
2845
- const stateRecord = buildStartedCaptureRecord({
2846
- repoRoot,
2847
- almanacDir: logsDir,
2848
- stem: logStem,
2849
- sessionId: options.sessionId,
2850
- transcriptPath,
2851
- model: statusModel,
2852
- startedAt
2853
- });
2854
- await writeCaptureRunRecord(statePath, stateRecord).catch(() => {
2855
- });
2856
- const logStream = createWriteStream2(logPath, { flags: "w" });
2857
- const out = process.stdout;
2858
- const formatter = new StreamingFormatter({
2859
- write: (line) => {
2860
- if (options.quiet !== true && options.json !== true) out.write(line);
2861
- }
2862
- });
2863
- formatter.setAgent("writer");
2864
- const onMessage = (msg) => {
2865
- try {
2866
- logStream.write(`${JSON.stringify(msg)}
2867
- `);
2868
- } catch {
2869
- }
2870
- formatter.handle(msg);
2871
- };
2872
- const userPrompt = `Capture this coding session.
2873
- Transcript: ${transcriptPath}.
2874
- Working directory: ${repoRoot}.`;
2875
- const runner = options.runAgent ?? runAgent;
2876
- let result;
2877
- try {
2878
- result = await runner({
2879
- systemPrompt,
2880
- prompt: userPrompt,
2881
- allowedTools: WRITER_TOOLS,
2882
- agents,
2883
- cwd: repoRoot,
2884
- provider,
2885
- model,
2886
- // Capture sessions can touch many pages; give it more headroom than
2887
- // bootstrap. The SDK treats `maxTurns` as a hard stop — better to
2888
- // overshoot than to cut off mid-review.
2889
- maxTurns: 150,
2890
- onMessage
2891
- });
2892
- } finally {
2893
- await closeStream2(logStream);
2894
- }
2895
- const snapshotAfter = await snapshotPages(pagesDir);
2896
- const delta = diffSnapshots(snapshotBefore, snapshotAfter);
2897
- const finishedAt = options.now?.() ?? /* @__PURE__ */ new Date();
2898
- const captureSummary = {
2899
- ...delta,
2900
- cost: result.cost,
2901
- turns: result.turns
2631
+ matches.sort((a, b) => b.mtime - a.mtime);
2632
+ const limit = normalizeLimit(options.limit);
2633
+ if (!limit.ok) return limit;
2634
+ const count = options.all === true ? limit.value ?? matches.length : limit.value ?? 1;
2635
+ return {
2636
+ ok: true,
2637
+ paths: matches.slice(0, count).map((entry) => entry.path),
2638
+ app: "claude"
2902
2639
  };
2903
- if (!result.success) {
2904
- await writeCaptureRunRecord(
2905
- statePath,
2906
- finishCaptureRecord({
2907
- record: stateRecord,
2908
- status: "failed",
2909
- finishedAt,
2910
- summary: captureSummary,
2911
- error: result.error ?? "unknown error"
2912
- })
2913
- ).catch(() => {
2914
- });
2915
- return renderOutcome(
2916
- {
2917
- type: "error",
2918
- message: `capture failed: ${result.error ?? "unknown error"}
2919
- (transcript: ${relative3(repoRoot, logPath)})`,
2920
- data: captureOutcomeData(result, delta, logPath, repoRoot)
2921
- },
2922
- {
2923
- json: options.json,
2924
- stdout: ""
2925
- }
2926
- );
2927
- }
2928
- await writeCaptureRunRecord(
2929
- statePath,
2930
- finishCaptureRecord({
2931
- record: stateRecord,
2932
- status: "done",
2933
- finishedAt,
2934
- summary: captureSummary
2935
- })
2936
- ).catch(() => {
2937
- });
2938
- const summary = formatSummary2(result, delta, logPath, repoRoot);
2939
- return renderOutcome(
2940
- {
2941
- type: isNoopDelta(delta) ? "noop" : "success",
2942
- message: summary,
2943
- data: captureOutcomeData(result, delta, logPath, repoRoot)
2944
- },
2945
- { json: options.json }
2946
- );
2947
2640
  }
2948
- function transcriptFix(error) {
2949
- if (error.includes("transcript not found")) {
2950
- return "pass a valid <transcript-path>";
2951
- }
2952
- return "pass --session <id> or <transcript-path>";
2641
+ function hasBulkScope(options) {
2642
+ return options.since !== void 0 || options.limit !== void 0 || options.all === true || options.allApps === true;
2953
2643
  }
2954
- function authFixFor2(provider) {
2955
- switch (provider) {
2956
- case "claude":
2957
- return "run: claude auth login --claudeai (or export ANTHROPIC_API_KEY)";
2958
- case "codex":
2959
- return "run: codex login";
2960
- case "cursor":
2961
- return "run: cursor-agent login";
2962
- default:
2963
- return "run: almanac agents doctor";
2644
+ function normalizeLimit(limit) {
2645
+ if (limit === void 0) return { ok: true };
2646
+ if (Number.isInteger(limit) && limit > 0) {
2647
+ return { ok: true, value: limit };
2964
2648
  }
2965
- }
2966
- function captureOutcomeData(result, delta, logPath, repoRoot) {
2967
2649
  return {
2968
- transcript: relative3(repoRoot, logPath),
2969
- updated: delta.updated,
2970
- created: delta.created,
2971
- archived: delta.archived,
2972
- cost: result.cost,
2973
- turns: result.turns,
2974
- sessionId: result.sessionId
2650
+ ok: false,
2651
+ error: "capture --limit must be a positive integer",
2652
+ fix: "pass --limit 1 or higher"
2975
2653
  };
2976
2654
  }
2977
- function isNoopDelta(delta) {
2978
- return delta.created === 0 && delta.updated === 0 && delta.archived === 0;
2979
- }
2980
- async function resolveTranscript(args) {
2981
- if (args.explicit !== void 0 && args.explicit.length > 0) {
2982
- if (!existsSync7(args.explicit)) {
2983
- return {
2984
- ok: false,
2985
- error: `transcript not found: ${args.explicit}`
2986
- };
2987
- }
2988
- return { ok: true, path: args.explicit };
2989
- }
2990
- const projectsDir = args.claudeProjectsDir ?? join8(homedir(), ".claude", "projects");
2991
- if (!existsSync7(projectsDir)) {
2655
+ function parseSinceCutoff(since, now) {
2656
+ if (since === void 0 || since.trim().length === 0) return { ok: true };
2657
+ const trimmed = since.trim();
2658
+ const parsedDate = Date.parse(trimmed);
2659
+ if (!Number.isNaN(parsedDate)) return { ok: true, mtime: parsedDate };
2660
+ try {
2992
2661
  return {
2993
- ok: false,
2994
- error: `could not auto-resolve transcript; ${projectsDir} does not exist. Pass --session <id> or <transcript-path>.`
2662
+ ok: true,
2663
+ mtime: now.getTime() - parseDuration(trimmed) * 1e3
2995
2664
  };
2996
- }
2997
- const allTranscripts = await collectTranscripts(projectsDir);
2998
- if (args.sessionId !== void 0 && args.sessionId.length > 0) {
2999
- const expected = `${args.sessionId}.jsonl`;
3000
- const match = allTranscripts.find((t) => basename3(t.path) === expected);
3001
- if (match === void 0) {
3002
- return {
3003
- ok: false,
3004
- error: `no transcript found for session ${args.sessionId} under ${projectsDir}`
3005
- };
3006
- }
3007
- return { ok: true, path: match.path };
3008
- }
3009
- const matches = await filterTranscriptsByCwd(allTranscripts, args.repoRoot);
3010
- if (matches.length === 0) {
2665
+ } catch {
3011
2666
  return {
3012
2667
  ok: false,
3013
- error: `could not auto-resolve transcript under ${projectsDir}; no session matches cwd ${args.repoRoot}. Pass --session <id> or <transcript-path>.`
2668
+ error: `invalid --since "${since}"`,
2669
+ fix: "pass a date or a duration like 2w, 30d, 12h, or 45m"
3014
2670
  };
3015
2671
  }
3016
- matches.sort((a, b) => b.mtime - a.mtime);
3017
- return { ok: true, path: matches[0].path };
3018
2672
  }
3019
2673
  async function collectTranscripts(projectsDir) {
3020
2674
  const out = [];
3021
2675
  let topLevel;
3022
2676
  try {
3023
- topLevel = await readdir3(projectsDir);
2677
+ topLevel = await readdir2(projectsDir);
3024
2678
  } catch {
3025
2679
  return out;
3026
2680
  }
3027
2681
  for (const name of topLevel) {
3028
- const projectDir = join8(projectsDir, name);
2682
+ const projectDir = join7(projectsDir, name);
3029
2683
  let entries;
3030
2684
  try {
3031
- entries = await readdir3(projectDir);
2685
+ entries = await readdir2(projectDir);
3032
2686
  } catch {
3033
2687
  continue;
3034
2688
  }
3035
2689
  for (const entry of entries) {
3036
2690
  if (!entry.endsWith(".jsonl")) continue;
3037
- const full = join8(projectDir, entry);
2691
+ const full = join7(projectDir, entry);
3038
2692
  try {
3039
2693
  const st = await stat(full);
3040
- if (st.isFile()) {
3041
- out.push({ path: full, mtime: st.mtimeMs });
3042
- }
2694
+ if (st.isFile()) out.push({ path: full, mtime: st.mtimeMs });
3043
2695
  } catch {
2696
+ continue;
3044
2697
  }
3045
2698
  }
3046
2699
  }
@@ -3048,17 +2701,17 @@ async function collectTranscripts(projectsDir) {
3048
2701
  }
3049
2702
  async function filterTranscriptsByCwd(transcripts, repoRoot) {
3050
2703
  const dirHash = `-${repoRoot.replace(/^\/+/, "").replace(/\//g, "-")}`;
3051
- const byDirName = transcripts.filter((t) => {
3052
- const parent = basename3(join8(t.path, ".."));
2704
+ const byDirName = transcripts.filter((entry) => {
2705
+ const parent = basename3(join7(entry.path, ".."));
3053
2706
  return parent === dirHash || parent.endsWith(dirHash);
3054
2707
  });
3055
2708
  if (byDirName.length > 0) return byDirName;
3056
2709
  const needle = `"cwd":"${repoRoot}"`;
3057
2710
  const hits = [];
3058
- for (const t of transcripts) {
2711
+ for (const transcript of transcripts) {
3059
2712
  try {
3060
- const head = await readHead(t.path, 4096);
3061
- if (head.includes(needle)) hits.push(t);
2713
+ const head = await readHead(transcript.path, 4096);
2714
+ if (head.includes(needle)) hits.push(transcript);
3062
2715
  } catch {
3063
2716
  continue;
3064
2717
  }
@@ -3069,73 +2722,293 @@ async function readHead(path2, bytes) {
3069
2722
  const content = await readFile7(path2, "utf8");
3070
2723
  return content.length > bytes ? content.slice(0, bytes) : content;
3071
2724
  }
3072
- async function snapshotPages(pagesDir) {
3073
- const out = /* @__PURE__ */ new Map();
3074
- if (!existsSync7(pagesDir)) return out;
3075
- let entries;
2725
+
2726
+ // src/commands/operations.ts
2727
+ async function runInitCommand(options) {
2728
+ const provider = await resolveProviderOrOutcome(options);
2729
+ if ("error" in provider) return provider.error;
2730
+ const background = options.background === true;
2731
+ if (options.json === true && !background) return jsonForegroundError(options.json);
3076
2732
  try {
3077
- entries = await readdir3(pagesDir);
3078
- } catch {
3079
- return out;
2733
+ const result = await runBuildOperation({
2734
+ cwd: options.cwd,
2735
+ provider: provider.value,
2736
+ background,
2737
+ context: initContext(options),
2738
+ force: options.force,
2739
+ onEvent: options.onEvent,
2740
+ startForeground: options.startForeground,
2741
+ startBackground: options.startBackground
2742
+ });
2743
+ return renderOperationResult("init", result, options.json);
2744
+ } catch (err) {
2745
+ return renderOperationError(err, options.json);
3080
2746
  }
3081
- for (const entry of entries) {
3082
- if (!entry.endsWith(".md")) continue;
3083
- const slug = entry.slice(0, -3);
3084
- const full = join8(pagesDir, entry);
3085
- try {
3086
- const st = statSync(full);
3087
- if (!st.isFile()) continue;
3088
- const content = await readFile7(full, "utf8");
3089
- const hash = createHash("sha256").update(content).digest("hex");
3090
- const fm = parseFrontmatter(content);
3091
- out.set(slug, {
3092
- slug,
3093
- hash,
3094
- archived: fm.archived_at !== null
3095
- });
3096
- } catch {
3097
- continue;
2747
+ }
2748
+ async function runCaptureCommand(options) {
2749
+ const provider = await resolveProviderOrOutcome(options);
2750
+ if ("error" in provider) return provider.error;
2751
+ if (options.json === true && options.foreground === true) {
2752
+ return jsonForegroundError(options.json);
2753
+ }
2754
+ try {
2755
+ const repoRoot = await resolveCaptureRepoRoot(options.cwd, options.json);
2756
+ if (typeof repoRoot !== "string") return repoRoot;
2757
+ const resolved = await resolveCaptureTranscripts({
2758
+ repoRoot,
2759
+ cwd: options.cwd,
2760
+ files: options.sessionFiles,
2761
+ app: options.app,
2762
+ session: options.session,
2763
+ since: options.since,
2764
+ limit: options.limit,
2765
+ all: options.all,
2766
+ allApps: options.allApps,
2767
+ claudeProjectsDir: options.claudeProjectsDir
2768
+ });
2769
+ if (!resolved.ok) {
2770
+ return renderOutcome(
2771
+ {
2772
+ type: "needs-action",
2773
+ message: resolved.error,
2774
+ fix: resolved.fix,
2775
+ data: {
2776
+ app: options.app,
2777
+ session: options.session,
2778
+ since: options.since,
2779
+ limit: options.limit,
2780
+ all: options.all,
2781
+ allApps: options.allApps
2782
+ }
2783
+ },
2784
+ { json: options.json }
2785
+ );
3098
2786
  }
2787
+ const paths = resolved.paths;
2788
+ const result = await runAbsorbOperation({
2789
+ cwd: options.cwd,
2790
+ provider: provider.value,
2791
+ background: options.foreground !== true,
2792
+ context: captureContext({ ...options, sessionFiles: paths }),
2793
+ targetKind: "session",
2794
+ targetPaths: paths,
2795
+ onEvent: options.onEvent,
2796
+ startForeground: options.startForeground,
2797
+ startBackground: options.startBackground
2798
+ });
2799
+ return renderOperationResult("capture", result, options.json);
2800
+ } catch (err) {
2801
+ return renderOperationError(err, options.json);
3099
2802
  }
3100
- return out;
3101
2803
  }
3102
- function diffSnapshots(before, after) {
3103
- let created = 0;
3104
- let updated = 0;
3105
- let archived = 0;
3106
- for (const [slug, entry] of after) {
3107
- const prev = before.get(slug);
3108
- if (prev === void 0) {
3109
- created += 1;
3110
- continue;
2804
+ async function runIngestCommand(options) {
2805
+ const provider = await resolveProviderOrOutcome(options);
2806
+ if ("error" in provider) return provider.error;
2807
+ if (options.paths.length === 0) {
2808
+ return renderOutcome(
2809
+ { type: "error", message: "ingest requires at least one file or folder" },
2810
+ { json: options.json }
2811
+ );
2812
+ }
2813
+ if (options.json === true && options.foreground === true) {
2814
+ return jsonForegroundError(options.json);
2815
+ }
2816
+ try {
2817
+ const paths = options.paths.map((path2) => resolve2(options.cwd, path2));
2818
+ const result = await runAbsorbOperation({
2819
+ cwd: options.cwd,
2820
+ provider: provider.value,
2821
+ background: options.foreground !== true,
2822
+ context: ingestContext(paths),
2823
+ targetKind: "path",
2824
+ targetPaths: paths,
2825
+ onEvent: options.onEvent,
2826
+ startForeground: options.startForeground,
2827
+ startBackground: options.startBackground
2828
+ });
2829
+ return renderOperationResult("ingest", result, options.json);
2830
+ } catch (err) {
2831
+ return renderOperationError(err, options.json);
2832
+ }
2833
+ }
2834
+ async function runGardenCommand(options) {
2835
+ const provider = await resolveProviderOrOutcome(options);
2836
+ if ("error" in provider) return provider.error;
2837
+ if (options.json === true && options.foreground === true) {
2838
+ return jsonForegroundError(options.json);
2839
+ }
2840
+ try {
2841
+ const result = await runGardenOperation({
2842
+ cwd: options.cwd,
2843
+ provider: provider.value,
2844
+ background: options.foreground !== true,
2845
+ onEvent: options.onEvent,
2846
+ startForeground: options.startForeground,
2847
+ startBackground: options.startBackground
2848
+ });
2849
+ return renderOperationResult("garden", result, options.json);
2850
+ } catch (err) {
2851
+ return renderOperationError(err, options.json);
2852
+ }
2853
+ }
2854
+ function parseUsing(value) {
2855
+ if (value === void 0 || value.trim().length === 0) {
2856
+ return { id: "claude" };
2857
+ }
2858
+ const [rawProvider, ...modelParts] = value.split("/");
2859
+ if (!isProviderId(rawProvider)) {
2860
+ throw new Error(
2861
+ `invalid --using "${value}" (expected claude, codex, or cursor)`
2862
+ );
2863
+ }
2864
+ const model = modelParts.join("/");
2865
+ return {
2866
+ id: rawProvider,
2867
+ model: model.length > 0 ? model : void 0
2868
+ };
2869
+ }
2870
+ async function resolveProviderOrOutcome(options) {
2871
+ try {
2872
+ if (options.using !== void 0) {
2873
+ return { value: parseUsing(options.using) };
3111
2874
  }
3112
- if (prev.hash !== entry.hash) {
3113
- if (!prev.archived && entry.archived) {
3114
- archived += 1;
3115
- } else {
3116
- updated += 1;
2875
+ const config = await readConfig({ cwd: options.cwd });
2876
+ const id = config.agent.default;
2877
+ const model = config.agent.models[id] ?? void 0;
2878
+ return { value: { id, model: model ?? void 0 } };
2879
+ } catch (err) {
2880
+ return {
2881
+ error: renderOutcome(
2882
+ { type: "error", message: err instanceof Error ? err.message : String(err) },
2883
+ { json: options.json }
2884
+ )
2885
+ };
2886
+ }
2887
+ }
2888
+ function isProviderId(value) {
2889
+ return value === "claude" || value === "codex" || value === "cursor";
2890
+ }
2891
+ function renderOperationResult(operation, result, json) {
2892
+ const record = result.background?.record ?? result.foreground?.record;
2893
+ const status = record?.status;
2894
+ const foregroundResult = result.foreground?.result;
2895
+ if (result.mode === "foreground" && (foregroundResult?.success === false || status === "failed")) {
2896
+ const failure = foregroundResult?.failure ?? record?.failure;
2897
+ return renderOutcome(
2898
+ {
2899
+ type: "error",
2900
+ message: renderOperationFailureMessage({
2901
+ operation,
2902
+ runId: result.runId,
2903
+ error: foregroundResult?.error,
2904
+ failure
2905
+ }),
2906
+ data: {
2907
+ operation,
2908
+ runId: result.runId,
2909
+ mode: result.mode,
2910
+ status,
2911
+ pid: record?.pid,
2912
+ logPath: record?.logPath,
2913
+ error: foregroundResult?.error,
2914
+ failure
2915
+ }
2916
+ },
2917
+ { json }
2918
+ );
2919
+ }
2920
+ return renderOutcome(
2921
+ {
2922
+ type: "success",
2923
+ message: result.mode === "background" ? `${operation} started: ${result.runId}` : `${operation} finished: ${result.runId}`,
2924
+ data: {
2925
+ operation,
2926
+ runId: result.runId,
2927
+ mode: result.mode,
2928
+ status,
2929
+ pid: record?.pid,
2930
+ logPath: record?.logPath
3117
2931
  }
3118
- }
2932
+ },
2933
+ { json }
2934
+ );
2935
+ }
2936
+ function renderOperationFailureMessage(args) {
2937
+ const lines = [`${args.operation} failed: ${args.runId}`];
2938
+ if (args.failure !== void 0) {
2939
+ lines.push(`Reason: ${args.failure.message}`);
2940
+ if (args.failure.fix !== void 0) lines.push(`Fix: ${args.failure.fix}`);
2941
+ return lines.join("\n");
3119
2942
  }
3120
- return { created, updated, archived };
2943
+ if (args.error !== void 0) lines[0] += `: ${args.error}`;
2944
+ return lines.join("\n");
3121
2945
  }
3122
- function formatSummary2(result, delta, logPath, repoRoot) {
3123
- const rel = relative3(repoRoot, logPath);
3124
- const usage = formatRunUsage(result);
3125
- const { created, updated, archived } = delta;
3126
- if (created === 0 && updated === 0 && archived === 0) {
3127
- return `[capture] no new knowledge met the notability bar (0 pages written), ${usage} (transcript: ${rel})`;
2946
+ function renderOperationError(err, json) {
2947
+ const message = err instanceof Error ? err.message : String(err);
2948
+ if (message.includes("no .almanac/")) {
2949
+ return renderOutcome(
2950
+ {
2951
+ type: "needs-action",
2952
+ message,
2953
+ fix: "run: almanac init"
2954
+ },
2955
+ { json }
2956
+ );
3128
2957
  }
3129
- return `[done] ${updated} page${updated === 1 ? "" : "s"} updated, ${created} created, ${archived} archived, ${usage} (transcript: ${rel})`;
2958
+ return renderOutcome({ type: "error", message }, { json });
3130
2959
  }
3131
- function formatTimestamp2(d) {
3132
- const pad = (n) => n.toString().padStart(2, "0");
3133
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
2960
+ async function resolveCaptureRepoRoot(cwd, json) {
2961
+ const { findNearestAlmanacDir: findNearestAlmanacDir2 } = await import("./paths-O5CZADP2.js");
2962
+ const repoRoot = findNearestAlmanacDir2(cwd);
2963
+ if (repoRoot !== null) return repoRoot;
2964
+ return renderOutcome(
2965
+ {
2966
+ type: "needs-action",
2967
+ message: "no .almanac/ found in this directory or any parent",
2968
+ fix: "run: almanac init"
2969
+ },
2970
+ { json }
2971
+ );
3134
2972
  }
3135
- function closeStream2(stream) {
3136
- return new Promise((resolve) => {
3137
- stream.end(() => resolve());
3138
- });
2973
+ function jsonForegroundError(json) {
2974
+ return renderOutcome({
2975
+ type: "error",
2976
+ message: "--json is only supported for background job start responses"
2977
+ }, { json });
2978
+ }
2979
+ function initContext(options) {
2980
+ return [
2981
+ "Command context:",
2982
+ `- Command: init`,
2983
+ `- Force requested: ${options.force === true ? "yes" : "no"}`,
2984
+ `- Non-interactive confirmation: ${options.yes === true ? "yes" : "no"}`
2985
+ ].join("\n");
2986
+ }
2987
+ function captureContext(options) {
2988
+ const lines = ["Command context:", "- Command: capture"];
2989
+ if (options.app !== void 0) lines.push(`- App: ${options.app}`);
2990
+ if (options.session !== void 0) lines.push(`- Session id: ${options.session}`);
2991
+ if (options.since !== void 0) lines.push(`- Since: ${options.since}`);
2992
+ if (options.limit !== void 0) lines.push(`- Limit: ${options.limit}`);
2993
+ if (options.all === true) lines.push("- Capture all matching sessions");
2994
+ if (options.allApps === true) lines.push("- Capture all supported apps");
2995
+ const paths = options.sessionFiles ?? [];
2996
+ if (paths.length > 0) {
2997
+ lines.push("- Session/transcript files:");
2998
+ for (const path2 of paths) lines.push(` - ${path2}`);
2999
+ }
3000
+ if (paths.length === 0 && options.session === void 0) {
3001
+ lines.push("- No explicit session file or session id was provided.");
3002
+ }
3003
+ return lines.join("\n");
3004
+ }
3005
+ function ingestContext(paths) {
3006
+ return [
3007
+ "Command context:",
3008
+ "- Command: ingest",
3009
+ "- Paths:",
3010
+ ...paths.map((path2) => ` - ${path2}`)
3011
+ ].join("\n");
3139
3012
  }
3140
3013
 
3141
3014
  // src/commands/reindex.ts
@@ -3153,57 +3026,132 @@ async function runReindex(options) {
3153
3026
 
3154
3027
  // src/cli/register-wiki-lifecycle-commands.ts
3155
3028
  function registerWikiLifecycleCommands(program) {
3156
- program.command("bootstrap").description(
3157
- "scaffold a wiki in this repo via an AI agent (requires ANTHROPIC_API_KEY or Claude subscription)"
3158
- ).option("--quiet", "suppress per-tool streaming; print only the final line").option("--agent <agent>", "agent provider: claude or codex").option("--model <model>", "override the agent model").option("--force", "overwrite an existing populated wiki (default: refuse)").option("--json", "emit structured CommandOutcome JSON").action(
3029
+ program.command("init").description("initialize and build this repo's CodeAlmanac wiki").option("--using <provider[/model]>", "provider and optional model").option("--background", "start as a background job").option("--json", "emit structured JSON for background job start").option("--force", "allow rebuilding an existing wiki").option("-y, --yes", "confirm non-interactively").action(
3159
3030
  async (opts) => {
3160
- const result = await runBootstrap({
3031
+ const result = await runInitCommand({
3161
3032
  cwd: process.cwd(),
3162
- quiet: opts.quiet,
3163
- agent: opts.agent,
3164
- model: opts.model,
3033
+ using: opts.using,
3034
+ background: opts.background,
3035
+ json: opts.json,
3165
3036
  force: opts.force,
3166
- json: opts.json
3037
+ yes: opts.yes,
3038
+ onEvent: opts.background === true ? void 0 : writeForegroundEvent
3167
3039
  });
3168
3040
  emit(result);
3169
3041
  }
3170
3042
  );
3171
- 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 or codex").option("--model <model>", "override the agent model").option("--json", "emit structured CommandOutcome JSON").action(
3172
- async (transcript, opts) => {
3043
+ const capture = program.command("capture [sessionFiles...]").alias("c").description("absorb coding-session knowledge into the wiki").option("--app <app>", "source app: claude, codex, cursor, or generic").option("--session <id>", "target a specific session by ID").option("--since <duration-or-date>", "capture sessions since a time").option("--limit <n>", "maximum sessions to capture", parsePositiveInt).option("--all", "capture all matching sessions").option("--all-apps", "capture from all supported apps").option("--using <provider[/model]>", "provider and optional model").option("--foreground", "run now instead of starting a background job").option("--json", "emit structured JSON for background job start").option("-y, --yes", "confirm non-interactively").action(
3044
+ async (sessionFiles, opts) => {
3173
3045
  await autoRegisterIfNeeded(process.cwd());
3174
- const result = await runCapture({
3046
+ const result = await runCaptureCommand({
3175
3047
  cwd: process.cwd(),
3176
- transcriptPath: transcript,
3177
- sessionId: opts.session,
3178
- quiet: opts.quiet,
3179
- agent: opts.agent,
3180
- model: opts.model,
3181
- json: opts.json
3048
+ sessionFiles,
3049
+ app: opts.app,
3050
+ session: opts.session,
3051
+ since: opts.since,
3052
+ limit: opts.limit,
3053
+ all: opts.all,
3054
+ allApps: opts.allApps,
3055
+ using: opts.using,
3056
+ foreground: opts.foreground,
3057
+ json: opts.json,
3058
+ yes: opts.yes,
3059
+ onEvent: opts.foreground === true ? writeForegroundEvent : void 0
3182
3060
  });
3183
3061
  emit(result);
3184
3062
  }
3185
3063
  );
3186
- capture.command("status").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
3187
- await autoRegisterIfNeeded(process.cwd());
3188
- const result = await runCaptureStatus({
3064
+ program.command("ingest <paths...>").description("absorb knowledge from one or more files or folders").option("--using <provider[/model]>", "provider and optional model").option("--foreground", "run now instead of starting a background job").option("--json", "emit structured JSON for background job start").option("-y, --yes", "confirm non-interactively").action(
3065
+ async (paths, opts) => {
3066
+ await autoRegisterIfNeeded(process.cwd());
3067
+ const result = await runIngestCommand({
3068
+ cwd: process.cwd(),
3069
+ paths,
3070
+ using: opts.using,
3071
+ foreground: opts.foreground,
3072
+ json: opts.json,
3073
+ yes: opts.yes,
3074
+ onEvent: opts.foreground === true ? writeForegroundEvent : void 0
3075
+ });
3076
+ emit(result);
3077
+ }
3078
+ );
3079
+ program.command("garden").description("clean up, reconcile, and improve the wiki").option("--using <provider[/model]>", "provider and optional model").option("--foreground", "run now instead of starting a background job").option("--json", "emit structured JSON for background job start").option("-y, --yes", "confirm non-interactively").action(
3080
+ async (opts) => {
3081
+ await autoRegisterIfNeeded(process.cwd());
3082
+ const result = await runGardenCommand({
3083
+ cwd: process.cwd(),
3084
+ using: opts.using,
3085
+ foreground: opts.foreground,
3086
+ json: opts.json,
3087
+ yes: opts.yes,
3088
+ onEvent: opts.foreground === true ? writeForegroundEvent : void 0
3089
+ });
3090
+ emit(result);
3091
+ }
3092
+ );
3093
+ const jobs = program.command("jobs").description("show and manage CodeAlmanac background jobs");
3094
+ jobs.command("list", { isDefault: true }).description("list runs for this wiki").option("--json", "emit structured JSON").action(async (opts) => {
3095
+ const result = await runJobsList({
3189
3096
  cwd: process.cwd(),
3190
3097
  json: opts.json
3191
3098
  });
3192
3099
  emit(result);
3193
3100
  });
3194
- program.command("ps").description("deprecated alias for capture status").option("--json", "emit structured JSON").action(async (opts) => {
3195
- await autoRegisterIfNeeded(process.cwd());
3196
- const result = await runCaptureStatus({
3101
+ jobs.command("show <run-id>").description("show one run record").option("--json", "emit structured JSON").action(async (runId, opts) => {
3102
+ const result = await runJobsShow({
3103
+ cwd: process.cwd(),
3104
+ runId,
3105
+ json: opts.json
3106
+ });
3107
+ emit(result);
3108
+ });
3109
+ jobs.command("logs <run-id>").description("print a run's JSONL event log").option("--json", "emit structured errors as JSON").action(async (runId, opts) => {
3110
+ const result = await runJobsLogs({
3111
+ cwd: process.cwd(),
3112
+ runId,
3113
+ json: opts.json
3114
+ });
3115
+ emit(result);
3116
+ });
3117
+ jobs.command("attach <run-id>").description("stream a run log until the job exits").option("--json", "emit structured errors as JSON").action(async (runId, opts) => {
3118
+ const result = await streamJobsAttach({
3119
+ cwd: process.cwd(),
3120
+ runId,
3121
+ json: opts.json
3122
+ });
3123
+ emit(result);
3124
+ });
3125
+ jobs.command("cancel <run-id>").description("cancel a running or queued job").option("--json", "emit structured JSON").action(async (runId, opts) => {
3126
+ const result = await runJobsCancel({
3127
+ cwd: process.cwd(),
3128
+ runId,
3129
+ json: opts.json
3130
+ });
3131
+ emit(result);
3132
+ });
3133
+ capture.command("status").description("deprecated alias for jobs").option("--json", "emit structured JSON").action(async (opts) => {
3134
+ const result = await runJobsList({
3197
3135
  cwd: process.cwd(),
3198
3136
  json: opts.json
3199
3137
  });
3200
3138
  emit(withWarning(
3201
3139
  result,
3202
- deprecationWarning("almanac ps", "almanac capture status")
3140
+ deprecationWarning("almanac capture status", "almanac jobs")
3141
+ ));
3142
+ });
3143
+ program.command("ps").description("deprecated alias for jobs").option("--json", "emit structured JSON").action(async (opts) => {
3144
+ const result = await runJobsList({
3145
+ cwd: process.cwd(),
3146
+ json: opts.json
3147
+ });
3148
+ emit(withWarning(
3149
+ result,
3150
+ deprecationWarning("almanac ps", "almanac jobs")
3203
3151
  ));
3204
3152
  });
3205
3153
  const hook = program.command("hook").description("manage the SessionEnd auto-capture hook");
3206
- hook.command("install").description("add a SessionEnd entry that runs 'almanac capture' on session end").option("--source <source>", "claude, codex, or all").action(async (opts) => {
3154
+ 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) => {
3207
3155
  const result = await runHookInstall({
3208
3156
  source: normalizeHookSource(opts.source)
3209
3157
  });
@@ -3227,6 +3175,27 @@ function registerWikiLifecycleCommands(program) {
3227
3175
  if (result.exitCode !== 0) process.exitCode = result.exitCode;
3228
3176
  });
3229
3177
  }
3178
+ function writeForegroundEvent(event) {
3179
+ const line = formatForegroundEvent(event);
3180
+ if (line !== null) process.stdout.write(`${line}
3181
+ `);
3182
+ }
3183
+ function formatForegroundEvent(event) {
3184
+ switch (event.type) {
3185
+ case "text":
3186
+ return event.content.trim().length > 0 ? event.content.trim() : null;
3187
+ case "tool_use":
3188
+ return `[tool] ${event.tool}`;
3189
+ case "tool_summary":
3190
+ return `[tool] ${event.summary}`;
3191
+ case "error":
3192
+ return null;
3193
+ case "done":
3194
+ return event.error !== void 0 ? null : "[done]";
3195
+ default:
3196
+ return null;
3197
+ }
3198
+ }
3230
3199
  function normalizeHookSource(source) {
3231
3200
  if (source === "claude" || source === "codex" || source === "cursor" || source === "all") {
3232
3201
  return source;
@@ -3244,4 +3213,4 @@ function registerCommands(program) {
3244
3213
  export {
3245
3214
  registerCommands
3246
3215
  };
3247
- //# sourceMappingURL=register-commands-2F6SXLDI.js.map
3216
+ //# sourceMappingURL=register-commands-LULZUSPO.js.map