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.
- package/LICENSE +21 -133
- package/README.md +20 -15
- package/dist/{agents-HYRWRHRX.js → agents-V2ZOIACP.js} +6 -5
- package/dist/{chunk-PDFS5VFE.js → chunk-447U3GQJ.js} +5 -17
- package/dist/chunk-447U3GQJ.js.map +1 -0
- package/dist/{chunk-3E7JNMTZ.js → chunk-5BWUMAOX.js} +4 -29
- package/dist/chunk-5BWUMAOX.js.map +1 -0
- package/dist/{chunk-KQUVMF27.js → chunk-BFIG2CXM.js} +2 -516
- package/dist/chunk-BFIG2CXM.js.map +1 -0
- package/dist/{chunk-K2JBCB7R.js → chunk-BQY5L3DL.js} +7 -43
- package/dist/chunk-BQY5L3DL.js.map +1 -0
- package/dist/{chunk-F53U6JQG.js → chunk-CQJVM34R.js} +2 -2
- package/dist/chunk-FUBE6KCO.js +124 -0
- package/dist/chunk-FUBE6KCO.js.map +1 -0
- package/dist/chunk-IZBXXAVL.js +524 -0
- package/dist/chunk-IZBXXAVL.js.map +1 -0
- package/dist/{chunk-7JUX4ADQ.js → chunk-IZT6RBHS.js} +1 -1
- package/dist/{chunk-DW32TL5W.js → chunk-JLQZELHQ.js} +18 -58
- package/dist/chunk-JLQZELHQ.js.map +1 -0
- package/dist/{chunk-2BNDNGUR.js → chunk-KZXWPG4P.js} +4 -8
- package/dist/{chunk-2BNDNGUR.js.map → chunk-KZXWPG4P.js.map} +1 -1
- package/dist/{chunk-GPFVEF6V.js → chunk-QIA22IAM.js} +6 -24
- package/dist/chunk-QIA22IAM.js.map +1 -0
- package/dist/{chunk-J7DNV2DH.js → chunk-RALBM6HZ.js} +43 -355
- package/dist/chunk-RALBM6HZ.js.map +1 -0
- package/dist/{chunk-HJ3WREGP.js → chunk-U5DLLWIC.js} +3 -3
- package/dist/chunk-WL4UE7Q6.js +1386 -0
- package/dist/chunk-WL4UE7Q6.js.map +1 -0
- package/dist/{chunk-VXDPUOQ5.js → chunk-ZUQN5Y3K.js} +129 -382
- package/dist/chunk-ZUQN5Y3K.js.map +1 -0
- package/dist/{chunk-ODJAAJGZ.js → chunk-ZZLLOAI6.js} +3 -3
- package/dist/{cli-MKXCNEMW.js → cli-XWPNARA6.js} +37 -20
- package/dist/cli-XWPNARA6.js.map +1 -0
- package/dist/codealmanac.js +1 -1
- package/dist/{config-F7FKEQ7F.js → config-KH3JUMG6.js} +4 -4
- package/dist/doctor-ENJT665Z.js +18 -0
- package/dist/{hook-4SVX446M.js → hook-2NP3UE7U.js} +2 -4
- package/dist/paths-O5CZADP2.js +14 -0
- package/dist/process-KFSLENL3.js +61 -0
- package/dist/{register-commands-2F6SXLDI.js → register-commands-LULZUSPO.js} +999 -1030
- package/dist/register-commands-LULZUSPO.js.map +1 -0
- package/dist/uninstall-BD4MMQ7M.js +16 -0
- package/dist/uninstall-BD4MMQ7M.js.map +1 -0
- package/dist/update-XSKPDFMJ.js +11 -0
- package/dist/update-XSKPDFMJ.js.map +1 -0
- package/dist/{wiki-IGNRNLUZ.js → wiki-O4RWMAE6.js} +8 -6
- package/dist/wiki-O4RWMAE6.js.map +1 -0
- package/guides/mini.md +8 -6
- package/guides/reference.md +89 -32
- package/hooks/almanac-capture.sh +7 -8
- package/package.json +3 -4
- package/prompts/agents/.gitkeep +1 -0
- package/prompts/base/notability.md +139 -0
- package/prompts/base/purpose.md +85 -0
- package/prompts/base/syntax.md +114 -0
- package/prompts/operations/absorb.md +43 -0
- package/prompts/operations/build.md +49 -0
- package/prompts/operations/garden.md +51 -0
- package/COMMERCIAL.md +0 -9
- package/dist/chunk-3E7JNMTZ.js.map +0 -1
- package/dist/chunk-DW32TL5W.js.map +0 -1
- package/dist/chunk-GPFVEF6V.js.map +0 -1
- package/dist/chunk-J7DNV2DH.js.map +0 -1
- package/dist/chunk-K2JBCB7R.js.map +0 -1
- package/dist/chunk-KQUVMF27.js.map +0 -1
- package/dist/chunk-PDFS5VFE.js.map +0 -1
- package/dist/chunk-VXDPUOQ5.js.map +0 -1
- package/dist/cli-MKXCNEMW.js.map +0 -1
- package/dist/doctor-37UH3HT5.js +0 -17
- package/dist/register-commands-2F6SXLDI.js.map +0 -1
- package/dist/uninstall-C62ZOK32.js +0 -17
- package/dist/update-2UGOFN5C.js +0 -11
- package/dist/wiki-IGNRNLUZ.js.map +0 -1
- package/prompts/bootstrap.md +0 -176
- package/prompts/reviewer.md +0 -129
- package/prompts/writer.md +0 -134
- /package/dist/{agents-HYRWRHRX.js.map → agents-V2ZOIACP.js.map} +0 -0
- /package/dist/{chunk-F53U6JQG.js.map → chunk-CQJVM34R.js.map} +0 -0
- /package/dist/{chunk-7JUX4ADQ.js.map → chunk-IZT6RBHS.js.map} +0 -0
- /package/dist/{chunk-HJ3WREGP.js.map → chunk-U5DLLWIC.js.map} +0 -0
- /package/dist/{chunk-ODJAAJGZ.js.map → chunk-ZZLLOAI6.js.map} +0 -0
- /package/dist/{config-F7FKEQ7F.js.map → config-KH3JUMG6.js.map} +0 -0
- /package/dist/{doctor-37UH3HT5.js.map → doctor-ENJT665Z.js.map} +0 -0
- /package/dist/{hook-4SVX446M.js.map → hook-2NP3UE7U.js.map} +0 -0
- /package/dist/{uninstall-C62ZOK32.js.map → paths-O5CZADP2.js.map} +0 -0
- /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-
|
|
31
|
+
} from "./chunk-QIA22IAM.js";
|
|
18
32
|
import {
|
|
19
33
|
runConfigGet,
|
|
20
34
|
runConfigList,
|
|
21
35
|
runConfigSet,
|
|
22
36
|
runConfigUnset
|
|
23
|
-
} from "./chunk-
|
|
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-
|
|
61
|
+
} from "./chunk-BFIG2CXM.js";
|
|
47
62
|
import {
|
|
48
63
|
runDoctor
|
|
49
|
-
} from "./chunk-
|
|
50
|
-
import "./chunk-
|
|
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-
|
|
76
|
+
} from "./chunk-BQY5L3DL.js";
|
|
61
77
|
import {
|
|
62
78
|
runSetup
|
|
63
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-ZUQN5Y3K.js";
|
|
64
80
|
import {
|
|
65
81
|
runHookInstall,
|
|
66
82
|
runHookStatus,
|
|
67
83
|
runHookUninstall
|
|
68
|
-
} from "./chunk-
|
|
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-
|
|
89
|
+
} from "./chunk-5BWUMAOX.js";
|
|
87
90
|
import {
|
|
88
91
|
findNearestAlmanacDir,
|
|
89
92
|
getRepoAlmanacDir
|
|
90
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
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/
|
|
1902
|
-
import {
|
|
1903
|
-
|
|
1904
|
-
|
|
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
|
|
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
|
-
"
|
|
1913
|
-
"
|
|
1914
|
-
"
|
|
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
|
|
2222
|
+
return readFile5(resolvePromptPath(dir, name), "utf8");
|
|
1950
2223
|
}
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
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
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
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/
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
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
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
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/
|
|
2023
|
-
async function
|
|
2024
|
-
|
|
2025
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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/
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
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
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
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
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
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
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
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
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
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
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
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
|
|
2609
|
-
|
|
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
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
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
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
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
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
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
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
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
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
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
|
-
|
|
2828
|
-
const
|
|
2829
|
-
|
|
2830
|
-
const
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
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
|
|
2949
|
-
|
|
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
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
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
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
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
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
if (
|
|
2982
|
-
|
|
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:
|
|
2994
|
-
|
|
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: `
|
|
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
|
|
2677
|
+
topLevel = await readdir2(projectsDir);
|
|
3024
2678
|
} catch {
|
|
3025
2679
|
return out;
|
|
3026
2680
|
}
|
|
3027
2681
|
for (const name of topLevel) {
|
|
3028
|
-
const projectDir =
|
|
2682
|
+
const projectDir = join7(projectsDir, name);
|
|
3029
2683
|
let entries;
|
|
3030
2684
|
try {
|
|
3031
|
-
entries = await
|
|
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 =
|
|
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((
|
|
3052
|
-
const parent = basename3(
|
|
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
|
|
2711
|
+
for (const transcript of transcripts) {
|
|
3059
2712
|
try {
|
|
3060
|
-
const head = await readHead(
|
|
3061
|
-
if (head.includes(needle)) hits.push(
|
|
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
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
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
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
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
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
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
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
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
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
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
|
-
|
|
2943
|
+
if (args.error !== void 0) lines[0] += `: ${args.error}`;
|
|
2944
|
+
return lines.join("\n");
|
|
3121
2945
|
}
|
|
3122
|
-
function
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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
|
|
2958
|
+
return renderOutcome({ type: "error", message }, { json });
|
|
3130
2959
|
}
|
|
3131
|
-
function
|
|
3132
|
-
const
|
|
3133
|
-
|
|
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
|
|
3136
|
-
return
|
|
3137
|
-
|
|
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("
|
|
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
|
|
3031
|
+
const result = await runInitCommand({
|
|
3161
3032
|
cwd: process.cwd(),
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3033
|
+
using: opts.using,
|
|
3034
|
+
background: opts.background,
|
|
3035
|
+
json: opts.json,
|
|
3165
3036
|
force: opts.force,
|
|
3166
|
-
|
|
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 [
|
|
3172
|
-
async (
|
|
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
|
|
3046
|
+
const result = await runCaptureCommand({
|
|
3175
3047
|
cwd: process.cwd(),
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
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
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
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
|
-
|
|
3195
|
-
await
|
|
3196
|
-
|
|
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
|
|
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-
|
|
3216
|
+
//# sourceMappingURL=register-commands-LULZUSPO.js.map
|