UNPKG

@aicodewith/ccstatusline

Version:

AiCodeWith credits display plugin for Claude Code with customizable status line

1,371 lines (1,352 loc) 60.1 kB
import { createRequire } from 'module'; const require = createRequire(import.meta.url); // node_modules/chalk/source/vendor/ansi-styles/index.js var ANSI_BACKGROUND_OFFSET = 10; var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`; var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`; var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`; var styles = { modifier: { reset: [0, 0], // 21 isn't widely supported and 22 does the same thing bold: [1, 22], dim: [2, 22], italic: [3, 23], underline: [4, 24], overline: [53, 55], inverse: [7, 27], hidden: [8, 28], strikethrough: [9, 29] }, color: { black: [30, 39], red: [31, 39], green: [32, 39], yellow: [33, 39], blue: [34, 39], magenta: [35, 39], cyan: [36, 39], white: [37, 39], // Bright color blackBright: [90, 39], gray: [90, 39], // Alias of `blackBright` grey: [90, 39], // Alias of `blackBright` redBright: [91, 39], greenBright: [92, 39], yellowBright: [93, 39], blueBright: [94, 39], magentaBright: [95, 39], cyanBright: [96, 39], whiteBright: [97, 39] }, bgColor: { bgBlack: [40, 49], bgRed: [41, 49], bgGreen: [42, 49], bgYellow: [43, 49], bgBlue: [44, 49], bgMagenta: [45, 49], bgCyan: [46, 49], bgWhite: [47, 49], // Bright color bgBlackBright: [100, 49], bgGray: [100, 49], // Alias of `bgBlackBright` bgGrey: [100, 49], // Alias of `bgBlackBright` bgRedBright: [101, 49], bgGreenBright: [102, 49], bgYellowBright: [103, 49], bgBlueBright: [104, 49], bgMagentaBright: [105, 49], bgCyanBright: [106, 49], bgWhiteBright: [107, 49] } }; var modifierNames = Object.keys(styles.modifier); var foregroundColorNames = Object.keys(styles.color); var backgroundColorNames = Object.keys(styles.bgColor); var colorNames = [...foregroundColorNames, ...backgroundColorNames]; function assembleStyles() { const codes = /* @__PURE__ */ new Map(); for (const [groupName, group] of Object.entries(styles)) { for (const [styleName, style] of Object.entries(group)) { styles[styleName] = { open: `\x1B[${style[0]}m`, close: `\x1B[${style[1]}m` }; group[styleName] = styles[styleName]; codes.set(style[0], style[1]); } Object.defineProperty(styles, groupName, { value: group, enumerable: false }); } Object.defineProperty(styles, "codes", { value: codes, enumerable: false }); styles.color.close = "\x1B[39m"; styles.bgColor.close = "\x1B[49m"; styles.color.ansi = wrapAnsi16(); styles.color.ansi256 = wrapAnsi256(); styles.color.ansi16m = wrapAnsi16m(); styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET); styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET); styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET); Object.defineProperties(styles, { rgbToAnsi256: { value(red, green, blue) { if (red === green && green === blue) { if (red < 8) { return 16; } if (red > 248) { return 231; } return Math.round((red - 8) / 247 * 24) + 232; } return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5); }, enumerable: false }, hexToRgb: { value(hex) { const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16)); if (!matches) { return [0, 0, 0]; } let [colorString] = matches; if (colorString.length === 3) { colorString = [...colorString].map((character) => character + character).join(""); } const integer = Number.parseInt(colorString, 16); return [ /* eslint-disable no-bitwise */ integer >> 16 & 255, integer >> 8 & 255, integer & 255 /* eslint-enable no-bitwise */ ]; }, enumerable: false }, hexToAnsi256: { value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)), enumerable: false }, ansi256ToAnsi: { value(code) { if (code < 8) { return 30 + code; } if (code < 16) { return 90 + (code - 8); } let red; let green; let blue; if (code >= 232) { red = ((code - 232) * 10 + 8) / 255; green = red; blue = red; } else { code -= 16; const remainder = code % 36; red = Math.floor(code / 36) / 5; green = Math.floor(remainder / 6) / 5; blue = remainder % 6 / 5; } const value = Math.max(red, green, blue) * 2; if (value === 0) { return 30; } let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red)); if (value === 2) { result += 60; } return result; }, enumerable: false }, rgbToAnsi: { value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)), enumerable: false }, hexToAnsi: { value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)), enumerable: false } }); return styles; } var ansiStyles = assembleStyles(); var ansi_styles_default = ansiStyles; // node_modules/chalk/source/vendor/supports-color/index.js import process2 from "node:process"; import os from "node:os"; import tty from "node:tty"; function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) { const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--"; const position = argv.indexOf(prefix + flag); const terminatorPosition = argv.indexOf("--"); return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); } var { env } = process2; var flagForceColor; if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) { flagForceColor = 0; } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) { flagForceColor = 1; } function envForceColor() { if ("FORCE_COLOR" in env) { if (env.FORCE_COLOR === "true") { return 1; } if (env.FORCE_COLOR === "false") { return 0; } return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); } } function translateLevel(level) { if (level === 0) { return false; } return { level, hasBasic: true, has256: level >= 2, has16m: level >= 3 }; } function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) { const noFlagForceColor = envForceColor(); if (noFlagForceColor !== void 0) { flagForceColor = noFlagForceColor; } const forceColor = sniffFlags ? flagForceColor : noFlagForceColor; if (forceColor === 0) { return 0; } if (sniffFlags) { if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) { return 3; } if (hasFlag("color=256")) { return 2; } } if ("TF_BUILD" in env && "AGENT_NAME" in env) { return 1; } if (haveStream && !streamIsTTY && forceColor === void 0) { return 0; } const min = forceColor || 0; if (env.TERM === "dumb") { return min; } if (process2.platform === "win32") { const osRelease = os.release().split("."); if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) { return Number(osRelease[2]) >= 14931 ? 3 : 2; } return 1; } if ("CI" in env) { if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) { return 3; } if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") { return 1; } return min; } if ("TEAMCITY_VERSION" in env) { return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; } if (env.COLORTERM === "truecolor") { return 3; } if (env.TERM === "xterm-kitty") { return 3; } if (env.TERM === "xterm-ghostty") { return 3; } if (env.TERM === "wezterm") { return 3; } if ("TERM_PROGRAM" in env) { const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10); switch (env.TERM_PROGRAM) { case "iTerm.app": { return version >= 3 ? 3 : 2; } case "Apple_Terminal": { return 2; } } } if (/-256(color)?$/i.test(env.TERM)) { return 2; } if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { return 1; } if ("COLORTERM" in env) { return 1; } return min; } function createSupportsColor(stream, options = {}) { const level = _supportsColor(stream, { streamIsTTY: stream && stream.isTTY, ...options }); return translateLevel(level); } var supportsColor = { stdout: createSupportsColor({ isTTY: tty.isatty(1) }), stderr: createSupportsColor({ isTTY: tty.isatty(2) }) }; var supports_color_default = supportsColor; // node_modules/chalk/source/utilities.js function stringReplaceAll(string, substring, replacer) { let index = string.indexOf(substring); if (index === -1) { return string; } const substringLength = substring.length; let endIndex = 0; let returnValue = ""; do { returnValue += string.slice(endIndex, index) + substring + replacer; endIndex = index + substringLength; index = string.indexOf(substring, endIndex); } while (index !== -1); returnValue += string.slice(endIndex); return returnValue; } function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) { let endIndex = 0; let returnValue = ""; do { const gotCR = string[index - 1] === "\r"; returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix; endIndex = index + 1; index = string.indexOf("\n", endIndex); } while (index !== -1); returnValue += string.slice(endIndex); return returnValue; } // node_modules/chalk/source/index.js var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default; var GENERATOR = Symbol("GENERATOR"); var STYLER = Symbol("STYLER"); var IS_EMPTY = Symbol("IS_EMPTY"); var levelMapping = [ "ansi", "ansi", "ansi256", "ansi16m" ]; var styles2 = /* @__PURE__ */ Object.create(null); var applyOptions = (object, options = {}) => { if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) { throw new Error("The `level` option should be an integer from 0 to 3"); } const colorLevel = stdoutColor ? stdoutColor.level : 0; object.level = options.level === void 0 ? colorLevel : options.level; }; var chalkFactory = (options) => { const chalk2 = (...strings) => strings.join(" "); applyOptions(chalk2, options); Object.setPrototypeOf(chalk2, createChalk.prototype); return chalk2; }; function createChalk(options) { return chalkFactory(options); } Object.setPrototypeOf(createChalk.prototype, Function.prototype); for (const [styleName, style] of Object.entries(ansi_styles_default)) { styles2[styleName] = { get() { const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]); Object.defineProperty(this, styleName, { value: builder }); return builder; } }; } styles2.visible = { get() { const builder = createBuilder(this, this[STYLER], true); Object.defineProperty(this, "visible", { value: builder }); return builder; } }; var getModelAnsi = (model, level, type, ...arguments_) => { if (model === "rgb") { if (level === "ansi16m") { return ansi_styles_default[type].ansi16m(...arguments_); } if (level === "ansi256") { return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_)); } return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_)); } if (model === "hex") { return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_)); } return ansi_styles_default[type][model](...arguments_); }; var usedModels = ["rgb", "hex", "ansi256"]; for (const model of usedModels) { styles2[model] = { get() { const { level } = this; return function(...arguments_) { const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]); return createBuilder(this, styler, this[IS_EMPTY]); }; } }; const bgModel = "bg" + model[0].toUpperCase() + model.slice(1); styles2[bgModel] = { get() { const { level } = this; return function(...arguments_) { const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]); return createBuilder(this, styler, this[IS_EMPTY]); }; } }; } var proto = Object.defineProperties(() => { }, { ...styles2, level: { enumerable: true, get() { return this[GENERATOR].level; }, set(level) { this[GENERATOR].level = level; } } }); var createStyler = (open, close, parent) => { let openAll; let closeAll; if (parent === void 0) { openAll = open; closeAll = close; } else { openAll = parent.openAll + open; closeAll = close + parent.closeAll; } return { open, close, openAll, closeAll, parent }; }; var createBuilder = (self, _styler, _isEmpty) => { const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" ")); Object.setPrototypeOf(builder, proto); builder[GENERATOR] = self; builder[STYLER] = _styler; builder[IS_EMPTY] = _isEmpty; return builder; }; var applyStyle = (self, string) => { if (self.level <= 0 || !string) { return self[IS_EMPTY] ? "" : string; } let styler = self[STYLER]; if (styler === void 0) { return string; } const { openAll, closeAll } = styler; if (string.includes("\x1B")) { while (styler !== void 0) { string = stringReplaceAll(string, styler.close, styler.open); styler = styler.parent; } } const lfIndex = string.indexOf("\n"); if (lfIndex !== -1) { string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); } return openAll + string + closeAll; }; Object.defineProperties(createChalk.prototype, styles2); var chalk = createChalk(); var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 }); var source_default = chalk; // src/utils/renderer.ts 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 os2 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(os2.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); source_default.level = 3; function applyColors(text, foregroundColor, backgroundColor, bold) { let result = text; if (foregroundColor && foregroundColor !== "dim") { const fgFunc = source_default[foregroundColor]; if (fgFunc) { result = fgFunc(result); } } if (backgroundColor && backgroundColor !== "none") { const bgFunc = source_default[backgroundColor]; if (bgFunc) { result = bgFunc(result); } } if (bold) { result = source_default.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 tty2 = execSync("ps -o tty= -p $(ps -o ppid= -p $$)", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], shell: "/bin/sh" }).trim(); if (tty2 && tty2 !== "??" && tty2 !== "?") { const width = execSync( `stty size < /dev/${tty2} | 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"