ccusage-on-cloud-client
Version:
Claude Code usage reporter client for ccusage-on-cloud-server
187 lines (162 loc) • 6.4 kB
JavaScript
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();