UNPKG

@aicodewith/ccstatusline

Version:

AiCodeWith credits display plugin for Claude Code with customizable status line

1,089 lines (1,074 loc) 45.2 kB
// src/utils/renderer.ts import chalk from "chalk"; import { execSync } from "child_process"; import * as fs2 from "fs"; import { promisify } from "util"; // 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; } 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); }); } // src/utils/renderer.ts var readFile2 = fs2.promises?.readFile || promisify(fs2.readFile); chalk.level = 3; function applyColors(text, foregroundColor, backgroundColor, bold) { let result = text; if (foregroundColor && foregroundColor !== "dim") { const fgFunc = chalk[foregroundColor]; if (fgFunc) { result = fgFunc(result); } } if (backgroundColor && backgroundColor !== "none") { const bgFunc = chalk[backgroundColor]; if (bgFunc) { result = bgFunc(result); } } if (bold) { result = chalk.bold(result); } return result; } function getItemDefaultColor(type) { switch (type) { case "model": return "cyan"; case "git-branch": return "magenta"; case "git-changes": return "yellow"; case "session-clock": return "yellow"; case "version": return "green"; case "tokens-input": return "blue"; case "tokens-output": return "white"; case "tokens-cached": return "cyan"; case "tokens-total": return "cyan"; case "context-length": return "gray"; case "context-percentage": return "blue"; case "context-percentage-usable": return "green"; case "terminal-width": return "gray"; case "custom-text": return "white"; case "custom-command": return "white"; case "separator": return "gray"; case "aicodewith-today": return "red"; default: return "white"; } } function formatTokens(count) { if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`; if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`; return count.toString(); } function getTerminalWidth() { try { const cols = process.stdout?.columns; if (typeof cols === "number" && cols > 0) { return cols; } } catch { } try { const envCols = process.env.COLUMNS; if (envCols) { const parsed = parseInt(envCols, 10); if (!isNaN(parsed) && parsed > 0) { return parsed; } } } catch { } if (process.platform === "win32") { try { const psOut = execSync('powershell -NoProfile -Command "[Console]::WindowWidth"', { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); const parsed = parseInt(psOut, 10); if (!isNaN(parsed) && parsed > 0) { return parsed; } } catch { } try { const modeOut = execSync("mode con", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }); const m = modeOut.match(/Columns:\s*(\d+)/i) || modeOut.match(/列[::]\s*(\d+)/); const parsed = m?.[1] ? parseInt(m[1], 10) : NaN; if (!isNaN(parsed) && parsed > 0) { return parsed; } } catch { } } try { const tty = execSync("ps -o tty= -p $(ps -o ppid= -p $$)", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], shell: "/bin/sh" }).trim(); if (tty && tty !== "??" && tty !== "?") { const width = execSync( `stty size < /dev/${tty} | awk '{print $2}'`, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], shell: "/bin/sh" } ).trim(); const parsed = parseInt(width, 10); if (!isNaN(parsed) && parsed > 0) { return parsed; } } } catch { } try { const width = execSync("tput cols 2>/dev/null", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim(); const parsed = parseInt(width, 10); if (!isNaN(parsed) && parsed > 0) { return parsed; } } catch { } return null; } function getGitBranch() { try { const branch = execSync("git branch --show-current 2>/dev/null", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim(); return branch || null; } catch { return null; } } function getGitChanges() { try { let totalInsertions = 0; let totalDeletions = 0; const unstagedStat = execSync("git diff --shortstat 2>/dev/null", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim(); const stagedStat = execSync("git diff --cached --shortstat 2>/dev/null", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim(); if (unstagedStat) { const insertMatch = unstagedStat.match(/(\d+) insertion/); const deleteMatch = unstagedStat.match(/(\d+) deletion/); totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0; totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0; } if (stagedStat) { const insertMatch = stagedStat.match(/(\d+) insertion/); const deleteMatch = stagedStat.match(/(\d+) deletion/); totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0; totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0; } return { insertions: totalInsertions, deletions: totalDeletions }; } catch { return null; } } async function getSessionDuration(transcriptPath) { try { if (!fs2.existsSync(transcriptPath)) { return null; } const content = await readFile2(transcriptPath, "utf-8"); const lines = content.trim().split("\n").filter((line) => line.trim()); if (lines.length === 0) { return null; } let firstTimestamp = null; let lastTimestamp = null; for (const line of lines) { try { const data = JSON.parse(line); if (data.timestamp) { firstTimestamp = new Date(data.timestamp); break; } } catch { } } for (let i = lines.length - 1; i >= 0; i--) { try { const data = JSON.parse(lines[i]); if (data.timestamp) { lastTimestamp = new Date(data.timestamp); break; } } catch { } } if (!firstTimestamp || !lastTimestamp) { return null; } const durationMs = lastTimestamp.getTime() - firstTimestamp.getTime(); const totalMinutes = Math.floor(durationMs / (1e3 * 60)); if (totalMinutes < 1) { return "<1m"; } const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; if (hours === 0) { return `${minutes}m`; } else if (minutes === 0) { return `${hours}hr`; } else { return `${hours}hr ${minutes}m`; } } catch { return null; } } async function getTokenMetrics(transcriptPath) { try { if (!fs2.existsSync(transcriptPath)) { return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 }; } const content = await readFile2(transcriptPath, "utf-8"); const lines = content.trim().split("\n"); let inputTokens = 0; let outputTokens = 0; let cachedTokens = 0; let contextLength = 0; let mostRecentMainChainEntry = null; let mostRecentTimestamp = null; for (const line of lines) { try { const data = JSON.parse(line); if (data.message?.usage) { inputTokens += data.message.usage.input_tokens || 0; outputTokens += data.message.usage.output_tokens || 0; cachedTokens += data.message.usage.cache_read_input_tokens || 0; cachedTokens += data.message.usage.cache_creation_input_tokens || 0; if (data.isSidechain !== true && data.timestamp) { const entryTime = new Date(data.timestamp); if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) { mostRecentTimestamp = entryTime; mostRecentMainChainEntry = data; } } } } catch { } } if (mostRecentMainChainEntry?.message?.usage) { const usage = mostRecentMainChainEntry.message.usage; contextLength = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0) + (usage.cache_creation_input_tokens || 0); } const totalTokens = inputTokens + outputTokens + cachedTokens; return { inputTokens, outputTokens, cachedTokens, totalTokens, contextLength }; } catch { return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 }; } } function renderStatusLine(items, settings, context) { const hasAiCodeWith = items.some((it) => String(it.type).startsWith("aicodewith-")); if (hasAiCodeWith) { try { prefetchFullDataSyncIfCold(); } catch { } } const applyColorsWithOverride = (text, foregroundColor, backgroundColor, bold) => { let fgColor = foregroundColor; if (settings.overrideForegroundColor && settings.overrideForegroundColor !== "none") { fgColor = settings.overrideForegroundColor; } let bgColor = backgroundColor; if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none") { bgColor = settings.overrideBackgroundColor; } const shouldBold = settings.globalBold || bold; return applyColors(text, fgColor, bgColor, shouldBold); }; const detectedWidth = context.terminalWidth || getTerminalWidth(); let terminalWidth = null; if (detectedWidth) { const flexMode = settings.flexMode || "full-minus-40"; if (context.isPreview) { if (flexMode === "full") { terminalWidth = detectedWidth - 6; } else if (flexMode === "full-minus-40") { terminalWidth = detectedWidth - 43; } else if (flexMode === "full-until-compact") { terminalWidth = detectedWidth - 6; } } else { if (flexMode === "full") { terminalWidth = detectedWidth - 4; } else if (flexMode === "full-minus-40") { terminalWidth = detectedWidth - 41; } else if (flexMode === "full-until-compact") { const threshold = settings.compactThreshold || 60; const contextPercentage = context.tokenMetrics ? Math.min(100, context.tokenMetrics.contextLength / 2e5 * 100) : 0; if (contextPercentage >= threshold) { terminalWidth = detectedWidth - 40; } else { terminalWidth = detectedWidth - 4; } } } } const elements = []; let hasFlexSeparator = false; for (const item of items) { switch (item.type) { case "model": if (context.isPreview) { const modelText = item.rawValue ? "Claude" : "Model: Claude"; elements.push({ content: applyColorsWithOverride(modelText, item.color || "cyan", item.backgroundColor, item.bold), type: "model", item }); } else if (context.data?.model) { const text = item.rawValue ? context.data.model.display_name : `Model: ${context.data.model.display_name}`; elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "model", item }); } break; case "git-branch": if (context.isPreview) { const branchText = item.rawValue ? "main" : "\u2387 main"; elements.push({ content: applyColorsWithOverride(branchText, item.color || "magenta", item.backgroundColor, item.bold), type: "git-branch", item }); } else { const branch = context.gitBranch || getGitBranch(); if (branch) { const text = item.rawValue ? branch : `\u2387 ${branch}`; elements.push({ content: applyColorsWithOverride(text, item.color || "magenta", item.backgroundColor, item.bold), type: "git-branch", item }); } } break; case "git-changes": if (context.isPreview) { const changesText = "(+42,-10)"; elements.push({ content: applyColorsWithOverride(changesText, item.color || "yellow", item.backgroundColor, item.bold), type: "git-changes", item }); } else { const changes = context.gitChanges || getGitChanges(); if (changes !== null) { const changeStr = `(+${changes.insertions},-${changes.deletions})`; elements.push({ content: applyColorsWithOverride(changeStr, item.color || "yellow", item.backgroundColor, item.bold), type: "git-changes", item }); } } break; case "tokens-input": if (context.isPreview) { const inputText = item.rawValue ? "15.2k" : "In: 15.2k"; elements.push({ content: applyColorsWithOverride(inputText, item.color || "blue", item.backgroundColor, item.bold), type: "tokens-input", item }); } else if (context.tokenMetrics) { const text = item.rawValue ? formatTokens(context.tokenMetrics.inputTokens) : `In: ${formatTokens(context.tokenMetrics.inputTokens)}`; elements.push({ content: applyColorsWithOverride(text, item.color || "blue", item.backgroundColor, item.bold), type: "tokens-input", item }); } break; case "tokens-output": if (context.isPreview) { const outputText = item.rawValue ? "3.4k" : "Out: 3.4k"; elements.push({ content: applyColorsWithOverride(outputText, item.color || "white", item.backgroundColor, item.bold), type: "tokens-output", item }); } else if (context.tokenMetrics) { const text = item.rawValue ? formatTokens(context.tokenMetrics.outputTokens) : `Out: ${formatTokens(context.tokenMetrics.outputTokens)}`; elements.push({ content: applyColorsWithOverride(text, item.color || "white", item.backgroundColor, item.bold), type: "tokens-output", item }); } break; case "tokens-cached": if (context.isPreview) { const cachedText = item.rawValue ? "12k" : "Cached: 12k"; elements.push({ content: applyColorsWithOverride(cachedText, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-cached", item }); } else if (context.tokenMetrics) { const text = item.rawValue ? formatTokens(context.tokenMetrics.cachedTokens) : `Cached: ${formatTokens(context.tokenMetrics.cachedTokens)}`; elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-cached", item }); } break; case "tokens-total": if (context.isPreview) { const totalText = item.rawValue ? "30.6k" : "Total: 30.6k"; elements.push({ content: applyColorsWithOverride(totalText, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-total", item }); } else if (context.tokenMetrics) { const text = item.rawValue ? formatTokens(context.tokenMetrics.totalTokens) : `Total: ${formatTokens(context.tokenMetrics.totalTokens)}`; elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-total", item }); } break; case "context-length": if (context.isPreview) { const ctxText = item.rawValue ? "18.6k" : "Ctx: 18.6k"; elements.push({ content: applyColorsWithOverride(ctxText, item.color || "gray", item.backgroundColor, item.bold), type: "context-length", item }); } else if (context.tokenMetrics) { const text = item.rawValue ? formatTokens(context.tokenMetrics.contextLength) : `Ctx: ${formatTokens(context.tokenMetrics.contextLength)}`; elements.push({ content: applyColorsWithOverride(text, item.color || "gray", item.backgroundColor, item.bold), type: "context-length", item }); } break; case "context-percentage": if (context.isPreview) { const ctxPctText = item.rawValue ? "9.3%" : "Ctx: 9.3%"; elements.push({ content: applyColorsWithOverride(ctxPctText, item.color || "blue", item.backgroundColor, item.bold), type: "context-percentage", item }); } else if (context.tokenMetrics) { const percentage = Math.min(100, context.tokenMetrics.contextLength / 2e5 * 100); const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`; elements.push({ content: applyColorsWithOverride(text, item.color || "blue", item.backgroundColor, item.bold), type: "context-percentage", item }); } break; case "context-percentage-usable": if (context.isPreview) { const ctxUsableText = item.rawValue ? "11.6%" : "Ctx(u): 11.6%"; elements.push({ content: applyColorsWithOverride(ctxUsableText, item.color || "green", item.backgroundColor, item.bold), type: "context-percentage-usable", item }); } else if (context.tokenMetrics) { const percentage = Math.min(100, context.tokenMetrics.contextLength / 16e4 * 100); const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx(u): ${percentage.toFixed(1)}%`; elements.push({ content: applyColorsWithOverride(text, item.color || "green", item.backgroundColor, item.bold), type: "context-percentage-usable", item }); } break; case "terminal-width": const width = terminalWidth || getTerminalWidth(); if (context.isPreview) { const detectedWidth2 = width || "??"; const termText = item.rawValue ? `${detectedWidth2}` : `Term: ${detectedWidth2}`; elements.push({ content: applyColorsWithOverride(termText, item.color || "gray", item.backgroundColor, item.bold), type: "terminal-width", item }); } else if (width) { const text = item.rawValue ? `${width}` : `Term: ${width}`; elements.push({ content: applyColorsWithOverride(text, item.color || "gray", item.backgroundColor, item.bold), type: "terminal-width", item }); } break; case "session-clock": if (context.isPreview) { const sessionText = item.rawValue ? "2hr 15m" : "Session: 2hr 15m"; elements.push({ content: applyColorsWithOverride(sessionText, item.color || "yellow", item.backgroundColor, item.bold), type: "session-clock", item }); } else if (context.sessionDuration) { const text = item.rawValue ? context.sessionDuration : `Session: ${context.sessionDuration}`; elements.push({ content: applyColorsWithOverride(text, item.color || "yellow", item.backgroundColor, item.bold), type: "session-clock", item }); } break; case "version": if (context.isPreview) { const versionText = item.rawValue ? "1.0.72" : "Version: 1.0.72"; elements.push({ content: applyColorsWithOverride(versionText, item.color || "green", item.backgroundColor, item.bold), type: "version", item }); } else if (context.data?.version) { const versionString = context.data.version || "Unknown"; const versionText = item.rawValue ? versionString : `Version: ${versionString}`; elements.push({ content: applyColorsWithOverride(versionText, item.color || "green", item.backgroundColor, item.bold), type: "version", item }); } break; case "separator": const sepChar = item.character || "|"; let sepText; if (sepChar === ",") { sepText = `${sepChar} `; } else if (sepChar === " ") { sepText = " "; } else { sepText = ` ${sepChar} `; } const coloredSep = applyColorsWithOverride(sepText, item.color || "gray", item.backgroundColor, item.bold); const sepContent = coloredSep + "\x1B[0m"; elements.push({ content: sepContent, type: "separator", item }); break; case "flex-separator": elements.push({ content: "FLEX", type: "flex-separator", item }); hasFlexSeparator = true; break; case "custom-text": const customText = item.customText || ""; elements.push({ content: applyColorsWithOverride(customText, item.color || "white", item.backgroundColor, item.bold), type: "custom-text", item }); break; case "aicodewith-today": if (context.isPreview) { const todayText = item.rawValue ? "43508" : "\u4ECA\u65E5\u6D88\u8017: 43508"; elements.push({ content: applyColorsWithOverride(todayText, item.color || "red", item.backgroundColor, item.bold), type: "aicodewith-today", item }); } else { const todayValue = getTodayCredits(); const todayText = item.rawValue ? todayValue : `\u4ECA\u65E5\u6D88\u8017: ${todayValue}`; elements.push({ content: applyColorsWithOverride(todayText, item.color || "red", item.backgroundColor, item.bold), type: "aicodewith-today", item }); } break; case "aicodewith-month-remain": if (context.isPreview) { const remainText = item.rawValue ? "27992" : "\u6708\u5361\u5269\u4F59: 27992"; elements.push({ content: applyColorsWithOverride(remainText, item.color || "green", item.backgroundColor, item.bold), type: "aicodewith-month-remain", item }); } else { const remainValue = getMonthRemain(); const remainText = item.rawValue ? remainValue : `\u6708\u5361\u5269\u4F59: ${remainValue}`; elements.push({ content: applyColorsWithOverride(remainText, item.color || "green", item.backgroundColor, item.bold), type: "aicodewith-month-remain", item }); } break; case "aicodewith-month-total": if (context.isPreview) { const totalText = item.rawValue ? "71500" : "\u6708\u5361\u603B\u989D: 71500"; elements.push({ content: applyColorsWithOverride(totalText, item.color || "blue", item.backgroundColor, item.bold), type: "aicodewith-month-total", item }); } else { const totalValue = getMonthTotal(); const totalText = item.rawValue ? totalValue : `\u6708\u5361\u603B\u989D: ${totalValue}`; elements.push({ content: applyColorsWithOverride(totalText, item.color || "blue", item.backgroundColor, item.bold), type: "aicodewith-month-total", item }); } break; case "aicodewith-bonus": if (context.isPreview) { const bonusText = item.rawValue ? "43246" : "\u52A0\u6CB9\u5305: 43246"; elements.push({ content: applyColorsWithOverride(bonusText, item.color || "yellow", item.backgroundColor, item.bold), type: "aicodewith-bonus", item }); } else { const bonusValue = getBonusCredits(); const bonusText = item.rawValue ? bonusValue : `\u52A0\u6CB9\u5305: ${bonusValue}`; elements.push({ content: applyColorsWithOverride(bonusText, item.color || "yellow", item.backgroundColor, item.bold), type: "aicodewith-bonus", item }); } break; case "aicodewith-total": if (context.isPreview) { const totalCreditsText = item.rawValue ? "71238" : "\u603B\u79EF\u5206: 71238"; elements.push({ content: applyColorsWithOverride(totalCreditsText, item.color || "cyan", item.backgroundColor, item.bold), type: "aicodewith-total", item }); } else { const totalCreditsValue = getTotalCredits(); const totalCreditsText = item.rawValue ? totalCreditsValue : `\u603B\u79EF\u5206: ${totalCreditsValue}`; elements.push({ content: applyColorsWithOverride(totalCreditsText, item.color || "cyan", item.backgroundColor, item.bold), type: "aicodewith-total", item }); } break; case "aicodewith-usage-percent": if (context.isPreview) { const usageText = item.rawValue ? "60.9%" : "\u4F7F\u7528\u7387: 60.9%"; elements.push({ content: applyColorsWithOverride(usageText, item.color || "magenta", item.backgroundColor, item.bold), type: "aicodewith-usage-percent", item }); } else { const usageValue = getUsagePercent(); const usageText = item.rawValue ? usageValue : `\u4F7F\u7528\u7387: ${usageValue}`; elements.push({ content: applyColorsWithOverride(usageText, item.color || "magenta", item.backgroundColor, item.bold), type: "aicodewith-usage-percent", item }); } break; case "aicodewith-remain-percent": if (context.isPreview) { const remainPercentText = item.rawValue ? "39.1%" : "\u5269\u4F59\u7387: 39.1%"; elements.push({ content: applyColorsWithOverride(remainPercentText, item.color || "green", item.backgroundColor, item.bold), type: "aicodewith-remain-percent", item }); } else { const remainPercentValue = getRemainPercent(); const remainPercentText = item.rawValue ? remainPercentValue : `\u5269\u4F59\u7387: ${remainPercentValue}`; elements.push({ content: applyColorsWithOverride(remainPercentText, item.color || "green", item.backgroundColor, item.bold), type: "aicodewith-remain-percent", item }); } break; case "aicodewith-days": if (context.isPreview) { const daysText = item.rawValue ? "15\u5929" : "\u5269\u4F59: 15\u5929"; elements.push({ content: applyColorsWithOverride(daysText, item.color || "blue", item.backgroundColor, item.bold), type: "aicodewith-days", item }); } else { const daysValue = getDaysRemaining(); const daysText = item.rawValue ? `${daysValue}\u5929` : `\u5269\u4F59: ${daysValue}\u5929`; elements.push({ content: applyColorsWithOverride(daysText, item.color || "blue", item.backgroundColor, item.bold), type: "aicodewith-days", item }); } break; case "aicodewith-expire": if (context.isPreview) { const expireText = item.rawValue ? "12/31" : "\u5230\u671F: 12/31"; elements.push({ content: applyColorsWithOverride(expireText, item.color || "gray", item.backgroundColor, item.bold), type: "aicodewith-expire", item }); } else { const expireValue = getExpireTime(); const expireText = item.rawValue ? expireValue : `\u5230\u671F: ${expireValue}`; elements.push({ content: applyColorsWithOverride(expireText, item.color || "gray", item.backgroundColor, item.bold), type: "aicodewith-expire", item }); } break; case "aicodewith-plan": if (context.isPreview) { const planText = item.rawValue ? "\u72EC\u4EAB" : "\u5957\u9910: \u72EC\u4EAB"; elements.push({ content: applyColorsWithOverride(planText, item.color || "white", item.backgroundColor, item.bold), type: "aicodewith-plan", item }); } else { const planValue = getPlanName(); const planText = item.rawValue ? planValue : `\u5957\u9910: ${planValue}`; elements.push({ content: applyColorsWithOverride(planText, item.color || "white", item.backgroundColor, item.bold), type: "aicodewith-plan", item }); } break; case "aicodewith-username": if (context.isPreview) { const usernameText = item.rawValue ? "\u7528\u6237" : "\u7528\u6237: \u7528\u6237"; elements.push({ content: applyColorsWithOverride(usernameText, item.color || "white", item.backgroundColor, item.bold), type: "aicodewith-username", item }); } else { const usernameValue = getUsername(); const usernameText = item.rawValue ? usernameValue : `\u7528\u6237: ${usernameValue}`; elements.push({ content: applyColorsWithOverride(usernameText, item.color || "white", item.backgroundColor, item.bold), type: "aicodewith-username", item }); } break; case "aicodewith-email": if (context.isPreview) { const emailText = item.rawValue ? "user@example.com" : "\u90AE\u7BB1: user@example.com"; elements.push({ content: applyColorsWithOverride(emailText, item.color || "white", item.backgroundColor, item.bold), type: "aicodewith-email", item }); } else { const emailValue = getEmail(); const emailText = item.rawValue ? emailValue : `\u90AE\u7BB1: ${emailValue}`; elements.push({ content: applyColorsWithOverride(emailText, item.color || "white", item.backgroundColor, item.bold), type: "aicodewith-email", item }); } break; case "custom-command": if (context.isPreview) { const cmdText = item.commandPath ? `[cmd: ${item.commandPath.substring(0, 20)}${item.commandPath.length > 20 ? "..." : ""}]` : "[No command]"; if (!item.preserveColors) { elements.push({ content: applyColorsWithOverride(cmdText, item.color || "white", item.backgroundColor, item.bold), type: "custom-command", item }); } else { elements.push({ content: cmdText, type: "custom-command", item }); } } else if (item.commandPath && context.data) { try { const timeout = item.timeout || 1e3; const output = execSync(item.commandPath, { encoding: "utf8", input: JSON.stringify(context.data), stdio: ["pipe", "pipe", "ignore"], timeout }).trim(); if (output) { let finalOutput = output; if (item.maxWidth && item.maxWidth > 0) { const plainLength = output.replace(/\x1b\[[0-9;]*m/g, "").length; if (plainLength > item.maxWidth) { let truncated = ""; let currentLength = 0; let inAnsiCode = false; let ansiBuffer = ""; for (let i = 0; i < output.length; i++) { const char = output[i]; if (char === "\x1B") { inAnsiCode = true; ansiBuffer = char; } else if (inAnsiCode) { ansiBuffer += char; if (char === "m") { truncated += ansiBuffer; inAnsiCode = false; ansiBuffer = ""; } } else { if (currentLength < item.maxWidth) { truncated += char; currentLength++; } else { break; } } } finalOutput = truncated; } } if (!item.preserveColors) { const stripped = finalOutput.replace(/\x1b\[[0-9;]*m/g, ""); elements.push({ content: applyColorsWithOverride(stripped, item.color || "white", item.backgroundColor, item.bold), type: "custom-command", item }); } else { elements.push({ content: finalOutput, type: "custom-command", item }); } } } catch { } } break; } } if (elements.length === 0) return ""; while (elements.length > 0 && elements[elements.length - 1]?.type === "separator") { elements.pop(); } const finalElements = []; const padding = settings.defaultPadding || ""; const defaultSep = settings.defaultSeparator || ""; elements.forEach((elem, index) => { const prevElem = index > 0 ? elements[index - 1] : null; const shouldAddSeparator = defaultSep && index > 0 && elem.type !== "flex-separator" && prevElem?.type !== "flex-separator"; if (shouldAddSeparator) { if (settings.inheritSeparatorColors && index > 0) { const prevElem2 = elements[index - 1]; if (prevElem2 && prevElem2.item) { const itemColor = prevElem2.item.color || getItemDefaultColor(prevElem2.item.type); const coloredSep = applyColorsWithOverride(defaultSep, itemColor, prevElem2.item.backgroundColor, prevElem2.item.bold); finalElements.push(coloredSep); } else { finalElements.push(defaultSep); } } else if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" || settings.overrideForegroundColor && settings.overrideForegroundColor !== "none") { const coloredSep = applyColorsWithOverride(defaultSep, void 0, void 0); finalElements.push(coloredSep); } else { finalElements.push(defaultSep); } } if (elem.type === "separator" || elem.type === "flex-separator") { finalElements.push(elem.content); } else { const hasColorOverride = settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" || settings.overrideForegroundColor && settings.overrideForegroundColor !== "none"; if (padding && (elem.item?.backgroundColor || hasColorOverride)) { const paddedContent = applyColorsWithOverride(padding, void 0, elem.item?.backgroundColor) + elem.content + applyColorsWithOverride(padding, void 0, elem.item?.backgroundColor); finalElements.push(paddedContent); } else if (padding) { const protectedPadding = chalk.reset(padding); finalElements.push(protectedPadding + elem.content + protectedPadding); } else { finalElements.push(elem.content); } } }); let statusLine = ""; if (hasFlexSeparator && terminalWidth) { const parts = [[]]; let currentPart = 0; for (let i = 0; i < finalElements.length; i++) { const elem = finalElements[i]; if (elem === "FLEX") { currentPart++; parts[currentPart] = []; } else { parts[currentPart]?.push(elem); } } const partLengths = parts.map((part) => { const joined = part.join(""); return joined.replace(/\x1b\[[0-9;]*m/g, "").length; }); const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0); const flexCount = parts.length - 1; const totalSpace = Math.max(0, terminalWidth - totalContentLength); const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0; const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0; statusLine = ""; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (part) { statusLine += part.join(""); } if (i < parts.length - 1) { const spaces = spacePerFlex + (i < extraSpace ? 1 : 0); statusLine += " ".repeat(spaces); } } } else { if (hasFlexSeparator && !terminalWidth) { statusLine = finalElements.map((e) => e === "FLEX" ? chalk.gray(" | ") : e).join(""); } else { statusLine = finalElements.join(""); } } const maxWidth = terminalWidth || detectedWidth; if (maxWidth && maxWidth > 0) { const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, "").length; } return statusLine; } export { applyColors, formatTokens, getGitBranch, getGitChanges, getItemDefaultColor, getSessionDuration, getTerminalWidth, getTokenMetrics, renderStatusLine };