@aicodewith/ccstatusline
Version:
AiCodeWith credits display plugin for Claude Code with customizable status line
1,371 lines (1,352 loc) • 60.1 kB
JavaScript
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"