@aicodewith/ccstatusline
Version:
AiCodeWith credits display plugin for Claude Code with customizable status line
403 lines (389 loc) • 13 kB
JavaScript
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
};