UNPKG

@aicodewith/ccstatusline

Version:

AiCodeWith credits display plugin for Claude Code with customizable status line

1,285 lines (1,263 loc) 128 kB
// src/tui.tsx import { useState, useEffect } from "react"; import { render, Box, Text, useInput, useApp } from "ink"; import Gradient from "ink-gradient"; import SelectInput from "ink-select-input"; import chalk2 from "chalk"; import { execSync as execSync2 } from "child_process"; // src/utils/config.ts import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import { promisify } from "util"; var readFile2 = fs.promises?.readFile || promisify(fs.readFile); var writeFile2 = fs.promises?.writeFile || promisify(fs.writeFile); var mkdir2 = fs.promises?.mkdir || promisify(fs.mkdir); var CONFIG_DIR = path.join(os.homedir(), ".config", "ccstatusline"); var SETTINGS_PATH = path.join(CONFIG_DIR, "settings.json"); var DEFAULT_SETTINGS = { lines: [ // 第1行:Model | Session | Version [ { "id": "1", "type": "model", "color": "cyan" }, { "id": "2", "type": "separator" }, { "id": "3", "type": "session-clock", "color": "green" }, { "id": "4", "type": "separator" }, { "id": "5", "type": "version", "color": "gray" } ], // 第2行:Context相关信息 [ { "id": "6", "type": "context-length", "color": "yellow" }, { "id": "7", "type": "separator" }, { "id": "8", "type": "context-percentage", "color": "yellow" }, { "id": "9", "type": "separator" }, { "id": "10", "type": "context-percentage-usable", "color": "yellow" } ], // 第3行:AiCodeWith积分信息 [ { "id": "11", "type": "aicodewith-month-remain", "color": "magenta" }, { "id": "12", "type": "separator" }, { "id": "13", "type": "aicodewith-bonus", "color": "cyan" }, { "id": "14", "type": "separator" }, { "id": "15", "type": "aicodewith-usage-percent", "color": "green" }, { "id": "16", "type": "separator" }, { "id": "17", "type": "aicodewith-username", "color": "blue" }, { "id": "18", "type": "separator" }, { "id": "19", "type": "aicodewith-plan", "color": "yellow" }, { "id": "20", "type": "separator" }, { "id": "21", "type": "aicodewith-days", "color": "red" } ] ], flexMode: "full-minus-40", compactThreshold: 60 }; async function loadSettings() { try { if (!fs.existsSync(SETTINGS_PATH)) { return DEFAULT_SETTINGS; } const content = await readFile2(SETTINGS_PATH, "utf-8"); let loaded; try { loaded = JSON.parse(content); } catch (parseError) { return DEFAULT_SETTINGS; } if (loaded.elements || loaded.layout) { return migrateOldSettings(loaded); } if (loaded.items && !loaded.lines) { loaded.lines = [loaded.items]; delete loaded.items; } if (loaded.lines) { if (!Array.isArray(loaded.lines)) { loaded.lines = [[]]; } loaded.lines = loaded.lines.slice(0, 3); } return { ...DEFAULT_SETTINGS, ...loaded }; } catch (error) { return DEFAULT_SETTINGS; } } function migrateOldSettings(old) { const items = []; let id = 1; if (old.elements?.model) { items.push({ id: String(id++), type: "model", color: old.colors?.model }); } if (items.length > 0 && old.elements?.gitBranch) { items.push({ id: String(id++), type: "separator" }); } if (old.elements?.gitBranch) { items.push({ id: String(id++), type: "git-branch", color: old.colors?.gitBranch }); } if (old.layout?.expandingSeparators) { items.forEach((item) => { if (item.type === "separator") { item.type = "flex-separator"; } }); } return { lines: [items], // Put migrated items in first line flexMode: DEFAULT_SETTINGS.flexMode, compactThreshold: DEFAULT_SETTINGS.compactThreshold }; } async function saveSettings(settings) { await mkdir2(CONFIG_DIR, { recursive: true }); await writeFile2(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8"); } // src/utils/claude-settings.ts import * as fs2 from "fs"; import * as path2 from "path"; import * as os2 from "os"; import { promisify as promisify2 } from "util"; var readFile4 = fs2.promises?.readFile || promisify2(fs2.readFile); var writeFile4 = fs2.promises?.writeFile || promisify2(fs2.writeFile); var mkdir4 = fs2.promises?.mkdir || promisify2(fs2.mkdir); var CLAUDE_SETTINGS_PATH = path2.join(os2.homedir(), ".claude", "settings.json"); async function loadClaudeSettings() { try { if (!fs2.existsSync(CLAUDE_SETTINGS_PATH)) { return {}; } const content = await readFile4(CLAUDE_SETTINGS_PATH, "utf-8"); return JSON.parse(content); } catch (error) { if (fs2.existsSync(CLAUDE_SETTINGS_PATH)) { let rawContent = "[Unable to read file content]"; try { rawContent = await readFile4(CLAUDE_SETTINGS_PATH, "utf-8"); } catch { } throw new Error( `Failed to load Claude settings from ~/.claude/settings.json Error: ${error.message} Current file content: ${rawContent} Please fix the file manually or backup it before proceeding.` ); } return {}; } } async function saveClaudeSettings(settings) { const dir = path2.dirname(CLAUDE_SETTINGS_PATH); await mkdir4(dir, { recursive: true }); await writeFile4(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8"); } async function isInstalled() { const settings = await loadClaudeSettings(); if (!settings.statusLine) return false; const command = settings.statusLine.command; const isNpmVersion = command === "npx -y @aicodewith/ccstatusline@latest" || command === "npx -y ccstatusline@latest"; const isLocalVersion = command.includes("ccstatusline.js") || command.includes("ccstatusline-main"); return (isNpmVersion || isLocalVersion) && (settings.statusLine.padding === 0 || settings.statusLine.padding === void 0); } async function installStatusLine() { let settings; try { settings = await loadClaudeSettings(); } catch (error) { throw error; } const isNpxRun = process.argv[1]?.includes("npm-cache") || process.argv[1]?.includes("_npx"); let command; if (isNpxRun) { command = "npx -y @aicodewith/ccstatusline@latest"; } else { const projectRoot = process.cwd(); const distPath = path2.join(projectRoot, "dist", "ccstatusline.js"); if (!fs2.existsSync(distPath)) { throw new Error(`Cannot find dist/ccstatusline.js in ${projectRoot}. Please run 'bun run build' or 'npm run build' first.`); } const absolutePath = path2.resolve(distPath); command = `node "${absolutePath}"`; } settings.statusLine = { type: "command", command, padding: 0 }; try { await saveClaudeSettings(settings); } catch (saveError) { const configToSave = JSON.stringify(settings, null, 2); throw new Error( `Failed to save settings: ${saveError.message} You can manually save this to ~/.claude/settings.json: ---------------------------------------- ${configToSave} ----------------------------------------` ); } } async function uninstallStatusLine() { let settings; try { settings = await loadClaudeSettings(); } catch (error) { throw error; } if (settings.statusLine) { delete settings.statusLine; try { await saveClaudeSettings(settings); } catch (saveError) { const configToSave = JSON.stringify(settings, null, 2); throw new Error( `Failed to save settings: ${saveError.message} You can manually save this to ~/.claude/settings.json: ---------------------------------------- ${configToSave} ----------------------------------------` ); } } } async function getExistingStatusLine() { const settings = await loadClaudeSettings(); return settings.statusLine?.command || null; } // src/tui.tsx import * as fs5 from "fs"; import * as path4 from "path"; import * as os4 from "os"; // src/utils/renderer.ts import chalk from "chalk"; import { execSync } from "child_process"; import * as fs4 from "fs"; import { promisify as promisify3 } from "util"; // src/utils/aicodewith.ts import * as fs3 from "fs"; import * as path3 from "path"; import * as os3 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 path3.join(os3.homedir(), ".config", "ccstatusline", "aicodewith"); } function getApiKey() { if (process.env.CCSTATUSLINE_API_KEY) { return process.env.CCSTATUSLINE_API_KEY; } const configDir = getConfigDir(); const apiConfigFile = path3.join(configDir, "api-config.json"); try { if (fs3.existsSync(apiConfigFile)) { const config = JSON.parse(fs3.readFileSync(apiConfigFile, "utf8")); if (config?.apiKey) { return config.apiKey; } } } catch (error) { } return null; } function fetchFullDataSync() { const configDir = getConfigDir(); const fullCacheFile = path3.join(configDir, "full-data-cache.json"); let staleCache = null; try { if (fs3.existsSync(fullCacheFile)) { const cacheData = JSON.parse(fs3.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 { fs3.mkdirSync(configDir, { recursive: true }); fs3.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 = path3.join(configDir, "full-data-cache.json"); if (fs3.existsSync(fullCacheFile)) { const cacheData = JSON.parse(fs3.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 = path3.join(getConfigDir(), "refresh.lock"); try { const fd = fs3.openSync(lockPath, "wx"); fs3.closeSync(fd); fetchFullDataSync(); lastFullDataFetch = now; try { fs3.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 readFile6 = fs4.promises?.readFile || promisify3(fs4.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; } } 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(""); } e