UNPKG

@aicodewith/ccstatusline

Version:

AiCodeWith credits display plugin for Claude Code with customizable status line

403 lines (389 loc) 13 kB
import { createRequire } from 'module'; const require = createRequire(import.meta.url); // src/utils/aicodewith.ts import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import { spawnSync } from "child_process"; var refreshTimer = null; var lastFullDataFetch = 0; var CACHE_TTL_MS = Number(process.env.CCSTATUSLINE_CREDITS_TTL_MS || process.env.CCSTATUSLINE_REFRESH_INTERVAL_MS || 3e4); function getConfigDir() { return path.join(os.homedir(), ".config", "ccstatusline", "aicodewith"); } function getApiKey() { if (process.env.CCSTATUSLINE_API_KEY) { return process.env.CCSTATUSLINE_API_KEY; } const configDir = getConfigDir(); const apiConfigFile = path.join(configDir, "api-config.json"); try { if (fs.existsSync(apiConfigFile)) { const config = JSON.parse(fs.readFileSync(apiConfigFile, "utf8")); if (config?.apiKey) { return config.apiKey; } } } catch (error) { } return null; } async function getAiCodeWithCredits() { try { const data = fetchFullDataSync(); if (data?.credits) { const monthly = data.credits.subscription_credits || 0; const booster = data.credits.bonus_credits || 0; const used = (data.credits.plan_credits || 0) - monthly; return { totalCredits: (monthly + booster).toString(), monthCredits: monthly.toString(), giftPackCredits: booster.toString(), todayConsumption: used.toString(), weeklyConsumption: "0", monthlyConsumption: used.toString(), currentRank: "\u672A\u77E5", remainingDays: data.subscription?.days_remaining?.toString() || "0", remainingTime: "0\u5206\u949F", lastUpdated: new Date(data.timestamp || Date.now()) }; } return getMockCreditsData(); } catch (error) { return getMockCreditsData(); } } function getMockCreditsData() { return { totalCredits: "106,016", monthCredits: "62,770", giftPackCredits: "43,246", todayConsumption: "8,730", weeklyConsumption: "265,428", monthlyConsumption: "637,974", currentRank: "\u524D40%", remainingDays: "5", remainingTime: "7\u5C0F\u65F659\u5206\u949F", lastUpdated: /* @__PURE__ */ new Date() }; } function formatCredits(credits) { const num = parseInt(credits.replace(/,/g, "")); if (isNaN(num)) return credits; if (num >= 1e6) { return `${(num / 1e6).toFixed(1)}M`; } else if (num >= 1e3) { return `${(num / 1e3).toFixed(1)}k`; } else { return num.toLocaleString(); } } function formatCreditsDisplay(monthly, gift, monthlyUsed, compact = false) { if (compact) { const monthFormatted = formatCredits(monthly); const giftFormatted = formatCredits(gift); const usedFormatted = formatCredits(monthlyUsed); return `M: ${monthFormatted} | G: ${giftFormatted} | U: ${usedFormatted}`; } else { return `\u6708\u5361: ${monthly} | \u52A0\u901F\u5305: ${gift} | \u672C\u6708\u5DF2\u7528: ${monthlyUsed}`; } } function fetchFullDataSync() { const configDir = getConfigDir(); const fullCacheFile = path.join(configDir, "full-data-cache.json"); let staleCache = null; try { if (fs.existsSync(fullCacheFile)) { const cacheData = JSON.parse(fs.readFileSync(fullCacheFile, "utf8")); if (cacheData.timestamp) { const ttl = Number(process.env.CCSTATUSLINE_CREDITS_TTL_MS || process.env.CCSTATUSLINE_REFRESH_INTERVAL_MS || 3e4); const fresh = Date.now() - cacheData.timestamp < ttl; if (fresh) { return cacheData; } staleCache = cacheData; } } } catch (err) { } const apiKey = getApiKey(); if (!apiKey) { return staleCache; } try { const nodeCode = ` const https = require('https'); const apiKey = '${apiKey}'; let pendingRequests = 4; const data = {}; function checkDone() { pendingRequests--; if (pendingRequests === 0) { process.stdout.write(JSON.stringify(data)); } } // \u5E76\u884C\u8BF7\u6C42\u4E09\u4E2AAPI\u7AEF\u70B9 // 1. \u83B7\u53D6\u79EF\u5206\u6570\u636E const creditsReq = https.get({ hostname: 'status.aicodewith.com', path: '/api/v1/user/credits', headers: { 'X-API-Key': apiKey }, timeout: 8000 }, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { if (res.statusCode === 200) { try { data.credits = JSON.parse(body).data; } catch (e) {} } checkDone(); }); }); creditsReq.on('error', () => checkDone()); creditsReq.on('timeout', () => { creditsReq.destroy(); checkDone(); }); // 2. \u83B7\u53D6\u8BA2\u9605\u4FE1\u606F const subReq = https.get({ hostname: 'status.aicodewith.com', path: '/api/v1/user/subscription', headers: { 'X-API-Key': apiKey }, timeout: 8000 }, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { if (res.statusCode === 200) { try { data.subscription = JSON.parse(body).data; } catch (e) {} } checkDone(); }); }); subReq.on('error', () => checkDone()); subReq.on('timeout', () => { subReq.destroy(); checkDone(); }); // 3. \u83B7\u53D6\u7528\u6237\u4FE1\u606F const briefReq = https.get({ hostname: 'status.aicodewith.com', path: '/api/v1/user/brief', headers: { 'X-API-Key': apiKey }, timeout: 8000 }, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { if (res.statusCode === 200) { try { const parsed = JSON.parse(body); data.brief = parsed.data; } catch (e) { } } else { } checkDone(); }); }); briefReq.on('error', () => checkDone()); briefReq.on('timeout', () => { briefReq.destroy(); checkDone(); }); // 4. \u83B7\u53D6\u7528\u6237\u6458\u8981\u4FE1\u606F const summaryReq = https.get({ hostname: 'status.aicodewith.com', path: '/api/v1/user/summary', headers: { 'X-API-Key': apiKey }, timeout: 8000 }, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { if (res.statusCode === 200) { try { const parsed = JSON.parse(body); data.summary = parsed.data; } catch (e) { } } else { } checkDone(); }); }); summaryReq.on('error', () => checkDone()); summaryReq.on('timeout', () => { summaryReq.destroy(); checkDone(); }); `; const result = spawnSync("node", ["-e", nodeCode], { encoding: "utf8", timeout: 15e3, windowsHide: true }); if (result.stdout) { const data = JSON.parse(result.stdout.trim()); const fullData = { ...data, timestamp: Date.now() }; try { fs.mkdirSync(configDir, { recursive: true }); fs.writeFileSync(fullCacheFile, JSON.stringify(fullData, null, 2)); } catch (err) { } return fullData; } } catch (err) { } return staleCache; } function getMonthUsed() { const data = fetchFullDataSync(); if (data?.credits?.plan_credits && data?.credits?.subscription_credits !== void 0) { return (data.credits.plan_credits - data.credits.subscription_credits).toString(); } return "-"; } function getDaysRemaining() { const data = fetchFullDataSync(); return data?.subscription?.days_remaining?.toString() || "-"; } function getPlanName() { const data = fetchFullDataSync(); return data?.subscription?.plan_name || "-"; } function getTodayCredits() { return getMonthUsed(); } function getMonthRemain() { const data = fetchFullDataSync(); return data?.credits?.subscription_credits?.toString() || "-"; } function getMonthTotal() { const data = fetchFullDataSync(); return data?.credits?.plan_credits?.toString() || "-"; } function getBonusCredits() { const data = fetchFullDataSync(); return data?.credits?.bonus_credits?.toString() || "-"; } function getTotalCredits() { const data = fetchFullDataSync(); return data?.credits?.total_credits?.toString() || "-"; } function getUsagePercent() { const data = fetchFullDataSync(); return data?.credits?.subscription_usage_percent || "-"; } function getRemainPercent() { const data = fetchFullDataSync(); return data?.credits?.subscription_remaining_percent || "-"; } function getExpireTime() { const data = fetchFullDataSync(); if (data?.subscription?.expires_at) { const date = new Date(data.subscription.expires_at); const hours = date.getHours().toString().padStart(2, "0"); const minutes = date.getMinutes().toString().padStart(2, "0"); return `${date.getMonth() + 1}/${date.getDate()} ${hours}:${minutes}`; } return "-"; } function getUsername() { const data = fetchFullDataSync(); const username = data?.summary?.full_name || data?.brief?.full_name || data?.summary?.name || data?.brief?.name || data?.summary?.username || data?.brief?.username; return username || "-"; } function getEmail() { const data = fetchFullDataSync(); const email = data?.summary?.email || data?.brief?.email; return email || "-"; } var prefetched = false; function prefetchFullDataSyncIfCold() { if (prefetched) return; prefetched = true; ensureBackgroundRefresh(); try { const configDir = getConfigDir(); const fullCacheFile = path.join(configDir, "full-data-cache.json"); if (fs.existsSync(fullCacheFile)) { const cacheData = JSON.parse(fs.readFileSync(fullCacheFile, "utf8")); const now = Date.now(); const freshness = Number(process.env.CCSTATUSLINE_REFRESH_INTERVAL_MS || 3e4) * 2; if (cacheData.timestamp && now - cacheData.timestamp < freshness) { return; } } fetchFullDataSync(); } catch (e) { } } function ensureBackgroundRefresh() { const shouldEnableAutoRefresh = () => { if (process.env.CCSTATUSLINE_AUTO_REFRESH === "0") return false; if (process.env.CCSTATUSLINE_AUTO_REFRESH === "1") return true; const hasTTY = Boolean(process.stdin?.isTTY) || Boolean(process.stdout?.isTTY); return hasTTY; }; if (!shouldEnableAutoRefresh()) { return; } if (refreshTimer) { return; } refreshTimer = setInterval(() => { try { const now = Date.now(); const minInterval = Number(process.env.CCSTATUSLINE_MIN_UPDATE_INTERVAL_MS || 25e3); if (now - lastFullDataFetch < minInterval) { return; } const lockPath = path.join(getConfigDir(), "refresh.lock"); try { const fd = fs.openSync(lockPath, "wx"); fs.closeSync(fd); fetchFullDataSync(); lastFullDataFetch = now; try { fs.unlinkSync(lockPath); } catch { } } catch { } } catch (e) { } }, CACHE_TTL_MS); if (typeof refreshTimer.unref === "function") { refreshTimer.unref(); } const clearTimer = () => { if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; } }; process.once("exit", clearTimer); process.once("SIGINT", () => { clearTimer(); process.exit(0); }); process.once("SIGTERM", () => { clearTimer(); process.exit(0); }); } export { formatCredits, formatCreditsDisplay, getAiCodeWithCredits, getBonusCredits, getDaysRemaining, getEmail, getExpireTime, getMonthRemain, getMonthTotal, getMonthUsed, getPlanName, getRemainPercent, getTodayCredits, getTotalCredits, getUsagePercent, getUsername, prefetchFullDataSyncIfCold };