claude.js (2755B)
1 const Anthropic = require('@anthropic-ai/sdk'); 2 const fs = require('fs'); 3 const path = require('path'); 4 5 const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); 6 7 const CLAUDE_MD_PATH = path.join(__dirname, '..', 'CLAUDE.md'); 8 9 const RESPONSE_FORMAT_INSTRUCTIONS = ` 10 ## Response Format 11 12 You MUST respond with a raw JSON object — no markdown fences, no extra text before or after. 13 The object must have exactly these four fields: 14 15 { 16 "reply": "<your conversational reply to send to the user via Telegram>", 17 "updatedToday": "<the full updated today.md content as a plain string>", 18 "updatedBacklog": "<the full updated backlog.md content as a plain string>", 19 "commitMessage": "<a short git commit message, e.g. 'add: book dentist (backlog)'>" 20 } 21 22 Rules: 23 - If a file did not change, return its original content unchanged. 24 - If you're asking a clarifying question, return both files unchanged. 25 - Always return valid JSON. Never wrap it in a code block. 26 `; 27 28 function getClaudeMd() { 29 if (!fs.existsSync(CLAUDE_MD_PATH)) return ''; 30 return fs.readFileSync(CLAUDE_MD_PATH, 'utf8'); 31 } 32 33 function buildSystemBlocks(todayContent, backlogContent, rotated) { 34 const parts = [ 35 `## today.mdnn```markdownn${todayContent || '(empty)'}n````, 36 `## backlog.mdnn```markdownn${backlogContent || '(empty)'}n````, 37 ]; 38 if (rotated) { 39 parts.unshift('> **New day.** today.md has been reset. Briefly suggest pulling any urgent backlog items into today — but don't do it automatically.'); 40 } 41 42 return [ 43 { 44 type: 'text', 45 text: getClaudeMd() + 'nn' + RESPONSE_FORMAT_INSTRUCTIONS, 46 cache_control: { type: 'ephemeral' }, 47 }, 48 { 49 type: 'text', 50 text: parts.join('nn'), 51 }, 52 ]; 53 } 54 55 async function chat(history, userMessage, todayContent, backlogContent, rotated = false) { 56 const messages = [ 57 ...history, 58 { role: 'user', content: userMessage }, 59 ]; 60 61 const response = await client.messages.create({ 62 model: 'claude-sonnet-4-6', 63 max_tokens: 2048, 64 system: buildSystemBlocks(todayContent, backlogContent, rotated), 65 messages, 66 }); 67 68 const raw = response.content[0].text.trim(); 69 70 try { 71 const parsed = JSON.parse(raw); 72 return { 73 reply: parsed.reply ?? '(no reply)', 74 updatedToday: parsed.updatedToday ?? todayContent, 75 updatedBacklog: parsed.updatedBacklog ?? backlogContent, 76 commitMessage: parsed.commitMessage ?? 'update: todo', 77 }; 78 } catch { 79 console.error('Failed to parse Claude response as JSON:', raw); 80 return { 81 reply: raw, 82 updatedToday: todayContent, 83 updatedBacklog: backlogContent, 84 commitMessage: null, 85 }; 86 } 87 } 88 89 module.exports = { chat };