codealmanac 0.1.10 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -104
- package/dist/agents-A4II4YJC.js +15 -0
- package/dist/auth-S5DVUIUJ.js +18 -0
- package/dist/{chunk-Z4MWLVS2.js → chunk-447U3GQJ.js} +162 -5
- package/dist/chunk-447U3GQJ.js.map +1 -0
- package/dist/{chunk-QLHJP2XK.js → chunk-B2AGSRXL.js} +13 -9
- package/dist/{chunk-QLHJP2XK.js.map → chunk-B2AGSRXL.js.map} +1 -1
- package/dist/{chunk-AXFPUHBN.js → chunk-F53U6JQG.js} +8 -49
- package/dist/chunk-F53U6JQG.js.map +1 -0
- package/dist/{chunk-3C5SY5SE.js → chunk-KQUVMF27.js} +5 -2
- package/dist/chunk-KQUVMF27.js.map +1 -0
- package/dist/{chunk-BJVZLP6O.js → chunk-MX2EW5MR.js} +3 -3
- package/dist/{chunk-Z6MBJ3D2.js → chunk-QQHIVTXT.js} +6 -4
- package/dist/{chunk-Z6MBJ3D2.js.map → chunk-QQHIVTXT.js.map} +1 -1
- package/dist/chunk-R3URPHGH.js +194 -0
- package/dist/chunk-R3URPHGH.js.map +1 -0
- package/dist/chunk-SSYMRT4I.js +126 -0
- package/dist/chunk-SSYMRT4I.js.map +1 -0
- package/dist/{chunk-QHQ6YH7U.js → chunk-V3QOQSXI.js} +5 -3
- package/dist/{chunk-QHQ6YH7U.js.map → chunk-V3QOQSXI.js.map} +1 -1
- package/dist/chunk-WRUSDYYE.js +97 -0
- package/dist/chunk-WRUSDYYE.js.map +1 -0
- package/dist/{chunk-3LC55TG6.js → chunk-ZDJSJIB6.js} +77 -126
- package/dist/chunk-ZDJSJIB6.js.map +1 -0
- package/dist/{cli-W3OYVJYH.js → cli-MZEXRV6E.js} +238 -24
- package/dist/cli-MZEXRV6E.js.map +1 -0
- package/dist/codealmanac.js +1 -1
- package/dist/doctor-3BYSF3JD.js +17 -0
- package/dist/{hook-CRJMWSSO.js → hook-2NP3UE7U.js} +2 -2
- package/dist/{register-commands-JHC2OFKM.js → register-commands-DPH4ZWEE.js} +621 -60
- package/dist/register-commands-DPH4ZWEE.js.map +1 -0
- package/dist/uninstall-FDIOBAAR.js +15 -0
- package/dist/uninstall-FDIOBAAR.js.map +1 -0
- package/dist/update-RAF7QRYF.js +11 -0
- package/dist/update-RAF7QRYF.js.map +1 -0
- package/dist/{wiki-IPSRRGOT.js → wiki-IGNRNLUZ.js} +2 -2
- package/hooks/almanac-capture.sh +40 -7
- package/package.json +3 -2
- package/dist/chunk-3C5SY5SE.js.map +0 -1
- package/dist/chunk-3LC55TG6.js.map +0 -1
- package/dist/chunk-AXFPUHBN.js.map +0 -1
- package/dist/chunk-Z4MWLVS2.js.map +0 -1
- package/dist/cli-W3OYVJYH.js.map +0 -1
- package/dist/doctor-ODFNJUKH.js +0 -15
- package/dist/register-commands-JHC2OFKM.js.map +0 -1
- package/dist/uninstall-HE2Z2LN2.js +0 -12
- package/dist/update-IL243I4E.js +0 -10
- /package/dist/{doctor-ODFNJUKH.js.map → agents-A4II4YJC.js.map} +0 -0
- /package/dist/{hook-CRJMWSSO.js.map → auth-S5DVUIUJ.js.map} +0 -0
- /package/dist/{chunk-BJVZLP6O.js.map → chunk-MX2EW5MR.js.map} +0 -0
- /package/dist/{uninstall-HE2Z2LN2.js.map → doctor-3BYSF3JD.js.map} +0 -0
- /package/dist/{update-IL243I4E.js.map → hook-2NP3UE7U.js.map} +0 -0
- /package/dist/{wiki-IPSRRGOT.js.map → wiki-IGNRNLUZ.js.map} +0 -0
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
collectOption,
|
|
4
|
+
emit,
|
|
5
|
+
parsePositiveInt,
|
|
6
|
+
readStdin
|
|
7
|
+
} from "./chunk-P3LDTCLB.js";
|
|
8
|
+
import {
|
|
9
|
+
assertAgentAuth,
|
|
10
|
+
runAgentsList,
|
|
11
|
+
runSetAgentModel,
|
|
12
|
+
runSetDefaultAgent
|
|
13
|
+
} from "./chunk-R3URPHGH.js";
|
|
2
14
|
import {
|
|
3
15
|
addEntry,
|
|
4
16
|
ancestorsInFile,
|
|
@@ -21,24 +33,12 @@ import {
|
|
|
21
33
|
titleCase,
|
|
22
34
|
toKebabCase,
|
|
23
35
|
writeTopicsFile
|
|
24
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-KQUVMF27.js";
|
|
25
37
|
import {
|
|
26
38
|
runDoctor
|
|
27
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-B2AGSRXL.js";
|
|
40
|
+
import "./chunk-V3QOQSXI.js";
|
|
28
41
|
import "./chunk-4CODZRHH.js";
|
|
29
|
-
import {
|
|
30
|
-
runUninstall
|
|
31
|
-
} from "./chunk-BJVZLP6O.js";
|
|
32
|
-
import {
|
|
33
|
-
runUpdate
|
|
34
|
-
} from "./chunk-Z6MBJ3D2.js";
|
|
35
|
-
import {
|
|
36
|
-
collectOption,
|
|
37
|
-
emit,
|
|
38
|
-
parsePositiveInt,
|
|
39
|
-
readStdin
|
|
40
|
-
} from "./chunk-P3LDTCLB.js";
|
|
41
|
-
import "./chunk-QHQ6YH7U.js";
|
|
42
42
|
import {
|
|
43
43
|
BLUE,
|
|
44
44
|
BOLD,
|
|
@@ -46,16 +46,27 @@ import {
|
|
|
46
46
|
RST
|
|
47
47
|
} from "./chunk-FM3VRDK7.js";
|
|
48
48
|
import {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
runUninstall
|
|
50
|
+
} from "./chunk-MX2EW5MR.js";
|
|
51
|
+
import {
|
|
51
52
|
runSetup
|
|
52
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-ZDJSJIB6.js";
|
|
53
54
|
import {
|
|
54
55
|
runHookInstall,
|
|
55
56
|
runHookStatus,
|
|
56
57
|
runHookUninstall
|
|
57
|
-
} from "./chunk-
|
|
58
|
-
import
|
|
58
|
+
} from "./chunk-447U3GQJ.js";
|
|
59
|
+
import {
|
|
60
|
+
resolveClaudeExecutable
|
|
61
|
+
} from "./chunk-SSYMRT4I.js";
|
|
62
|
+
import {
|
|
63
|
+
runUpdate
|
|
64
|
+
} from "./chunk-QQHIVTXT.js";
|
|
65
|
+
import "./chunk-F53U6JQG.js";
|
|
66
|
+
import {
|
|
67
|
+
isAgentProviderId,
|
|
68
|
+
readConfig
|
|
69
|
+
} from "./chunk-WRUSDYYE.js";
|
|
59
70
|
import {
|
|
60
71
|
findNearestAlmanacDir,
|
|
61
72
|
getRepoAlmanacDir
|
|
@@ -1761,10 +1772,33 @@ function registerQueryCommands(program) {
|
|
|
1761
1772
|
|
|
1762
1773
|
// src/cli/register-setup-commands.ts
|
|
1763
1774
|
function registerSetupCommands(program) {
|
|
1764
|
-
program.command("
|
|
1775
|
+
const agents = program.command("agents").description("list supported AI agent providers and readiness");
|
|
1776
|
+
agents.command("list").description("show Claude, Codex, and Cursor provider status").action(async () => {
|
|
1777
|
+
emit(await runAgentsList());
|
|
1778
|
+
});
|
|
1779
|
+
program.command("set").description("configure codealmanac defaults").argument("<key>", "setting key, e.g. default-agent or model").argument("[value...]", "setting value").action(async (key, value) => {
|
|
1780
|
+
if (key === "default-agent") {
|
|
1781
|
+
emit(await runSetDefaultAgent({ provider: value[0] ?? "" }));
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
if (key === "model") {
|
|
1785
|
+
emit(await runSetAgentModel({
|
|
1786
|
+
provider: value[0] ?? "",
|
|
1787
|
+
model: value[1]
|
|
1788
|
+
}));
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
emit({
|
|
1792
|
+
stdout: "",
|
|
1793
|
+
stderr: "almanac: unknown setting. Use `default-agent` or `model`.\n",
|
|
1794
|
+
exitCode: 1
|
|
1795
|
+
});
|
|
1796
|
+
});
|
|
1797
|
+
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("--skip-hook", "opt out of the SessionEnd hook").option("--skip-guides", "opt out of the CLAUDE.md guides").action(
|
|
1765
1798
|
async (opts) => {
|
|
1766
1799
|
const result = await runSetup({
|
|
1767
1800
|
yes: opts.yes,
|
|
1801
|
+
agent: opts.agent,
|
|
1768
1802
|
skipHook: opts.skipHook,
|
|
1769
1803
|
skipGuides: opts.skipGuides
|
|
1770
1804
|
});
|
|
@@ -1869,8 +1903,20 @@ async function loadPrompt(name) {
|
|
|
1869
1903
|
}
|
|
1870
1904
|
|
|
1871
1905
|
// src/agent/sdk.ts
|
|
1906
|
+
import { spawn } from "child_process";
|
|
1872
1907
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1908
|
+
var DEFAULT_AGENT_MODEL = "claude-sonnet-4-6";
|
|
1873
1909
|
async function runAgent(opts) {
|
|
1910
|
+
const provider = opts.provider ?? "claude";
|
|
1911
|
+
if (provider === "codex") {
|
|
1912
|
+
return await runCodexAgent(opts);
|
|
1913
|
+
}
|
|
1914
|
+
if (provider === "cursor") {
|
|
1915
|
+
return await runCursorAgent(opts);
|
|
1916
|
+
}
|
|
1917
|
+
return await runClaudeAgent(opts);
|
|
1918
|
+
}
|
|
1919
|
+
async function runClaudeAgent(opts) {
|
|
1874
1920
|
const claudeExecutable = resolveClaudeExecutable();
|
|
1875
1921
|
const q = query({
|
|
1876
1922
|
prompt: opts.prompt,
|
|
@@ -1879,7 +1925,7 @@ async function runAgent(opts) {
|
|
|
1879
1925
|
allowedTools: opts.allowedTools,
|
|
1880
1926
|
agents: opts.agents ?? {},
|
|
1881
1927
|
cwd: opts.cwd,
|
|
1882
|
-
model: opts.model ??
|
|
1928
|
+
model: opts.model ?? DEFAULT_AGENT_MODEL,
|
|
1883
1929
|
maxTurns: opts.maxTurns ?? 100,
|
|
1884
1930
|
...claudeExecutable !== void 0 ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
|
|
1885
1931
|
env: {
|
|
@@ -1925,6 +1971,186 @@ async function runAgent(opts) {
|
|
|
1925
1971
|
}
|
|
1926
1972
|
return { success, cost, turns, result, sessionId, error: errorMsg };
|
|
1927
1973
|
}
|
|
1974
|
+
function combinedPrompt(opts) {
|
|
1975
|
+
const reviewerFallback = buildReviewerFallback(opts);
|
|
1976
|
+
return `${opts.systemPrompt}${reviewerFallback}
|
|
1977
|
+
|
|
1978
|
+
---
|
|
1979
|
+
|
|
1980
|
+
${opts.prompt}`;
|
|
1981
|
+
}
|
|
1982
|
+
function buildReviewerFallback(opts) {
|
|
1983
|
+
if ((opts.provider ?? "claude") === "claude") return "";
|
|
1984
|
+
const reviewer = opts.agents?.reviewer;
|
|
1985
|
+
if (reviewer === void 0) return "";
|
|
1986
|
+
return "\n\nNon-Claude provider note: this runtime does not receive Claude's nested Agent tool contract. When the writer prompt asks you to invoke the reviewer subagent, perform that review pass yourself before final wiki edits. Treat this reviewer prompt as read-only review guidance:\n\n" + reviewer.prompt;
|
|
1987
|
+
}
|
|
1988
|
+
async function runCodexAgent(opts) {
|
|
1989
|
+
const args = [
|
|
1990
|
+
"exec",
|
|
1991
|
+
"--json",
|
|
1992
|
+
"--sandbox",
|
|
1993
|
+
"workspace-write",
|
|
1994
|
+
"--skip-git-repo-check",
|
|
1995
|
+
"-C",
|
|
1996
|
+
opts.cwd
|
|
1997
|
+
];
|
|
1998
|
+
if (opts.model !== void 0 && opts.model.length > 0) {
|
|
1999
|
+
args.push("--model", opts.model);
|
|
2000
|
+
}
|
|
2001
|
+
args.push(combinedPrompt(opts));
|
|
2002
|
+
return await runJsonlCli({
|
|
2003
|
+
command: "codex",
|
|
2004
|
+
args,
|
|
2005
|
+
cwd: opts.cwd,
|
|
2006
|
+
env: { ...process.env, CODEALMANAC_INTERNAL_SESSION: "1" },
|
|
2007
|
+
onMessage: opts.onMessage,
|
|
2008
|
+
parseFinal: parseCodexFinal
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
async function runCursorAgent(opts) {
|
|
2012
|
+
const args = [
|
|
2013
|
+
"--print",
|
|
2014
|
+
"--output-format",
|
|
2015
|
+
"stream-json",
|
|
2016
|
+
"--stream-partial-output",
|
|
2017
|
+
"--trust",
|
|
2018
|
+
"--workspace",
|
|
2019
|
+
opts.cwd
|
|
2020
|
+
];
|
|
2021
|
+
if (opts.model !== void 0 && opts.model.length > 0) {
|
|
2022
|
+
args.push("--model", opts.model);
|
|
2023
|
+
}
|
|
2024
|
+
args.push(combinedPrompt(opts));
|
|
2025
|
+
return await runJsonlCli({
|
|
2026
|
+
command: "cursor-agent",
|
|
2027
|
+
args,
|
|
2028
|
+
cwd: opts.cwd,
|
|
2029
|
+
env: { ...process.env, CODEALMANAC_INTERNAL_SESSION: "1" },
|
|
2030
|
+
onMessage: opts.onMessage,
|
|
2031
|
+
parseFinal: parseCursorFinal
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
function runJsonlCli(opts) {
|
|
2035
|
+
return new Promise((resolve) => {
|
|
2036
|
+
const child = spawn(opts.command, opts.args, {
|
|
2037
|
+
cwd: opts.cwd,
|
|
2038
|
+
env: opts.env,
|
|
2039
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2040
|
+
});
|
|
2041
|
+
let stdoutBuf = "";
|
|
2042
|
+
let stderr = "";
|
|
2043
|
+
let cost = 0;
|
|
2044
|
+
let turns = 0;
|
|
2045
|
+
let result = "";
|
|
2046
|
+
let sessionId;
|
|
2047
|
+
let success = false;
|
|
2048
|
+
let finalSeen = false;
|
|
2049
|
+
let error;
|
|
2050
|
+
const observe = (msg) => {
|
|
2051
|
+
opts.onMessage?.(msg);
|
|
2052
|
+
if (sessionId === void 0 && typeof msg.session_id === "string" && msg.session_id.length > 0) {
|
|
2053
|
+
sessionId = msg.session_id;
|
|
2054
|
+
}
|
|
2055
|
+
const final = opts.parseFinal(msg);
|
|
2056
|
+
if (final === null) return;
|
|
2057
|
+
finalSeen = true;
|
|
2058
|
+
if (final.cost !== void 0) cost = final.cost;
|
|
2059
|
+
if (final.turns !== void 0) turns = final.turns;
|
|
2060
|
+
if (final.result !== void 0) result = final.result;
|
|
2061
|
+
if (final.sessionId !== void 0) sessionId = final.sessionId;
|
|
2062
|
+
if (final.success !== void 0) success = final.success;
|
|
2063
|
+
if (final.error !== void 0) error = final.error;
|
|
2064
|
+
};
|
|
2065
|
+
const flushLines = () => {
|
|
2066
|
+
let idx = stdoutBuf.indexOf("\n");
|
|
2067
|
+
while (idx !== -1) {
|
|
2068
|
+
const rawLine = stdoutBuf.slice(0, idx);
|
|
2069
|
+
stdoutBuf = stdoutBuf.slice(idx + 1);
|
|
2070
|
+
const line = rawLine.trim();
|
|
2071
|
+
if (line.length > 0) {
|
|
2072
|
+
try {
|
|
2073
|
+
const parsed = JSON.parse(line);
|
|
2074
|
+
observe(parsed);
|
|
2075
|
+
} catch {
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
idx = stdoutBuf.indexOf("\n");
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
child.stdout.on("data", (chunk) => {
|
|
2082
|
+
stdoutBuf += chunk.toString("utf8");
|
|
2083
|
+
flushLines();
|
|
2084
|
+
});
|
|
2085
|
+
child.stderr.on("data", (chunk) => {
|
|
2086
|
+
stderr += chunk.toString("utf8");
|
|
2087
|
+
});
|
|
2088
|
+
child.on("error", (err) => {
|
|
2089
|
+
resolve({
|
|
2090
|
+
success: false,
|
|
2091
|
+
cost,
|
|
2092
|
+
turns,
|
|
2093
|
+
result,
|
|
2094
|
+
sessionId,
|
|
2095
|
+
error: err.code === "ENOENT" ? `${opts.command} not found on PATH` : err.message
|
|
2096
|
+
});
|
|
2097
|
+
});
|
|
2098
|
+
child.on("close", (code) => {
|
|
2099
|
+
flushLines();
|
|
2100
|
+
if (stdoutBuf.trim().length > 0) {
|
|
2101
|
+
try {
|
|
2102
|
+
observe(JSON.parse(stdoutBuf.trim()));
|
|
2103
|
+
} catch {
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (code === 0 && finalSeen && success) {
|
|
2107
|
+
resolve({ success, cost, turns, result, sessionId });
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
const firstStderr = stderr.trim().split("\n")[0];
|
|
2111
|
+
resolve({
|
|
2112
|
+
success: false,
|
|
2113
|
+
cost,
|
|
2114
|
+
turns,
|
|
2115
|
+
result,
|
|
2116
|
+
sessionId,
|
|
2117
|
+
error: error ?? (firstStderr !== void 0 && firstStderr.length > 0 ? firstStderr : `${opts.command} exited ${code ?? 1}`)
|
|
2118
|
+
});
|
|
2119
|
+
});
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
function parseCodexFinal(msg) {
|
|
2123
|
+
if (msg.type === "item.completed") {
|
|
2124
|
+
const item = msg.item;
|
|
2125
|
+
if (item !== null && typeof item === "object") {
|
|
2126
|
+
const obj = item;
|
|
2127
|
+
if (obj.type === "agent_message" && typeof obj.text === "string") {
|
|
2128
|
+
return { result: obj.text };
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
if (msg.type === "turn.completed") {
|
|
2134
|
+
return { success: true };
|
|
2135
|
+
}
|
|
2136
|
+
if (msg.type === "turn.failed" || msg.type === "error") {
|
|
2137
|
+
return {
|
|
2138
|
+
success: false,
|
|
2139
|
+
error: typeof msg.message === "string" ? msg.message : typeof msg.error === "string" ? msg.error : "codex turn failed"
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
function parseCursorFinal(msg) {
|
|
2145
|
+
if (msg.type !== "result") return null;
|
|
2146
|
+
const isError = msg.is_error === true || msg.subtype !== "success";
|
|
2147
|
+
return {
|
|
2148
|
+
success: !isError,
|
|
2149
|
+
result: typeof msg.result === "string" ? msg.result : "",
|
|
2150
|
+
sessionId: typeof msg.session_id === "string" ? msg.session_id : void 0,
|
|
2151
|
+
error: isError ? typeof msg.result === "string" ? msg.result : `cursor result: ${String(msg.subtype ?? "error")}` : void 0
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
1928
2154
|
|
|
1929
2155
|
// src/commands/init.ts
|
|
1930
2156
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -2069,8 +2295,21 @@ and optional \`files:\`. The rest is prose.
|
|
|
2069
2295
|
// src/commands/bootstrap.ts
|
|
2070
2296
|
var BOOTSTRAP_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
2071
2297
|
async function runBootstrap(options) {
|
|
2298
|
+
const providerResolution = await resolveAgentSelection({
|
|
2299
|
+
agent: options.agent,
|
|
2300
|
+
model: options.model
|
|
2301
|
+
});
|
|
2302
|
+
if (!providerResolution.ok) {
|
|
2303
|
+
return {
|
|
2304
|
+
stdout: "",
|
|
2305
|
+
stderr: `almanac: ${providerResolution.error}
|
|
2306
|
+
`,
|
|
2307
|
+
exitCode: 1
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
const { provider, model } = providerResolution;
|
|
2072
2311
|
try {
|
|
2073
|
-
await
|
|
2312
|
+
await assertAgentAuth({ provider, spawnCli: options.spawnCli });
|
|
2074
2313
|
} catch (err) {
|
|
2075
2314
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2076
2315
|
return {
|
|
@@ -2137,7 +2376,8 @@ async function runBootstrap(options) {
|
|
|
2137
2376
|
prompt: userPrompt,
|
|
2138
2377
|
allowedTools: BOOTSTRAP_TOOLS,
|
|
2139
2378
|
cwd: repoRoot,
|
|
2140
|
-
|
|
2379
|
+
provider,
|
|
2380
|
+
model,
|
|
2141
2381
|
onMessage
|
|
2142
2382
|
});
|
|
2143
2383
|
} finally {
|
|
@@ -2160,6 +2400,19 @@ async function runBootstrap(options) {
|
|
|
2160
2400
|
exitCode: 1
|
|
2161
2401
|
};
|
|
2162
2402
|
}
|
|
2403
|
+
async function resolveAgentSelection(args) {
|
|
2404
|
+
const config = await readConfig();
|
|
2405
|
+
const rawProvider = args.agent ?? config.agent.default;
|
|
2406
|
+
if (!isAgentProviderId(rawProvider)) {
|
|
2407
|
+
return {
|
|
2408
|
+
ok: false,
|
|
2409
|
+
error: `unknown agent '${rawProvider}'. Expected one of: claude, codex, cursor.`
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
const configuredModel = config.agent.models[rawProvider] ?? void 0;
|
|
2413
|
+
const model = args.model !== void 0 ? args.model : configuredModel === null ? void 0 : configuredModel;
|
|
2414
|
+
return { ok: true, provider: rawProvider, model };
|
|
2415
|
+
}
|
|
2163
2416
|
function formatFinalLine(result, logPath, repoRoot) {
|
|
2164
2417
|
const status = result.success ? "done" : "failed";
|
|
2165
2418
|
const rel = relative(repoRoot, logPath);
|
|
@@ -2210,17 +2463,23 @@ var StreamingFormatter = class {
|
|
|
2210
2463
|
this.currentAgent = name;
|
|
2211
2464
|
}
|
|
2212
2465
|
handle(msg) {
|
|
2213
|
-
if (msg
|
|
2466
|
+
if (!isRecord(msg)) return;
|
|
2467
|
+
if (msg.type === "assistant" && isRecord(msg.message)) {
|
|
2468
|
+
const content = msg.message.content;
|
|
2469
|
+
if (!Array.isArray(content)) return;
|
|
2214
2470
|
for (const block of msg.message.content) {
|
|
2215
|
-
if (block.type !== "tool_use") continue;
|
|
2471
|
+
if (!isRecord(block) || block.type !== "tool_use") continue;
|
|
2472
|
+
if (typeof block.name !== "string") continue;
|
|
2216
2473
|
this.handleToolUse(block.name, block.input);
|
|
2217
2474
|
}
|
|
2218
2475
|
return;
|
|
2219
2476
|
}
|
|
2220
2477
|
if (msg.type === "result") {
|
|
2221
2478
|
const status = msg.subtype === "success" ? "done" : `failed (${msg.subtype})`;
|
|
2479
|
+
const cost = typeof msg.total_cost_usd === "number" ? msg.total_cost_usd : 0;
|
|
2480
|
+
const turns = typeof msg.num_turns === "number" ? msg.num_turns : 0;
|
|
2222
2481
|
this.sink.write(
|
|
2223
|
-
`[${status}] cost: $${
|
|
2482
|
+
`[${status}] cost: $${cost.toFixed(3)}, turns: ${turns}
|
|
2224
2483
|
`
|
|
2225
2484
|
);
|
|
2226
2485
|
return;
|
|
@@ -2292,23 +2551,246 @@ function stringField(input, key) {
|
|
|
2292
2551
|
const value = input[key];
|
|
2293
2552
|
return typeof value === "string" ? value : void 0;
|
|
2294
2553
|
}
|
|
2554
|
+
function isRecord(value) {
|
|
2555
|
+
return value !== null && typeof value === "object";
|
|
2556
|
+
}
|
|
2295
2557
|
|
|
2296
2558
|
// src/commands/capture.ts
|
|
2297
2559
|
import { createHash } from "crypto";
|
|
2298
2560
|
import {
|
|
2299
2561
|
createWriteStream as createWriteStream2,
|
|
2300
|
-
existsSync as
|
|
2562
|
+
existsSync as existsSync7,
|
|
2301
2563
|
statSync
|
|
2302
2564
|
} from "fs";
|
|
2303
|
-
import { mkdir as
|
|
2565
|
+
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir3, stat } from "fs/promises";
|
|
2304
2566
|
import { homedir } from "os";
|
|
2305
|
-
import { basename as basename3, join as
|
|
2567
|
+
import { basename as basename3, join as join8, relative as relative3 } from "path";
|
|
2568
|
+
|
|
2569
|
+
// src/commands/captureStatus.ts
|
|
2570
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2571
|
+
import { mkdir as mkdir3, readFile as readFile6, readdir as readdir2, rename as rename2, writeFile as writeFile3 } from "fs/promises";
|
|
2572
|
+
import { dirname, join as join7, relative as relative2 } from "path";
|
|
2573
|
+
function captureStatePath(dir, stem) {
|
|
2574
|
+
return join7(dir, `.capture-${stem}.state.json`);
|
|
2575
|
+
}
|
|
2576
|
+
async function writeCaptureRunRecord(path2, record) {
|
|
2577
|
+
await mkdir3(dirname(path2), { recursive: true });
|
|
2578
|
+
const tmp = `${path2}.tmp-${process.pid}`;
|
|
2579
|
+
await writeFile3(tmp, `${JSON.stringify(record, null, 2)}
|
|
2580
|
+
`, "utf8");
|
|
2581
|
+
await rename2(tmp, path2);
|
|
2582
|
+
}
|
|
2583
|
+
function buildStartedCaptureRecord(args) {
|
|
2584
|
+
return {
|
|
2585
|
+
version: 1,
|
|
2586
|
+
kind: "capture",
|
|
2587
|
+
status: "running",
|
|
2588
|
+
sessionId: args.sessionId ?? args.stem,
|
|
2589
|
+
repoRoot: args.repoRoot,
|
|
2590
|
+
pid: process.pid,
|
|
2591
|
+
model: args.model ?? DEFAULT_AGENT_MODEL,
|
|
2592
|
+
transcriptPath: args.transcriptPath,
|
|
2593
|
+
startedAt: args.startedAt.toISOString(),
|
|
2594
|
+
logPath: join7(args.almanacDir, `.capture-${args.stem}.log`),
|
|
2595
|
+
jsonlPath: join7(args.almanacDir, `.capture-${args.stem}.jsonl`)
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
function finishCaptureRecord(args) {
|
|
2599
|
+
const started = Date.parse(args.record.startedAt);
|
|
2600
|
+
const finished = args.finishedAt.getTime();
|
|
2601
|
+
return {
|
|
2602
|
+
...args.record,
|
|
2603
|
+
status: args.status,
|
|
2604
|
+
finishedAt: args.finishedAt.toISOString(),
|
|
2605
|
+
durationMs: Number.isFinite(started) ? Math.max(0, finished - started) : void 0,
|
|
2606
|
+
summary: args.summary,
|
|
2607
|
+
error: args.error
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
async function runCaptureStatus(options) {
|
|
2611
|
+
const repoRoot = findNearestAlmanacDir(options.cwd);
|
|
2612
|
+
if (repoRoot === null) {
|
|
2613
|
+
return {
|
|
2614
|
+
stdout: "",
|
|
2615
|
+
stderr: "almanac: no .almanac/ found in this directory or any parent. Run 'almanac bootstrap' first.\n",
|
|
2616
|
+
exitCode: 1
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
const almanacDir = getRepoAlmanacDir(repoRoot);
|
|
2620
|
+
const records = await readCaptureRecords(almanacDir);
|
|
2621
|
+
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
2622
|
+
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
2623
|
+
const views = records.map((record) => toView(record, repoRoot, now, isPidAlive)).sort((a, b) => b.sortTime - a.sortTime);
|
|
2624
|
+
if (options.json === true) {
|
|
2625
|
+
return {
|
|
2626
|
+
stdout: `${JSON.stringify(
|
|
2627
|
+
{
|
|
2628
|
+
repo: repoRoot,
|
|
2629
|
+
captures: views.map(({ sortTime: _sortTime, ...v }) => v)
|
|
2630
|
+
},
|
|
2631
|
+
null,
|
|
2632
|
+
2
|
|
2633
|
+
)}
|
|
2634
|
+
`,
|
|
2635
|
+
stderr: "",
|
|
2636
|
+
exitCode: 0
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
return {
|
|
2640
|
+
stdout: formatCaptureStatus(views),
|
|
2641
|
+
stderr: "",
|
|
2642
|
+
exitCode: 0
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
async function readCaptureRecords(almanacDir) {
|
|
2646
|
+
if (!existsSync6(almanacDir)) return [];
|
|
2647
|
+
const out = [];
|
|
2648
|
+
const dirs = [join7(almanacDir, "logs"), almanacDir];
|
|
2649
|
+
for (const dir of dirs) {
|
|
2650
|
+
let entries;
|
|
2651
|
+
try {
|
|
2652
|
+
entries = await readdir2(dir);
|
|
2653
|
+
} catch {
|
|
2654
|
+
continue;
|
|
2655
|
+
}
|
|
2656
|
+
for (const entry of entries) {
|
|
2657
|
+
if (!entry.startsWith(".capture-") || !entry.endsWith(".state.json")) {
|
|
2658
|
+
continue;
|
|
2659
|
+
}
|
|
2660
|
+
try {
|
|
2661
|
+
const parsed = JSON.parse(await readFile6(join7(dir, entry), "utf8"));
|
|
2662
|
+
if (isCaptureRunRecord(parsed)) out.push(parsed);
|
|
2663
|
+
} catch {
|
|
2664
|
+
continue;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return out;
|
|
2669
|
+
}
|
|
2670
|
+
function isCaptureRunRecord(value) {
|
|
2671
|
+
if (value === null || typeof value !== "object") return false;
|
|
2672
|
+
const v = value;
|
|
2673
|
+
return v.version === 1 && v.kind === "capture" && (v.status === "running" || v.status === "done" || v.status === "failed") && typeof v.sessionId === "string" && typeof v.repoRoot === "string" && typeof v.pid === "number" && typeof v.model === "string" && typeof v.transcriptPath === "string" && typeof v.startedAt === "string" && typeof v.logPath === "string" && typeof v.jsonlPath === "string";
|
|
2674
|
+
}
|
|
2675
|
+
function toView(record, repoRoot, now, isPidAlive) {
|
|
2676
|
+
const started = Date.parse(record.startedAt);
|
|
2677
|
+
const finished = record.finishedAt !== void 0 ? Date.parse(record.finishedAt) : void 0;
|
|
2678
|
+
const elapsedMs = record.durationMs ?? (Number.isFinite(started) ? Math.max(0, (finished ?? now.getTime()) - started) : 0);
|
|
2679
|
+
const status = record.status === "running" && !isPidAlive(record.pid) ? "stale" : record.status;
|
|
2680
|
+
return {
|
|
2681
|
+
status,
|
|
2682
|
+
sessionId: record.sessionId,
|
|
2683
|
+
model: record.model,
|
|
2684
|
+
elapsedMs,
|
|
2685
|
+
startedAt: record.startedAt,
|
|
2686
|
+
finishedAt: record.finishedAt,
|
|
2687
|
+
pid: record.pid,
|
|
2688
|
+
logPath: relative2(repoRoot, record.logPath),
|
|
2689
|
+
jsonlPath: relative2(repoRoot, record.jsonlPath),
|
|
2690
|
+
summary: record.summary,
|
|
2691
|
+
error: status === "stale" ? "process ended without a final status" : record.error,
|
|
2692
|
+
sortTime: finished ?? (Number.isFinite(started) ? started : 0)
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
function formatCaptureStatus(views) {
|
|
2696
|
+
const lines = ["Capture jobs", ""];
|
|
2697
|
+
if (views.length === 0) {
|
|
2698
|
+
lines.push("No capture jobs found.");
|
|
2699
|
+
return `${lines.join("\n")}
|
|
2700
|
+
`;
|
|
2701
|
+
}
|
|
2702
|
+
const active = views.filter((v) => v.status === "running" || v.status === "stale");
|
|
2703
|
+
const finished = views.filter((v) => v.status === "done" || v.status === "failed");
|
|
2704
|
+
if (active.length === 0) {
|
|
2705
|
+
lines.push("No active captures.", "");
|
|
2706
|
+
} else {
|
|
2707
|
+
for (const view of active) {
|
|
2708
|
+
lines.push(formatRow(view));
|
|
2709
|
+
lines.push(` log: ${view.logPath}`);
|
|
2710
|
+
if (view.error !== void 0) lines.push(` error: ${view.error}`);
|
|
2711
|
+
lines.push("");
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
if (finished.length > 0) {
|
|
2715
|
+
lines.push(active.length === 0 ? "Last capture:" : "Last finished:");
|
|
2716
|
+
for (const view of finished.slice(0, 3)) {
|
|
2717
|
+
lines.push(formatRow(view));
|
|
2718
|
+
if (view.status === "failed") {
|
|
2719
|
+
lines.push(` log: ${view.logPath}`);
|
|
2720
|
+
if (view.error !== void 0) lines.push(` error: ${view.error}`);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
return `${trimTrailingBlank(lines).join("\n")}
|
|
2725
|
+
`;
|
|
2726
|
+
}
|
|
2727
|
+
function formatRow(view) {
|
|
2728
|
+
const status = view.status.padEnd(7, " ");
|
|
2729
|
+
const session = view.sessionId.padEnd(12, " ");
|
|
2730
|
+
const model = view.model.padEnd(17, " ");
|
|
2731
|
+
const elapsed = formatDuration(view.elapsedMs);
|
|
2732
|
+
const summary = formatSummary(view);
|
|
2733
|
+
return `${status} ${session} ${model} ${elapsed}${summary.length > 0 ? ` ${summary}` : ""}`;
|
|
2734
|
+
}
|
|
2735
|
+
function formatSummary(view) {
|
|
2736
|
+
if (view.status === "failed") return "failed; see log";
|
|
2737
|
+
if (view.summary === void 0) return "";
|
|
2738
|
+
const parts = [];
|
|
2739
|
+
if (view.summary.updated > 0) {
|
|
2740
|
+
parts.push(`${view.summary.updated} updated`);
|
|
2741
|
+
}
|
|
2742
|
+
if (view.summary.created > 0) {
|
|
2743
|
+
parts.push(`${view.summary.created} created`);
|
|
2744
|
+
}
|
|
2745
|
+
if (view.summary.archived > 0) {
|
|
2746
|
+
parts.push(`${view.summary.archived} archived`);
|
|
2747
|
+
}
|
|
2748
|
+
return parts.length > 0 ? parts.join(", ") : "0 pages written";
|
|
2749
|
+
}
|
|
2750
|
+
function formatDuration(ms) {
|
|
2751
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
2752
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
2753
|
+
const seconds = totalSeconds % 60;
|
|
2754
|
+
if (minutes < 60) return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
|
|
2755
|
+
const hours = Math.floor(minutes / 60);
|
|
2756
|
+
const restMinutes = minutes % 60;
|
|
2757
|
+
return `${hours}h${restMinutes.toString().padStart(2, "0")}m`;
|
|
2758
|
+
}
|
|
2759
|
+
function trimTrailingBlank(lines) {
|
|
2760
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
2761
|
+
lines.pop();
|
|
2762
|
+
}
|
|
2763
|
+
return lines;
|
|
2764
|
+
}
|
|
2765
|
+
function defaultIsPidAlive(pid) {
|
|
2766
|
+
try {
|
|
2767
|
+
process.kill(pid, 0);
|
|
2768
|
+
return true;
|
|
2769
|
+
} catch {
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// src/commands/capture.ts
|
|
2306
2775
|
var WRITER_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "Agent"];
|
|
2307
2776
|
var REVIEWER_TOOLS = ["Read", "Grep", "Glob", "Bash"];
|
|
2308
2777
|
var REVIEWER_DESCRIPTION = "Reviews proposed wiki changes against the full knowledge base for cohesion, duplication, missing links, notability, and writing conventions.";
|
|
2309
2778
|
async function runCapture(options) {
|
|
2779
|
+
const providerResolution = await resolveAgentSelection2({
|
|
2780
|
+
agent: options.agent,
|
|
2781
|
+
model: options.model
|
|
2782
|
+
});
|
|
2783
|
+
if (!providerResolution.ok) {
|
|
2784
|
+
return {
|
|
2785
|
+
stdout: "",
|
|
2786
|
+
stderr: `almanac: ${providerResolution.error}
|
|
2787
|
+
`,
|
|
2788
|
+
exitCode: 1
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
const { provider, model } = providerResolution;
|
|
2310
2792
|
try {
|
|
2311
|
-
await
|
|
2793
|
+
await assertAgentAuth({ provider, spawnCli: options.spawnCli });
|
|
2312
2794
|
} catch (err) {
|
|
2313
2795
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2314
2796
|
return {
|
|
@@ -2327,7 +2809,7 @@ async function runCapture(options) {
|
|
|
2327
2809
|
};
|
|
2328
2810
|
}
|
|
2329
2811
|
const almanacDir = getRepoAlmanacDir(repoRoot);
|
|
2330
|
-
const pagesDir =
|
|
2812
|
+
const pagesDir = join8(almanacDir, "pages");
|
|
2331
2813
|
const transcriptResolution = await resolveTranscript({
|
|
2332
2814
|
repoRoot,
|
|
2333
2815
|
explicit: options.transcriptPath,
|
|
@@ -2353,12 +2835,24 @@ async function runCapture(options) {
|
|
|
2353
2835
|
tools: REVIEWER_TOOLS
|
|
2354
2836
|
}
|
|
2355
2837
|
};
|
|
2356
|
-
const
|
|
2357
|
-
const logStem = options.sessionId !== void 0 && options.sessionId.length > 0 ? options.sessionId : formatTimestamp2(
|
|
2358
|
-
const logsDir =
|
|
2359
|
-
await
|
|
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 });
|
|
2360
2842
|
const logName = `.capture-${logStem}.jsonl`;
|
|
2361
|
-
const logPath =
|
|
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: options.model,
|
|
2852
|
+
startedAt
|
|
2853
|
+
});
|
|
2854
|
+
await writeCaptureRunRecord(statePath, stateRecord).catch(() => {
|
|
2855
|
+
});
|
|
2362
2856
|
const logStream = createWriteStream2(logPath, { flags: "w" });
|
|
2363
2857
|
const out = process.stdout;
|
|
2364
2858
|
const formatter = new StreamingFormatter({
|
|
@@ -2387,7 +2881,8 @@ Working directory: ${repoRoot}.`;
|
|
|
2387
2881
|
allowedTools: WRITER_TOOLS,
|
|
2388
2882
|
agents,
|
|
2389
2883
|
cwd: repoRoot,
|
|
2390
|
-
|
|
2884
|
+
provider,
|
|
2885
|
+
model,
|
|
2391
2886
|
// Capture sessions can touch many pages; give it more headroom than
|
|
2392
2887
|
// bootstrap. The SDK treats `maxTurns` as a hard stop — better to
|
|
2393
2888
|
// overshoot than to cut off mid-review.
|
|
@@ -2399,16 +2894,43 @@ Working directory: ${repoRoot}.`;
|
|
|
2399
2894
|
}
|
|
2400
2895
|
const snapshotAfter = await snapshotPages(pagesDir);
|
|
2401
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
|
|
2902
|
+
};
|
|
2402
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
|
+
});
|
|
2403
2915
|
return {
|
|
2404
2916
|
stdout: "",
|
|
2405
2917
|
stderr: `almanac: capture failed: ${result.error ?? "unknown error"}
|
|
2406
|
-
(transcript: ${
|
|
2918
|
+
(transcript: ${relative3(repoRoot, logPath)})
|
|
2407
2919
|
`,
|
|
2408
2920
|
exitCode: 1
|
|
2409
2921
|
};
|
|
2410
2922
|
}
|
|
2411
|
-
|
|
2923
|
+
await writeCaptureRunRecord(
|
|
2924
|
+
statePath,
|
|
2925
|
+
finishCaptureRecord({
|
|
2926
|
+
record: stateRecord,
|
|
2927
|
+
status: "done",
|
|
2928
|
+
finishedAt,
|
|
2929
|
+
summary: captureSummary
|
|
2930
|
+
})
|
|
2931
|
+
).catch(() => {
|
|
2932
|
+
});
|
|
2933
|
+
const summary = formatSummary2(result, delta, logPath, repoRoot);
|
|
2412
2934
|
return {
|
|
2413
2935
|
stdout: `${summary}
|
|
2414
2936
|
`,
|
|
@@ -2416,9 +2938,22 @@ Working directory: ${repoRoot}.`;
|
|
|
2416
2938
|
exitCode: 0
|
|
2417
2939
|
};
|
|
2418
2940
|
}
|
|
2941
|
+
async function resolveAgentSelection2(args) {
|
|
2942
|
+
const config = await readConfig();
|
|
2943
|
+
const rawProvider = args.agent ?? config.agent.default;
|
|
2944
|
+
if (!isAgentProviderId(rawProvider)) {
|
|
2945
|
+
return {
|
|
2946
|
+
ok: false,
|
|
2947
|
+
error: `unknown agent '${rawProvider}'. Expected one of: claude, codex, cursor.`
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
const configuredModel = config.agent.models[rawProvider] ?? void 0;
|
|
2951
|
+
const model = args.model !== void 0 ? args.model : configuredModel === null ? void 0 : configuredModel;
|
|
2952
|
+
return { ok: true, provider: rawProvider, model };
|
|
2953
|
+
}
|
|
2419
2954
|
async function resolveTranscript(args) {
|
|
2420
2955
|
if (args.explicit !== void 0 && args.explicit.length > 0) {
|
|
2421
|
-
if (!
|
|
2956
|
+
if (!existsSync7(args.explicit)) {
|
|
2422
2957
|
return {
|
|
2423
2958
|
ok: false,
|
|
2424
2959
|
error: `transcript not found: ${args.explicit}`
|
|
@@ -2426,8 +2961,8 @@ async function resolveTranscript(args) {
|
|
|
2426
2961
|
}
|
|
2427
2962
|
return { ok: true, path: args.explicit };
|
|
2428
2963
|
}
|
|
2429
|
-
const projectsDir = args.claudeProjectsDir ??
|
|
2430
|
-
if (!
|
|
2964
|
+
const projectsDir = args.claudeProjectsDir ?? join8(homedir(), ".claude", "projects");
|
|
2965
|
+
if (!existsSync7(projectsDir)) {
|
|
2431
2966
|
return {
|
|
2432
2967
|
ok: false,
|
|
2433
2968
|
error: `could not auto-resolve transcript; ${projectsDir} does not exist. Pass --session <id> or <transcript-path>.`
|
|
@@ -2459,21 +2994,21 @@ async function collectTranscripts(projectsDir) {
|
|
|
2459
2994
|
const out = [];
|
|
2460
2995
|
let topLevel;
|
|
2461
2996
|
try {
|
|
2462
|
-
topLevel = await
|
|
2997
|
+
topLevel = await readdir3(projectsDir);
|
|
2463
2998
|
} catch {
|
|
2464
2999
|
return out;
|
|
2465
3000
|
}
|
|
2466
3001
|
for (const name of topLevel) {
|
|
2467
|
-
const projectDir =
|
|
3002
|
+
const projectDir = join8(projectsDir, name);
|
|
2468
3003
|
let entries;
|
|
2469
3004
|
try {
|
|
2470
|
-
entries = await
|
|
3005
|
+
entries = await readdir3(projectDir);
|
|
2471
3006
|
} catch {
|
|
2472
3007
|
continue;
|
|
2473
3008
|
}
|
|
2474
3009
|
for (const entry of entries) {
|
|
2475
3010
|
if (!entry.endsWith(".jsonl")) continue;
|
|
2476
|
-
const full =
|
|
3011
|
+
const full = join8(projectDir, entry);
|
|
2477
3012
|
try {
|
|
2478
3013
|
const st = await stat(full);
|
|
2479
3014
|
if (st.isFile()) {
|
|
@@ -2488,7 +3023,7 @@ async function collectTranscripts(projectsDir) {
|
|
|
2488
3023
|
async function filterTranscriptsByCwd(transcripts, repoRoot) {
|
|
2489
3024
|
const dirHash = `-${repoRoot.replace(/^\/+/, "").replace(/\//g, "-")}`;
|
|
2490
3025
|
const byDirName = transcripts.filter((t) => {
|
|
2491
|
-
const parent = basename3(
|
|
3026
|
+
const parent = basename3(join8(t.path, ".."));
|
|
2492
3027
|
return parent === dirHash || parent.endsWith(dirHash);
|
|
2493
3028
|
});
|
|
2494
3029
|
if (byDirName.length > 0) return byDirName;
|
|
@@ -2505,26 +3040,26 @@ async function filterTranscriptsByCwd(transcripts, repoRoot) {
|
|
|
2505
3040
|
return hits;
|
|
2506
3041
|
}
|
|
2507
3042
|
async function readHead(path2, bytes) {
|
|
2508
|
-
const content = await
|
|
3043
|
+
const content = await readFile7(path2, "utf8");
|
|
2509
3044
|
return content.length > bytes ? content.slice(0, bytes) : content;
|
|
2510
3045
|
}
|
|
2511
3046
|
async function snapshotPages(pagesDir) {
|
|
2512
3047
|
const out = /* @__PURE__ */ new Map();
|
|
2513
|
-
if (!
|
|
3048
|
+
if (!existsSync7(pagesDir)) return out;
|
|
2514
3049
|
let entries;
|
|
2515
3050
|
try {
|
|
2516
|
-
entries = await
|
|
3051
|
+
entries = await readdir3(pagesDir);
|
|
2517
3052
|
} catch {
|
|
2518
3053
|
return out;
|
|
2519
3054
|
}
|
|
2520
3055
|
for (const entry of entries) {
|
|
2521
3056
|
if (!entry.endsWith(".md")) continue;
|
|
2522
3057
|
const slug = entry.slice(0, -3);
|
|
2523
|
-
const full =
|
|
3058
|
+
const full = join8(pagesDir, entry);
|
|
2524
3059
|
try {
|
|
2525
3060
|
const st = statSync(full);
|
|
2526
3061
|
if (!st.isFile()) continue;
|
|
2527
|
-
const content = await
|
|
3062
|
+
const content = await readFile7(full, "utf8");
|
|
2528
3063
|
const hash = createHash("sha256").update(content).digest("hex");
|
|
2529
3064
|
const fm = parseFrontmatter(content);
|
|
2530
3065
|
out.set(slug, {
|
|
@@ -2558,8 +3093,8 @@ function diffSnapshots(before, after) {
|
|
|
2558
3093
|
}
|
|
2559
3094
|
return { created, updated, archived };
|
|
2560
3095
|
}
|
|
2561
|
-
function
|
|
2562
|
-
const rel =
|
|
3096
|
+
function formatSummary2(result, delta, logPath, repoRoot) {
|
|
3097
|
+
const rel = relative3(repoRoot, logPath);
|
|
2563
3098
|
const cost = `$${result.cost.toFixed(3)}`;
|
|
2564
3099
|
const { created, updated, archived } = delta;
|
|
2565
3100
|
if (created === 0 && updated === 0 && archived === 0) {
|
|
@@ -2594,18 +3129,19 @@ async function runReindex(options) {
|
|
|
2594
3129
|
function registerWikiLifecycleCommands(program) {
|
|
2595
3130
|
program.command("bootstrap").description(
|
|
2596
3131
|
"scaffold a wiki in this repo via an AI agent (requires ANTHROPIC_API_KEY or Claude subscription)"
|
|
2597
|
-
).option("--quiet", "suppress per-tool streaming; print only the final line").option("--model <model>", "override the agent model").option("--force", "overwrite an existing populated wiki (default: refuse)").action(
|
|
3132
|
+
).option("--quiet", "suppress per-tool streaming; print only the final line").option("--agent <agent>", "agent provider: claude, codex, or cursor").option("--model <model>", "override the agent model").option("--force", "overwrite an existing populated wiki (default: refuse)").action(
|
|
2598
3133
|
async (opts) => {
|
|
2599
3134
|
const result = await runBootstrap({
|
|
2600
3135
|
cwd: process.cwd(),
|
|
2601
3136
|
quiet: opts.quiet,
|
|
3137
|
+
agent: opts.agent,
|
|
2602
3138
|
model: opts.model,
|
|
2603
3139
|
force: opts.force
|
|
2604
3140
|
});
|
|
2605
3141
|
emit(result);
|
|
2606
3142
|
}
|
|
2607
3143
|
);
|
|
2608
|
-
program.command("capture [transcript]").description("run the writer/reviewer pipeline on a session (usually automatic)").option("--session <id>", "target a specific session by ID").option("--quiet", "suppress per-tool streaming; print only the final summary").option("--model <model>", "override the agent model").action(
|
|
3144
|
+
const capture = program.command("capture [transcript]").alias("c").description("run the writer/reviewer pipeline on a session (usually automatic)").option("--session <id>", "target a specific session by ID").option("--quiet", "suppress per-tool streaming; print only the final summary").option("--agent <agent>", "agent provider: claude, codex, or cursor").option("--model <model>", "override the agent model").action(
|
|
2609
3145
|
async (transcript, opts) => {
|
|
2610
3146
|
await autoRegisterIfNeeded(process.cwd());
|
|
2611
3147
|
const result = await runCapture({
|
|
@@ -2613,14 +3149,33 @@ function registerWikiLifecycleCommands(program) {
|
|
|
2613
3149
|
transcriptPath: transcript,
|
|
2614
3150
|
sessionId: opts.session,
|
|
2615
3151
|
quiet: opts.quiet,
|
|
3152
|
+
agent: opts.agent,
|
|
2616
3153
|
model: opts.model
|
|
2617
3154
|
});
|
|
2618
3155
|
emit(result);
|
|
2619
3156
|
}
|
|
2620
3157
|
);
|
|
3158
|
+
capture.command("status").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
|
|
3159
|
+
await autoRegisterIfNeeded(process.cwd());
|
|
3160
|
+
const result = await runCaptureStatus({
|
|
3161
|
+
cwd: process.cwd(),
|
|
3162
|
+
json: opts.json
|
|
3163
|
+
});
|
|
3164
|
+
emit(result);
|
|
3165
|
+
});
|
|
3166
|
+
program.command("ps").description("show running and recent capture jobs").option("--json", "emit structured JSON").action(async (opts) => {
|
|
3167
|
+
await autoRegisterIfNeeded(process.cwd());
|
|
3168
|
+
const result = await runCaptureStatus({
|
|
3169
|
+
cwd: process.cwd(),
|
|
3170
|
+
json: opts.json
|
|
3171
|
+
});
|
|
3172
|
+
emit(result);
|
|
3173
|
+
});
|
|
2621
3174
|
const hook = program.command("hook").description("manage the SessionEnd auto-capture hook");
|
|
2622
|
-
hook.command("install").description("add a SessionEnd entry that runs 'almanac capture' on session end").action(async () => {
|
|
2623
|
-
const result = await runHookInstall(
|
|
3175
|
+
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) => {
|
|
3176
|
+
const result = await runHookInstall({
|
|
3177
|
+
source: normalizeHookSource(opts.source)
|
|
3178
|
+
});
|
|
2624
3179
|
emit(result);
|
|
2625
3180
|
});
|
|
2626
3181
|
hook.command("uninstall").description("remove codealmanac's SessionEnd entry; leaves foreign entries alone").action(async () => {
|
|
@@ -2641,6 +3196,12 @@ function registerWikiLifecycleCommands(program) {
|
|
|
2641
3196
|
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
2642
3197
|
});
|
|
2643
3198
|
}
|
|
3199
|
+
function normalizeHookSource(source) {
|
|
3200
|
+
if (source === "claude" || source === "codex" || source === "cursor" || source === "all") {
|
|
3201
|
+
return source;
|
|
3202
|
+
}
|
|
3203
|
+
return void 0;
|
|
3204
|
+
}
|
|
2644
3205
|
|
|
2645
3206
|
// src/cli/register-commands.ts
|
|
2646
3207
|
function registerCommands(program) {
|
|
@@ -2652,4 +3213,4 @@ function registerCommands(program) {
|
|
|
2652
3213
|
export {
|
|
2653
3214
|
registerCommands
|
|
2654
3215
|
};
|
|
2655
|
-
//# sourceMappingURL=register-commands-
|
|
3216
|
+
//# sourceMappingURL=register-commands-DPH4ZWEE.js.map
|