Files
claude-mem/src/commands/generate-title.ts
T
Alex Newman 5244a12422 Release v3.9.11
Published from npm package build
Source: https://github.com/thedotmack/claude-mem-source
2025-10-03 20:55:36 -04:00

182 lines
5.2 KiB
TypeScript

import { OptionValues } from 'commander';
import { query } from '@anthropic-ai/claude-agent-sdk';
import { getClaudePath } from '../shared/settings.js';
import { DatabaseManager } from '../services/sqlite/Database.js';
import { StreamingSessionStore } from '../services/sqlite/StreamingSessionStore.js';
import { migrations } from '../services/sqlite/migrations.js';
/**
* Generate a session title and subtitle from a user prompt
* CLI command that uses Agent SDK (like changelog.ts)
*
* Can be called in two modes:
* 1. Standalone: generate-title "user prompt" --json
* 2. With session: generate-title "user prompt" --session-id <id> --save
*/
export async function generateTitle(prompt: string, options: OptionValues): Promise<void> {
if (!prompt || prompt.trim().length === 0) {
console.error(JSON.stringify({
success: false,
error: 'Prompt is required'
}));
process.exit(1);
}
// If --session-id provided, validate that session exists
let streamingStore: StreamingSessionStore | null = null;
let sessionRecord = null;
if (options.sessionId) {
try {
const dbManager = DatabaseManager.getInstance();
for (const migration of migrations) {
dbManager.registerMigration(migration);
}
const db = await dbManager.initialize();
streamingStore = new StreamingSessionStore(db);
sessionRecord = streamingStore.getByClaudeSessionId(options.sessionId);
if (!sessionRecord) {
console.error(JSON.stringify({
success: false,
error: `Session not found: ${options.sessionId}`
}));
process.exit(1);
}
} catch (error: any) {
console.error(JSON.stringify({
success: false,
error: `Database error: ${error.message}`
}));
process.exit(1);
}
}
const systemPrompt = `You are a title and subtitle generator for claude-mem session metadata.
Your job is to analyze a user's request and generate:
1. A concise title (3-8 words)
2. A one-sentence subtitle (max 20 words)
TITLE GUIDELINES:
- 3-8 words maximum
- Scannable and clear
- Captures the core action or topic
- Professional and informative
- Examples:
* "Dark Mode Implementation"
* "Authentication Bug Fix"
* "API Rate Limiting Setup"
* "React Component Refactoring"
SUBTITLE GUIDELINES:
- One sentence, max 20 words
- Descriptive and specific
- Focus on the outcome or benefit
- Use active voice when possible
- Examples:
* "Adding theme toggle and dark color scheme support to the application"
* "Resolving login timeout issue affecting user session persistence"
* "Implementing request throttling to prevent API quota exhaustion"
OUTPUT FORMAT:
You must output EXACTLY two lines:
Line 1: Title only (no prefix, no quotes)
Line 2: Subtitle only (no prefix, no quotes)
EXAMPLE:
User request: "Help me add dark mode to my app"
Output:
Dark Mode Implementation
Adding theme toggle and dark color scheme support to the application
USER REQUEST:
${prompt}
Now generate the title and subtitle (two lines exactly):`;
try {
const response = await query({
prompt: systemPrompt,
options: {
allowedTools: [],
pathToClaudeCodeExecutable: getClaudePath()
}
});
// Extract text from response (same pattern as changelog.ts)
let fullResponse = '';
if (response && typeof response === 'object' && Symbol.asyncIterator in response) {
for await (const message of response) {
if (message?.type === 'assistant' && message?.message?.content) {
const content = message.message.content;
if (typeof content === 'string') {
fullResponse += content;
} else if (Array.isArray(content)) {
for (const block of content) {
if (block.type === 'text' && block.text) {
fullResponse += block.text;
}
}
}
}
}
}
// Parse the response - expecting exactly 2 lines
const lines = fullResponse.trim().split('\n').filter(line => line.trim().length > 0);
if (lines.length < 2) {
console.error(JSON.stringify({
success: false,
error: 'Could not generate title and subtitle',
response: fullResponse
}));
process.exit(1);
}
const title = lines[0].trim();
const subtitle = lines[1].trim();
// If --save and we have a session, update the database
if (options.save && streamingStore && sessionRecord) {
try {
streamingStore.update(sessionRecord.id, {
title,
subtitle
});
} catch (error: any) {
console.error(JSON.stringify({
success: false,
error: `Failed to save title: ${error.message}`
}));
process.exit(1);
}
}
// Output format depends on options
if (options.json) {
console.log(JSON.stringify({
success: true,
title,
subtitle,
sessionId: sessionRecord?.claude_session_id
}, null, 2));
} else if (options.oneline) {
console.log(`${title} - ${subtitle}`);
} else {
console.log(title);
console.log(subtitle);
}
} catch (error: any) {
console.error(JSON.stringify({
success: false,
error: error.message || 'Unknown error generating title'
}));
process.exit(1);
}
}