claude-sesh
Version:
Session explorer for Claude Code - find, search, and resume all your past sessions
624 lines • 31.9 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import { parser, formatModelName } from './services/parser.js';
// Claude's warm color palette
const colors = {
beige: chalk.hex('#F5F1EB'),
sand: chalk.hex('#E8DFD5'),
stone: chalk.hex('#C9BFB1'),
warmGray: chalk.hex('#9A8F82'),
brown: chalk.hex('#6B5D4D'),
accent: chalk.hex('#D4A574'),
success: chalk.hex('#7FB069'),
error: chalk.hex('#E57373'),
info: chalk.hex('#81B1D4'),
};
const program = new Command();
program
.name('sesh')
.description('🗓️ Claude Session Viewer - Beautiful insights into your Claude Code sessions')
.version('1.0.0');
// List sessions
program
.command('list')
.alias('ls')
.description('List recent sessions')
.option('-n, --limit <number>', 'Number of sessions to show', '20')
.option('-p, --project <path>', 'Filter by project path')
.action((options) => {
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🗓️ Claude Sessions ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
const limit = parseInt(options.limit, 10);
const sessions = options.project
? parser.getProjectSessions(options.project, limit)
: parser.getAllSessions(limit);
if (sessions.length === 0) {
console.log(colors.warmGray(' No sessions found.'));
return;
}
for (const session of sessions) {
printSessionCard(session);
}
console.log();
console.log(colors.stone(` Showing ${sessions.length} session${sessions.length !== 1 ? 's' : ''}`));
console.log();
});
// Show session details
program
.command('show <sessionId>')
.description('Show detailed session information')
.action((sessionId) => {
const session = parser.getSession(sessionId);
if (!session) {
console.log(colors.error(` Session not found: ${sessionId}`));
return;
}
printSessionDetails(session);
});
// Show resume context or resume natively
program
.command('resume [sessionId]')
.description('Resume a session (uses Claude\'s native --resume if no context needed)')
.option('-c, --copy', 'Copy rich context to clipboard')
.option('-n, --native', 'Use Claude\'s native resume (claude --resume)')
.option('-f, --fork', 'Fork the session instead of continuing it')
.action(async (sessionId, options) => {
const { execSync, spawn } = await import('child_process');
// If no sessionId, show resumable sessions picker
if (!sessionId) {
const resumable = parser.getResumableSessions(10);
if (resumable.length === 0) {
console.log(colors.stone(' No resumable sessions found.'));
return;
}
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🔄 Sessions to Resume ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
resumable.forEach((s, i) => {
const timeAgo = formatTimeAgo(s.startedAt);
console.log(colors.accent(` ${i + 1}. `) + colors.beige(s.projectName));
console.log(colors.stone(` ${s.resumeReason} • ${timeAgo}`));
console.log(colors.warmGray(` ID: ${s.id.slice(0, 12)}...`));
console.log();
});
console.log(colors.stone(' Run: sesh resume <sessionId> --native'));
console.log();
return;
}
const session = parser.getSession(sessionId);
if (!session) {
console.log(colors.error(` Session not found: ${sessionId}`));
return;
}
// Native resume using Claude's built-in --resume
if (options.native) {
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🔄 Native Resume ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
console.log(colors.beige(' Project: ') + colors.accent(session.projectName));
console.log(colors.beige(' Session: ') + colors.info(sessionId.slice(0, 20) + '...'));
console.log();
const args = ['--resume', sessionId];
if (options.fork)
args.push('--fork-session');
console.log(colors.stone(` Starting: claude ${args.join(' ')}`));
console.log();
// Change to project directory and spawn claude
try {
const claude = spawn('claude', args, {
cwd: session.projectPath,
stdio: 'inherit',
shell: true,
});
claude.on('error', (err) => {
console.log(colors.error(` Failed to start Claude: ${err.message}`));
});
}
catch (err) {
console.log(colors.error(` Error: ${err}`));
}
return;
}
// Build rich context
const context = parser.buildResumeContext(sessionId);
if (!context) {
console.log(colors.error(` Could not build resume context`));
return;
}
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 📋 Resume Context ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
console.log(colors.beige(context.split('\n').map(l => ' ' + l).join('\n')));
console.log();
if (options.copy) {
try {
execSync('pbcopy', { input: context });
console.log(colors.success(' ✓ Copied to clipboard!'));
console.log();
}
catch {
console.log(colors.error(' Failed to copy to clipboard'));
}
}
console.log(colors.stone(' Tip: Use --native to resume with Claude\'s built-in resume'));
console.log(colors.warmGray(` sesh resume ${sessionId} --native`));
console.log();
});
// List resumable sessions
program
.command('continue')
.alias('c')
.description('Show sessions you might want to continue')
.option('-l, --limit <n>', 'Number of sessions to show', '10')
.action((options) => {
const limit = parseInt(options.limit, 10) || 10;
const resumable = parser.getResumableSessions(limit);
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🔄 Continue Where You Left Off ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
if (resumable.length === 0) {
console.log(colors.stone(' No sessions with pending work found.'));
console.log();
return;
}
resumable.forEach((s, i) => {
const timeAgo = formatTimeAgo(s.startedAt);
const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : ' ';
console.log(` ${medal} ` + colors.accent(s.projectName));
console.log(colors.beige(` ${s.resumeReason}`));
console.log(colors.stone(` ${timeAgo} • ${s.messageCount} msgs • ${formatDuration(s.durationSeconds)}`));
console.log(colors.warmGray(` sesh resume ${s.id.slice(0, 12)} --native`));
console.log();
});
});
// Start new session with resume context (legacy)
program
.command('start <sessionId>')
.description('Copy resume context to clipboard and show instructions')
.action(async (sessionId) => {
const session = parser.getSession(sessionId);
if (!session) {
console.log(colors.error(` Session not found: ${sessionId}`));
return;
}
const context = parser.buildResumeContext(sessionId);
if (!context) {
console.log(colors.error(` Could not build resume context`));
return;
}
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🚀 Starting New Session ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
const { execSync } = await import('child_process');
// Copy context to clipboard
try {
execSync('pbcopy', { input: context });
console.log(colors.success(' ✓ Resume context copied to clipboard'));
}
catch {
console.log(colors.error(' Failed to copy to clipboard'));
}
console.log();
console.log(colors.beige(' Project: ') + colors.accent(session.projectPath));
console.log(colors.beige(' Branch: ') + colors.info(session.gitBranch || 'N/A'));
console.log();
console.log(colors.stone(' Option 1: Use native resume (recommended):'));
console.log(colors.warmGray(` cd ${session.projectPath} && claude --resume ${sessionId}`));
console.log();
console.log(colors.stone(' Option 2: Start fresh with context in clipboard:'));
console.log(colors.warmGray(` cd ${session.projectPath} && claude`));
console.log();
});
// Show stats
program
.command('stats')
.description('Show session statistics')
.action(() => {
const sessions = parser.getAllSessions(500);
const stats = calculateStats(sessions);
printStats(stats);
});
// List projects
program
.command('projects')
.description('List all projects with sessions')
.action(() => {
const projects = parser.getProjects();
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 📁 Projects with Sessions ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
for (const project of projects) {
const sessions = parser.getProjectSessions(project, 100);
const totalTokens = sessions.reduce((sum, s) => sum + s.totalTokens, 0);
const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCostUsd, 0);
console.log(colors.beige(` 📁 ${project}`));
console.log(colors.stone(` ${sessions.length} sessions • ${formatTokens(totalTokens)} tokens • $${totalCost.toFixed(2)}`));
console.log();
}
});
// Start web dashboard
program
.command('web')
.description('Start the web dashboard')
.option('-p, --port <number>', 'Port to run on', '3847')
.action(async (options) => {
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🌐 Starting Web Dashboard ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
const { startServer } = await import('./web/server.js');
startServer(parseInt(options.port, 10));
});
// Start MCP server
program
.command('mcp')
.description('Start the MCP server for Claude Code integration')
.action(async () => {
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🔗 Starting MCP Server ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
console.log(colors.stone(' claude-sesh MCP server starting...'));
console.log(colors.warmGray(' This exposes your session data to Claude via MCP protocol.'));
console.log();
console.log(colors.info(' Available tools:'));
console.log(colors.stone(' • search_sessions - Search across all sessions'));
console.log(colors.stone(' • get_session - Get detailed session info'));
console.log(colors.stone(' • get_project_context - Get rich project context'));
console.log(colors.stone(' • get_pending_todos - Find incomplete tasks'));
console.log(colors.stone(' • search_decisions - Find past decisions'));
console.log(colors.stone(' • search_knowledge - Search accumulated learnings'));
console.log(colors.stone(' • get_file_history - Track file changes'));
console.log(colors.stone(' • get_recent_activity - Recent coding activity'));
console.log();
console.log(colors.warmGray(' To add to Claude Code:'));
console.log(colors.beige(' claude mcp add claude-sesh -- npx claude-sesh mcp'));
console.log();
const { startMCPServer } = await import('./mcp/server.js');
await startMCPServer();
});
// Enrich sessions using Claude AI
program
.command('enrich')
.description('Enrich sessions without summaries using Claude AI')
.option('-n, --limit <number>', 'Number of sessions to enrich', '10')
.option('--stats', 'Show enrichment statistics only')
.action(async (options) => {
const { enricher } = await import('./services/enricher.js');
console.log();
console.log(colors.accent(' ╭─────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(' 🧠 Session Enrichment ') + colors.accent('│'));
console.log(colors.accent(' ╰─────────────────────────────────────────╯'));
console.log();
const stats = enricher.getStats();
if (options.stats) {
console.log(colors.beige(' Total Sessions: ') + colors.accent(stats.total.toString()));
console.log(colors.beige(' Already Enriched: ') + colors.success(stats.enriched.toString()));
console.log(colors.beige(' Pending: ') + colors.info(stats.pending.toString()));
console.log(colors.beige(' API Available: ') + (enricher.isAvailable() ? colors.success('Yes') : colors.error('No (set ANTHROPIC_API_KEY)')));
console.log();
return;
}
// Check if API key is available
if (!enricher.isAvailable()) {
console.log(colors.error(' ✗ ANTHROPIC_API_KEY not set'));
console.log();
console.log(colors.stone(' To enable enrichment, set your API key:'));
console.log(colors.warmGray(' export ANTHROPIC_API_KEY=your-api-key'));
console.log();
return;
}
if (stats.pending === 0) {
console.log(colors.success(' ✓ All sessions are already enriched!'));
console.log();
return;
}
const limit = parseInt(options.limit, 10);
console.log(colors.stone(` Enriching up to ${limit} sessions...`));
console.log(colors.warmGray(' Using Claude Haiku for cost-effective analysis'));
console.log();
const result = await enricher.enrichBatch(limit, (done, total) => {
process.stdout.write(`\r Progress: ${done}/${total} sessions...`);
});
console.log();
console.log();
console.log(colors.success(` ✓ Enriched: ${result.enriched}`));
if (result.failed > 0) {
console.log(colors.error(` ✗ Failed: ${result.failed}`));
}
console.log();
console.log(colors.stone(` Run 'sesh enrich --stats' to see overall progress`));
console.log();
});
function printSessionCard(session) {
const date = session.startedAt.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
const duration = formatDuration(session.durationSeconds);
const cost = `$${session.estimatedCostUsd.toFixed(2)}`;
const model = formatModelName(session.model);
console.log(colors.stone(' ┌────────────────────────────────────────────────────────────┐'));
// Header with project and date
console.log(colors.stone(' │ ') +
colors.accent(`📁 ${session.projectName.padEnd(25)}`) +
colors.warmGray(date.padStart(28)) +
colors.stone(' │'));
// Summary
const summary = session.summaries[session.summaries.length - 1] || 'No summary';
const truncatedSummary = summary.length > 55 ? summary.slice(0, 52) + '...' : summary;
console.log(colors.stone(' │ ') +
colors.beige(` ${truncatedSummary.padEnd(55)}`) +
colors.stone(' │'));
// Stats line
console.log(colors.stone(' │ ') +
colors.info(`⏱️ ${duration.padEnd(8)}`) +
colors.success(`💬 ${session.messageCount.toString().padEnd(6)}`) +
colors.warmGray(`🔧 ${session.toolCallCount.toString().padEnd(6)}`) +
colors.accent(`${formatTokens(session.totalTokens).padEnd(10)}`) +
colors.success(cost.padEnd(8)) +
colors.stone(' │'));
// Session ID (truncated)
console.log(colors.stone(' │ ') +
colors.warmGray(` ID: ${session.id.slice(0, 8)}... • ${model}`.padEnd(55)) +
colors.stone(' │'));
console.log(colors.stone(' └────────────────────────────────────────────────────────────┘'));
console.log();
}
function printSessionDetails(session) {
console.log();
console.log(colors.accent(' ╭────────────────────────────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(` 📊 Session Details `) + colors.accent('│'));
console.log(colors.accent(' ╰────────────────────────────────────────────────────────────────╯'));
console.log();
// Basic info
console.log(colors.beige(' Project: ') + colors.accent(session.projectName));
console.log(colors.beige(' Path: ') + colors.stone(session.projectPath));
console.log(colors.beige(' Session ID: ') + colors.warmGray(session.id));
console.log(colors.beige(' Branch: ') + colors.info(session.gitBranch || 'N/A'));
console.log(colors.beige(' Model: ') + colors.accent(formatModelName(session.model)));
console.log();
// Time info
console.log(colors.beige(' Started: ') + colors.stone(session.startedAt.toLocaleString()));
console.log(colors.beige(' Ended: ') + colors.stone(session.endedAt?.toLocaleString() || 'Active'));
console.log(colors.beige(' Duration: ') + colors.info(formatDuration(session.durationSeconds)));
console.log();
// Token usage
console.log(colors.accent(' ─── Token Usage ───────────────────────────────────────────────'));
console.log(colors.beige(' Input: ') + colors.stone(formatTokens(session.inputTokens)));
console.log(colors.beige(' Output: ') + colors.stone(formatTokens(session.outputTokens)));
console.log(colors.beige(' Total: ') + colors.accent(formatTokens(session.totalTokens)));
console.log(colors.beige(' Est. Cost: ') + colors.success(`$${session.estimatedCostUsd.toFixed(4)}`));
console.log();
// Activity
console.log(colors.accent(' ─── Activity ──────────────────────────────────────────────────'));
console.log(colors.beige(' Messages: ') + colors.stone(`${session.messageCount} (${session.userMessageCount} user, ${session.assistantMessageCount} assistant)`));
console.log(colors.beige(' Tool Calls: ') + colors.stone(session.toolCallCount.toString()));
console.log(colors.beige(' Errors: ') + (session.errors.length > 0 ? colors.error(session.errors.length.toString()) : colors.success('0')));
console.log();
// Files
if (session.filesWritten.length > 0 || session.filesEdited.length > 0) {
console.log(colors.accent(' ─── Files Modified ────────────────────────────────────────────'));
for (const file of [...session.filesWritten, ...session.filesEdited].slice(0, 10)) {
console.log(colors.stone(' • ') + colors.beige(file));
}
if (session.filesWritten.length + session.filesEdited.length > 10) {
console.log(colors.warmGray(` ... and ${session.filesWritten.length + session.filesEdited.length - 10} more`));
}
console.log();
}
// Summaries
if (session.summaries.length > 0) {
console.log(colors.accent(' ─── Session Summaries ─────────────────────────────────────────'));
for (const summary of session.summaries.slice(-5)) {
console.log(colors.stone(' • ') + colors.beige(summary));
}
console.log();
}
// Tool breakdown
const toolCounts = session.toolCalls.reduce((acc, tc) => {
acc[tc.name] = (acc[tc.name] || 0) + 1;
return acc;
}, {});
if (Object.keys(toolCounts).length > 0) {
console.log(colors.accent(' ─── Tools Used ────────────────────────────────────────────────'));
const sortedTools = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]);
for (const [tool, count] of sortedTools.slice(0, 10)) {
const bar = '█'.repeat(Math.min(20, Math.ceil(count / 2)));
console.log(colors.stone(` ${tool.padEnd(15)} `) + colors.info(bar) + colors.warmGray(` ${count}`));
}
console.log();
}
}
function printStats(stats) {
console.log();
console.log(colors.accent(' ╭────────────────────────────────────────────────────────────────╮'));
console.log(colors.accent(' │') + colors.beige(` 📈 Session Statistics `) + colors.accent('│'));
console.log(colors.accent(' ╰────────────────────────────────────────────────────────────────╯'));
console.log();
// Overview
console.log(colors.accent(' ─── Overview ──────────────────────────────────────────────────'));
console.log(colors.beige(' Total Sessions: ') + colors.accent(stats.totalSessions.toString()));
console.log(colors.beige(' Total Tokens: ') + colors.stone(formatTokens(stats.totalTokens)));
console.log(colors.beige(' Total Cost: ') + colors.success(`$${stats.totalCost.toFixed(2)}`));
console.log(colors.beige(' Total Time: ') + colors.info(formatDuration(stats.totalDuration)));
console.log(colors.beige(' Avg Duration: ') + colors.stone(formatDuration(stats.averageSessionDuration)));
console.log();
// Today
console.log(colors.accent(' ─── Today ─────────────────────────────────────────────────────'));
console.log(colors.beige(' Sessions: ') + colors.accent(stats.sessionsToday.toString()));
console.log(colors.beige(' Tokens: ') + colors.stone(formatTokens(stats.tokensToday)));
console.log();
// Project breakdown
if (stats.projectBreakdown.length > 0) {
console.log(colors.accent(' ─── Top Projects ──────────────────────────────────────────────'));
for (const proj of stats.projectBreakdown.slice(0, 5)) {
console.log(colors.stone(` ${proj.project.padEnd(30)} `) +
colors.warmGray(`${proj.sessions} sessions`) +
colors.stone(' • ') +
colors.info(formatTokens(proj.tokens)));
}
console.log();
}
// Model breakdown
if (stats.modelBreakdown.length > 0) {
console.log(colors.accent(' ─── Models Used ───────────────────────────────────────────────'));
for (const m of stats.modelBreakdown) {
if (!m.model || m.model === 'unknown')
continue;
const shortName = formatModelName(m.model);
console.log(colors.stone(` ${shortName.padEnd(25)} `) +
colors.warmGray(`${m.sessions} sessions`) +
colors.stone(' • ') +
colors.info(formatTokens(m.tokens)));
}
console.log();
}
}
function calculateStats(sessions) {
const today = new Date().toDateString();
const projectMap = new Map();
const modelMap = new Map();
let totalTokens = 0;
let totalCost = 0;
let totalDuration = 0;
let sessionsToday = 0;
let tokensToday = 0;
for (const session of sessions) {
totalTokens += session.totalTokens;
totalCost += session.estimatedCostUsd;
totalDuration += session.durationSeconds;
if (session.startedAt.toDateString() === today) {
sessionsToday++;
tokensToday += session.totalTokens;
}
// Project breakdown
const proj = projectMap.get(session.projectName) || { sessions: 0, tokens: 0 };
proj.sessions++;
proj.tokens += session.totalTokens;
projectMap.set(session.projectName, proj);
// Model breakdown
const model = modelMap.get(session.model) || { sessions: 0, tokens: 0 };
model.sessions++;
model.tokens += session.totalTokens;
modelMap.set(session.model, model);
}
return {
totalSessions: sessions.length,
totalTokens,
totalCost,
totalDuration,
averageSessionDuration: sessions.length > 0 ? totalDuration / sessions.length : 0,
sessionsToday,
tokensToday,
projectBreakdown: Array.from(projectMap.entries())
.map(([project, data]) => ({ project, ...data }))
.sort((a, b) => b.tokens - a.tokens),
modelBreakdown: Array.from(modelMap.entries())
.map(([model, data]) => ({ model, ...data }))
.sort((a, b) => b.tokens - a.tokens),
dailyActivity: [],
};
}
function formatTimeAgo(date) {
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1)
return 'Just now';
if (diffMins < 60)
return `${diffMins}m ago`;
if (diffHours < 24)
return `${diffHours}h ago`;
if (diffDays === 1)
return 'Yesterday';
if (diffDays < 7)
return `${diffDays} days ago`;
if (diffDays < 30)
return `${Math.floor(diffDays / 7)} weeks ago`;
return date.toLocaleDateString();
}
function formatDuration(seconds) {
if (seconds < 60)
return `${seconds}s`;
if (seconds < 3600) {
const mins = Math.floor(seconds / 60);
return `${mins}m`;
}
const hours = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
return `${hours}h ${mins}m`;
}
function formatTokens(tokens) {
if (tokens < 1000)
return tokens.toString();
if (tokens < 1000000)
return `${(tokens / 1000).toFixed(1)}K`;
return `${(tokens / 1000000).toFixed(2)}M`;
}
// Parse fixture command for CI smoke tests
program
.command('parse-fixture <file>')
.description('Parse a session fixture file (for testing)')
.action(async (file) => {
const fs = await import('fs');
const path = await import('path');
const filePath = path.resolve(file);
if (!fs.existsSync(filePath)) {
console.error(colors.error(`File not found: ${filePath}`));
process.exit(1);
}
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.trim().split('\n').filter(l => l.trim());
let userMessages = 0;
let assistantMessages = 0;
let toolCalls = 0;
let totalTokens = 0;
for (const line of lines) {
try {
const entry = JSON.parse(line);
if (entry.type === 'user')
userMessages++;
if (entry.type === 'assistant')
assistantMessages++;
if (entry.type === 'tool_use')
toolCalls++;
if (entry.type === 'result' && entry.cost) {
totalTokens += (entry.cost.input_tokens || 0) + (entry.cost.output_tokens || 0);
}
}
catch (e) {
console.error(colors.error(`Failed to parse line: ${line.slice(0, 50)}...`));
}
}
console.log(colors.success('✓ Fixture parsed successfully'));
console.log(colors.beige(` Lines: ${lines.length}`));
console.log(colors.beige(` User messages: ${userMessages}`));
console.log(colors.beige(` Assistant messages: ${assistantMessages}`));
console.log(colors.beige(` Tool calls: ${toolCalls}`));
console.log(colors.beige(` Total tokens: ${totalTokens}`));
});
program.parse();
//# sourceMappingURL=cli.js.map