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