dotfiles/claude/statusline.isaacaudet.sh

850 lines
38 KiB
Shell
Raw Normal View History

#!/bin/bash
# Claude Code Status Line
# Based on https://github.com/daniel3303/ClaudeCodeStatusLine
# Enhanced: git branch/ahead-behind, token bar, caching, improved visuals
set -f # disable globbing
# ===== Config =====
SHOW_GIT=true # git branch, dirty status, ahead/behind
SHOW_TOKENS=true # token usage bar
SHOW_THINKING=true # extended thinking indicator
SHOW_RATE_LIMITS=true # 5h / 7d rate limit bars
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
SHOW_MODEL_LIMIT=true # per-model weekly limit bar (e.g. Fable), when the API reports one
WRAP_NARROW=true # wrap onto a second line rather than dropping segments or being clipped
WRAP_MIN_WIDTH=100 # below this, keep wide-tier line-one content (it can wrap)
BRANCH_MAX_LEN=28 # truncate branch names longer than this
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
CWD_MAX_LEN=20 # truncate the cwd basename longer than this
GIT_CACHE_SECS=10 # seconds to cache git status (git diff is slow on large repos)
USAGE_CACHE_SECS=300 # seconds to cache the usage API response (the 5h/7d bars)
USAGE_RETRY_SECS=60 # seconds to wait before retrying a failed usage API fetch
TOKEN_BAR_WIDTH=8 # width of token progress bar
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
SL_WORK_DAYS="${SL_WORK_DAYS:-12345}" # ISO weekdays you work: Mon=1 ... Sun=7
# Where the git and usage-API caches live. Overridable via the environment so a
# test run can render into its own directory: ~/.claude/statusline.sh is a
# symlink to this script, so a fixture written to the live cache is picked up by
# the next redraw and shown as real usage until USAGE_CACHE_SECS is up.
CACHE_DIR="${STATUSLINE_CACHE_DIR:-/tmp/claude}"
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
# "Now", overridable so a test can pin the day of the week: the pace figure
# counts the working days left before the reset, so it moves with today's
# weekday and would otherwise be unassertable. Only the pace code reads it --
# the cache-age checks below stay on the real clock deliberately, so pinning a
# time cannot make a fresh fixture look stale and send a test to the network.
SL_NOW="${SL_NOW:-}"
now_ts() { [ -n "$SL_NOW" ] && printf '%s' "$SL_NOW" || date +%s; }
# Terminal width detection.
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Claude Code exports COLUMNS for this subprocess. There is no controlling
# terminal, so the stty fallback fails; when both fail we default to 80
# (safe/compact) rather than wide.
#
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# The `padding` setting indents the status line on both sides, and Claude Code
# applies it on top of COLUMNS rather than deducting it first — measured: with
# COLUMNS=121 and padding 2, a 121-column line is clipped with an ellipsis. So
# deduct it here, plus a column of margin.
#
# To override detection entirely, set TERM_WIDTH in settings.json:
# "command": "TERM_WIDTH=160 ~/.claude/statusline.sh"
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Only the user-level file: Claude Code also merges project .claude/settings.json,
# .claude/settings.local.json and managed policy, so a statusLine.padding set at
# project level would be applied by the renderer but missed here.
sl_padding=0
if [ -f "$HOME/.claude/settings.json" ]; then
sl_padding=$(jq -r '.statusLine.padding // 0' "$HOME/.claude/settings.json" 2>/dev/null)
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
[ "${sl_padding:-0}" -ge 0 ] 2>/dev/null || sl_padding=0
fi
if [ "${TERM_WIDTH:-0}" -le 0 ] 2>/dev/null; then
if [ "${COLUMNS:-0}" -gt 0 ] 2>/dev/null; then
TERM_WIDTH=$COLUMNS
else
_w=$(stty size </dev/tty 2>/dev/null | awk '{print $2}')
[ "${_w:-0}" -gt 0 ] 2>/dev/null && TERM_WIDTH=$_w || TERM_WIDTH=80
unset _w
fi
fi
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Usable width: minus padding on both sides, minus a column of margin.
USABLE_WIDTH=$(( TERM_WIDTH - 2 * sl_padding - 1 ))
[ "$USABLE_WIDTH" -lt 20 ] && USABLE_WIDTH=20
# ${#str} counts characters only under a UTF-8 locale; under LC_ALL=C it counts
# bytes, which would mis-measure the box/block glyphs and break line wrapping.
case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in
*UTF-8*|*utf8*|*UTF8*) ;;
*)
# C.UTF-8 on glibc, en_US.UTF-8 on macOS (which has no C.UTF-8).
# Picking one that does not exist makes bash warn on every render and
# silently fall back to byte counting, so verify before committing.
for _loc in C.UTF-8 en_US.UTF-8; do
if LC_ALL="$_loc" locale charmap 2>/dev/null | grep -qi utf; then
export LC_ALL="$_loc"; break
fi
done
unset _loc
;;
esac
input=$(cat)
[ -z "$input" ] && printf "Claude" && exit 0
mkdir -p "$CACHE_DIR"
# ===== Colors =====
# Palette indices rather than RGB, so these resolve through the terminal's own
# palette and follow the ghostty tangere-light/tangere-dark swap for free. No
# appearance detection, so no cost per statusline render.
#
# The previous values were One Dark RGB: fine on dark (4.3-10.8:1) but 1.35-3.4:1
# on a light background, with 'white' effectively invisible. These clear WCAG AA
# in both modes (5.8-14.3:1 on dark, 6.3-20.6:1 on light).
blue='\033[38;5;4m'
orange='\033[38;5;3m'
amber='\033[38;5;11m'
green='\033[38;5;2m'
cyan='\033[38;5;14m'
red='\033[38;5;1m'
yellow='\033[38;5;3m'
white='\033[39m'
magenta='\033[38;5;5m'
dim='\033[2m'
reset='\033[0m'
sep=" ${dim}${reset} "
# ===== Helpers =====
format_tokens() {
local num=$1
if [ "$num" -ge 1000000 ]; then
awk "BEGIN {printf \"%.1fm\", $num / 1000000}"
elif [ "$num" -ge 1000 ]; then
awk "BEGIN {printf \"%.0fk\", $num / 1000}"
else
printf "%d" "$num"
fi
}
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Display width of a string, ignoring ANSI colour escapes. Every glyph the
# statusline uses is single-width, so a character count is the display width.
#
# The colour vars hold the escapes as literal backslash-033 text (they are
# single-quoted, and printf %b only interprets them when the line is finally
# emitted), so strip that literal form as well as a real ESC byte.
vis_len() {
local plain
plain=$(printf '%s' "$1" | sed -e 's/\\033\[[0-9;]*m//g' -e $'s/\033\[[0-9;]*m//g' -e 's/\\\\/\\/g')
printf '%d' "${#plain}"
}
# printf %b interprets backslash escapes -- that is how the colour variables
# above are applied. Data from the API or the filesystem must NOT be
# interpreted: a directory or model name containing a literal \n would split
# the line, breaking both the width measurement and the two-line guarantee.
# Double the backslashes so %b renders them literally.
esc_data() {
local v="$1"
# Real control characters (jq decodes \n in the JSON payload into an actual
# newline) would split the line regardless of escaping, so drop them first.
v="${v//$'\n'/ }"; v="${v//$'\r'/ }"; v="${v//$'\t'/ }"; v="${v//$'\033'/}"
printf '%s' "${v//\\/\\\\}"
}
truncate_str() {
local str="$1" max="$2"
[ "${#str}" -gt "$max" ] \
&& printf "%s…" "${str:0:$((max-1))}" \
|| printf "%s" "$str"
}
# Colored progress bar using block chars
build_bar() {
local pct=$1 width=$2
[ "$pct" -lt 0 ] 2>/dev/null && pct=0
[ "$pct" -gt 100 ] 2>/dev/null && pct=100
local filled=$(( pct * width / 100 ))
local empty=$(( width - filled ))
local bar_color
if [ "$pct" -ge 90 ]; then bar_color="$red"
elif [ "$pct" -ge 70 ]; then bar_color="$yellow"
elif [ "$pct" -ge 50 ]; then bar_color="$orange"
else bar_color="$green"
fi
local f="" e=""
for ((i=0; i<filled; i++)); do f+="█"; done
for ((i=0; i<empty; i++)); do e+="░"; done
printf "${bar_color}${f}${dim}${e}${reset}"
}
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
# Colour for the sustainable %/day, measured against the window's OWN even-burn
# baseline (100 / workdays in the window) rather than a fixed number of points
# per day: on a five-day week healthy is 20%/day, so a threshold tuned to the
# 14.3%/day calendar baseline would still call 12%/day green -- by then two
# fifths of the budget has been overspent. The ratios reproduce the 12/8/5
# thresholds at a 14.3 baseline, whatever SL_WORK_DAYS is set to.
#
# A high pace means plenty of runway per working day, so it reads cool; a low
# one means the rest of the week has to be rationed. Both arguments are in
# tenths, which keeps the comparison integer.
pacecol() {
local p=$1 base=$2
if [ "$p" -ge $(( base * 84 / 100 )) ]; then printf '%s' "$green"
elif [ "$p" -ge $(( base * 56 / 100 )) ]; then printf '%s' "$yellow"
elif [ "$p" -ge $(( base * 35 / 100 )) ]; then printf '%s' "$orange"
else printf '%s' "$red"
fi
}
# ===== Git info with per-directory caching =====
get_git_info() {
local dir="$1"
[ -z "$dir" ] && return
# Stable cache key per directory path
local dir_hash
dir_hash=$(printf '%s' "$dir" | cksum | awk '{print $1}')
local cache_file="$CACHE_DIR/git-${dir_hash}"
local needs_refresh=true
if [ -f "$cache_file" ]; then
local mtime now age
mtime=$(stat -f %m "$cache_file" 2>/dev/null || stat -c %Y "$cache_file" 2>/dev/null)
now=$(date +%s)
age=$(( now - mtime ))
[ "$age" -lt "$GIT_CACHE_SECS" ] && needs_refresh=false
fi
if $needs_refresh; then
# Branch name (or short hash for detached HEAD)
local branch
branch=$(git -C "$dir" symbolic-ref --short HEAD 2>/dev/null)
if [ -z "$branch" ]; then
branch=$(git -C "$dir" rev-parse --short HEAD 2>/dev/null)
if [ -z "$branch" ]; then
: > "$cache_file" # not a git repo — write empty cache
return
fi
branch="(${branch})"
fi
# Dirty: any staged or unstaged changes
local dirty=""
[ -n "$(git -C "$dir" status --porcelain 2>/dev/null)" ] && dirty="dirty"
# Ahead / behind upstream (skip if no upstream set)
local ahead=0 behind=0
local upstream
upstream=$(git -C "$dir" rev-parse --abbrev-ref "@{upstream}" 2>/dev/null)
if [ -n "$upstream" ]; then
local ab
ab=$(git -C "$dir" rev-list --left-right --count "HEAD...${upstream}" 2>/dev/null)
ahead=$(echo "$ab" | awk '{print $1}')
behind=$(echo "$ab" | awk '{print $2}')
fi
# Tab-delimited cache (branch names can't contain tabs per git spec)
printf '%s\t%s\t%s\t%s' "$branch" "$dirty" "${ahead:-0}" "${behind:-0}" > "$cache_file"
fi
cat "$cache_file" 2>/dev/null
}
# ===== OAuth token resolution =====
get_oauth_token() {
[ -n "$CLAUDE_CODE_OAUTH_TOKEN" ] && echo "$CLAUDE_CODE_OAUTH_TOKEN" && return 0
if command -v security >/dev/null 2>&1; then
local blob token
blob=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null)
if [ -n "$blob" ]; then
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
[ -n "$token" ] && [ "$token" != "null" ] && echo "$token" && return 0
fi
fi
local creds_file="${HOME}/.claude/.credentials.json"
if [ -f "$creds_file" ]; then
local token
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null)
[ -n "$token" ] && [ "$token" != "null" ] && echo "$token" && return 0
fi
if command -v secret-tool >/dev/null 2>&1; then
local blob token
blob=$(timeout 2 secret-tool lookup service "Claude Code-credentials" 2>/dev/null)
if [ -n "$blob" ]; then
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
[ -n "$token" ] && [ "$token" != "null" ] && echo "$token" && return 0
fi
fi
echo ""
}
# ===== ISO 8601 → epoch (cross-platform) =====
iso_to_epoch() {
local iso_str="$1"
local epoch
epoch=$(date -d "${iso_str}" +%s 2>/dev/null)
[ -n "$epoch" ] && echo "$epoch" && return 0
local stripped="${iso_str%%.*}"
stripped="${stripped%%Z}"
stripped="${stripped%%+*}"
stripped="${stripped%%-[0-9][0-9]:[0-9][0-9]}"
if [[ "$iso_str" == *"Z"* ]] || [[ "$iso_str" == *"+00:00"* ]] || [[ "$iso_str" == *"-00:00"* ]]; then
epoch=$(env TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
else
epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
fi
[ -n "$epoch" ] && echo "$epoch" && return 0
return 1
}
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
# Local midnight of the day an epoch falls in, plus that day's ISO weekday
# (1 = Monday), as "<epoch> <weekday>". BSD date first -- it is the hot path
# here -- then GNU date, which has no -v.
day_start_dow() {
date -j -r "$1" -v0H -v0M -v0S +'%s %u' 2>/dev/null && return 0
local d
d=$(date -d "@$1" +%F 2>/dev/null) || return 1
date -d "$d 00:00:00" +'%s %u' 2>/dev/null
}
format_reset_time() {
local iso_str="$1" style="$2"
[ -z "$iso_str" ] || [ "$iso_str" = "null" ] && return
local epoch
epoch=$(iso_to_epoch "$iso_str")
[ -z "$epoch" ] && return
case "$style" in
time)
date -j -r "$epoch" +"%l:%M%p" 2>/dev/null | sed 's/^ //' | tr '[:upper:]' '[:lower:]' ||
date -d "@$epoch" +"%l:%M%P" 2>/dev/null | sed 's/^ //'
;;
datetime)
date -j -r "$epoch" +"%b %-d, %l:%M%p" 2>/dev/null | sed 's/ / /g; s/^ //' | tr '[:upper:]' '[:lower:]' ||
date -d "@$epoch" +"%b %-d, %l:%M%P" 2>/dev/null | sed 's/ / /g; s/^ //'
;;
esac
}
# ===== Extract JSON =====
model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"')
cwd=$(echo "$input" | jq -r '.cwd // empty')
cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // empty')
# Formatted here rather than where it is rendered: the wrap-mode branch budget
# needs its width, and it is not fixed ("$4.61" vs "$1234.56").
cost_fmt=""
[ -n "$cost_usd" ] && cost_fmt=$(printf '%.2f' "$cost_usd" 2>/dev/null)
size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
[ "$size" -eq 0 ] 2>/dev/null && size=200000
input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
current=$(( input_tokens + cache_create + cache_read ))
used_tokens=$(format_tokens $current)
total_tokens=$(format_tokens $size)
pct_used=$(( size > 0 ? current * 100 / size : 0 ))
# Thinking and effort come from the payload, not from alwaysThinkingEnabled in
# settings.json: Option+T toggles thinking for the session only and never
# writes the file, so a settings read reports the wrong state for the rest of
# the session. Absent means enabled, mirroring Claude Code's own
# `thinking:{enabled: lt !== false}`.
# Both in one jq pass: the status line re-renders on every redraw, so a
# process per field is the dominant cost (see the rate-limit section below).
# `== false` rather than `// false`, which jq's falsy `//` would flip to true.
{ IFS= read -r sl_thinking; IFS= read -r effort; } <<EOF
$(echo "$input" | jq -r '(if .thinking.enabled == false then "false" else "true" end), (.effort.level // "")')
EOF
thinking_on=true
[ "$sl_thinking" = "false" ] && thinking_on=false
# The effort level replaces the static "thinking" label. Whitelisted rather
# than printed as-is: an unrecognised value would escape the width budget that
# the wrap branch computes from this string.
case "$effort" in
low|medium|high|xhigh|max) thinking_label="$effort" ;;
*) thinking_label="thinking" ;;
esac
# ===== Adaptive width tiers =====
#
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Tiers now choose only how much of LINE ONE to show. What the rate-limit
# group contains, and whether it wraps, is decided by measurement at the end of
# the script, not here -- tiers cannot see how long the branch, cwd or model
# name is, which is how content used to end up clipped.
#
# full (≥150): CWD, ahead/behind, "◆ high" effort, cost
# wide (100149): ahead/behind, "◆ high" effort, cost
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# split (7699): short model, ahead/behind, "◆" symbol
# (reachable only with WRAP_NARROW=false; otherwise the wrap
# band below overrides this range to wide)
# narrow (<76): short model + branch + token only; no rate limits at all
#
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
if [ "$USABLE_WIDTH" -ge 150 ] 2>/dev/null; then width_tier="full"
elif [ "$USABLE_WIDTH" -ge 100 ] 2>/dev/null; then width_tier="wide"
elif [ "$USABLE_WIDTH" -ge 76 ] 2>/dev/null; then width_tier="split"
else width_tier="narrow"
fi
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Two-line mode. Between WRAP_FLOOR and WRAP_MIN_WIDTH there isn't room for
# everything on one line, but there IS room across two — so instead of dropping
# the rate-limit group we break before it and render the wide-tier content.
# Below WRAP_FLOOR the narrow tier applies instead, which drops the rate-limit
# group entirely -- so there is nothing to wrap and no second line to put it on.
WRAP_FLOOR=68
wrap_mode=false
if $WRAP_NARROW \
&& [ "$USABLE_WIDTH" -lt "$WRAP_MIN_WIDTH" ] 2>/dev/null \
&& [ "$USABLE_WIDTH" -ge "$WRAP_FLOOR" ] 2>/dev/null; then
wrap_mode=true
width_tier="wide"
fi
# Shorten model name for tight spaces
short_model() {
case "$1" in
*Opus*) echo "Opus" ;;
*Sonnet*) echo "Sonnet" ;;
*Haiku*) echo "Haiku" ;;
*) echo "$1" | awk '{print $NF}' ;; # last word
esac
}
# ===== Build output =====
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Explicitly empty: bash imports same-named environment variables, and a
# stray $rl_lean would otherwise leak into the rendered line.
out=""
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
rl_bare=""
rl_lean=""
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
rl_pace=""
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
rl_mid=""
rl_rich=""
# Model — color by family
model_color="$blue"
case "$model_name" in
*Opus*) model_color="$amber" ;;
*Haiku*) model_color="$cyan" ;;
esac
display_model="$model_name"
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# split/narrow: shorten so the 5h + 7d bars still fit on one line.
# wrap mode too: it renders wide-tier content at a split-tier width, and a long
# display name ("Opus 5 (1M context)" is 19 cols) would overflow line one.
if [ "$width_tier" = "split" -o "$width_tier" = "narrow" ] || $wrap_mode; then
display_model=$(short_model "$model_name")
fi
out+="${model_color}$(esc_data "$display_model")${reset}"
# CWD — full tier only (branch name gives enough context below that)
if [ "$width_tier" = "full" ] && [ -n "$cwd" ]; then
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Capped like the branch name: nothing else shortens line one, so an
# unusually long project directory would push it past the terminal edge.
display_dir=$(truncate_str "${cwd##*/}" "$CWD_MAX_LEN")
out+="${sep}${dim}$(esc_data "$display_dir")${reset}"
fi
bar_w="$TOKEN_BAR_WIDTH"
[ "$width_tier" = "wide" ] && bar_w=6
[ "$width_tier" = "split" ] && bar_w=5
[ "$width_tier" = "narrow" ] && bar_w=4
# Git branch + dirty + ahead/behind
if $SHOW_GIT && [ -n "$cwd" ]; then
git_info=$(get_git_info "$cwd")
if [ -n "$git_info" ]; then
IFS=$'\t' read -r g_branch g_dirty g_ahead g_behind <<< "$git_info"
# Progressively tighten branch truncation
local_max="$BRANCH_MAX_LEN"
[ "$width_tier" = "wide" ] && local_max=24
[ "$width_tier" = "split" ] && local_max=18
[ "$width_tier" = "narrow" ] && local_max=12
# In wrap mode line one is
# model │ ⎇ branch ✔ ↑1 │ <bar> used/total pct% │ ◇ high │ $cost
# and the branch gets whatever the rest of it leaves. Everything after
# the branch is appended below, so its width is added up here rather
# than assumed: a flat 56 columns was three short of a four-digit cost,
# which pushed line one past the edge to be clipped by the renderer.
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
if $wrap_mode; then
tail_len=$(( 5 + 2 )) # "│ ⎇ " and the dirty mark
[ "${g_ahead:-0}" -gt 0 ] && tail_len=$(( tail_len + 2 + ${#g_ahead} ))
[ "${g_behind:-0}" -gt 0 ] && tail_len=$(( tail_len + 2 + ${#g_behind} ))
$SHOW_TOKENS && tail_len=$(( tail_len + 3 + bar_w + 1 \
+ ${#used_tokens} + 1 + ${#total_tokens} + 1 + ${#pct_used} + 1 ))
$SHOW_THINKING && tail_len=$(( tail_len + 3 + 2 + ${#thinking_label} ))
[ -n "$cost_fmt" ] && tail_len=$(( tail_len + 3 + 1 + ${#cost_fmt} ))
local_max=$(( USABLE_WIDTH - $(vis_len "$out") - tail_len ))
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
[ "$local_max" -lt 8 ] && local_max=8
[ "$local_max" -gt 24 ] && local_max=24
fi
g_branch_display=$(truncate_str "$g_branch" "$local_max")
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
out+="${sep}${dim}${reset} ${magenta}$(esc_data "$g_branch_display")${reset}"
if [ "$g_dirty" = "dirty" ]; then
out+=" ${red}${reset}"
else
out+=" ${green}${reset}"
fi
# Ahead/behind: shown in all tiers except narrow (only if non-zero)
if [ "$width_tier" != "narrow" ]; then
[ "${g_ahead:-0}" -gt 0 ] && out+=" ${green}${g_ahead}${reset}"
[ "${g_behind:-0}" -gt 0 ] && out+=" ${orange}${g_behind}${reset}"
fi
fi
fi
# Token bar
if $SHOW_TOKENS; then
token_bar=$(build_bar "$pct_used" "$bar_w")
out+="${sep}${token_bar} ${orange}${used_tokens}${dim}/${reset}${white}${total_tokens}${reset} ${dim}${pct_used}%${reset}"
fi
# Thinking and effort. The diamond is thinking state, the label is the effort
# level ("thinking" only when the payload reports no level):
# full/wide → "◆ high" / "◇ high" (label)
# split → "◆" / "◇" (symbol only, saves the label's columns)
# narrow → hidden
if $SHOW_THINKING && [ "$width_tier" != "narrow" ]; then
out+="${sep}"
if $thinking_on; then
if [ "$width_tier" = "split" ]; then out+="${amber}${reset}"
else out+="${amber}${thinking_label}${reset}"; fi
else
if [ "$width_tier" = "split" ]; then out+="${dim}${reset}"
else out+="${dim}${thinking_label}${reset}"; fi
fi
fi
# Session cost — wide/full only
if [ -n "$cost_fmt" ] && [ "$width_tier" = "wide" -o "$width_tier" = "full" ]; then
out+="${sep}${dim}\$${cost_fmt}${reset}"
fi
# ===== Rate limits (API, cached USAGE_CACHE_SECS) =====
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Built at every tier except narrow. Which variant is actually emitted,
# and on how many lines, is decided by the measured ladder at the end.
if $SHOW_RATE_LIMITS && [ "$width_tier" != "narrow" ]; then
api_cache="$CACHE_DIR/statusline-usage-cache.json"
fail_marker="$CACHE_DIR/statusline-usage-fail"
needs_refresh=true
usage_data=""
if [ -f "$api_cache" ]; then
cache_mtime=$(stat -c %Y "$api_cache" 2>/dev/null || stat -f %m "$api_cache" 2>/dev/null)
now=$(date +%s)
cache_age=$(( now - cache_mtime ))
if [ "$cache_age" -lt "$USAGE_CACHE_SECS" ]; then
cached_content=$(cat "$api_cache" 2>/dev/null)
# Only use cache if it's valid data (not an error response)
if [ -n "$cached_content" ] && ! echo "$cached_content" | jq -e '.error' >/dev/null 2>&1; then
needs_refresh=false
usage_data="$cached_content"
fi
fi
fi
if $needs_refresh; then
# Hold off after a failed fetch. With no network curl can burn its
# --max-time before giving up, and a redraw happens on every keystroke,
# so retrying each time would stall the whole status line. The marker is
# separate from the cached response, rather than a touch of it: a cold
# /tmp has no response to touch, which is exactly when there is also no
# stale data to fall back on and the timeout is paid in full.
attempt=true
if [ -f "$fail_marker" ]; then
fail_mtime=$(stat -c %Y "$fail_marker" 2>/dev/null || stat -f %m "$fail_marker" 2>/dev/null)
[ $(( $(date +%s) - fail_mtime )) -lt "$USAGE_RETRY_SECS" ] && attempt=false
fi
if $attempt; then
token=$(get_oauth_token)
if [ -n "$token" ] && [ "$token" != "null" ]; then
# Marked before the call, cleared when it lands. A fetch is in
# flight for as long as curl takes to time out, and this cache
# dir is shared by every session, so redraws that start inside
# that window are real: they now serve stale data instead of
# each launching their own doomed request. Written on every
# attempt, so a repeated failure restarts the backoff, and only
# by an attempt, so the redraws it suppresses cannot keep
# re-stamping it and it always lapses.
#
# No credentials is not a failure worth suppressing: it costs no
# timeout, and the next redraw after a login should show the
# bars rather than wait out a retry window. Hence inside the
# token check.
: > "$fail_marker"
response=$(curl -s --max-time 10 \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "User-Agent: claude-code/2.1.34" \
"https://api.anthropic.com/api/oauth/usage" 2>/dev/null)
if [ -n "$response" ] && echo "$response" | jq . >/dev/null 2>&1; then
# Only cache successful (non-error) responses
if ! echo "$response" | jq -e '.error' >/dev/null 2>&1; then
usage_data="$response"
echo "$response" > "$api_cache"
rm -f "$fail_marker"
fi
fi
fi
fi
# Fall back to stale cache if refresh failed — skip if it's an error response
if [ -z "$usage_data" ] && [ -f "$api_cache" ]; then
stale=$(cat "$api_cache" 2>/dev/null)
! echo "$stale" | jq -e '.error' >/dev/null 2>&1 && usage_data="$stale"
fi
fi
if [ -n "$usage_data" ] && echo "$usage_data" | jq -e . >/dev/null 2>&1; then
bar_width=6
[ "$width_tier" = "split" ] && bar_width=4
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# One jq pass for the whole payload. Parsing it field by field meant
# ~10 jq processes per render on the same JSON; the status line runs on
# every redraw, so that was the dominant cost. Rounding happens in jq
# too, which removes the per-field awk calls.
#
# The per-model weekly limit (e.g. Fable) lives in .limits[] under kind
# "weekly_scoped" -- the legacy seven_day_opus / seven_day_sonnet fields
# are always null now. Prefer an is_active entry, else take the first.
# `.limits[]?` tolerates limits being absent or not an array, and
# `tonumber? // 0` tolerates a percentage arriving as a string.
# One field per line, not @tsv: tab is IFS whitespace, so `read` collapses
# runs of it and an empty field (no scoped limit -> empty display name)
# would silently shift every later field along by one.
{
IFS= read -r five_hour_pct
IFS= read -r seven_day_pct
IFS= read -r five_hour_reset_iso
IFS= read -r seven_day_reset_iso
IFS= read -r scoped_name
IFS= read -r scoped_pct
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
IFS= read -r scoped_reset_iso
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
IFS= read -r extra_enabled
IFS= read -r extra_pct
IFS= read -r extra_used
IFS= read -r extra_limit
} <<EOF
$(echo "$usage_data" | jq -r '
def num: (tonumber? // 0);
([.limits[]? | select(.kind == "weekly_scoped")]
| (map(select(.is_active)) + .) | first) as $scoped
| [ (.five_hour.utilization | num | round),
(.seven_day.utilization | num | round),
(.five_hour.resets_at // ""),
(.seven_day.resets_at // ""),
(($scoped.scope.model.display_name // "") | gsub("[\\n\\r\\t]"; " ")),
($scoped.percent | num | round),
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
($scoped.resets_at // ""),
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
(.extra_usage.is_enabled // false),
(.extra_usage.utilization | num | round),
((.extra_usage.used_credits | num) / 100 * 100 | round / 100),
((.extra_usage.monthly_limit | num) / 100 * 100 | round / 100)
] | .[] | tostring' 2>/dev/null)
EOF
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
# ===== Sustainable burn: pace + trend =====
# pace = the limit's remaining points divided by the WORKING days left
# before it resets, i.e. what can be spent per working day and still
# land on 100% exactly at the reset. A five-day week spreads 100 points
# over 5 days, so a fresh window paces at 20%/day; the 14.3%/day
# calendar figure quietly assumes the weekend is worked too.
# trend compares used% against a BAND rather than a point: during
# workday n of m, anything from (n-1)/m to n/m of the budget is on
# track, because the whole of today's allowance is today's to spend. A
# point built from complete workdays only made every Monday morning red
# the moment 3 points had gone. Ahead of the band means the limit will
# cap before the week is out, behind it means part of the subscription
# goes unused; a +-3 slack outside the band stops it flickering at the
# edges. On a non-workday no allowance is in play, so the band
# collapses to its floor.
#
# Whole workdays only, no time-of-day interpolation: the percentages
# arrive as integers anyway, and "three working days left" is the
# granularity the decision actually gets made at.
#
# Measured against the per-model limit whenever one is being shown --
# that is the limit that runs out first, and the bar this annotates.
# Same gate as seg_scoped below, so pace always describes the bar it is
# printed next to. Its own resets_at wins, falling back to the 7-day
# timestamp (the two reset together) when the payload omits it.
seg_pace=""
if $SHOW_MODEL_LIMIT && [ -n "$scoped_name" ]; then
pace_used="${scoped_pct:-0}"
pace_reset_iso="$scoped_reset_iso"
else
pace_used="${seven_day_pct:-0}"
pace_reset_iso=""
fi
case "$pace_reset_iso" in ''|null) pace_reset_iso="$seven_day_reset_iso" ;; esac
pace_epoch=""
case "$pace_reset_iso" in ''|null) ;; *) pace_epoch=$(iso_to_epoch "$pace_reset_iso") ;; esac
if [ -n "$pace_epoch" ]; then
ds_now=$(day_start_dow "$(now_ts)")
ds_reset=$(day_start_dow "$pace_epoch")
# Whole local days from today to the reset's day. Rounded rather
# than divided: a DST change makes one of those days 23 or 25 hours
# long, and truncation would lose a day either side of it.
pace_days=""
if [ -n "$ds_now" ] && [ -n "$ds_reset" ]; then
pace_days=$(( (${ds_reset%% *} - ${ds_now%% *} + 43200) / 86400 ))
fi
# No strftime in macOS awk, so the weekday of each day in the
# window is derived from today's, which bash passes in.
pace_tv=""
[ -n "$pace_days" ] && pace_tv=$(awk \
-v used="$pace_used" -v dtr="$pace_days" \
-v dow0="${ds_now##* }" -v wd=",${SL_WORK_DAYS}," 'BEGIN{
# Day indices, 0 = today. The reset is 7 days after the
# previous one, so the window is [dtr-7, dtr) and today has to
# lie inside it; anything else is stale or nonsense data.
if (dtr < 0 || dtr > 7 || dow0 < 1 || dow0 > 7) exit 1
for (i = dtr - 7; i < dtr; i++) {
dow = ((dow0 - 1 + i) % 7 + 7) % 7 + 1
if (index(wd, dow) == 0) continue
total++
if (i < 0) elapsed++; else left++ # today counts as left
}
if (total <= 0) exit 1 # no workdays: no baseline
rem = 100 - used; if (rem < 0) rem = 0
d = (left < 1) ? 1 : left # nothing but today: all that remains
pr = rem / d
# Today is workday elapsed+1 of total, so its band runs from
# elapsed/total to (elapsed+1)/total. Only its distance is
# reported, signed: + past the top, - short of the floor, 0
# anywhere inside the band or its slack. Comparing here rather
# than in bash keeps the band edges off a second rounding.
lo = elapsed / total * 100
hi = (index(wd, dow0) > 0) ? (elapsed + 1) / total * 100 : lo
dv = (used > hi + 3) ? used - hi : (used < lo - 3) ? used - lo : 0
# One decimal below 10, where rounding starts to matter. The
# last two fields are pace and the baseline in tenths, for
# the integer comparison in pacecol.
printf "%s %.0f %.0f %.0f\n", \
(pr < 10) ? sprintf("%.1f", pr) : sprintf("%.0f", pr), \
dv, pr * 10, 1000 / total
}' 2>/dev/null)
if [ -n "$pace_tv" ]; then
read -r pace_val pace_trend pace_tenths pace_base <<< "$pace_tv"
seg_pace=" $(pacecol "$pace_tenths" "$pace_base")${pace_val}%/d${reset}"
if [ "$pace_trend" -gt 0 ] 2>/dev/null; then seg_pace+=" ${red}▲+${pace_trend}${reset}"
elif [ "$pace_trend" -lt 0 ] 2>/dev/null; then seg_pace+=" ${cyan}${pace_trend}${reset}"
else seg_pace+=" ${green}${reset}"
fi
fi
fi
# Four variants of the rate-limit group, richest first:
# rl_rich bars + reset times + pace + extra usage
# rl_mid bars + pace + extra usage
# rl_pace bars + pace
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# rl_lean bars only
# The ladder at the end emits the richest one that fits the space it
# has. Keeping a lean variant matters: extra-usage credits add ~30
# columns, which can overflow line two on its own.
seg_5h="${dim}5h${reset} $(build_bar "${five_hour_pct:-0}" "$bar_width") ${cyan}${five_hour_pct:-0}%${reset}"
seg_7d="${sep}${dim}7d${reset} $(build_bar "${seven_day_pct:-0}" "$bar_width") ${cyan}${seven_day_pct:-0}%${reset}"
# Rendered inside the 7d segment rather than as its own: both are weekly
# limits resetting at the same time, so one shared reset label covers
# the pair and saves a separator plus a second timestamp.
seg_scoped=""
if $SHOW_MODEL_LIMIT && [ -n "$scoped_name" ]; then
seg_scoped=" ${dim}$(esc_data "$(truncate_str "$scoped_name" 8)")${reset} $(build_bar "${scoped_pct:-0}" "$bar_width") ${cyan}${scoped_pct:-0}%${reset}"
fi
# Extra usage, when the account has it enabled.
seg_extra=""
if [ "$extra_enabled" = "true" ]; then
seg_extra="${sep}${dim}extra${reset} $(build_bar "${extra_pct:-0}" "$bar_width") ${cyan}\$$(printf '%.2f' "${extra_used:-0}")${dim}/\$$(printf '%.2f' "${extra_limit:-0}")${reset}"
fi
# rl_bare drops the per-model bar too: the last thing worth giving up,
# and the only way to fit at all when wrapping is disabled and the
# terminal is narrow.
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
#
# rl_pace is its own rung so pace is given up BEFORE the per-model bar
# it annotates: a bar with no pace still says something, a pace with no
# bar does not. seg_pace lands after the per-model percentage, or after
# the 7d one on an account with no per-model limit (seg_scoped empty).
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
rl_bare="${seg_5h}${seg_7d}"
rl_lean="${rl_bare}${seg_scoped}"
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
rl_pace="${rl_lean}${seg_pace}"
rl_mid="${rl_pace}${seg_extra}"
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
rl_rich="$rl_mid"
# Formatting the two reset timestamps costs ~10 subprocesses (date has
# no portable one-shot form here), so only pay for it when there is a
# chance they will be shown: appended to line one, or alone on line two.
# RESET_COST is the combined width of " reset 4:40p.m." and
# " reset aug 28, 3:00a.m.".
line1_len=$(vis_len "$out")
mid_len=$(vis_len "$rl_mid")
RESET_COST=30
if [ $(( line1_len + 3 + mid_len + RESET_COST )) -le "$USABLE_WIDTH" ] \
|| { $WRAP_NARROW && [ $(( mid_len + RESET_COST )) -le "$USABLE_WIDTH" ]; }; then
five_hour_reset=$(format_reset_time "$five_hour_reset_iso" "time")
seven_day_reset=$(format_reset_time "$seven_day_reset_iso" "datetime")
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
r5=""; [ -n "$five_hour_reset" ] && r5=" ${dim}${five_hour_reset}${reset}"
r7=""; [ -n "$seven_day_reset" ] && r7=" ${dim}${seven_day_reset}${reset}"
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
rl_rich="${seg_5h}${r5}${seg_7d}${seg_scoped}${seg_pace}${r7}${seg_extra}"
fi
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
fi
fi
# Attach the rate-limit group, emitting the richest layout that fits.
#
# A second line is worth it for the per-model bar and nothing else: rl_bare is
# the only variant that drops that bar, and it is the whole reason the group is
# worth showing on an account that has one. Reset times and extra-usage credits
# are given up instead of wrapped for, so content is NOT monotone in width -- at
# one column narrower, rung 3 stops fitting and the wrap that follows has room
# for the timestamps as well. Only wrapping being switched off makes rl_bare the
# answer.
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
# 1-4. one line: with resets / with extra usage / with pace / bars + per-model
# 5-8. two lines: same order, line two having room the single line lacked
# 9. one line, bars only -- WRAP_NARROW=false, the least-bad single line
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
# Every branch is fit-checked, so no layout is chosen that would be clipped by
# the renderer, whatever the branch name, cwd, model name or extra-usage width.
if [ -n "$rl_bare" ]; then
rich_len=$(vis_len "$rl_rich")
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
pace_len=$(vis_len "$rl_pace")
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
lean_len=$(vis_len "$rl_lean")
if [ $(( line1_len + 3 + rich_len )) -le "$USABLE_WIDTH" ]; then out+="${sep}${rl_rich}"
elif [ $(( line1_len + 3 + mid_len )) -le "$USABLE_WIDTH" ]; then out+="${sep}${rl_mid}"
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
elif [ $(( line1_len + 3 + pace_len )) -le "$USABLE_WIDTH" ]; then out+="${sep}${rl_pace}"
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
elif [ $(( line1_len + 3 + lean_len )) -le "$USABLE_WIDTH" ]; then out+="${sep}${rl_lean}"
elif $WRAP_NARROW; then
if [ "$rich_len" -le "$USABLE_WIDTH" ]; then out+=$'\n'"$rl_rich"
elif [ "$mid_len" -le "$USABLE_WIDTH" ]; then out+=$'\n'"$rl_mid"
Pace the per-model weekly limit over the days you actually work The weekly limit resets every 7 days, but a 5-day week means the sustainable burn is 20% a day, not the 100/7 the calendar implies. Nothing on the line said that, so staying inside the limit meant doing the division by hand. Adds pace and trend to the per-model bar: "Fable 6% 19%/d ✓". pace is what is left divided by the working days still in the window, so it is the envelope for today. trend compares usage against today's band - during working day n of m, anywhere between (n-1)/m and n/m of the budget is on track - and reports the distance outside it. A band rather than a point because a point built from completed days expects 0% on the first working day of the window, so any usage at all reads as overspending: 6% on a Monday morning showed a red arrow. The band is also all whole-day granularity supports, and the sleep-aware glide in statusline.burnrate.sh is what it would take to say more. Working days come from SL_WORK_DAYS (ISO weekdays, default Mon-Fri) and are counted inside the reset window rather than assumed, so a window that starts mid-week still divides correctly. The colour thresholds derive from 100/total_workdays instead of the hardcoded 14.3-a-day ones in burnrate.sh, which would call 13%/d healthy against a 20-a-day budget. SL_NOW is a test seam for the clock. The cache-age checks deliberately stay on the real clock: a pinned SL_NOW could otherwise age a fresh fixture past USAGE_CACHE_SECS and send a test to the network with real credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:02:47 +01:00
elif [ "$pace_len" -le "$USABLE_WIDTH" ]; then out+=$'\n'"$rl_pace"
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
elif [ "$lean_len" -le "$USABLE_WIDTH" ]; then out+=$'\n'"$rl_lean"
else out+=$'\n'"$rl_bare"
fi
Show the per-model usage limit, and size the status line by measurement /usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:55:35 +01:00
else
out+="${sep}${rl_bare}"
fi
fi
printf "%b" "$out"
exit 0