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.
217 lines (182 loc) • 5.98 kB
JavaScript
#!/usr/bin/env node
/**
* Build script to create ccusage bundle for vibetime CLI
* This handles the special case of JSR dependencies and bun runtime
*/
import { execSync } from 'child_process';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, '..');
const ccusagePath = join(projectRoot, 'ccusage');
const cliPath = __dirname;
const bundleDir = join(cliPath, 'src/lib/ccusage-bundle');
console.log('Building ccusage bundle for vibetime CLI...');
// Step 1: Check prerequisites
if (!existsSync(ccusagePath)) {
console.warn('Warning: ccusage submodule not found.');
console.warn('Falling back to npm package. Run: git submodule update --init for latest features.');
// Don't exit, let postinstall handle fallback
process.exit(0);
}
// Check if bun is available
try {
execSync('bun --version', { stdio: 'ignore' });
} catch (error) {
console.warn('Warning: bun is not available.');
console.warn('Falling back to npm package. Install bun from: https://bun.sh for latest features.');
// Don't exit, let postinstall handle fallback
process.exit(0);
}
// Step 2: Create bundle directory
if (!existsSync(bundleDir)) {
mkdirSync(bundleDir, { recursive: true });
}
// Step 3: Create a wrapper that can be bundled
const wrapperContent = `
/**
* Auto-generated ccusage bundle wrapper
* This file provides ccusage functionality through subprocess execution
*/
import { spawn } from 'node:child_process';
import { join } from 'node:path';
import { existsSync } from 'node:fs';
const ccusagePath = join(process.cwd(), '../ccusage');
export async function runCcusage(args) {
if (!existsSync(ccusagePath)) {
throw new Error('ccusage submodule not found at: ' + ccusagePath);
}
return new Promise((resolve, reject) => {
const bunArgs = ['run', 'start', ...args, '--json'];
const child = spawn('bun', bunArgs, {
cwd: ccusagePath,
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 {
// Parse JSON output
const lines = stdout.split('\\n');
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
const data = JSON.parse(trimmed);
resolve(data);
return;
}
}
resolve(null);
} catch (error) {
reject(new Error('Failed to parse ccusage output: ' + error.message));
}
});
child.on('error', reject);
});
}
// Export convenience functions
export const ccusage = {
daily: (options = {}) => runCcusage(['daily', ...buildArgs(options)]),
monthly: (options = {}) => runCcusage(['monthly', ...buildArgs(options)]),
session: (options = {}) => runCcusage(['session', ...buildArgs(options)]),
blocks: (options = {}) => runCcusage(['blocks', ...buildArgs(options)])
};
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);
// Step 4: Create TypeScript definitions
const typeDefsContent = `
/**
* Auto-generated TypeScript definitions for ccusage bundle
*/
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);
// Step 5: 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"
};
writeFileSync(
join(bundleDir, 'package.json'),
JSON.stringify(bundlePackageJson, null, 2)
);
console.log('✅ ccusage bundle created successfully at:', bundleDir);
console.log('\nTo use the bundle:');
console.log('1. Ensure bun is installed on the target system');
console.log('2. Ensure git submodules are initialized');
console.log('3. Import from src/lib/ccusage-bundle');