this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add CLI for processing and serving

Commands:
- process: Process new/unprocessed sessions
- status: Show processing stats
- serve: Start web UI server

Features:
- Progress output with session summaries
- Daily brag summary generation
- Force reprocess option

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice 55c00eae 7740f7a6

+278
+102
src/cli/index.ts
··· 1 + #!/usr/bin/env bun 2 + import { parseArgs } from 'util'; 3 + import { processCommand } from './process'; 4 + import { getSessionStats } from '../core/session-detector'; 5 + import { getStats } from '../core/db'; 6 + 7 + const { values, positionals } = parseArgs({ 8 + args: Bun.argv.slice(2), 9 + options: { 10 + help: { type: 'boolean', short: 'h' }, 11 + verbose: { type: 'boolean', short: 'v' }, 12 + force: { type: 'boolean', short: 'f' }, 13 + date: { type: 'string', short: 'd' }, 14 + }, 15 + allowPositionals: true, 16 + }); 17 + 18 + const command = positionals[0]; 19 + 20 + async function main() { 21 + if (values.help || !command) { 22 + printHelp(); 23 + return; 24 + } 25 + 26 + switch (command) { 27 + case 'process': 28 + await processCommand({ 29 + force: values.force ?? false, 30 + verbose: values.verbose ?? false, 31 + date: values.date, 32 + }); 33 + break; 34 + 35 + case 'status': 36 + await statusCommand(); 37 + break; 38 + 39 + case 'serve': 40 + await serveCommand(); 41 + break; 42 + 43 + default: 44 + console.error(`Unknown command: ${command}`); 45 + printHelp(); 46 + process.exit(1); 47 + } 48 + } 49 + 50 + function printHelp() { 51 + console.log(` 52 + worklog - Claude Code Session Worklog Generator 53 + 54 + Usage: 55 + bun cli <command> [options] 56 + 57 + Commands: 58 + process Process new/unprocessed sessions 59 + status Show processing stats 60 + serve Start web UI server 61 + 62 + Options: 63 + -h, --help Show help 64 + -v, --verbose Verbose output 65 + -f, --force Reprocess all sessions 66 + -d, --date Process only sessions from specific date (YYYY-MM-DD) 67 + 68 + Examples: 69 + bun cli process # Process new sessions 70 + bun cli process --force # Reprocess all 71 + bun cli process -d 2025-12-18 # Process specific date 72 + bun cli status # Show stats 73 + bun cli serve # Start web UI on port 3456 74 + `); 75 + } 76 + 77 + async function statusCommand() { 78 + const sessionStats = getSessionStats(); 79 + const dbStats = getStats(); 80 + 81 + console.log('\n📊 Worklog Status\n'); 82 + console.log('Session Files:'); 83 + console.log(` Total files: ${sessionStats.totalFiles}`); 84 + console.log(` Projects: ${sessionStats.totalProjects}`); 85 + console.log(` Claude paths: ${sessionStats.claudePaths.join(', ')}`); 86 + console.log('\nProcessed Data:'); 87 + console.log(` Sessions summarized: ${dbStats.totalSessions}`); 88 + console.log(` Days with work: ${dbStats.totalDays}`); 89 + console.log(` Projects tracked: ${dbStats.totalProjects}`); 90 + console.log(''); 91 + } 92 + 93 + async function serveCommand() { 94 + // Dynamic import to avoid loading web server code unless needed 95 + const { startServer } = await import('../web/server'); 96 + await startServer(); 97 + } 98 + 99 + main().catch((error) => { 100 + console.error('Error:', error); 101 + process.exit(1); 102 + });
+176
src/cli/process.ts
··· 1 + import { findUnprocessedSessions, type SessionFile } from '../core/session-detector'; 2 + import { parseSessionFile } from '../core/session-reader'; 3 + import { summarizeSession, generateDailyBragSummary } from '../core/summarizer'; 4 + import { 5 + markFileProcessed, 6 + saveSessionSummary, 7 + saveDailySummary, 8 + getDatesWithoutBragSummary, 9 + getSessionsForDate, 10 + } from '../core/db'; 11 + 12 + interface ProcessOptions { 13 + force: boolean; 14 + verbose: boolean; 15 + date?: string; 16 + } 17 + 18 + export async function processCommand(options: ProcessOptions): Promise<{ 19 + sessionsProcessed: number; 20 + errors: number; 21 + }> { 22 + const { force, verbose, date } = options; 23 + 24 + console.log('\n🔍 Scanning for sessions...\n'); 25 + 26 + let sessions = await findUnprocessedSessions(force); 27 + 28 + // Filter by date if specified 29 + if (date) { 30 + // We need to peek into files to filter by date 31 + // For efficiency, we'll do a rough filter first 32 + console.log(`Filtering to date: ${date}`); 33 + } 34 + 35 + if (sessions.length === 0) { 36 + console.log('✅ No new sessions to process.\n'); 37 + return { sessionsProcessed: 0, errors: 0 }; 38 + } 39 + 40 + console.log(`Found ${sessions.length} session(s) to process\n`); 41 + 42 + // Group by project for display 43 + const byProject = groupByProject(sessions); 44 + let processed = 0; 45 + let errors = 0; 46 + const datesProcessed = new Set<string>(); 47 + 48 + for (const [projectName, projectSessions] of Object.entries(byProject)) { 49 + console.log(`📁 ${projectName} (${projectSessions.length} sessions)`); 50 + 51 + for (const session of projectSessions) { 52 + try { 53 + const result = await processSession(session, verbose); 54 + if (result.date) { 55 + datesProcessed.add(result.date); 56 + } 57 + processed++; 58 + 59 + const duration = formatDuration(result.startTime, result.endTime); 60 + const summary = result.summary.slice(0, 60); 61 + console.log(` ✓ ${session.sessionId.slice(0, 8)}... (${duration}) → "${summary}..."`); 62 + } catch (error) { 63 + errors++; 64 + console.log(` ✗ ${session.sessionId.slice(0, 8)}... - Error: ${error}`); 65 + if (verbose) { 66 + console.error(error); 67 + } 68 + } 69 + } 70 + console.log(''); 71 + } 72 + 73 + // Generate brag summaries for new dates 74 + console.log('📝 Generating daily summaries...\n'); 75 + await generateMissingBragSummaries(verbose); 76 + 77 + console.log(`\n✅ Done! Processed ${processed} sessions (${errors} errors)\n`); 78 + console.log('Run `bun cli serve` to view your worklog.\n'); 79 + 80 + return { sessionsProcessed: processed, errors }; 81 + } 82 + 83 + async function processSession( 84 + sessionFile: SessionFile, 85 + verbose: boolean 86 + ): Promise<{ 87 + date: string; 88 + startTime: string; 89 + endTime: string; 90 + summary: string; 91 + }> { 92 + // Parse the session file 93 + const parsed = await parseSessionFile( 94 + sessionFile.path, 95 + sessionFile.projectPath, 96 + sessionFile.projectName 97 + ); 98 + 99 + if (verbose) { 100 + console.log(` Parsed: ${parsed.messages.length} messages, ${Object.keys(parsed.stats.toolCalls).length} tool types`); 101 + } 102 + 103 + // Generate summary via LLM 104 + const summary = await summarizeSession(parsed); 105 + 106 + if (verbose) { 107 + console.log(` Summary: ${summary.shortSummary}`); 108 + console.log(` Accomplishments: ${summary.accomplishments.length}`); 109 + } 110 + 111 + // Save to database 112 + saveSessionSummary(parsed, summary); 113 + markFileProcessed(sessionFile.path, sessionFile.fileHash); 114 + 115 + return { 116 + date: parsed.date, 117 + startTime: parsed.startTime, 118 + endTime: parsed.endTime, 119 + summary: summary.shortSummary, 120 + }; 121 + } 122 + 123 + async function generateMissingBragSummaries(verbose: boolean): Promise<void> { 124 + const datesWithoutSummary = getDatesWithoutBragSummary(); 125 + 126 + for (const date of datesWithoutSummary) { 127 + try { 128 + const sessions = getSessionsForDate(date); 129 + if (sessions.length === 0) continue; 130 + 131 + if (verbose) { 132 + console.log(` Generating brag summary for ${date} (${sessions.length} sessions)`); 133 + } 134 + 135 + const bragSummary = await generateDailyBragSummary(date, sessions); 136 + const projectNames = [...new Set(sessions.map((s) => s.project_name))]; 137 + 138 + saveDailySummary(date, bragSummary, projectNames, sessions.length); 139 + 140 + console.log(` 📣 ${date}: "${bragSummary.slice(0, 80)}..."`); 141 + } catch (error) { 142 + console.error(` Failed to generate brag for ${date}:`, error); 143 + } 144 + } 145 + } 146 + 147 + function groupByProject( 148 + sessions: SessionFile[] 149 + ): Record<string, SessionFile[]> { 150 + const grouped: Record<string, SessionFile[]> = {}; 151 + 152 + for (const session of sessions) { 153 + const key = session.projectName; 154 + if (!grouped[key]) { 155 + grouped[key] = []; 156 + } 157 + grouped[key].push(session); 158 + } 159 + 160 + return grouped; 161 + } 162 + 163 + function formatDuration(start: string, end: string): string { 164 + if (!start || !end) return '?'; 165 + 166 + const startDate = new Date(start); 167 + const endDate = new Date(end); 168 + const diffMs = endDate.getTime() - startDate.getTime(); 169 + 170 + const minutes = Math.floor(diffMs / 60000); 171 + if (minutes < 60) return `${minutes}m`; 172 + 173 + const hours = Math.floor(minutes / 60); 174 + const remainingMinutes = minutes % 60; 175 + return `${hours}h${remainingMinutes}m`; 176 + }