diff --git a/src/statusline-claude/README.md b/src/statusline-claude/README.md index 365af66..57b41d7 100644 --- a/src/statusline-claude/README.md +++ b/src/statusline-claude/README.md @@ -7,20 +7,20 @@ A persistent three-line status bar for Claude Code that displays model info, pro **Pro / Max / Teams** — rate limits segment: ``` -╭─╮ Opus 4.6 │ plan2code │ feature/statusline │ +12 -3 +╭─╮ Opus 4.6 | High │ plan2code │ feature/statusline │ +12 -3 │★│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ -╰─╯ 3h 5m ($4.62) │ ▰▰▰▰▰▱▱▱▱▱▱▱ 42% │ 5h: 28% · 7d: 61% +╰─╯ 3h 5m ($4.62) │ ▰▰▰▰▰▱▱▱▱▱▱▱ 42% (84k) │ 5h: 28% · 7d: 61% ``` **Enterprise / Bedrock / Vertex / PAYG** — session token counts (no `rate_limits` in stdin): ``` -╭─╮ Sonnet 4.5 │ plan2code │ main │ +12 -3 +╭─╮ Sonnet 4.5 | Medium │ plan2code │ main │ +12 -3 │★│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ -╰─╯ 2m ($0.18) │ ▰▰▱▱▱▱▱▱▱▱▱▱ 18% │ 88k in · 3k out +╰─╯ 2m ($0.18) │ ▰▰▱▱▱▱▱▱▱▱▱▱ 18% (36k) │ 88k in · 3k out ``` -**Line 1:** Planny icon | Model name | Project name | Git branch | Uncommitted changes +**Line 1:** Planny icon | Model name + reasoning effort | Project name | Git branch | Uncommitted changes **Line 2:** Planny icon | Gray separator **Line 3:** Planny icon | Session duration + cost | Context window bar | Usage (rate limits or token counts) @@ -63,6 +63,7 @@ Edit `~/.claude/statusline-config.json`: "compact": false, "items": { "model": true, + "effort": true, "project": true, "branch": true, "contextBar": true, @@ -80,9 +81,11 @@ Edit `~/.claude/statusline-config.json`: | `color` | `true` | Enable ANSI color output. Set `false` for monochrome. The `NO_COLOR` environment variable (per [no-color.org](https://no-color.org)) also disables color. | | `compact` | `false` | Two-line mode — drops the mascot icons and the separator line. Goes from 3 lines to 2, no horizontal truncation. | | `items.model` | `true` | Show model name (Opus, Sonnet, Haiku) | +| `items.effort` | `true` | Append the current reasoning effort level (Low/Medium/High/XHigh/Max) to the model segment, e.g. `Sonnet 5 \| High`. Reads `effort.level` from stdin; hidden when the current model doesn't support an effort parameter (field absent from stdin). | | `items.project` | `true` | Show project directory name | | `items.branch` | `true` | Show current git branch (falls back to short SHA when detached HEAD) | | `items.contextBar` | `true` | Show context window usage bar with percentage | +| `items.contextTokens` | `true` | Append raw tokens used in the context window next to the percentage, e.g. `42% (84k)`. Reads `context_window.total_input_tokens` (the same input-token count `used_percentage` is derived from — excludes output tokens). Requires `items.contextBar` to also be enabled. | | `items.planUsage` | `true` | Show usage info: rate limits (Pro/Max) or token counts (Bedrock/Vertex/PAYG) | | `items.linesChanged` | `true` | Show uncommitted git diff stats (+added -removed) | | `items.duration` | `true` | Show session duration | diff --git a/src/statusline-claude/statusline-config.json b/src/statusline-claude/statusline-config.json index bfef506..14231af 100644 --- a/src/statusline-claude/statusline-config.json +++ b/src/statusline-claude/statusline-config.json @@ -4,9 +4,11 @@ "compact": false, "items": { "model": true, + "effort": true, "project": true, "branch": true, "contextBar": true, + "contextTokens": true, "planUsage": true, "linesChanged": true, "duration": true, diff --git a/src/statusline-claude/statusline.js b/src/statusline-claude/statusline.js index 937daf4..abffff4 100644 --- a/src/statusline-claude/statusline.js +++ b/src/statusline-claude/statusline.js @@ -26,9 +26,11 @@ const DEFAULTS = { compact: false, items: { model: true, + effort: true, project: true, branch: true, contextBar: true, + contextTokens: true, planUsage: true, linesChanged: true, duration: true, @@ -176,6 +178,14 @@ function getModelName(stdinData) { return model; } +const EFFORT_LABELS = { low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh', max: 'Max' }; + +function getEffortLevel(stdinData) { + const level = stdinData?.effort?.level; + if (!level || typeof level !== 'string') return ''; + return EFFORT_LABELS[level] || (level.charAt(0).toUpperCase() + level.slice(1)); +} + function getProjectName(stdinData) { const projectDir = stdinData?.workspace?.project_dir; if (!projectDir || typeof projectDir !== 'string') return ''; @@ -194,7 +204,7 @@ function calculateContextPercent(stdinData, config) { return Math.round(Math.min(100, (rawUsedTokens / usableTokens) * 100)); } -function formatContextBar(percent, config) { +function formatContextBar(percent, tokens, config) { if (percent == null) return null; const clamped = Math.max(0, Math.min(100, percent)); @@ -202,12 +212,15 @@ function formatContextBar(percent, config) { const filledStr = '▰'.repeat(filled); const emptyStr = '▱'.repeat(CONTEXT_BAR_LEN - filled); - if (!config.color) return `${filledStr}${emptyStr} ${clamped}%`; + const tokenStr = config.items.contextTokens && tokens != null ? ` (${formatTokenCount(tokens)})` : ''; + + if (!config.color) return `${filledStr}${emptyStr} ${clamped}%${tokenStr}`; const barColor = clamped >= CONTEXT_THRESHOLDS.yellow ? C.barRed : clamped >= CONTEXT_THRESHOLDS.green ? C.barYellow : C.barGreen; - return `${barColor}${filledStr}${C.gray}${emptyStr}${C.reset} ${barColor}${clamped}%${C.reset}`; + const tokenPart = tokenStr ? `${C.label}${tokenStr}${C.reset}` : ''; + return `${barColor}${filledStr}${C.gray}${emptyStr}${C.reset} ${barColor}${clamped}%${C.reset}${tokenPart}`; } function formatLinesChanged(stdinData, config) { @@ -314,7 +327,16 @@ function formatLine1(stdinData, config) { if (config.items.model) { const model = getModelName(stdinData); - if (model) segments.push(config.color ? `${C.muted}${model}${C.reset}` : model); + if (model) { + const effort = config.items.effort ? getEffortLevel(stdinData) : ''; + if (!effort) { + segments.push(config.color ? `${C.muted}${model}${C.reset}` : model); + } else if (!config.color) { + segments.push(`${model} | ${effort}`); + } else { + segments.push(`${C.muted}${model}${C.reset}${C.gray} | ${C.reset}${C.label}${effort}${C.reset}`); + } + } } if (config.items.project) { @@ -345,7 +367,8 @@ function formatLine2(stdinData, config) { if (config.items.contextBar) { const percent = calculateContextPercent(stdinData, config); - const bar = formatContextBar(percent, config); + const tokens = stdinData?.context_window?.total_input_tokens; + const bar = formatContextBar(percent, tokens, config); if (bar) segments.push(bar); }