UNPKG

@aicodewith/ccstatusline

Version:

AiCodeWith credits display plugin for Claude Code with customizable status line

202 lines (176 loc) 5.84 kB
#!/usr/bin/env node /** * 后台刷新进程(已废弃) * * 注意:根据 CLAUDE.md 的教训,独立进程模式已被废弃 * 原因:spawn detached 模式会导致进程泄露 * * 当前方案:主程序使用内置定时器机制,避免进程泄露 * 保留此文件仅为向后兼容 */ // 立即退出,不再作为独立进程运行 console.log('[Background Refresh] This script is deprecated. Using timer-based refresh instead.'); process.exit(0); const https = require('https'); const fs = require('fs'); const path = require('path'); const os = require('os'); // ========== 配置常量 ========== const REFRESH_INTERVAL = Number(process.env.CCSTATUSLINE_REFRESH_INTERVAL_MS || 30000); // 刷新间隔 const CONFIG_DIR = path.join(os.homedir(), '.config', 'ccstatusline', 'aicodewith'); const CACHE_FILE = path.join(CONFIG_DIR, 'full-data-cache.json'); const PID_FILE = path.join(CONFIG_DIR, 'refresh.pid'); const LOG_FILE = path.join(CONFIG_DIR, 'refresh.log'); // API 配置 const API_HOST = 'status.aicodewith.com'; const TIMEOUT_MS = 8000; // 单个请求超时时间 // ========== 初始化 ========== // 确保配置目录存在 if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } // 清理旧版缓存文件(如果存在) const LEGACY_CACHE = path.join(CONFIG_DIR, 'credits-cache.json'); try { if (fs.existsSync(LEGACY_CACHE)) { fs.unlinkSync(LEGACY_CACHE); } } catch {} // 写入进程ID,便于其他进程检查 fs.writeFileSync(PID_FILE, process.pid.toString()); // ========== 工具函数 ========== /** * 写入日志(控制日志文件大小,超过1MB自动清空) */ function log(message) { try { // 检查日志文件大小 if (fs.existsSync(LOG_FILE)) { const stats = fs.statSync(LOG_FILE); if (stats.size > 1024 * 1024) { // 1MB fs.writeFileSync(LOG_FILE, ''); // 清空 } } const timestamp = new Date().toISOString(); const logMessage = `[${timestamp}] ${message}\n`; fs.appendFileSync(LOG_FILE, logMessage); } catch {} } /** * 获取API Key * 优先级:环境变量 > 配置文件 */ function getApiKey() { // 从环境变量获取 if (process.env.CCSTATUSLINE_API_KEY) { return process.env.CCSTATUSLINE_API_KEY; } // 从配置文件获取 const apiConfigFile = path.join(CONFIG_DIR, 'api-config.json'); try { if (fs.existsSync(apiConfigFile)) { const config = JSON.parse(fs.readFileSync(apiConfigFile, 'utf8')); return config.apiKey || null; } } catch {} return null; } /** * 发送HTTPS请求获取JSON数据 */ function fetchJSON(path, apiKey) { return new Promise((resolve) => { const req = https.get({ hostname: API_HOST, path: path, headers: { 'X-API-Key': apiKey, 'Cache-Control': 'no-cache', 'Accept': 'application/json' }, timeout: TIMEOUT_MS }, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { try { if (res.statusCode === 200) { const json = JSON.parse(body); resolve(json.data); } else { resolve(null); } } catch { resolve(null); } }); }); req.on('error', () => resolve(null)); req.on('timeout', () => { req.destroy(); resolve(null); }); }); } /** * 刷新所有数据 * 并行请求三个API端点,获取完整数据 */ async function refreshData() { const apiKey = getApiKey(); if (!apiKey) { log('ERROR: 未找到 API Key'); return; } log('开始刷新数据...'); try { // 并行请求四个端点 const [credits, subscription, brief, summary] = await Promise.all([ fetchJSON('/api/v1/user/credits', apiKey), fetchJSON('/api/v1/user/subscription', apiKey), fetchJSON('/api/v1/user/brief', apiKey), fetchJSON('/api/v1/user/summary', apiKey) ]); // 组装完整数据 const fullData = { credits, subscription, brief, summary, timestamp: Date.now() }; // 保存到缓存文件 fs.writeFileSync(CACHE_FILE, JSON.stringify(fullData, null, 2)); const creditsValue = credits?.subscription_credits || '-'; log(`数据刷新成功,月卡剩余: ${creditsValue}`); } catch (error) { log(`ERROR: 刷新失败 - ${error.message}`); } } /** * 清理并退出 */ function cleanup() { log('后台刷新进程关闭'); try { fs.unlinkSync(PID_FILE); } catch {} process.exit(0); } // ========== 主程序 ========== // 注册退出信号处理 process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); process.on('exit', () => { try { fs.unlinkSync(PID_FILE); } catch {} }); // 启动日志 log('========================================'); log(`后台刷新进程启动 (PID: ${process.pid})`); log(`刷新间隔: ${REFRESH_INTERVAL}ms`); // 立即执行一次刷新 refreshData(); // 设置定时刷新 setInterval(refreshData, REFRESH_INTERVAL); // 保持进程运行 process.stdin.resume();