codealmanac 0.2.5 → 0.2.6
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/COMMERCIAL.md +9 -0
- package/LICENSE +133 -21
- package/README.md +11 -11
- package/dist/{agents-RVTQYE6A.js → agents-HYRWRHRX.js} +4 -4
- package/dist/{chunk-TT6ZP4GS.js → chunk-2BNDNGUR.js} +8 -4
- package/dist/{chunk-TT6ZP4GS.js.map → chunk-2BNDNGUR.js.map} +1 -1
- package/dist/{chunk-P5WGG4FJ.js → chunk-3E7JNMTZ.js} +28 -3
- package/dist/chunk-3E7JNMTZ.js.map +1 -0
- package/dist/{chunk-SMIK2YLU.js → chunk-DW32TL5W.js} +117 -83
- package/dist/chunk-DW32TL5W.js.map +1 -0
- package/dist/{chunk-6BJUYZ43.js → chunk-GPFVEF6V.js} +28 -18
- package/dist/chunk-GPFVEF6V.js.map +1 -0
- package/dist/{chunk-TILAKDN6.js → chunk-HJ3WREGP.js} +2 -2
- package/dist/{chunk-BGUID5BS.js → chunk-J7DNV2DH.js} +219 -26
- package/dist/chunk-J7DNV2DH.js.map +1 -0
- package/dist/{chunk-DL5BXZCX.js → chunk-K2JBCB7R.js} +40 -54
- package/dist/chunk-K2JBCB7R.js.map +1 -0
- package/dist/{chunk-MRRX4UQB.js → chunk-ODJAAJGZ.js} +2 -2
- package/dist/{chunk-447U3GQJ.js → chunk-PDFS5VFE.js} +17 -5
- package/dist/chunk-PDFS5VFE.js.map +1 -0
- package/dist/{chunk-GFUB57IT.js → chunk-VXDPUOQ5.js} +384 -207
- package/dist/chunk-VXDPUOQ5.js.map +1 -0
- package/dist/{cli-CL4ID7EO.js → cli-MKXCNEMW.js} +14 -14
- package/dist/codealmanac.js +1 -1
- package/dist/{config-ML2RCR7J.js → config-F7FKEQ7F.js} +3 -3
- package/dist/doctor-37UH3HT5.js +17 -0
- package/dist/{hook-2NP3UE7U.js → hook-4SVX446M.js} +4 -2
- package/dist/{register-commands-FBJ6XQ3L.js → register-commands-2F6SXLDI.js} +30 -21
- package/dist/register-commands-2F6SXLDI.js.map +1 -0
- package/dist/uninstall-C62ZOK32.js +17 -0
- package/dist/{update-P2IPG7RO.js → update-2UGOFN5C.js} +3 -3
- package/guides/mini.md +3 -3
- package/guides/reference.md +7 -7
- package/package.json +4 -3
- package/dist/chunk-447U3GQJ.js.map +0 -1
- package/dist/chunk-6BJUYZ43.js.map +0 -1
- package/dist/chunk-BGUID5BS.js.map +0 -1
- package/dist/chunk-DL5BXZCX.js.map +0 -1
- package/dist/chunk-GFUB57IT.js.map +0 -1
- package/dist/chunk-P5WGG4FJ.js.map +0 -1
- package/dist/chunk-SMIK2YLU.js.map +0 -1
- package/dist/doctor-DOLJRGS4.js +0 -17
- package/dist/register-commands-FBJ6XQ3L.js.map +0 -1
- package/dist/uninstall-DX6LFKMX.js +0 -15
- /package/dist/{agents-RVTQYE6A.js.map → agents-HYRWRHRX.js.map} +0 -0
- /package/dist/{chunk-TILAKDN6.js.map → chunk-HJ3WREGP.js.map} +0 -0
- /package/dist/{chunk-MRRX4UQB.js.map → chunk-ODJAAJGZ.js.map} +0 -0
- /package/dist/{cli-CL4ID7EO.js.map → cli-MKXCNEMW.js.map} +0 -0
- /package/dist/{config-ML2RCR7J.js.map → config-F7FKEQ7F.js.map} +0 -0
- /package/dist/{doctor-DOLJRGS4.js.map → doctor-37UH3HT5.js.map} +0 -0
- /package/dist/{hook-2NP3UE7U.js.map → hook-4SVX446M.js.map} +0 -0
- /package/dist/{uninstall-DX6LFKMX.js.map → uninstall-C62ZOK32.js.map} +0 -0
- /package/dist/{update-P2IPG7RO.js.map → update-2UGOFN5C.js.map} +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CODEX_INSTRUCTIONS_END,
|
|
4
|
+
CODEX_INSTRUCTIONS_START,
|
|
3
5
|
IMPORT_LINE
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VXDPUOQ5.js";
|
|
5
7
|
import {
|
|
6
8
|
runHookUninstall
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PDFS5VFE.js";
|
|
8
10
|
|
|
9
11
|
// src/commands/uninstall.ts
|
|
10
12
|
import { existsSync } from "fs";
|
|
@@ -19,6 +21,7 @@ async function runUninstall(options = {}) {
|
|
|
19
21
|
const isTTY = options.isTTY ?? process.stdin.isTTY === true;
|
|
20
22
|
const interactive = isTTY && options.yes !== true;
|
|
21
23
|
const claudeDir = options.claudeDir ?? path.join(homedir(), ".claude");
|
|
24
|
+
const codexDir = options.codexDir ?? path.join(homedir(), ".codex");
|
|
22
25
|
out.write("\n");
|
|
23
26
|
let removeHook = true;
|
|
24
27
|
if (options.keepHook === true) {
|
|
@@ -50,12 +53,12 @@ async function runUninstall(options = {}) {
|
|
|
50
53
|
} else if (interactive) {
|
|
51
54
|
removeGuides = await confirm(
|
|
52
55
|
out,
|
|
53
|
-
"Remove
|
|
56
|
+
"Remove agent instructions?",
|
|
54
57
|
true
|
|
55
58
|
);
|
|
56
59
|
}
|
|
57
60
|
if (removeGuides) {
|
|
58
|
-
const summary = await removeGuideFiles(claudeDir);
|
|
61
|
+
const summary = await removeGuideFiles(claudeDir, codexDir);
|
|
59
62
|
if (summary.anyChanges) {
|
|
60
63
|
out.write(
|
|
61
64
|
` ${BLUE}\u25C7${RST} Guides removed (${summary.filesTouched.join(", ")})
|
|
@@ -75,7 +78,7 @@ async function runUninstall(options = {}) {
|
|
|
75
78
|
`);
|
|
76
79
|
return { stdout: "", stderr: "", exitCode: 0 };
|
|
77
80
|
}
|
|
78
|
-
async function removeGuideFiles(claudeDir) {
|
|
81
|
+
async function removeGuideFiles(claudeDir, codexDir) {
|
|
79
82
|
const touched = [];
|
|
80
83
|
const mini = path.join(claudeDir, "codealmanac.md");
|
|
81
84
|
const ref = path.join(claudeDir, "codealmanac-reference.md");
|
|
@@ -101,57 +104,27 @@ async function removeGuideFiles(claudeDir) {
|
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const mini = path.join(codexDir, "codealmanac.md");
|
|
112
|
-
const ref = path.join(codexDir, "codealmanac-reference.md");
|
|
113
|
-
const agents = path.join(codexDir, "AGENTS.md");
|
|
114
|
-
if (existsSync(mini)) {
|
|
115
|
-
await rm(mini, { force: true });
|
|
116
|
-
touched.push("~/.codex/codealmanac.md");
|
|
117
|
-
}
|
|
118
|
-
if (existsSync(ref)) {
|
|
119
|
-
await rm(ref, { force: true });
|
|
120
|
-
touched.push("~/.codex/codealmanac-reference.md");
|
|
121
|
-
}
|
|
122
|
-
if (existsSync(agents)) {
|
|
123
|
-
const existing = await readFile(agents, "utf8");
|
|
124
|
-
const body = removeManagedBlock(
|
|
107
|
+
for (const agentsFile of [
|
|
108
|
+
path.join(codexDir, "AGENTS.md"),
|
|
109
|
+
path.join(codexDir, "AGENTS.override.md")
|
|
110
|
+
]) {
|
|
111
|
+
if (!existsSync(agentsFile)) continue;
|
|
112
|
+
const existing = await readFile(agentsFile, "utf8");
|
|
113
|
+
const { changed, body } = removeManagedBlock(
|
|
125
114
|
existing,
|
|
126
|
-
|
|
127
|
-
|
|
115
|
+
CODEX_INSTRUCTIONS_START,
|
|
116
|
+
CODEX_INSTRUCTIONS_END
|
|
128
117
|
);
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
118
|
+
if (!changed) continue;
|
|
119
|
+
if (body.trim().length === 0) {
|
|
120
|
+
await rm(agentsFile, { force: true });
|
|
121
|
+
touched.push(`${path.basename(agentsFile)} (deleted)`);
|
|
122
|
+
} else {
|
|
123
|
+
await writeFile(agentsFile, body, "utf8");
|
|
124
|
+
touched.push(path.basename(agentsFile));
|
|
137
125
|
}
|
|
138
126
|
}
|
|
139
|
-
return touched;
|
|
140
|
-
}
|
|
141
|
-
async function removeCursorGuides(cwd) {
|
|
142
|
-
const rule = path.join(cwd, ".cursor", "rules", "codealmanac.mdc");
|
|
143
|
-
if (!existsSync(rule)) return [];
|
|
144
|
-
await rm(rule, { force: true });
|
|
145
|
-
return [".cursor/rules/codealmanac.mdc"];
|
|
146
|
-
}
|
|
147
|
-
function removeManagedBlock(contents, start, end) {
|
|
148
|
-
const pattern = new RegExp(
|
|
149
|
-
`\\n?${escapeRegex(start)}[\\s\\S]*?${escapeRegex(end)}\\n?`
|
|
150
|
-
);
|
|
151
|
-
return contents.replace(pattern, "\n").replace(/\n\n\n+/g, "\n\n");
|
|
152
|
-
}
|
|
153
|
-
function escapeRegex(value) {
|
|
154
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
127
|
+
return { anyChanges: touched.length > 0, filesTouched: touched };
|
|
155
128
|
}
|
|
156
129
|
function removeImportLine(contents) {
|
|
157
130
|
const eol = contents.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -168,6 +141,18 @@ function removeImportLine(contents) {
|
|
|
168
141
|
body = body.replace(/\n\n\n+/g, "\n\n");
|
|
169
142
|
return { changed: true, body };
|
|
170
143
|
}
|
|
144
|
+
function removeManagedBlock(contents, start, end) {
|
|
145
|
+
const startIndex = contents.indexOf(start);
|
|
146
|
+
const endIndex = contents.indexOf(end);
|
|
147
|
+
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
|
|
148
|
+
return { changed: false, body: contents };
|
|
149
|
+
}
|
|
150
|
+
const afterEnd = endIndex + end.length;
|
|
151
|
+
let body = `${contents.slice(0, startIndex)}${contents.slice(afterEnd)}`;
|
|
152
|
+
body = body.replace(/\n\n\n+/g, "\n\n");
|
|
153
|
+
body = body.replace(/^\n+/, "");
|
|
154
|
+
return { changed: true, body };
|
|
155
|
+
}
|
|
171
156
|
function confirm(out, question, defaultYes) {
|
|
172
157
|
return new Promise((resolve) => {
|
|
173
158
|
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
@@ -190,6 +175,7 @@ function confirm(out, question, defaultYes) {
|
|
|
190
175
|
|
|
191
176
|
export {
|
|
192
177
|
runUninstall,
|
|
193
|
-
removeImportLine
|
|
178
|
+
removeImportLine,
|
|
179
|
+
removeManagedBlock
|
|
194
180
|
};
|
|
195
|
-
//# sourceMappingURL=chunk-
|
|
181
|
+
//# sourceMappingURL=chunk-K2JBCB7R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/uninstall.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport { runHookUninstall } from \"./hook.js\";\nimport {\n CODEX_INSTRUCTIONS_END,\n CODEX_INSTRUCTIONS_START,\n IMPORT_LINE,\n} from \"./setup.js\";\n\n/**\n * `almanac uninstall` — the reverse of `setup`.\n *\n * Idempotent and order-insensitive: each step is a no-op if that\n * artifact was never installed. We remove exactly the things setup added,\n * nothing else:\n *\n * 1. The `@~/.claude/codealmanac.md` line from `~/.claude/CLAUDE.md`.\n * Other content stays untouched. If removing our line leaves the\n * file empty, we delete the file so our fingerprint doesn't persist\n * as zero bytes.\n * 2. The guide files `~/.claude/codealmanac.md` and\n * `~/.claude/codealmanac-reference.md`.\n * 3. The managed codealmanac block from Codex's global AGENTS file.\n * 4. The SessionEnd hook entry (delegated to `runHookUninstall`, which\n * already knows how to leave foreign entries alone).\n *\n * Flags:\n * --yes skip confirmations; remove everything\n * --keep-hook leave the hook alone\n * --keep-guides leave the guides + CLAUDE.md import alone\n *\n * Non-interactive (no TTY) → behaves as if `--yes` was passed. Same\n * contract as `setup`.\n */\n\nexport interface UninstallOptions {\n yes?: boolean;\n keepHook?: boolean;\n keepGuides?: boolean;\n\n // ─── Injection points ────────────────────────────────────────────\n settingsPath?: string;\n hookScriptPath?: string;\n claudeDir?: string;\n codexDir?: string;\n isTTY?: boolean;\n stdout?: NodeJS.WritableStream;\n}\n\nexport interface UninstallResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nconst BLUE = \"\\x1b[38;5;75m\";\nconst DIM = \"\\x1b[2m\";\nconst RST = \"\\x1b[0m\";\n\nexport async function runUninstall(\n options: UninstallOptions = {},\n): Promise<UninstallResult> {\n const out = options.stdout ?? process.stdout;\n const isTTY =\n options.isTTY ?? (process.stdin.isTTY === true);\n const interactive = isTTY && options.yes !== true;\n const claudeDir = options.claudeDir ?? path.join(homedir(), \".claude\");\n const codexDir = options.codexDir ?? path.join(homedir(), \".codex\");\n\n out.write(\"\\n\");\n\n // Hook removal.\n let removeHook = true;\n if (options.keepHook === true) {\n removeHook = false;\n } else if (interactive) {\n removeHook = await confirm(\n out,\n \"Remove the SessionEnd hook from ~/.claude/settings.json?\",\n true,\n );\n }\n if (removeHook) {\n const res = await runHookUninstall({\n settingsPath: options.settingsPath,\n hookScriptPath: options.hookScriptPath,\n });\n if (res.exitCode !== 0) {\n return { stdout: \"\", stderr: res.stderr, exitCode: res.exitCode };\n }\n out.write(` ${BLUE}\\u25c7${RST} ${res.stdout.trim()}\\n`);\n } else {\n out.write(` ${DIM}\\u25cb Hook kept${RST}\\n`);\n }\n\n // Guide + import removal.\n let removeGuides = true;\n if (options.keepGuides === true) {\n removeGuides = false;\n } else if (interactive) {\n removeGuides = await confirm(\n out,\n \"Remove agent instructions?\",\n true,\n );\n }\n if (removeGuides) {\n const summary = await removeGuideFiles(claudeDir, codexDir);\n if (summary.anyChanges) {\n out.write(\n ` ${BLUE}\\u25c7${RST} Guides removed (${summary.filesTouched.join(\", \")})\\n`,\n );\n } else {\n out.write(` ${DIM}\\u25cb Guides not installed${RST}\\n`);\n }\n } else {\n out.write(` ${DIM}\\u25cb Guides kept${RST}\\n`);\n }\n\n out.write(`\\n ${BLUE}\\u25c7${RST} ${BLUE}Uninstall complete${RST}\\n\\n`);\n\n return { stdout: \"\", stderr: \"\", exitCode: 0 };\n}\n\ninterface RemoveGuidesResult {\n anyChanges: boolean;\n filesTouched: string[];\n}\n\nasync function removeGuideFiles(\n claudeDir: string,\n codexDir: string,\n): Promise<RemoveGuidesResult> {\n const touched: string[] = [];\n\n const mini = path.join(claudeDir, \"codealmanac.md\");\n const ref = path.join(claudeDir, \"codealmanac-reference.md\");\n const claudeMd = path.join(claudeDir, \"CLAUDE.md\");\n\n if (existsSync(mini)) {\n await rm(mini, { force: true });\n touched.push(\"codealmanac.md\");\n }\n if (existsSync(ref)) {\n await rm(ref, { force: true });\n touched.push(\"codealmanac-reference.md\");\n }\n\n if (existsSync(claudeMd)) {\n const existing = await readFile(claudeMd, \"utf8\");\n const { changed, body } = removeImportLine(existing);\n if (changed) {\n // If the file is now content-free, delete it outright so our\n // installation leaves no trace. A user who was using CLAUDE.md\n // before we touched it still has their content; only the case\n // where CLAUDE.md contained nothing but our line gets cleaned up.\n if (body.trim().length === 0) {\n await rm(claudeMd, { force: true });\n touched.push(\"CLAUDE.md (deleted)\");\n } else {\n await writeFile(claudeMd, body, \"utf8\");\n touched.push(\"CLAUDE.md\");\n }\n }\n }\n\n for (const agentsFile of [\n path.join(codexDir, \"AGENTS.md\"),\n path.join(codexDir, \"AGENTS.override.md\"),\n ]) {\n if (!existsSync(agentsFile)) continue;\n const existing = await readFile(agentsFile, \"utf8\");\n const { changed, body } = removeManagedBlock(\n existing,\n CODEX_INSTRUCTIONS_START,\n CODEX_INSTRUCTIONS_END,\n );\n if (!changed) continue;\n if (body.trim().length === 0) {\n await rm(agentsFile, { force: true });\n touched.push(`${path.basename(agentsFile)} (deleted)`);\n } else {\n await writeFile(agentsFile, body, \"utf8\");\n touched.push(path.basename(agentsFile));\n }\n }\n\n return { anyChanges: touched.length > 0, filesTouched: touched };\n}\n\n/**\n * Remove the import line from a CLAUDE.md body. Match is line-anchored\n * (trimmed equality) so we don't munge a line that happens to include\n * the token as part of a longer string. Returns the unchanged body (and\n * `changed: false`) if the line isn't present — this is what makes the\n * command safe to run repeatedly.\n */\nexport function removeImportLine(contents: string): {\n changed: boolean;\n body: string;\n} {\n const eol = contents.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n const lines = contents.split(/\\r?\\n/);\n\n const indices: number[] = [];\n for (let i = 0; i < lines.length; i++) {\n if (lines[i]!.trim() === IMPORT_LINE) indices.push(i);\n }\n if (indices.length === 0) return { changed: false, body: contents };\n\n // Remove the line(s). Iterate from the end so earlier indices stay\n // valid as we splice.\n for (let i = indices.length - 1; i >= 0; i--) {\n lines.splice(indices[i]!, 1);\n }\n\n let body = lines.join(eol);\n\n // Cleanup: collapse any double-blank that our removal created at the\n // spot the line used to live. A best-effort tidy — we don't try to\n // normalize the whole file.\n body = body.replace(/\\n\\n\\n+/g, \"\\n\\n\");\n\n return { changed: true, body };\n}\n\nexport function removeManagedBlock(\n contents: string,\n start: string,\n end: string,\n): { changed: boolean; body: string } {\n const startIndex = contents.indexOf(start);\n const endIndex = contents.indexOf(end);\n if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {\n return { changed: false, body: contents };\n }\n\n const afterEnd = endIndex + end.length;\n let body = `${contents.slice(0, startIndex)}${contents.slice(afterEnd)}`;\n body = body.replace(/\\n\\n\\n+/g, \"\\n\\n\");\n body = body.replace(/^\\n+/, \"\");\n return { changed: true, body };\n}\n\nfunction confirm(\n out: NodeJS.WritableStream,\n question: string,\n defaultYes: boolean,\n): Promise<boolean> {\n return new Promise((resolve) => {\n const hint = defaultYes ? \"[Y/n]\" : \"[y/N]\";\n out.write(` ${BLUE}\\u25c6${RST} ${question} ${DIM}${hint}${RST} `);\n\n let buf = \"\";\n const onData = (chunk: Buffer): void => {\n buf += chunk.toString(\"utf8\");\n const nl = buf.indexOf(\"\\n\");\n if (nl === -1) return;\n process.stdin.removeListener(\"data\", onData);\n process.stdin.pause();\n\n const answer = buf.slice(0, nl).trim().toLowerCase();\n const accepted =\n answer.length === 0\n ? defaultYes\n : answer === \"y\" || answer === \"yes\";\n resolve(accepted);\n };\n\n process.stdin.resume();\n process.stdin.on(\"data\", onData);\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
readConfig,
|
|
10
10
|
writeConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-3E7JNMTZ.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/update.ts
|
|
14
14
|
import { spawn } from "child_process";
|
|
@@ -202,4 +202,4 @@ function readInstalledVersion() {
|
|
|
202
202
|
export {
|
|
203
203
|
runUpdate
|
|
204
204
|
};
|
|
205
|
-
//# sourceMappingURL=chunk-
|
|
205
|
+
//# sourceMappingURL=chunk-ODJAAJGZ.js.map
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isCursorEnabled
|
|
4
|
+
} from "./chunk-3E7JNMTZ.js";
|
|
2
5
|
|
|
3
6
|
// src/commands/hook.ts
|
|
4
7
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -131,15 +134,17 @@ async function runHookInstall(options = {}) {
|
|
|
131
134
|
eventName: "Stop",
|
|
132
135
|
shape: "wrapped",
|
|
133
136
|
scriptPath: script.path
|
|
134
|
-
})
|
|
135
|
-
|
|
137
|
+
})
|
|
138
|
+
];
|
|
139
|
+
if (isCursorEnabled()) {
|
|
140
|
+
results.push(await installGenericHook({
|
|
136
141
|
label: "Cursor sessionEnd",
|
|
137
142
|
settingsPath: path2.join(homedir2(), ".cursor", "hooks.json"),
|
|
138
143
|
eventName: "sessionEnd",
|
|
139
144
|
shape: "flat",
|
|
140
145
|
scriptPath: script.path
|
|
141
|
-
})
|
|
142
|
-
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
143
148
|
const failed = results.find((r) => r.exitCode !== 0);
|
|
144
149
|
if (failed !== void 0) return failed;
|
|
145
150
|
return {
|
|
@@ -158,6 +163,13 @@ async function runHookInstall(options = {}) {
|
|
|
158
163
|
});
|
|
159
164
|
}
|
|
160
165
|
if (source === "cursor") {
|
|
166
|
+
if (!isCursorEnabled()) {
|
|
167
|
+
return {
|
|
168
|
+
stdout: "",
|
|
169
|
+
stderr: "almanac: cursor hooks are disabled. Set CODEALMANAC_ENABLE_CURSOR=1 to enable experimental Cursor support.\n",
|
|
170
|
+
exitCode: 1
|
|
171
|
+
};
|
|
172
|
+
}
|
|
161
173
|
return await installGenericHook({
|
|
162
174
|
label: "Cursor sessionEnd",
|
|
163
175
|
settingsPath: path2.join(homedir2(), ".cursor", "hooks.json"),
|
|
@@ -509,4 +521,4 @@ export {
|
|
|
509
521
|
runHookUninstall,
|
|
510
522
|
runHookStatus
|
|
511
523
|
};
|
|
512
|
-
//# sourceMappingURL=chunk-
|
|
524
|
+
//# sourceMappingURL=chunk-PDFS5VFE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/hook.ts","../src/commands/hook/script.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport {\n copyToStableHooksDir,\n resolveHookScriptPath,\n resolveSettingsPath,\n type ScriptResolution,\n} from \"./hook/script.js\";\nimport { isCursorEnabled } from \"../update/config.js\";\n\n/**\n * `almanac hook install|uninstall|status` — wires the bundled\n * `hooks/almanac-capture.sh` into `~/.claude/settings.json` as a\n * `SessionEnd` hook.\n *\n * Design notes:\n *\n * - **Schema.** Claude Code validates `settings.json` against a strict\n * schema: each entry in an event array (like `SessionEnd`) is a\n * `{matcher, hooks: [...]}` container, and the actual command objects\n * live in the nested `hooks` array. v0.1.0–v0.1.4 wrote command objects\n * directly at the event-array level; newer Claude Code versions now\n * reject that shape. We produce the wrapped form on install, and when\n * encountering a legacy unwrapped entry that we recognize as ours (by\n * `command` ending in `almanac-capture.sh`) we migrate it on next\n * install. `SessionEnd` never uses the `matcher` field to discriminate\n * anything — we always emit an empty `matcher: \"\"` (matches\n * everything, which is what session-end lifecycle hooks want).\n *\n * - **Idempotent.** `install` twice leaves one entry, not two. We match by\n * `command` string equality on the inner `hooks[]` entries. If the user\n * replaces our absolute path with a symlink pointing at the same\n * script, we'll treat it as foreign. That's acceptable; the `status`\n * output shows the path we'd use, so the user can reconcile manually.\n *\n * - **Refuse foreign entries.** If `SessionEnd` is already populated with\n * a command we don't recognize, we print the existing value and exit\n * non-zero. Claude Code lets users wire their own hooks (notifications,\n * git autocommit scripts, etc.) and silently replacing them would be\n * rude. Foreign wrapped containers that don't reference our script are\n * preserved byte-for-byte.\n *\n * - **Atomic write.** `settings.json` is small but heavily touched by\n * Claude Code. Writing via tmp-file + rename avoids corrupting the file\n * if we crash mid-write.\n *\n * - **Non-interactive.** No prompts, no confirmations. The caller is\n * already making an intentional choice by running `almanac hook\n * install`.\n */\n\nexport interface HookCommandOptions {\n /** Which agent app to install hooks for. Default keeps legacy Claude behavior. */\n source?: \"claude\" | \"codex\" | \"cursor\" | \"all\";\n /**\n * Override the hook script path. Production code leaves this undefined\n * and we resolve the bundled `hooks/almanac-capture.sh`. Tests pass a\n * fixture path to avoid depending on the runtime-install layout.\n */\n hookScriptPath?: string;\n /**\n * Override `~/.claude/settings.json`. Tests sandbox this to a tmpdir;\n * production code leaves it undefined.\n */\n settingsPath?: string;\n /**\n * Override the stable hooks directory where we copy the script.\n * Defaults to `~/.claude/hooks/`. Tests sandbox this to a tmpdir.\n *\n * Bug #1 fix: we always copy the bundled script to this stable path\n * before writing it into settings.json. This way the settings entry\n * points at a user-owned location that survives npm version bumps,\n * npx cache evictions, and nvm version switches — instead of an\n * ephemeral path inside ~/.npm/_npx/<sha>/... or the nvm-versioned\n * node_modules/.\n */\n stableHooksDir?: string;\n}\n\nexport interface HookCommandResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nconst HOOK_TIMEOUT_SECONDS = 10;\n\n/** A single command invocation inside a wrapper's `hooks[]` array. */\ninterface HookCommand {\n type: \"command\";\n command: string;\n timeout?: number;\n}\n\n/** A wrapped SessionEnd entry per Claude Code's schema. */\ninterface WrappedEntry {\n matcher: string;\n hooks: HookCommand[];\n}\n\n/**\n * What we read from `settings.hooks.SessionEnd`. During a read we may\n * encounter the legacy unwrapped shape (`HookCommand` directly) written\n * by v0.1.0–v0.1.4 — we recognize and migrate it. Unknown entries we\n * can't classify are preserved as-is via `unknown`.\n */\ntype RawEntry = WrappedEntry | HookCommand | unknown;\n\n/**\n * Claude Code's `settings.json` is a free-form JSON object; we only care\n * about the `hooks.SessionEnd` array. Preserve everything else verbatim\n * so we don't drop user settings when we write the file back.\n */\ntype SettingsJson = Record<string, unknown> & {\n hooks?: Record<string, RawEntry[] | undefined>;\n};\n\n/**\n * Heuristic: does this command path look like one we installed?\n *\n * We match on the filename `almanac-capture.sh` regardless of the parent\n * directory. This covers:\n * - the stable path: `~/.claude/hooks/almanac-capture.sh`\n * - legacy paths from v0.1.0–v0.1.5: inside the nvm node_modules or\n * npx cache\n * The stable path is what new installs produce; legacy paths are what\n * we migrate when the user runs `almanac hook install` again.\n */\nfunction isOurCommandPath(command: string): boolean {\n return command.endsWith(\"almanac-capture.sh\");\n}\n\n/**\n * Classify a raw SessionEnd entry. Wrapped entries are the canonical\n * shape; unwrapped-command entries are legacy output from v0.1.0–v0.1.4.\n * Anything else (random user JSON) is `unknown` and we leave it alone.\n */\ntype Classified =\n | { kind: \"wrapped\"; entry: WrappedEntry }\n | { kind: \"legacy\"; entry: HookCommand }\n | { kind: \"unknown\"; entry: unknown };\n\nfunction classifyEntry(raw: RawEntry): Classified {\n if (raw === null || typeof raw !== \"object\") {\n return { kind: \"unknown\", entry: raw };\n }\n const obj = raw as Record<string, unknown>;\n if (Array.isArray(obj.hooks)) {\n // Wrapped shape. `matcher` may be absent in hand-edited files; treat\n // absent as \"\" so we don't throw on slightly malformed input.\n const matcher = typeof obj.matcher === \"string\" ? obj.matcher : \"\";\n const hooks: HookCommand[] = [];\n for (const h of obj.hooks as unknown[]) {\n if (h !== null && typeof h === \"object\") {\n const ho = h as Record<string, unknown>;\n if (ho.type === \"command\" && typeof ho.command === \"string\") {\n const cmd: HookCommand = {\n type: \"command\",\n command: ho.command,\n };\n if (typeof ho.timeout === \"number\") cmd.timeout = ho.timeout;\n hooks.push(cmd);\n }\n }\n }\n return { kind: \"wrapped\", entry: { matcher, hooks } };\n }\n if (obj.type === \"command\" && typeof obj.command === \"string\") {\n // Legacy unwrapped shape — v0.1.0–v0.1.4 wrote this form.\n const cmd: HookCommand = {\n type: \"command\",\n command: obj.command as string,\n };\n if (typeof obj.timeout === \"number\") cmd.timeout = obj.timeout;\n return { kind: \"legacy\", entry: cmd };\n }\n return { kind: \"unknown\", entry: raw };\n}\n\n/** True when the entry references our script and is safely ours to manage. */\nfunction isOurWrapped(entry: WrappedEntry): boolean {\n return entry.hooks.some((h) => isOurCommandPath(h.command));\n}\n\nexport async function runHookInstall(\n options: HookCommandOptions = {},\n): Promise<HookCommandResult> {\n const bundled = resolveHookScriptPath(options);\n if (!bundled.ok) {\n return { stdout: \"\", stderr: `almanac: ${bundled.error}\\n`, exitCode: 1 };\n }\n\n // Copy the bundled hook script to a stable user-owned location before\n // writing that path into settings.json. This is the Bug #1 fix:\n //\n // OLD behavior: settings.json pointed at the bundled path (inside\n // ~/.nvm/versions/node/<ver>/lib/node_modules/codealmanac/hooks/... or\n // ~/.npm/_npx/<sha>/node_modules/codealmanac/hooks/...). When the user\n // switches Node versions or the npx cache is evicted, the path breaks\n // silently and captures stop firing.\n //\n // NEW behavior: we copy almanac-capture.sh to ~/.claude/hooks/ (same\n // directory Claude Code uses for its own built-in hooks, always present)\n // and point settings.json there. The stable path is independent of\n // Node version and npm cache state. When the user upgrades codealmanac,\n // `almanac hook install` copies a fresh script and updates settings.json\n // if the path changed.\n //\n // When `hookScriptPath` is explicitly provided (test injection), the\n // caller has already specified the destination path — skip the copy and\n // use that path directly. The stable-copy concern only applies to the\n // production flow where we resolved from the bundled package layout.\n const script: ScriptResolution = options.hookScriptPath !== undefined\n ? bundled // already the caller-provided path, no copy needed\n : await copyToStableHooksDir(bundled.path, options);\n if (!script.ok) {\n return { stdout: \"\", stderr: `almanac: ${script.error}\\n`, exitCode: 1 };\n }\n\n const source = options.source ?? \"claude\";\n if (source === \"all\") {\n const results = [\n await installClaudeHook(options, script.path),\n await installGenericHook({\n label: \"Codex Stop\",\n settingsPath: path.join(homedir(), \".codex\", \"hooks.json\"),\n eventName: \"Stop\",\n shape: \"wrapped\",\n scriptPath: script.path,\n }),\n ];\n if (isCursorEnabled()) {\n results.push(await installGenericHook({\n label: \"Cursor sessionEnd\",\n settingsPath: path.join(homedir(), \".cursor\", \"hooks.json\"),\n eventName: \"sessionEnd\",\n shape: \"flat\",\n scriptPath: script.path,\n }));\n }\n const failed = results.find((r) => r.exitCode !== 0);\n if (failed !== undefined) return failed;\n return {\n stdout: results.map((r) => r.stdout.trimEnd()).join(\"\\n\") + \"\\n\",\n stderr: \"\",\n exitCode: 0,\n };\n }\n if (source === \"codex\") {\n return await installGenericHook({\n label: \"Codex Stop\",\n settingsPath: path.join(homedir(), \".codex\", \"hooks.json\"),\n eventName: \"Stop\",\n shape: \"wrapped\",\n scriptPath: script.path,\n });\n }\n if (source === \"cursor\") {\n if (!isCursorEnabled()) {\n return {\n stdout: \"\",\n stderr:\n \"almanac: cursor hooks are disabled. Set CODEALMANAC_ENABLE_CURSOR=1 to enable experimental Cursor support.\\n\",\n exitCode: 1,\n };\n }\n return await installGenericHook({\n label: \"Cursor sessionEnd\",\n settingsPath: path.join(homedir(), \".cursor\", \"hooks.json\"),\n eventName: \"sessionEnd\",\n shape: \"flat\",\n scriptPath: script.path,\n });\n }\n\n return await installClaudeHook(options, script.path);\n}\n\nasync function installClaudeHook(\n options: HookCommandOptions,\n scriptPath: string,\n): Promise<HookCommandResult> {\n\n const settingsPath = resolveSettingsPath(options);\n const settings = await readSettings(settingsPath);\n const existing = (settings.hooks?.SessionEnd ?? []).slice();\n\n // Walk existing entries and split them into buckets:\n // - `preserved` — foreign wrapped/unknown entries we leave alone.\n // - `oursAlready` — a wrapped entry that already points at OUR exact\n // script path (makes install a no-op).\n // - `oursStale` — a wrapped or legacy entry that references our\n // capture script but at a different absolute path\n // (old install, `npm i` moved us) or in the legacy\n // unwrapped shape. We'll collapse these into a\n // single fresh entry at the new path.\n const preserved: RawEntry[] = [];\n let oursAlready: WrappedEntry | null = null;\n const staleCount = { n: 0 };\n\n for (const raw of existing) {\n const c = classifyEntry(raw);\n if (c.kind === \"wrapped\") {\n if (!isOurWrapped(c.entry)) {\n preserved.push(raw);\n continue;\n }\n // Entry belongs to us. Does it already point at the exact script\n // path? If every command in its `hooks[]` that looks like ours is\n // already at `script.path`, it's up to date.\n const exactMatch = c.entry.hooks.some(\n (h) => h.command === scriptPath,\n );\n if (exactMatch && oursAlready === null) {\n oursAlready = c.entry;\n } else {\n staleCount.n += 1;\n }\n } else if (c.kind === \"legacy\") {\n if (isOurCommandPath(c.entry.command)) {\n // Legacy unwrapped entry of ours — always migrate to wrapped.\n staleCount.n += 1;\n } else {\n // Foreign legacy entry (user had their own script before\n // settings.json required wrapping). Leave it alone.\n preserved.push(raw);\n }\n } else {\n // Unknown shape — we can't classify it. Preserve verbatim.\n preserved.push(raw);\n }\n }\n\n // If every non-ours entry is a foreign unwrapped command (not a\n // wrapped one) we refuse to touch the file — Claude Code's newer\n // schema will already reject such files, but surfacing it here lets\n // the user clean up before we stack our entry on top. Wrapped foreign\n // entries are fine to leave alongside ours.\n const foreignLegacy = preserved.filter((raw) => {\n const c = classifyEntry(raw);\n return c.kind === \"legacy\";\n });\n if (foreignLegacy.length > 0) {\n const lines = foreignLegacy\n .map((raw) => {\n const c = classifyEntry(raw);\n if (c.kind === \"legacy\") return ` - ${c.entry.command}`;\n return \" - <unrecognized>\";\n })\n .join(\"\\n\");\n return {\n stdout: \"\",\n stderr:\n `almanac: SessionEnd has a foreign legacy entry:\\n${lines}\\n` +\n `Remove or rewrap it manually in ${settingsPath} before installing.\\n`,\n exitCode: 1,\n };\n }\n\n if (oursAlready !== null && staleCount.n === 0) {\n return {\n stdout: `almanac: SessionEnd hook already installed at ${scriptPath}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n // Build the fresh wrapped entry and append to preserved foreign\n // entries. Stale entries of ours are dropped (we only ever want a\n // single active entry; multiple copies of the capture hook would\n // double-fire on session end).\n const fresh: WrappedEntry = {\n matcher: \"\",\n hooks: [\n {\n type: \"command\",\n command: scriptPath,\n timeout: HOOK_TIMEOUT_SECONDS,\n },\n ],\n };\n\n const newEntries: RawEntry[] = [...preserved, fresh];\n\n settings.hooks = { ...(settings.hooks ?? {}), SessionEnd: newEntries };\n await writeSettings(settingsPath, settings);\n\n return {\n stdout:\n `almanac: SessionEnd hook installed\\n` +\n ` script: ${scriptPath}\\n` +\n ` settings: ${settingsPath}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\nasync function installGenericHook(args: {\n label: string;\n settingsPath: string;\n eventName: string;\n shape: \"flat\" | \"wrapped\";\n scriptPath: string;\n}): Promise<HookCommandResult> {\n const settings = await readSettings(args.settingsPath);\n const hooksObj =\n settings.hooks !== undefined &&\n settings.hooks !== null &&\n typeof settings.hooks === \"object\"\n ? settings.hooks\n : {};\n const existing = Array.isArray(hooksObj[args.eventName])\n ? (hooksObj[args.eventName] as RawEntry[])\n : [];\n const kept = existing.filter((entry) => !entryHasOurCommand(entry));\n const already = existing.some((entry) =>\n entryHasExactCommand(entry, args.scriptPath),\n );\n if (already && kept.length === existing.length - 1) {\n return {\n stdout: `almanac: ${args.label} hook already installed at ${args.scriptPath}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n const fresh =\n args.shape === \"wrapped\"\n ? {\n hooks: [\n {\n type: \"command\",\n command: args.scriptPath,\n timeout: HOOK_TIMEOUT_SECONDS,\n },\n ],\n }\n : {\n command: args.scriptPath,\n timeout: HOOK_TIMEOUT_SECONDS,\n };\n hooksObj[args.eventName] = [\n ...kept,\n fresh,\n ];\n settings.hooks = hooksObj;\n await writeSettings(args.settingsPath, settings);\n if (args.label.startsWith(\"Codex \")) {\n await ensureCodexHooksFeature(path.join(homedir(), \".codex\", \"config.toml\"));\n }\n return {\n stdout:\n `almanac: ${args.label} hook installed\\n` +\n ` script: ${args.scriptPath}\\n` +\n ` settings: ${args.settingsPath}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\nfunction entryHasOurCommand(entry: unknown): boolean {\n return collectHookCommands(entry).some(isOurCommandPath);\n}\n\nfunction entryHasExactCommand(entry: unknown, command: string): boolean {\n return collectHookCommands(entry).some((candidate) => candidate === command);\n}\n\nfunction collectHookCommands(entry: unknown): string[] {\n if (entry === null || typeof entry !== \"object\") return [];\n const obj = entry as Record<string, unknown>;\n const direct = typeof obj.command === \"string\" ? [obj.command] : [];\n const nested = Array.isArray(obj.hooks)\n ? obj.hooks.flatMap((hook) => collectHookCommands(hook))\n : [];\n return [...direct, ...nested];\n}\n\nasync function ensureCodexHooksFeature(configPath: string): Promise<void> {\n let body = \"\";\n if (existsSync(configPath)) {\n body = await readFile(configPath, \"utf8\");\n }\n if (/^\\s*codex_hooks\\s*=\\s*true\\s*$/m.test(body)) return;\n\n const next = setTomlFeatureFlag(body, \"codex_hooks\", true);\n await mkdir(path.dirname(configPath), { recursive: true });\n const tmp = `${configPath}.almanac-tmp-${process.pid}`;\n await writeFile(tmp, next.endsWith(\"\\n\") ? next : `${next}\\n`, \"utf8\");\n await rename(tmp, configPath);\n}\n\nfunction setTomlFeatureFlag(\n body: string,\n key: string,\n value: boolean,\n): string {\n const desired = `${key} = ${value ? \"true\" : \"false\"}`;\n const lines = body.split(/\\r?\\n/);\n let featuresStart = -1;\n let featuresEnd = lines.length;\n\n for (let i = 0; i < lines.length; i++) {\n if (/^\\s*\\[features\\]\\s*$/.test(lines[i] ?? \"\")) {\n featuresStart = i;\n continue;\n }\n if (featuresStart !== -1 && i > featuresStart && /^\\s*\\[.*\\]\\s*$/.test(lines[i] ?? \"\")) {\n featuresEnd = i;\n break;\n }\n }\n\n if (featuresStart === -1) {\n const prefix = body.trim().length === 0 ? \"\" : `${body.trimEnd()}\\n\\n`;\n return `${prefix}[features]\\n${desired}\\n`;\n }\n\n const keyPattern = new RegExp(`^\\\\s*${escapeRegex(key)}\\\\s*=`);\n for (let i = featuresStart + 1; i < featuresEnd; i++) {\n if (keyPattern.test(lines[i] ?? \"\")) {\n lines[i] = desired;\n return lines.join(\"\\n\");\n }\n }\n\n lines.splice(featuresStart + 1, 0, desired);\n return lines.join(\"\\n\");\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport async function runHookUninstall(\n options: HookCommandOptions = {},\n): Promise<HookCommandResult> {\n const settingsPath = resolveSettingsPath(options);\n\n if (!existsSync(settingsPath)) {\n return {\n stdout: `almanac: SessionEnd hook not installed (no settings file)\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n const settings = await readSettings(settingsPath);\n const existing = (settings.hooks?.SessionEnd ?? []).slice();\n\n const kept: RawEntry[] = [];\n let removed = 0;\n\n for (const raw of existing) {\n const c = classifyEntry(raw);\n if (c.kind === \"wrapped\") {\n // Filter out our command(s) from the inner hooks array. Keep\n // anything else in the array intact — a foreign wrapper that\n // happened to include our script alongside its own commands\n // (unusual, but survivable) loses our entry and keeps theirs.\n const innerKept = c.entry.hooks.filter(\n (h) => !isOurCommandPath(h.command),\n );\n const innerRemoved = c.entry.hooks.length - innerKept.length;\n removed += innerRemoved;\n if (innerKept.length === 0) {\n // Only drop the outer wrapper when it was entirely ours. A\n // foreign wrapper that never contained our script stays verbatim\n // below (handled by `innerRemoved === 0`, which leaves\n // `innerKept.length === c.entry.hooks.length`, hence we fall\n // through to the else-branch).\n if (innerRemoved === 0) kept.push(raw);\n // else: fully owned by us, drop the container.\n } else if (innerRemoved === 0) {\n // Untouched foreign wrapper — preserve the raw object to keep\n // any fields (like matcher) byte-for-byte.\n kept.push(raw);\n } else {\n // Partial: rebuild with just the kept inner entries, preserving\n // the original matcher string.\n kept.push({ matcher: c.entry.matcher, hooks: innerKept });\n }\n } else if (c.kind === \"legacy\") {\n if (isOurCommandPath(c.entry.command)) {\n removed += 1;\n } else {\n kept.push(raw);\n }\n } else {\n kept.push(raw);\n }\n }\n\n if (removed === 0) {\n return {\n stdout: `almanac: SessionEnd hook not installed\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n if (settings.hooks !== undefined) {\n if (kept.length === 0) {\n // Empty SessionEnd array confuses some linters; drop the key when\n // nothing's left.\n const { SessionEnd: _dropped, ...rest } = settings.hooks;\n void _dropped;\n settings.hooks = rest;\n } else {\n settings.hooks = { ...settings.hooks, SessionEnd: kept };\n }\n\n // If `hooks` itself is now empty (user had only our SessionEnd entry\n // and no other hook categories), drop the `hooks` key entirely so\n // uninstall leaves the settings file in the same shape it would be\n // in had we never run install. An empty `\"hooks\": {}` is an obvious\n // breadcrumb in commit diffs.\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n }\n\n await writeSettings(settingsPath, settings);\n\n return {\n stdout: `almanac: SessionEnd hook removed\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\nexport async function runHookStatus(\n options: HookCommandOptions = {},\n): Promise<HookCommandResult> {\n const script = resolveHookScriptPath(options);\n const settingsPath = resolveSettingsPath(options);\n\n if (!existsSync(settingsPath)) {\n return {\n stdout:\n `SessionEnd hook: not installed\\n` +\n `settings: ${settingsPath} (does not exist)\\n` +\n (script.ok ? `script would be: ${script.path}\\n` : \"\"),\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n const settings = await readSettings(settingsPath);\n const existing = settings.hooks?.SessionEnd ?? [];\n\n // Walk the array looking for any entry (wrapped or legacy) that\n // references our capture script. Gathering foreign entries separately\n // lets us show them to the user if nothing of ours was found.\n let ourCommand: string | null = null;\n const foreignSummary: string[] = [];\n for (const raw of existing) {\n const c = classifyEntry(raw);\n if (c.kind === \"wrapped\") {\n for (const h of c.entry.hooks) {\n if (isOurCommandPath(h.command)) {\n ourCommand ??= h.command;\n } else {\n foreignSummary.push(h.command);\n }\n }\n } else if (c.kind === \"legacy\") {\n if (isOurCommandPath(c.entry.command)) {\n ourCommand ??= c.entry.command;\n } else {\n foreignSummary.push(c.entry.command);\n }\n }\n }\n\n if (ourCommand === null) {\n const foreignLines = foreignSummary\n .map((c) => ` - ${c}`)\n .join(\"\\n\");\n return {\n stdout:\n `SessionEnd hook: not installed\\n` +\n `settings: ${settingsPath}\\n` +\n (foreignSummary.length > 0\n ? `(${foreignSummary.length} foreign entr${foreignSummary.length === 1 ? \"y\" : \"ies\"} present:\\n${foreignLines})\\n`\n : \"\") +\n (script.ok ? `script would be: ${script.path}\\n` : \"\"),\n stderr: \"\",\n exitCode: 0,\n };\n }\n\n return {\n stdout:\n `SessionEnd hook: installed\\n` +\n `script: ${ourCommand}\\n` +\n `settings: ${settingsPath}\\n`,\n stderr: \"\",\n exitCode: 0,\n };\n}\n\n// ─── Settings JSON helpers ───────────────────────────────────────────\n\nasync function readSettings(settingsPath: string): Promise<SettingsJson> {\n if (!existsSync(settingsPath)) return {};\n try {\n const raw = await readFile(settingsPath, \"utf8\");\n if (raw.trim().length === 0) return {};\n const parsed = JSON.parse(raw) as unknown;\n if (parsed === null || typeof parsed !== \"object\") return {};\n return parsed as SettingsJson;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`failed to read ${settingsPath}: ${msg}`);\n }\n}\n\nasync function writeSettings(\n settingsPath: string,\n settings: SettingsJson,\n): Promise<void> {\n const dir = path.dirname(settingsPath);\n await mkdir(dir, { recursive: true });\n\n // Atomic write: JSON.stringify → tmp file → rename. `rename` within the\n // same filesystem is atomic on POSIX; Claude Code never sees a partial\n // file. Formatted with 2-space indent to match the existing settings.\n const tmp = `${settingsPath}.almanac-tmp-${process.pid}`;\n const body = `${JSON.stringify(settings, null, 2)}\\n`;\n await writeFile(tmp, body, \"utf8\");\n await rename(tmp, settingsPath);\n}\n","import { existsSync } from \"node:fs\";\nimport { copyFile, mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport interface HookPathOptions {\n hookScriptPath?: string;\n settingsPath?: string;\n stableHooksDir?: string;\n}\n\nexport type ScriptResolution =\n | { ok: true; path: string }\n | { ok: false; error: string };\n\nexport function resolveSettingsPath(options: HookPathOptions): string {\n if (options.settingsPath !== undefined) return options.settingsPath;\n return path.join(homedir(), \".claude\", \"settings.json\");\n}\n\n/**\n * Copy the bundled hook script to `~/.claude/hooks/almanac-capture.sh`.\n *\n * This stable, user-owned destination survives Node version switches and\n * npm/npx cache evictions. The copy is idempotent: if bytes already match\n * we skip writing so repeated setup runs do not bump mtimes.\n */\nexport async function copyToStableHooksDir(\n bundledPath: string,\n options: HookPathOptions,\n): Promise<ScriptResolution> {\n const stableHooksDir =\n options.stableHooksDir ?? path.join(homedir(), \".claude\", \"hooks\");\n const dest = path.join(stableHooksDir, \"almanac-capture.sh\");\n\n try {\n await mkdir(stableHooksDir, { recursive: true });\n const srcBytes = await readFile(bundledPath);\n let needsCopy = true;\n if (existsSync(dest)) {\n try {\n const destBytes = await readFile(dest);\n if (srcBytes.equals(destBytes)) needsCopy = false;\n } catch {\n // Can't read dest — overwrite.\n }\n }\n if (needsCopy) {\n await copyFile(bundledPath, dest);\n }\n return { ok: true, path: dest };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n ok: false,\n error: `could not copy hook script to ${dest}: ${msg}`,\n };\n }\n}\n\n/**\n * Locate the bundled `hooks/almanac-capture.sh`. Mirrors\n * `resolvePromptsDir` from `src/agent/prompts.ts`: two plausible layouts\n * (installed dist vs. source dev), probe each.\n */\nexport function resolveHookScriptPath(\n options: HookPathOptions,\n): ScriptResolution {\n if (options.hookScriptPath !== undefined) {\n return { ok: true, path: options.hookScriptPath };\n }\n\n const here = path.dirname(fileURLToPath(import.meta.url));\n\n const candidates = [\n // Bundled: `.../codealmanac/dist/codealmanac.js` → `../hooks/…`\n path.resolve(here, \"..\", \"hooks\", \"almanac-capture.sh\"),\n // Source after ts-node-style module layout or nested dist helpers.\n path.resolve(here, \"..\", \"..\", \"hooks\", \"almanac-capture.sh\"),\n // Source: `.../codealmanac/src/commands/hook/script.ts` → `../../../hooks/…`\n path.resolve(here, \"..\", \"..\", \"..\", \"hooks\", \"almanac-capture.sh\"),\n // Defensive nested fallback.\n path.resolve(here, \"..\", \"..\", \"..\", \"..\", \"hooks\", \"almanac-capture.sh\"),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return { ok: true, path: candidate };\n }\n }\n\n return {\n ok: false,\n error:\n `could not locate hooks/almanac-capture.sh. Tried:\\n` +\n candidates.map((c) => ` - ${c}`).join(\"\\n\"),\n };\n}\n"],"mappings":"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","names":["existsSync","mkdir","readFile","homedir","path","path","homedir","existsSync","readFile","mkdir"]}
|