* feat(#2161): opt-in absolute token count on the statusline context meter New statusline.show_context_tokens config (default false). When enabled, the context meter shows the absolute token total after the percentage, e.g. "████░░░░░░ 46% (156k)" — summing input, cache-creation, cache-read, and output tokens from context_window.current_usage (matching /context). Default output is byte-for-byte unchanged when the flag is absent or false. The .planning config is now read once per render and shared with the last-command/position block instead of being re-read. Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg * docs(#2161): changeset fragment for PR #2174 * fix(#2161): review fixes — k-to-M threshold, boundary tests, changeset format - formatTokens promotes to the M branch when k-rounding reaches 1000 (999,500-999,999 rendered "1000k" instead of "1.0M") - boundary tests at 999499/999500/999999/1000000/1000001 - Number() guards on the four usage fields (silent string-concat gap) - changeset body ends with the (#2161) citation per house convention Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg * fix(#2161): round-2 review fixes — config-set coverage, precision claim, exports style - config-set accept/reject tests for statusline.show_context_tokens (mirrors the post-planning-gaps precedent the issue scope names) - changeset + docs no longer claim parity with /context: the suffix sums four fields while the meter %% derives from used_percentage (three), so the figures can diverge slightly - module.exports one entry per line Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg * test: regenerate golden-install-parity fixtures for the statusline hook change Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg --------- Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
@@ -286,6 +286,36 @@ function formatGsdState(s) {
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
// --- Context token count (opt-in) ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Format a token count compactly: 156342 → '156k', 1234567 → '1.2M'.
|
||||
*/
|
||||
function formatTokens(tokens) {
|
||||
// Promote to the M branch when k-rounding would reach 1000 (999,500-999,999
|
||||
// must render "1.0M", never "1000k").
|
||||
if (tokens >= 1000000 || Math.round(tokens / 1000) >= 1000) {
|
||||
return (tokens / 1000000).toFixed(1) + 'M';
|
||||
}
|
||||
if (tokens >= 1000) return Math.round(tokens / 1000) + 'k';
|
||||
return String(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure function: build the token-count suffix for the context meter from the
|
||||
* hook input's context_window.current_usage block. Sums input, cache-creation,
|
||||
* cache-read, and output tokens (the same total Claude Code's /context shows).
|
||||
* Returns ' (156k)' or '' when usage is absent/empty.
|
||||
*/
|
||||
function contextTokenSuffix(currentUsage) {
|
||||
if (!currentUsage || typeof currentUsage !== 'object') return '';
|
||||
const total = (Number(currentUsage.input_tokens) || 0) +
|
||||
(Number(currentUsage.cache_creation_input_tokens) || 0) +
|
||||
(Number(currentUsage.cache_read_input_tokens) || 0) +
|
||||
(Number(currentUsage.output_tokens) || 0);
|
||||
return total > 0 ? ` (${formatTokens(total)})` : '';
|
||||
}
|
||||
|
||||
// --- stdin ------------------------------------------------------------------
|
||||
|
||||
function runStatusline() {
|
||||
@@ -304,6 +334,11 @@ function runStatusline() {
|
||||
const session = data.session_id || '';
|
||||
const remaining = data.context_window?.remaining_percentage;
|
||||
|
||||
// Read .planning config once — used by the context meter (token suffix)
|
||||
// and the last-command/position block below. Fail-soft to {}.
|
||||
let cfg = {};
|
||||
try { cfg = readGsdConfig(dir); } catch (e) {}
|
||||
|
||||
// Context window display (shows USED percentage scaled to usable context)
|
||||
// Claude Code reserves a buffer for autocompact. By default this is ~16.5%
|
||||
// of the total window, but users can override it via CLAUDE_CODE_AUTO_COMPACT_WINDOW
|
||||
@@ -349,15 +384,21 @@ function runStatusline() {
|
||||
const filled = Math.floor(used / 10);
|
||||
const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
|
||||
|
||||
// Opt-in absolute token count after the percentage (statusline.show_context_tokens)
|
||||
let tokenSuffix = '';
|
||||
if (getConfigValue(cfg, 'statusline.show_context_tokens') === true) {
|
||||
tokenSuffix = contextTokenSuffix(data.context_window?.current_usage);
|
||||
}
|
||||
|
||||
// Color based on usable context thresholds
|
||||
if (used < 50) {
|
||||
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[32m${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
} else if (used < 65) {
|
||||
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[33m${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
} else if (used < 80) {
|
||||
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[38;5;208m${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
} else {
|
||||
ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[5;31m💀 ${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +463,6 @@ function runStatusline() {
|
||||
let lastCmdSuffix = '';
|
||||
let position = 'end';
|
||||
try {
|
||||
const cfg = readGsdConfig(dir);
|
||||
if (getConfigValue(cfg, 'statusline.show_last_command') === true) {
|
||||
const transcriptPath = data.transcript_path;
|
||||
const lastCmd = readLastSlashCommand(transcriptPath);
|
||||
@@ -531,6 +571,8 @@ module.exports = {
|
||||
composeStatusline,
|
||||
isInstalledAheadOfLatest,
|
||||
evaluateUpdateCache,
|
||||
formatTokens,
|
||||
contextTokenSuffix,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user