UNPKG

ccusage-on-cloud-client

Version:

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

144 lines (123 loc) 4.64 kB
#!/usr/bin/env node const cron = require('node-cron'); const axios = require('axios'); const { exec } = require('child_process'); const { validateConfig, printConfigHelp } = require('./lib/validator'); const CLIENT_VERSION = '1.4.3'; const REQUIRED_SERVER_VERSION = '1.1.0'; // 環境変数から設定を取得 const config = { apiUrl: process.env.CCUSAGE_ON_CLOUD_API_URL || 'https://ccusage-on-cloud-server.onrender.com/usage', username: process.env.CCUSAGE_ON_CLOUD_USERNAME, password: process.env.CCUSAGE_ON_CLOUD_PASSWORD, serverId: process.env.CCUSAGE_ON_CLOUD_SERVER_ID, interval: process.env.CCUSAGE_ON_CLOUD_INTERVAL || '*/1 * * * *', // 1分ごと }; // 設定の検証 const { errors, warnings } = validateConfig(config); if (errors.length > 0) { console.error('\n❌ Configuration errors detected:\n'); errors.forEach(error => console.error(error)); printConfigHelp(); process.exit(1); } if (warnings.length > 0) { console.warn('\n⚠️ Configuration warnings:\n'); warnings.forEach(warning => console.warn(warning)); } console.log(`🚀 ccusage-on-cloud-client send-data started`); console.log(`📡 API URL: ${config.apiUrl}`); console.log(`👤 Username: ${config.username}`); console.log(`🔒 Password set: ${config.password ? 'Yes' : 'No'}`); console.log(`🖥️ Server ID: ${config.serverId}`); console.log(`⏰ Interval: ${config.interval}`); // ccusageデータを取得 async function getCcusageData() { return new Promise((resolve, reject) => { exec('npx ccusage@latest blocks -t max --json', (error, stdout, stderr) => { if (error) { reject(new Error(`ccusage command failed: ${error.message}`)); return; } try { const data = JSON.parse(stdout); resolve({ blocks: data.blocks || [], rawJsonl: data.blocks || [] }); } catch (parseError) { reject(new Error(`Failed to parse ccusage output: ${parseError.message}`)); } }); }); } // APIにデータを送信 async function sendUsageData(ccusageData) { try { // Basic認証用のヘッダーを作成 const auth = Buffer.from(`${config.username}:${config.password}`).toString('base64'); const payload = { serverId: config.serverId, blocks: ccusageData.blocks || [], rawJsonl: ccusageData.rawJsonl || [], timestamp: new Date().toISOString() }; const response = await axios.post(config.apiUrl, payload, { headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${auth}` }, timeout: 10000 }); console.log(`✅ Usage data sent successfully at ${new Date().toISOString()}`); console.log(`📊 Blocks: ${payload.blocks.length}, Status: ${response.data.status}`); // バージョンチェック if (response.data.serverVersion) { if (response.data.serverVersion !== REQUIRED_SERVER_VERSION) { console.warn(`\n⚠️ Server version mismatch!`); console.warn(` Client requires: v${REQUIRED_SERVER_VERSION}`); console.warn(` Server running: v${response.data.serverVersion}`); console.warn(` Some features may not work correctly.\n`); } } else { console.warn(`\n⚠️ Server version unknown. Please update the server.\n`); } return response.data; } catch (error) { if (error.response) { console.error(`❌ API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`); if (error.response.status === 401) { console.error('\n🔐 Authentication failed. Please check your username and password.'); } } else if (error.request) { console.error(`❌ Network Error: Could not reach ${config.apiUrl}`); } else { console.error(`❌ Error: ${error.message}`); } throw error; } } // メイン処理 async function reportUsage() { try { console.log(`🔄 Fetching ccusage data...`); const ccusageData = await getCcusageData(); await sendUsageData(ccusageData); } catch (error) { console.error(`💥 Failed to report usage: ${error.message}`); } } // 即座に1回実行 console.log(`🎯 Running initial usage report...`); reportUsage(); // cronスケジュールで定期実行 console.log(`⏰ Scheduling usage reports with cron: ${config.interval}`); cron.schedule(config.interval, () => { reportUsage(); }); // プロセス終了時の処理 process.on('SIGINT', () => { console.log('\n🛑 ccusage-client send-data stopped'); process.exit(0); }); process.on('SIGTERM', () => { console.log('\n🛑 ccusage-client send-data terminated'); process.exit(0); });