claude-baton 2.0.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/dist/cli.js ADDED
@@ -0,0 +1,392 @@
1
+ import { Command } from "commander";
2
+ import { readFileSync, writeFileSync, copyFileSync, existsSync, statSync, readdirSync, unlinkSync, rmSync, } from "fs";
3
+ import { execSync } from "child_process";
4
+ import { fileURLToPath } from "url";
5
+ import path from "path";
6
+ import os from "os";
7
+ import { createInterface } from "readline";
8
+ import { initDatabase, getDefaultDbPath, saveDatabase, countAll, listProjects, insertCheckpoint, getAllCheckpoints, getAllDailySummaries, deleteProjectData, deleteAllData, } from "./store.js";
9
+ import { ensureDir } from "./utils.js";
10
+ import { callClaudeJson } from "./llm.js";
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ function readPromptTemplate(name) {
13
+ return readFileSync(path.join(__dirname, "..", "prompts", name), "utf-8");
14
+ }
15
+ function gitCmd(cmd) {
16
+ try {
17
+ return execSync(cmd, { encoding: "utf-8", timeout: 5000 }).trim();
18
+ }
19
+ catch {
20
+ return "";
21
+ }
22
+ }
23
+ export async function handleAutoCheckpoint() {
24
+ try {
25
+ // Read hook metadata from stdin
26
+ let stdinData = "";
27
+ try {
28
+ stdinData = readFileSync(0, "utf-8");
29
+ }
30
+ catch {
31
+ console.error("[claude-baton] No stdin data, skipping auto-checkpoint");
32
+ return;
33
+ }
34
+ let transcriptPath;
35
+ try {
36
+ const metadata = JSON.parse(stdinData);
37
+ transcriptPath = metadata?.input?.metadata?.transcript_path;
38
+ }
39
+ catch {
40
+ console.error("[claude-baton] Could not parse hook metadata, skipping auto-checkpoint");
41
+ return;
42
+ }
43
+ if (!transcriptPath || !existsSync(transcriptPath)) {
44
+ console.error("[claude-baton] Transcript not found, skipping auto-checkpoint");
45
+ return;
46
+ }
47
+ // Read and truncate transcript
48
+ const fullTranscript = readFileSync(transcriptPath, "utf-8");
49
+ const MAX_CHARS = 50000;
50
+ const transcript = fullTranscript.length > MAX_CHARS
51
+ ? fullTranscript.slice(-MAX_CHARS)
52
+ : fullTranscript;
53
+ // Gather git state
54
+ const branch = gitCmd("git branch --show-current");
55
+ const status = gitCmd("git status --short");
56
+ const log = gitCmd("git log --oneline -10");
57
+ // Build prompt
58
+ const template = readPromptTemplate("auto_checkpoint.txt");
59
+ const prompt = template.replace("{TRANSCRIPT}", transcript);
60
+ // Call LLM
61
+ const result = await callClaudeJson(prompt, "haiku", 30000);
62
+ // Save checkpoint
63
+ const dbPath = getDefaultDbPath();
64
+ const db = await initDatabase(dbPath);
65
+ const projectPath = process.cwd();
66
+ const sessionId = new Date().toISOString();
67
+ const uncommittedFiles = status
68
+ ? status.split("\n").map((l) => l.trim())
69
+ : [];
70
+ insertCheckpoint(db, projectPath, sessionId, result.current_state || "Unknown", result.what_was_built || "Unknown", result.next_steps || "Unknown", {
71
+ branch: branch || undefined,
72
+ decisionsMade: result.decisions_made || undefined,
73
+ blockers: result.blockers || undefined,
74
+ uncommittedFiles,
75
+ gitSnapshot: log || undefined,
76
+ planReference: result.plan_reference || undefined,
77
+ }, dbPath);
78
+ console.error("[claude-baton] Auto-checkpoint saved before compaction");
79
+ }
80
+ catch (error) {
81
+ const msg = error instanceof Error ? error.message : String(error);
82
+ console.error(`[claude-baton] Auto-checkpoint failed: ${msg}`);
83
+ // Exit gracefully — don't block compaction
84
+ }
85
+ }
86
+ // --- Setup command ---
87
+ export async function handleSetup() {
88
+ const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
89
+ const settingsDir = path.dirname(settingsPath);
90
+ ensureDir(settingsDir);
91
+ let settings = {};
92
+ if (existsSync(settingsPath)) {
93
+ try {
94
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
95
+ }
96
+ catch {
97
+ settings = {};
98
+ }
99
+ }
100
+ // Register MCP server
101
+ const mcpServers = (settings.mcpServers ?? {});
102
+ mcpServers["claude-baton"] = {
103
+ command: "npx",
104
+ args: ["-y", "claude-baton", "serve"],
105
+ };
106
+ settings.mcpServers = mcpServers;
107
+ // Register PreCompact hook (idempotent — skip if already present)
108
+ const hooks = (settings.hooks ?? {});
109
+ const preCompactHooks = (hooks.PreCompact ?? []);
110
+ const hasMemoriaHook = preCompactHooks.some((h) => h.command && h.command.includes("claude-baton"));
111
+ if (!hasMemoriaHook) {
112
+ preCompactHooks.push({
113
+ type: "command",
114
+ command: "npx -y claude-baton auto-checkpoint",
115
+ });
116
+ hooks.PreCompact = preCompactHooks;
117
+ settings.hooks = hooks;
118
+ console.error(" Registered PreCompact hook");
119
+ }
120
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
121
+ const dbPath = getDefaultDbPath();
122
+ await initDatabase(dbPath);
123
+ const cmdResult = installCommands();
124
+ console.error(`Setup complete.`);
125
+ console.error(` Database: ${dbPath}`);
126
+ console.error(` MCP server: registered`);
127
+ console.error(` Commands: ${cmdResult.installed} installed, ${cmdResult.skipped} skipped`);
128
+ }
129
+ // --- Install memo- commands ---
130
+ export function installCommands() {
131
+ const sourceDir = path.join(__dirname, "..", "commands");
132
+ const targetDir = path.join(os.homedir(), ".claude", "commands");
133
+ ensureDir(targetDir);
134
+ let installed = 0;
135
+ let skipped = 0;
136
+ const files = readdirSync(sourceDir).filter((f) => f.endsWith(".md"));
137
+ for (const file of files) {
138
+ const targetPath = path.join(targetDir, file);
139
+ if (existsSync(targetPath)) {
140
+ const name = file.replace(".md", "");
141
+ console.error(` Skipping ${name} -- already exists`);
142
+ skipped++;
143
+ }
144
+ else {
145
+ copyFileSync(path.join(sourceDir, file), targetPath);
146
+ const name = file.replace(".md", "");
147
+ console.error(` Installed /${name}`);
148
+ installed++;
149
+ }
150
+ }
151
+ return { installed, skipped };
152
+ }
153
+ // --- Uninstall command ---
154
+ export async function handleUninstall(opts) {
155
+ // 1. Remove MCP server from settings.json
156
+ const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
157
+ if (existsSync(settingsPath)) {
158
+ try {
159
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
160
+ // Remove MCP server registration
161
+ if (settings.mcpServers &&
162
+ typeof settings.mcpServers === "object" &&
163
+ settings.mcpServers["claude-baton"]) {
164
+ delete settings.mcpServers["claude-baton"];
165
+ if (Object.keys(settings.mcpServers).length === 0) {
166
+ delete settings.mcpServers;
167
+ }
168
+ console.error(" Removed MCP server from settings.json");
169
+ }
170
+ // Remove PreCompact hook
171
+ if (settings.hooks &&
172
+ typeof settings.hooks === "object" &&
173
+ settings.hooks.PreCompact) {
174
+ const hooksObj = settings.hooks;
175
+ const preCompact = hooksObj.PreCompact;
176
+ const filtered = preCompact.filter((h) => !h.command || !h.command.includes("claude-baton"));
177
+ if (filtered.length === 0) {
178
+ delete hooksObj.PreCompact;
179
+ }
180
+ else {
181
+ hooksObj.PreCompact = filtered;
182
+ }
183
+ if (Object.keys(hooksObj).length === 0) {
184
+ delete settings.hooks;
185
+ }
186
+ console.error(" Removed PreCompact hook");
187
+ }
188
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
189
+ }
190
+ catch {
191
+ console.error(" Warning: could not parse settings.json");
192
+ }
193
+ }
194
+ // 2. Remove memo-* command files
195
+ const commandsDir = path.join(os.homedir(), ".claude", "commands");
196
+ let commandsRemoved = 0;
197
+ if (existsSync(commandsDir)) {
198
+ const files = readdirSync(commandsDir).filter((f) => f.startsWith("memo-") && f.endsWith(".md"));
199
+ for (const file of files) {
200
+ unlinkSync(path.join(commandsDir, file));
201
+ commandsRemoved++;
202
+ }
203
+ }
204
+ console.error(` Removed ${commandsRemoved} slash commands`);
205
+ // 3. Optionally remove database
206
+ const dbDir = path.join(os.homedir(), ".claude-baton");
207
+ if (!opts.keepData && existsSync(dbDir)) {
208
+ if (!opts.force) {
209
+ const answer = await askConfirmation(" Delete database (~/.claude-baton)? This cannot be undone. [y/N] ");
210
+ if (answer.toLowerCase() !== "y") {
211
+ console.error(" Kept database.");
212
+ console.error("Uninstall complete (database preserved).");
213
+ return;
214
+ }
215
+ }
216
+ rmSync(dbDir, { recursive: true });
217
+ console.error(" Deleted database");
218
+ }
219
+ else if (opts.keepData) {
220
+ console.error(" Kept database (--keep-data)");
221
+ }
222
+ console.error("Uninstall complete. Run 'npm uninstall -g claude-baton' to remove the binary.");
223
+ }
224
+ // --- Status command ---
225
+ export async function handleStatus(opts) {
226
+ const dbPath = getDefaultDbPath();
227
+ if (!existsSync(dbPath)) {
228
+ console.error("No database found. Run 'claude-baton setup' first.");
229
+ return;
230
+ }
231
+ const db = await initDatabase(dbPath);
232
+ const projectPath = opts.project ?? process.cwd();
233
+ const counts = countAll(db, projectPath);
234
+ const dbSize = statSync(dbPath).size;
235
+ console.log(`Project: ${projectPath}`);
236
+ console.log(`Database: ${dbPath} (${(dbSize / 1024).toFixed(1)} KB)`);
237
+ console.log();
238
+ console.log("Counts:");
239
+ for (const [key, value] of Object.entries(counts)) {
240
+ console.log(` ${key}: ${value}`);
241
+ }
242
+ }
243
+ // --- Projects command ---
244
+ export async function handleProjects() {
245
+ const dbPath = getDefaultDbPath();
246
+ if (!existsSync(dbPath)) {
247
+ console.error("No database found. Run 'claude-baton setup' first.");
248
+ return;
249
+ }
250
+ const db = await initDatabase(dbPath);
251
+ const projects = listProjects(db);
252
+ if (projects.length === 0) {
253
+ console.log("No projects with checkpoints yet.");
254
+ return;
255
+ }
256
+ for (const p of projects) {
257
+ console.log(`${p.project_path} (${p.checkpoint_count} checkpoints)`);
258
+ }
259
+ }
260
+ // --- Export command ---
261
+ export async function handleExport(opts) {
262
+ const dbPath = getDefaultDbPath();
263
+ if (!existsSync(dbPath)) {
264
+ console.error("No database found. Run 'claude-baton setup' first.");
265
+ return;
266
+ }
267
+ const db = await initDatabase(dbPath);
268
+ const projectPath = opts.project;
269
+ const data = {
270
+ version: 2,
271
+ exported_at: new Date().toISOString(),
272
+ checkpoints: getAllCheckpoints(db, projectPath),
273
+ daily_summaries: getAllDailySummaries(db, projectPath),
274
+ };
275
+ console.log(JSON.stringify(data, null, 2));
276
+ }
277
+ // --- Import command ---
278
+ export async function handleImport(file) {
279
+ const dbPath = getDefaultDbPath();
280
+ const db = await initDatabase(dbPath);
281
+ let data;
282
+ try {
283
+ const raw = readFileSync(file, "utf-8");
284
+ data = JSON.parse(raw);
285
+ }
286
+ catch (error) {
287
+ const msg = error instanceof Error ? error.message : String(error);
288
+ console.error(`Failed to read import file: ${msg}`);
289
+ return;
290
+ }
291
+ let imported = 0;
292
+ if (Array.isArray(data.checkpoints)) {
293
+ for (const cp of data.checkpoints) {
294
+ insertCheckpoint(db, cp.project_path, cp.session_id, cp.current_state, cp.what_was_built, cp.next_steps, {
295
+ branch: cp.branch,
296
+ decisionsMade: cp.decisions_made,
297
+ blockers: cp.blockers,
298
+ uncommittedFiles: cp.uncommitted_files,
299
+ gitSnapshot: cp.git_snapshot,
300
+ });
301
+ imported++;
302
+ }
303
+ }
304
+ saveDatabase(db, dbPath);
305
+ console.error(`Imported ${imported} items.`);
306
+ }
307
+ // --- Reset command ---
308
+ export async function handleReset(opts) {
309
+ const dbPath = getDefaultDbPath();
310
+ if (!existsSync(dbPath)) {
311
+ console.error("No database found. Nothing to reset.");
312
+ return;
313
+ }
314
+ if (!opts.force) {
315
+ const target = opts.project ?? "ALL projects";
316
+ const answer = await askConfirmation(`Reset data for ${target}? This cannot be undone. [y/N] `);
317
+ if (answer.toLowerCase() !== "y") {
318
+ console.error("Aborted.");
319
+ return;
320
+ }
321
+ }
322
+ const db = await initDatabase(dbPath);
323
+ if (opts.project) {
324
+ deleteProjectData(db, opts.project, dbPath);
325
+ console.error(`Reset data for project: ${opts.project}`);
326
+ }
327
+ else {
328
+ deleteAllData(db, dbPath);
329
+ console.error("Reset all data.");
330
+ }
331
+ }
332
+ function askConfirmation(prompt) {
333
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
334
+ return new Promise((resolve) => {
335
+ rl.question(prompt, (answer) => {
336
+ rl.close();
337
+ resolve(answer);
338
+ });
339
+ });
340
+ }
341
+ // --- CLI program ---
342
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf-8"));
343
+ const program = new Command();
344
+ program
345
+ .name("claude-baton")
346
+ .description("Session lifecycle management for Claude Code")
347
+ .version(pkg.version);
348
+ program
349
+ .command("serve")
350
+ .description("Start the MCP server (stdio transport)")
351
+ .action(async () => {
352
+ await import("./index.js");
353
+ });
354
+ program
355
+ .command("setup")
356
+ .description("Register MCP server and initialize database")
357
+ .action(() => handleSetup());
358
+ program
359
+ .command("auto-checkpoint")
360
+ .description("Auto-save checkpoint (called by PreCompact hook)")
361
+ .action(() => handleAutoCheckpoint());
362
+ program
363
+ .command("uninstall")
364
+ .description("Remove MCP server, slash commands, and optionally the database")
365
+ .option("--keep-data", "Keep the database (~/.claude-baton)")
366
+ .option("--force", "Skip confirmation for database deletion")
367
+ .action((opts) => handleUninstall(opts));
368
+ program
369
+ .command("status")
370
+ .description("Show checkpoint counts and status")
371
+ .option("--project <path>", "Project path (default: cwd)")
372
+ .action((opts) => handleStatus(opts));
373
+ program
374
+ .command("projects")
375
+ .description("List projects with checkpoints")
376
+ .action(() => handleProjects());
377
+ program
378
+ .command("export")
379
+ .description("Export all data as JSON to stdout")
380
+ .option("--project <path>", "Filter by project path")
381
+ .action((opts) => handleExport(opts));
382
+ program
383
+ .command("import <file>")
384
+ .description("Import data from JSON file")
385
+ .action((file) => handleImport(file));
386
+ program
387
+ .command("reset")
388
+ .description("Delete all data (or data for a project)")
389
+ .option("--project <path>", "Only reset this project")
390
+ .option("--force", "Skip confirmation")
391
+ .action((opts) => handleReset(opts));
392
+ program.parse();
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,251 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { initDatabase, getDefaultDbPath, insertCheckpoint, getLatestCheckpoint, getCheckpoint, getCheckpointsByDate, insertDailySummary, } from "./store.js";
5
+ import { callClaudeJson } from "./llm.js";
6
+ import { readFileSync, statSync } from "fs";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ let db;
11
+ let dbPath;
12
+ let lastDbMtime = 0;
13
+ /** Reload the database from disk if the file has been modified externally. */
14
+ async function reloadDbIfChanged() {
15
+ try {
16
+ const mtimeMs = statSync(dbPath).mtimeMs;
17
+ if (mtimeMs > lastDbMtime) {
18
+ db = await initDatabase(dbPath);
19
+ lastDbMtime = mtimeMs;
20
+ }
21
+ }
22
+ catch {
23
+ // File doesn't exist or stat failed — keep current db as-is
24
+ }
25
+ }
26
+ /** Stable fallback session ID — generated once at module load, not per call. */
27
+ const fallbackSessionId = `session-${Date.now()}`;
28
+ /** Build an MCP error response with a descriptive message. */
29
+ function toolError(message) {
30
+ return {
31
+ content: [{ type: "text", text: `Error: ${message}` }],
32
+ isError: true,
33
+ };
34
+ }
35
+ /**
36
+ * Validate that a required string argument is present and is a string.
37
+ * Returns the validated string, or throws with a descriptive message.
38
+ */
39
+ function requireString(args, field, toolName) {
40
+ const value = args?.[field];
41
+ if (!value || typeof value !== "string") {
42
+ throw new ValidationError(`${toolName} requires a "${field}" string argument`);
43
+ }
44
+ return value;
45
+ }
46
+ /** Sentinel error class for argument validation — caught in the handler to return toolError. */
47
+ class ValidationError extends Error {
48
+ constructor(message) {
49
+ super(message);
50
+ this.name = "ValidationError";
51
+ }
52
+ }
53
+ const server = new Server({ name: "claude-baton", version: "1.0.0" }, { capabilities: { tools: {} } });
54
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
55
+ tools: [
56
+ {
57
+ name: "save_checkpoint",
58
+ description: "Save session state before context loss",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: {
62
+ what_was_built: { type: "string" },
63
+ current_state: { type: "string" },
64
+ next_steps: { type: "string" },
65
+ decisions: { type: "string" },
66
+ blockers: { type: "string" },
67
+ branch: {
68
+ type: "string",
69
+ description: "Current git branch name",
70
+ },
71
+ uncommitted_files: {
72
+ type: "array",
73
+ items: { type: "string" },
74
+ description: "Output of git status --short",
75
+ },
76
+ git_snapshot: {
77
+ type: "string",
78
+ description: "Recent commits, e.g. output of git log --oneline -10",
79
+ },
80
+ plan_reference: {
81
+ type: "string",
82
+ description: "Reference to active plan document and section, e.g. 'docs/plan.md Phase 2 Step 3'",
83
+ },
84
+ project: {
85
+ type: "string",
86
+ description: "Project path (defaults to cwd)",
87
+ },
88
+ },
89
+ required: ["what_was_built", "current_state", "next_steps"],
90
+ },
91
+ },
92
+ {
93
+ name: "get_checkpoint",
94
+ description: "Retrieve a checkpoint by ID, or the latest for the project",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ id: {
99
+ type: "string",
100
+ description: "Checkpoint ID to fetch; if omitted, returns the latest",
101
+ },
102
+ project: {
103
+ type: "string",
104
+ description: "Project path (defaults to cwd)",
105
+ },
106
+ },
107
+ },
108
+ },
109
+ {
110
+ name: "list_checkpoints",
111
+ description: "List all checkpoints for a date",
112
+ inputSchema: {
113
+ type: "object",
114
+ properties: {
115
+ date: {
116
+ type: "string",
117
+ description: "YYYY-MM-DD (defaults to today)",
118
+ },
119
+ project: {
120
+ type: "string",
121
+ description: "Project path (defaults to cwd)",
122
+ },
123
+ },
124
+ },
125
+ },
126
+ {
127
+ name: "daily_summary",
128
+ description: "Generate EOD summary from the day's activity",
129
+ inputSchema: {
130
+ type: "object",
131
+ properties: {
132
+ date: {
133
+ type: "string",
134
+ description: "Date (YYYY-MM-DD, defaults to today)",
135
+ },
136
+ project: {
137
+ type: "string",
138
+ description: "Project path (defaults to cwd)",
139
+ },
140
+ },
141
+ },
142
+ },
143
+ ],
144
+ }));
145
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
146
+ const { name, arguments: args } = request.params;
147
+ const projectPath = args?.project ?? process.cwd();
148
+ await reloadDbIfChanged();
149
+ try {
150
+ switch (name) {
151
+ case "save_checkpoint": {
152
+ const a = args;
153
+ const whatWasBuilt = requireString(a, "what_was_built", "save_checkpoint");
154
+ const currentState = requireString(a, "current_state", "save_checkpoint");
155
+ const nextSteps = requireString(a, "next_steps", "save_checkpoint");
156
+ const sessionId = process.env.CLAUDE_SESSION_ID ?? fallbackSessionId;
157
+ const id = insertCheckpoint(db, projectPath, sessionId, currentState, whatWasBuilt, nextSteps, {
158
+ branch: a?.branch,
159
+ decisionsMade: a?.decisions,
160
+ blockers: a?.blockers,
161
+ uncommittedFiles: a?.uncommitted_files,
162
+ gitSnapshot: a?.git_snapshot,
163
+ planReference: a?.plan_reference,
164
+ }, dbPath);
165
+ return { content: [{ type: "text", text: `Checkpoint saved: ${id}` }] };
166
+ }
167
+ case "get_checkpoint": {
168
+ const cpId = args?.id;
169
+ const cp = cpId
170
+ ? getCheckpoint(db, cpId)
171
+ : getLatestCheckpoint(db, projectPath);
172
+ if (!cp)
173
+ return { content: [{ type: "text", text: "No checkpoint found" }] };
174
+ return {
175
+ content: [{ type: "text", text: JSON.stringify(cp, null, 2) }],
176
+ };
177
+ }
178
+ case "list_checkpoints": {
179
+ const cpDate = args?.date ?? new Date().toISOString().slice(0, 10);
180
+ const cps = getCheckpointsByDate(db, projectPath, cpDate);
181
+ const summary = cps.map((cp) => ({
182
+ id: cp.id,
183
+ created_at: cp.created_at,
184
+ what_was_built: cp.what_was_built,
185
+ branch: cp.branch,
186
+ current_state: cp.current_state,
187
+ }));
188
+ return {
189
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
190
+ };
191
+ }
192
+ case "daily_summary": {
193
+ const date = args?.date ?? new Date().toISOString().slice(0, 10);
194
+ const checkpoints = getCheckpointsByDate(db, projectPath, date);
195
+ if (checkpoints.length === 0) {
196
+ return {
197
+ content: [{ type: "text", text: `No activity found for ${date}` }],
198
+ };
199
+ }
200
+ const activityParts = [];
201
+ activityParts.push("CHECKPOINTS:\n" +
202
+ checkpoints
203
+ .map((cp) => `- [${cp.created_at}] Built: ${cp.what_was_built} | State: ${cp.current_state} | Next: ${cp.next_steps}`)
204
+ .join("\n"));
205
+ const summaryTemplate = readFileSync(path.join(__dirname, "..", "prompts", "daily_summary.txt"), "utf-8");
206
+ const summaryPrompt = summaryTemplate
207
+ .replace("{{DATE}}", date)
208
+ .replace("{{ACTIVITY}}", activityParts.join("\n\n"));
209
+ const summaryResult = await callClaudeJson(summaryPrompt, "haiku", 30000);
210
+ insertDailySummary(db, projectPath, date, summaryResult, dbPath);
211
+ return {
212
+ content: [
213
+ {
214
+ type: "text",
215
+ text: JSON.stringify(summaryResult, null, 2),
216
+ },
217
+ ],
218
+ };
219
+ }
220
+ default:
221
+ return {
222
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
223
+ isError: true,
224
+ };
225
+ }
226
+ }
227
+ catch (error) {
228
+ const msg = error instanceof Error ? error.message : String(error);
229
+ console.error(`Tool error (${name}):`, msg);
230
+ return {
231
+ content: [{ type: "text", text: `Error: ${msg}` }],
232
+ isError: true,
233
+ };
234
+ }
235
+ });
236
+ async function main() {
237
+ dbPath = getDefaultDbPath();
238
+ db = await initDatabase(dbPath);
239
+ try {
240
+ lastDbMtime = statSync(dbPath).mtimeMs;
241
+ }
242
+ catch {
243
+ // File may not exist yet if initDatabase created it in-memory only
244
+ }
245
+ const transport = new StdioServerTransport();
246
+ await server.connect(transport);
247
+ }
248
+ main().catch((error) => {
249
+ console.error("Server error:", error);
250
+ process.exit(1);
251
+ });
package/dist/llm.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Strip markdown code fences from LLM output.
3
+ * LLMs frequently wrap JSON in ```json ... ``` blocks.
4
+ */
5
+ export declare function stripCodeFences(text: string): string;
6
+ /**
7
+ * Call claude -p and return the result as a string.
8
+ * If the raw output is JSON with a `result` field, extracts it.
9
+ * Always returns a string — non-string result values are JSON.stringified.
10
+ */
11
+ export declare function callClaude(prompt: string, model?: string, timeout?: number): Promise<string>;
12
+ /**
13
+ * Call claude -p and parse the response as JSON of type T.
14
+ * Uses the raw stdout (not the string-extracted callClaude) to avoid double-parse.
15
+ */
16
+ export declare function callClaudeJson<T>(prompt: string, model?: string, timeout?: number): Promise<T>;