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