vibetime
Version:
Track your Claude AI usage and costs. Built on ccusage. See rankings, sync data, and monitor your AI spending. Works with all Claude models.
239 lines (204 loc) • 6.65 kB
JavaScript
/**
* Create fallback ccusage bundle using npm package
* This is used when git submodules are not available
*/
import { mkdirSync, writeFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const bundleDir = join(__dirname, 'src/lib/ccusage-bundle');
console.log('Creating fallback ccusage bundle using npm package...');
// Create bundle directory
if (!existsSync(bundleDir)) {
mkdirSync(bundleDir, { recursive: true });
}
// Create wrapper that uses npm ccusage package as subprocess
const wrapperContent = `
/**
* Fallback ccusage bundle wrapper using npm package v15.5.2
* This file provides ccusage functionality through npm package subprocess execution
*/
import { spawn } from 'node:child_process';
import { join } from 'node:path';
export async function runCcusage(args) {
return new Promise((resolve, reject) => {
// Use npx to run ccusage from npm package
const nodeArgs = ['ccusage', ...args, '--json'];
const child = spawn('npx', nodeArgs, {
stdio: ['ignore', 'pipe', 'pipe'],
shell: false
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
stdout += data.toString();
});
child.stderr.on('data', (data) => {
stderr += data.toString();
});
child.on('close', (code) => {
if (code !== 0) {
reject(new Error(\`ccusage exited with code \${code}: \${stderr}\`));
return;
}
try {
// Try to parse the entire stdout as JSON first
const trimmed = stdout.trim();
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
const data = JSON.parse(trimmed);
resolve(data);
return;
}
// Fallback: look for JSON in individual lines
const lines = stdout.split('\\n');
for (const line of lines) {
const lineTrimmed = line.trim();
if (lineTrimmed.startsWith('[') || lineTrimmed.startsWith('{')) {
try {
const data = JSON.parse(lineTrimmed);
resolve(data);
return;
} catch (lineError) {
// Continue to next line
continue;
}
}
}
resolve([]);
} catch (error) {
reject(new Error('Failed to parse ccusage output: ' + error.message));
}
});
child.on('error', (error) => {
reject(new Error('Failed to execute ccusage: ' + error.message));
});
});
}
// Export convenience functions
export const ccusage = {
daily: async (options = {}) => {
try {
const result = await runCcusage(['daily', ...buildArgs(options)]);
// ccusage returns {daily: [...], totals: {...}}, extract the daily array
return result && result.daily ? result.daily : [];
} catch (error) {
console.warn('ccusage daily failed:', error.message);
return [];
}
},
monthly: async (options = {}) => {
try {
const result = await runCcusage(['monthly', ...buildArgs(options)]);
// ccusage returns {monthly: [...], totals: {...}}, extract the monthly array
return result && result.monthly ? result.monthly : [];
} catch (error) {
console.warn('ccusage monthly failed:', error.message);
return [];
}
},
session: async (options = {}) => {
try {
const result = await runCcusage(['session', ...buildArgs(options)]);
// ccusage returns {sessions: [...], totals: {...}}, extract the sessions array
return result && result.sessions ? result.sessions : [];
} catch (error) {
console.warn('ccusage session failed:', error.message);
return [];
}
},
blocks: async (options = {}) => {
try {
const result = await runCcusage(['blocks', ...buildArgs(options)]);
// ccusage returns {blocks: [...], totals: {...}}, extract the blocks array
return result && result.blocks ? result.blocks : [];
} catch (error) {
console.warn('ccusage blocks failed:', error.message);
return [];
}
}
};
function buildArgs(options) {
const args = [];
if (options.since) args.push('--since', options.since);
if (options.until) args.push('--until', options.until);
if (options.mode) args.push('--mode', options.mode);
if (options.dir) args.push('--dir', options.dir);
return args;
}
`;
writeFileSync(join(bundleDir, 'index.js'), wrapperContent);
// Create TypeScript definitions (same as before)
const typeDefsContent = `
/**
* TypeScript definitions for ccusage bundle (npm fallback)
*/
export interface CcusageOptions {
since?: string;
until?: string;
mode?: 'auto' | 'calculate' | 'display';
dir?: string;
}
export interface TokenUsage {
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
totalTokens: number;
}
export interface ModelBreakdown extends TokenUsage {
modelName: string;
cost: number;
}
export interface DailyUsage extends TokenUsage {
date: string;
cost: number;
modelBreakdowns: ModelBreakdown[];
}
export interface MonthlyUsage extends TokenUsage {
month: string;
cost: number;
modelBreakdowns: ModelBreakdown[];
}
export interface SessionUsage extends TokenUsage {
sessionId: string;
projectName: string;
startTime: string;
endTime: string;
duration: string;
cost: number;
modelBreakdowns: ModelBreakdown[];
}
export interface BlockUsage extends TokenUsage {
startTime: string;
endTime: string;
cost: number;
isActive?: boolean;
modelBreakdowns: ModelBreakdown[];
}
export declare function runCcusage(args: string[]): Promise<any>;
export declare const ccusage: {
daily(options?: CcusageOptions): Promise<DailyUsage[]>;
monthly(options?: CcusageOptions): Promise<MonthlyUsage[]>;
session(options?: CcusageOptions): Promise<SessionUsage[]>;
blocks(options?: CcusageOptions): Promise<BlockUsage[]>;
};
`;
writeFileSync(join(bundleDir, 'index.d.ts'), typeDefsContent);
// Create package.json for the bundle
const bundlePackageJson = {
name: "@vibetime/ccusage-bundle",
version: "1.0.0",
private: true,
type: "module",
main: "./index.js",
types: "./index.d.ts",
description: "ccusage functionality bundled for vibetime CLI (npm fallback)"
};
writeFileSync(
join(bundleDir, 'package.json'),
JSON.stringify(bundlePackageJson, null, 2)
);
console.log('✅ Fallback ccusage bundle created successfully at:', bundleDir);
console.log(' Using npm ccusage package for compatibility.');