cost-claude
Version:
Claude Code cost monitoring, analytics, and optimization toolkit
189 lines • 9.43 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { logger } from '../utils/logger.js';
import { analyzeCommand } from './commands/analyze.js';
import { watchCommand } from './commands/watch.js';
import { statsCommand } from './commands/stats.js';
import { predictCommand } from './commands/predict.js';
import { insightsCommand } from './commands/insights.js';
import { dashboardCommand } from './commands/dashboard.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
const program = new Command();
program
.name('cost-claude')
.description('Real-time cost monitoring and analytics for Claude Code usage')
.version(packageJson.version)
.option('-v, --verbose', 'Enable verbose logging')
.option('--log-level <level>', 'Set log level (error, warn, info, debug)', 'info')
.option('-m, --model <model>', 'Claude model ID (default: claude-opus-4-20250514)')
.hook('preAction', (thisCommand) => {
const options = thisCommand.opts();
if (options.verbose) {
logger.level = 'debug';
}
else if (options.logLevel) {
logger.level = options.logLevel;
}
});
program
.command('analyze')
.description('Analyze Claude usage and costs from JSONL files')
.option('-p, --path <path>', 'Path to Claude projects directory', '~/.claude/projects')
.option('-f, --from <date>', 'Start date (YYYY-MM-DD)')
.option('-t, --to <date>', 'End date (YYYY-MM-DD)')
.option('-s, --session <id>', 'Analyze specific session')
.option('--format <type>', 'Output format (table, json, csv, html)', 'table')
.option('--export <file>', 'Export results to file')
.option('-g, --group-by <type>', 'Group results by (session, project, date, hour)', 'session')
.option('-d, --detailed', 'Show detailed cost breakdown in tables', false)
.option('--top <n>', 'Number of top results to show (default: 5, hour: 10)', '5')
.option('--live', 'Show all messages as they would appear in watch mode', false)
.option('--simple', 'Show simplified overview only (default: show everything)')
.option('--no-daily', 'Skip daily patterns analysis')
.option('--no-insights', 'Skip usage insights and recommendations')
.action(analyzeCommand);
program
.command('watch')
.description('Watch Claude files for real-time cost notifications')
.option('-p, --path <path>', 'Path to Claude projects directory', '~/.claude/projects')
.option('-n, --notify', 'Enable desktop notifications', true)
.option('--min-cost <amount>', 'Minimum cost to trigger notification', '0.01')
.option('--sound', 'Enable notification sound', false)
.option('--session-sound-only', 'Enable sound only for session completion notifications', false)
.option('--task-sound <sound>', 'Sound for task completion (macOS: Pop, Ping, Glass, etc.)')
.option('--session-sound <sound>', 'Sound for session completion (macOS: Glass, Hero, etc.)')
.option('--notify-task', 'Enable task completion notifications (delayed completion only)', true)
.option('--notify-session', 'Enable session completion notifications (when session ends)', true)
.option('--notify-cost', 'Enable cost update notifications (for each message)', false)
.option('--notify-progress', 'Enable task progress notifications', true)
.option('--min-task-cost <amount>', 'Minimum cost for task notifications', '0.01')
.option('--min-task-messages <n>', 'Minimum messages for task notifications', '1')
.option('--delayed-timeout <ms>', 'Timeout for delayed task completion (default: 30000ms)', '30000')
.option('--progress-interval <ms>', 'Interval for progress checks (default: 10000ms)', '10000')
.option('--include-existing', 'Process all existing messages on startup', false)
.option('--recent <n>', 'Show only the N most recent messages on startup', '5')
.option('--max-age <minutes>', 'Maximum age of messages to display in minutes (default: 5)', '5')
.option('--test', 'Enable test mode with sample data generation', false)
.option('--verbose', 'Enable verbose logging for debugging', false)
.action(watchCommand);
program
.command('stats')
.description('Show detailed statistics and analytics')
.option('-p, --period <period>', 'Time period (today, yesterday, week, month)', 'today')
.option('-g, --group-by <type>', 'Group by (session, hour, day)', 'session')
.option('--top <n>', 'Show top N results', '10')
.option('--format <type>', 'Output format (table, json, chart)', 'table')
.action(statsCommand);
program
.command('config')
.description('Manage configuration settings')
.argument('[action]', 'Action to perform (show, set, reset)')
.argument('[key]', 'Configuration key')
.argument('[value]', 'Configuration value')
.action(() => {
console.log(chalk.yellow('Config command not yet implemented'));
});
program
.command('pricing')
.description('Manage pricing data and view model costs')
.option('-l, --list', 'List all available models and their pricing')
.option('-r, --refresh', 'Force refresh pricing data from remote sources')
.option('-c, --clear-cache', 'Clear local pricing cache')
.option('-a, --add <json>', 'Add custom model pricing (JSON format)')
.action(async (options) => {
const { PricingService } = await import('../services/pricing-service.js');
const pricingService = PricingService.getInstance();
try {
if (options.refresh) {
console.log(chalk.blue('Refreshing pricing data...'));
await pricingService.refreshPricing();
console.log(chalk.green('Pricing data refreshed successfully'));
}
if (options.clearCache) {
pricingService.clearCache();
console.log(chalk.green('Pricing cache cleared'));
}
if (options.add) {
try {
const customPricing = JSON.parse(options.add);
await pricingService.addCustomPricing(customPricing);
console.log(chalk.green('Custom pricing added successfully'));
}
catch (error) {
console.error(chalk.red('Invalid JSON format for custom pricing'));
}
}
if (options.list || (!options.refresh && !options.clearCache && !options.add)) {
const models = await pricingService.getAllModels();
console.log(chalk.bold('\nAvailable Models and Pricing:'));
console.log(chalk.dim('(All prices per million tokens)\n'));
for (const model of models) {
console.log(chalk.cyan(`${model.modelName} (${model.modelId}):`));
console.log(` Input: $${model.input.toFixed(2)}`);
console.log(` Output: $${model.output.toFixed(2)}`);
if (model.cacheCreation) {
console.log(` Cache Creation: $${model.cacheCreation.toFixed(2)}`);
}
if (model.cacheRead) {
console.log(` Cache Read: $${model.cacheRead.toFixed(2)}`);
}
console.log(` Source: ${model.source}`);
console.log(` Last Updated: ${new Date(model.lastUpdated).toLocaleDateString()}\n`);
}
}
}
catch (error) {
console.error(chalk.red('Error managing pricing:'), error.message);
process.exit(1);
}
});
program
.command('predict')
.description('Predict future costs based on usage patterns')
.option('-p, --path <path>', 'Path to Claude projects directory', '~/.claude/projects')
.option('-d, --days <days>', 'Days of historical data to analyze', '30')
.option('--format <type>', 'Output format (text, json)', 'text')
.option('--detailed', 'Show detailed usage patterns')
.action(predictCommand);
program
.command('insights')
.description('Get AI-powered insights about your usage patterns')
.option('-p, --path <path>', 'Path to Claude projects directory', '~/.claude/projects')
.option('-d, --days <days>', 'Days of data to analyze', '30')
.option('--format <type>', 'Output format (text, json)', 'text')
.option('--export <file>', 'Export insights to file')
.action(insightsCommand);
program
.command('dashboard')
.description('Launch web dashboard for visualizing usage')
.option('-p, --path <path>', 'Path to Claude projects directory', '~/.claude/projects')
.option('--port <port>', 'Server port', '3000')
.option('--no-open', 'Don\'t open browser automatically')
.action(dashboardCommand);
program.exitOverride();
try {
await program.parseAsync(process.argv);
}
catch (error) {
if (error.code === 'commander.unknownCommand') {
console.error(chalk.red(`Unknown command: ${error.message}`));
program.outputHelp();
}
else if (error.code === 'commander.help' || error.code === 'commander.helpDisplayed') {
process.exit(0);
}
else {
logger.error('Command failed:', error);
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
}
if (process.argv.length === 2) {
program.outputHelp();
}
//# sourceMappingURL=index.js.map