UNPKG

ccusage-on-cloud-client

Version:

Claude Code usage reporter client for ccusage-on-cloud-server

187 lines (162 loc) 6.4 kB
#!/usr/bin/env node const axios = require('axios'); const { exec } = require('child_process'); const path = require('path'); const fs = require('fs'); const os = require('os'); // 環境変数から設定を取得 const config = { apiUrl: process.env.CCUSAGE_ON_CLOUD_API_URL || 'http://localhost:21456/usage', username: process.env.CCUSAGE_ON_CLOUD_USERNAME || 'default-user', }; // 一時ファイルパス const tempJsonPath = path.join(os.tmpdir(), 'ccusage-server-data.json'); // サーバーからデータを取得 async function fetchServerData() { try { console.log(`🔍 Fetching usage data from server...`); const response = await axios.get(`${config.apiUrl.replace('/usage', '')}/usage/${config.username}`, { timeout: 10000 }); if (!response.data || !response.data.usage || response.data.usage.length === 0) { console.log(`📊 No usage data available for ${config.username}`); return null; } return response.data; } catch (error) { if (error.response) { console.error(`❌ Failed to get data from server: ${error.response.status}`); } else { console.error(`❌ Failed to get data from server: ${error.message}`); } return null; } } // サーバーデータをccusage形式に変換 function convertToBlocksFormat(serverData) { // サーバーのデータをccusageのblocks形式に変換 const blocks = []; // 最新のデータを取得(最大10件) const recentData = serverData.usage.slice(-10); recentData.forEach((entry, index) => { if (!entry.blocks || entry.blocks.length === 0) return; // ccusageのデータから必要な情報を抽出 const ccusageBlock = entry.blocks[0]; // 最初のブロックを使用 blocks.push({ id: entry.timestamp, startTime: entry.timestamp, endTime: new Date(new Date(entry.timestamp).getTime() + 5 * 60 * 60 * 1000).toISOString(), // 5時間後 actualEndTime: entry.timestamp, isActive: index === recentData.length - 1, // 最新のエントリのみアクティブ isGap: false, entries: 1, tokenCounts: ccusageBlock.tokenCounts || { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 }, totalTokens: ccusageBlock.totalTokens || 0, costUSD: ccusageBlock.costUSD || 0, models: ccusageBlock.models || [], burnRate: ccusageBlock.burnRate || {}, projection: ccusageBlock.projection || {}, tokenLimitStatus: ccusageBlock.tokenLimitStatus || {} }); }); return { blocks }; } // ccusageコマンドを実行してサーバーデータを表示 async function displayWithCcusage(jsonData) { return new Promise((resolve, reject) => { // 一時ファイルにJSONデータを書き込む fs.writeFileSync(tempJsonPath, JSON.stringify(jsonData, null, 2)); // ccusageのblocks.tsを使って表示(環境変数でデータソースを指定) const ccusagePath = path.join(__dirname, 'ccusage'); const env = { ...process.env, CCUSAGE_SERVER_DATA: tempJsonPath // カスタムデータソースを指定 }; // ccusageのソースコードを直接実行 exec(`cd ${ccusagePath} && bun run start blocks --json`, { env }, (error, stdout, stderr) => { // 一時ファイルを削除 try { fs.unlinkSync(tempJsonPath); } catch (e) { // エラーは無視 } if (error) { reject(error); return; } // JSONデータをパース try { const blocksData = JSON.parse(stdout); // ccusageのblocks表示を再現 exec(`cd ${ccusagePath} && bun run start blocks`, { env: { ...process.env, CCUSAGE_SERVER_DATA: JSON.stringify(blocksData) } }, (error2, stdout2, stderr2) => { if (error2) { // フォールバック: 自前で表示 console.log('\n╭───────────────────────────────────────────────────╮'); console.log('│ │'); console.log('│ Claude Code Token Usage Report - Server Data │'); console.log('│ │'); console.log('╰───────────────────────────────────────────────────╯\n'); console.log(`📊 Username: ${jsonData.username}`); console.log(`📊 Total records: ${jsonData.usage.length}\n`); // 最新のデータを表示 const recent = jsonData.usage.slice(-5); recent.forEach((entry, idx) => { console.log(`[${idx + 1}] ${new Date(entry.timestamp).toLocaleString()}`); console.log(` Server: ${entry.serverId}`); if (entry.blocks && entry.blocks.length > 0) { const block = entry.blocks[0]; if (block.totalTokens) { console.log(` Tokens: ${block.totalTokens.toLocaleString()}`); } if (block.costUSD) { console.log(` Cost: $${block.costUSD.toFixed(2)}`); } } console.log(''); }); } else { console.log(stdout2); } resolve(); }); } catch (parseError) { // パースエラーの場合は生のデータを表示 console.log(stdout); resolve(); } }); }); } // メイン処理 async function main() { try { // サーバーからデータを取得 const serverData = await fetchServerData(); if (!serverData) { process.exit(1); } // データを変換 const blocksData = convertToBlocksFormat(serverData); // ccusageで表示 await displayWithCcusage({ username: serverData.username, usage: serverData.usage, blocks: blocksData.blocks }); } catch (error) { console.error(`💥 Error: ${error.message}`); process.exit(1); } } // 実行 main();