Created
April 6, 2026 07:56
-
-
Save N0K0/e8edcead8c93bff3845a9b37e4febf62 to your computer and use it in GitHub Desktop.
Claude Code statusline — II layout: 3-line split panel with truecolor bars, pace gauge, responsive truncation, and Nerd Font icons
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # ============================================================================ | |
| # Claude Code Status Line — "II" Layout | |
| # ============================================================================ | |
| # | |
| # A two-panel, 3-line status bar with truecolor gradients, a pace gauge, | |
| # and context-aware annotations. Designed for terminals with Nerd Fonts. | |
| # | |
| # LAYOUT | |
| # ------ | |
| # The display is split by a vertical ┃ separator into: | |
| # Left panel: project info (variable width, content-driven) | |
| # Right panel: usage meters + pace gauge (fixed structure) | |
| # | |
| # With git: Without git: | |
| # ╭ ~/project/path ┃ 5h bar ╭ ~/path ┃ 5h bar | |
| # │ branch +N ●M [] ┃ 7d bar │ Opus 4.6 │ 1.2K/340 ┃ 7d bar | |
| # ╰ Opus │ tok ctx% │ $ ┃ pace ╰ ctx 32% │ $1.60 ┃ pace | |
| # | |
| # When there is no git repo, model name and token I/O move up to line 2 | |
| # (where git would be), and ctx/cost take line 3. | |
| # | |
| # ALIGNMENT | |
| # --------- | |
| # - The ┃ separator column is computed from plain-text segment widths. | |
| # Every line follows the structure: prefix(3) + icon(ICO) + space + content. | |
| # The separator is placed at the max width across all 3 lines. | |
| # - In no-git mode, the inner │ separators (model│tokens, ctx│cost) are | |
| # vertically aligned by padding the left-of-│ segments to equal width. | |
| # - Colored output uses printf '%b' to interpret \033 escapes without | |
| # treating content '%' as format specifiers. | |
| # - ICO constant (default 1) controls assumed nerd font icon display width. | |
| # Adjust if your terminal renders PUA glyphs wider. | |
| # | |
| # TRUNCATION | |
| # ---------- | |
| # - Left panel budget = 80% of terminal width (from tput cols). | |
| # - If content exceeds budget, cwd is truncated first (with …), then | |
| # git branch. The info line (model/tokens/cost) is never truncated. | |
| # | |
| # RIGHT PANEL METERS | |
| # ------------------ | |
| # - 5h bar: gradient fill (TC_MINT), percentage, reset countdown | |
| # - 7d bar: gradient fill (TC_SKY), percentage, reset countdown | |
| # - Pace gauge: ◆ marker on a zone-colored track. | |
| # Center │ = 100% (on pace). Left = under, right = over. | |
| # Green zone (80-120%), yellow (50-160%), red (outside). | |
| # Annotated with human-readable label: "on pace", "under budget", | |
| # "running hot", "over budget!" | |
| # | |
| # USAGE DATA | |
| # ---------- | |
| # Fetched by probing the Anthropic API with a Haiku request and reading | |
| # rate-limit headers. Cached to /tmp/claude-usage-cache.json for 5 minutes. | |
| # Pace = (actual_7d_usage / ideal_linear_usage) * 100, where ideal is | |
| # based on elapsed time in the 7-day window. | |
| # | |
| # TERMINAL TITLE | |
| # -------------- | |
| # Set via OSC escape (or Konsole D-Bus). Format: project | branch | summary. | |
| # Brainstorming summary is generated by a Haiku call and cached per session. | |
| # | |
| # FILES | |
| # ----- | |
| # - ~/.claude/statusline-ii.sh this script (active) | |
| # - ~/.claude/statusline-command.sh previous statusline (retained) | |
| # - ~/.claude/settings.json statusLine.command points here | |
| # - /tmp/claude-usage-cache.json cached API usage data | |
| # - /tmp/claude-usage-cache.ts cache timestamp | |
| # - /tmp/claude-brainstorm-summary-* cached brainstorm summaries | |
| # | |
| # CONFIGURATION | |
| # ------------- | |
| # In ~/.claude/settings.json: | |
| # "statusLine": { | |
| # "type": "command", | |
| # "command": "bash /home/nikolas/.claude/statusline-ii.sh" | |
| # } | |
| # | |
| # ============================================================================ | |
| input=$(cat) | |
| # ---- Extract fields (single jq call for performance) ---- | |
| eval "$(echo "$input" | jq -r ' | |
| "model_id=" + (.model.id // "claude-sonnet" | @sh), | |
| "model_name=" + (.model.display_name // "Claude" | @sh), | |
| "cwd_path=" + (.cwd // .workspace.current_dir // "" | @sh), | |
| "transcript_path=" + (.transcript_path // "" | @sh), | |
| "total_in=" + (.context_window.total_input_tokens // 0 | tostring), | |
| "total_out=" + (.context_window.total_output_tokens // 0 | tostring), | |
| "used_pct=" + (.context_window.used_percentage // empty | tostring | @sh), | |
| "session_cost=" + (.cost.total_cost_usd // 0 | tostring), | |
| "h5_pct=" + (.rate_limits.five_hour.used_percentage // empty | tostring | @sh), | |
| "h5_reset=" + (.rate_limits.five_hour.resets_at // empty | tostring | @sh), | |
| "h7_pct=" + (.rate_limits.seven_day.used_percentage // empty | tostring | @sh), | |
| "h7_reset=" + (.rate_limits.seven_day.resets_at // empty | tostring | @sh) | |
| ' 2>/dev/null)" | |
| creds_file="$HOME/.claude/.credentials.json" | |
| # ---- ANSI colors ---- | |
| RST='\033[0m' | |
| DIM='\033[2m' | |
| ITALIC='\033[3m' | |
| RED='\033[0;31m' | |
| YEL='\033[0;33m' | |
| GRN='\033[0;32m' | |
| CYN='\033[0;36m' | |
| MAG='\033[0;35m' | |
| BLU='\033[0;34m' | |
| GRAY='\033[38;5;242m' | |
| # Truecolor | |
| TC_MINT='\033[38;2;130;220;170m' | |
| TC_SKY='\033[38;2;100;180;255m' | |
| TC_GOLD='\033[38;2;255;200;60m' | |
| TC_ROSE='\033[38;2;255;100;130m' | |
| TC_LAVENDER='\033[38;2;180;160;255m' | |
| TC_TEAL='\033[38;2;0;188;180m' | |
| TC_CORAL='\033[38;2;255;127;80m' | |
| TC_DARK='\033[38;2;50;50;50m' | |
| # Pace zone colors for gauge | |
| TC_ZONE_OK='\033[38;2;50;75;50m' | |
| TC_ZONE_WARN='\033[38;2;50;35;35m' | |
| TC_GAUGE_MID='\033[38;2;70;70;70m' | |
| # ---- Nerd Font icons ---- | |
| ICON_FOLDER="" | |
| ICON_GIT="" | |
| ICON_MODEL="" | |
| ICON_CTX="" # nf-md-memory (context/memory) | |
| ICON_COST="" # nf-fa-dollar | |
| ICON_DIRTY="●" | |
| SEP_INNER="·" # inner separator (dot) | |
| # ---- Helpers ---- | |
| fmt_time() { | |
| local secs="${1:-0}" | |
| if [ "$secs" -le 0 ] 2>/dev/null; then echo "now!"; return; fi | |
| local d=$(( secs / 86400 )) | |
| local h=$(( (secs % 86400) / 3600 )) | |
| local m=$(( (secs % 3600) / 60 )) | |
| if [ "$d" -gt 0 ]; then printf "%dd%dh" "$d" "$h" | |
| elif [ "$h" -gt 0 ]; then printf "%dh%02dm" "$h" "$m" | |
| else printf "%dm" "$m" | |
| fi | |
| } | |
| fmt_k() { | |
| awk -v n="$1" 'BEGIN { | |
| if (n >= 1000) printf "%.1fK", n/1000 | |
| else printf "%d", n | |
| }' | |
| } | |
| # Gradient bar: filled portion in color, empty in dark | |
| gbar() { | |
| local pct=$1 width=$2 color=$3 | |
| local filled=$(( pct * width / 100 )) | |
| [ "$filled" -gt "$width" ] && filled=$width | |
| [ "$filled" -lt 0 ] && filled=0 | |
| local empty=$(( width - filled )) | |
| printf "${color}" | |
| for ((i=0; i<filled; i++)); do printf "█"; done | |
| printf "${TC_DARK}" | |
| for ((i=0; i<empty; i++)); do printf "░"; done | |
| printf "${RST}" | |
| } | |
| # Pace gauge: ◆ marker on a zone-colored track, center │ = 100% | |
| pace_gauge() { | |
| local pace=$1 width=$2 | |
| local pos=$(( pace * width / 200 )) | |
| [ "$pos" -ge "$width" ] && pos=$(( width - 1 )) | |
| [ "$pos" -lt 0 ] && pos=0 | |
| local mid=$(( width / 2 )) | |
| # Pick marker color | |
| local marker_color | |
| if [ "$pace" -ge 80 ] && [ "$pace" -le 120 ]; then | |
| marker_color="${TC_MINT}" | |
| elif [ "$pace" -ge 50 ] && [ "$pace" -le 160 ]; then | |
| marker_color="${TC_GOLD}" | |
| else | |
| marker_color="${TC_ROSE}" | |
| fi | |
| for ((i=0; i<width; i++)); do | |
| if [ $i -eq $pos ]; then | |
| printf "${marker_color}◆" | |
| elif [ $i -eq $mid ]; then | |
| printf "${TC_GAUGE_MID}│" | |
| elif [ $i -ge $(( mid - 2 )) ] && [ $i -le $(( mid + 2 )) ]; then | |
| printf "${TC_ZONE_OK}─" | |
| else | |
| printf "${TC_ZONE_WARN}─" | |
| fi | |
| done | |
| printf "${RST}" | |
| } | |
| # Color a percentage value based on usage severity | |
| color_pct() { | |
| local pct="${1:-0}" | |
| local color=$2 | |
| printf "${color}%.0f%%${RST}" "$pct" | |
| } | |
| tin_fmt=$(fmt_k "$total_in") | |
| tout_fmt=$(fmt_k "$total_out") | |
| # ---- Session cost ---- | |
| session_cost_str=$(awk -v c="$session_cost" 'BEGIN { printf "$%.2f", c }') | |
| # ---- Context % ---- | |
| ctx_str="" | |
| if [ -n "$used_pct" ]; then | |
| ipct=${used_pct%.*} | |
| if [ "$ipct" -ge 80 ] 2>/dev/null; then ctx_str="${RED}ctx ${ipct}%${RST}" | |
| elif [ "$ipct" -ge 50 ] 2>/dev/null; then ctx_str="${YEL}ctx ${ipct}%${RST}" | |
| else ctx_str="${GRN}ctx ${ipct}%${RST}"; fi | |
| fi | |
| # ---- Git status ---- | |
| git_branch="" | |
| git_status_str="" | |
| git_dirty_count=0 | |
| git_is_worktree=false | |
| if [ -n "$cwd_path" ] && [ -d "$cwd_path" ]; then | |
| git_branch=$(git -C "$cwd_path" symbolic-ref --short HEAD 2>/dev/null) | |
| if [ -n "$git_branch" ]; then | |
| git_toplevel=$(git -C "$cwd_path" rev-parse --show-toplevel 2>/dev/null) | |
| git_commondir=$(git -C "$cwd_path" rev-parse --git-common-dir 2>/dev/null) | |
| if [ "$git_commondir" != ".git" ] && [ "$git_commondir" != "$git_toplevel/.git" ]; then | |
| git_is_worktree=true | |
| fi | |
| ahead=0; behind=0 | |
| if [ "$git_branch" != "main" ]; then | |
| ahead_behind=$(git -C "$cwd_path" rev-list --left-right --count "$git_branch"...main 2>/dev/null) | |
| else | |
| ahead_behind=$(git -C "$cwd_path" rev-list --left-right --count main...origin/main 2>/dev/null) | |
| fi | |
| if [ -n "$ahead_behind" ]; then | |
| ahead=$(echo "$ahead_behind" | awk '{print $1}') | |
| behind=$(echo "$ahead_behind" | awk '{print $2}') | |
| fi | |
| git_dirty_count=$(git -C "$cwd_path" status --porcelain 2>/dev/null | wc -l | tr -d ' ') | |
| git_status_str="${git_branch}" | |
| [ "$ahead" -gt 0 ] 2>/dev/null && git_status_str="${git_status_str} +${ahead}" | |
| [ "$behind" -gt 0 ] 2>/dev/null && git_status_str="${git_status_str} -${behind}" | |
| [ "$git_dirty_count" -gt 0 ] 2>/dev/null && git_status_str="${git_status_str} ${ICON_DIRTY}${git_dirty_count}" | |
| fi | |
| fi | |
| # ---- Brainstorming summary ---- | |
| brainstorm_summary="" | |
| if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then | |
| session_hash=$(printf '%s' "$transcript_path" | md5sum | cut -d' ' -f1) | |
| summary_cache="/tmp/claude-brainstorm-summary-${session_hash}.txt" | |
| last_user_content=$(tac "$transcript_path" 2>/dev/null \ | |
| | jq -r 'select(.type == "user" and .userType == "external") | .message.content' 2>/dev/null \ | |
| | head -1) | |
| is_brainstorming=false | |
| echo "$last_user_content" | grep -q 'superpowers-extended-cc:brainstorming' 2>/dev/null && is_brainstorming=true | |
| if [ "$is_brainstorming" = true ]; then | |
| transcript_size=$(stat -c%s "$transcript_path" 2>/dev/null || echo "0") | |
| cached_size=$(cat "${summary_cache}.size" 2>/dev/null || echo "-1") | |
| if [ -f "$summary_cache" ] && [ "$transcript_size" = "$cached_size" ]; then | |
| brainstorm_summary=$(cat "$summary_cache") | |
| else | |
| token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null) | |
| if [ -n "$token" ]; then | |
| transcript_tail=$(tail -c 3000 "$transcript_path" 2>/dev/null || true) | |
| request_body=$(jq -n \ | |
| --arg context "$transcript_tail" \ | |
| '{model: "claude-haiku-4-5-20251001", max_tokens: 30, messages: [{role: "user", content: ("Summarize what this brainstorming session is about in under 8 words for a terminal window title. Reply with ONLY the summary, no quotes or punctuation:\n\n" + $context)}]}') | |
| summary_response=$(curl --silent --max-time 5 \ | |
| --header "Authorization: Bearer $token" \ | |
| --header "Content-Type: application/json" \ | |
| --header "User-Agent: claude-code/2.1.72" \ | |
| --header "anthropic-version: 2023-06-01" \ | |
| --header "anthropic-beta: oauth-2025-04-20" \ | |
| -d "$request_body" \ | |
| "https://api.anthropic.com/v1/messages" 2>/dev/null) | |
| if [ -n "$summary_response" ]; then | |
| brainstorm_summary=$(echo "$summary_response" | jq -r '.content[0].text // empty' 2>/dev/null | head -c 60) | |
| if [ -n "$brainstorm_summary" ]; then | |
| echo "$brainstorm_summary" > "$summary_cache" | |
| echo "$transcript_size" > "${summary_cache}.size" | |
| fi | |
| fi | |
| fi | |
| fi | |
| elif [ -f "$summary_cache" ]; then | |
| brainstorm_summary=$(cat "$summary_cache") | |
| fi | |
| fi | |
| # ---- Usage data (from CC JSON, fallback to cached API probe) ---- | |
| now=$(date +%s) | |
| five_hour_pct="" | |
| five_hour_reset="" | |
| seven_day_pct="" | |
| seven_day_reset="" | |
| pace_value="" | |
| pace_label="" | |
| # Prefer rate_limits from CC JSON (instant, no API call) | |
| if [ -n "$h5_pct" ] && [ "$h5_pct" != "null" ]; then | |
| five_hour_pct=$(awk "BEGIN{printf \"%.0f\", $h5_pct}" 2>/dev/null) | |
| fi | |
| if [ -n "$h7_pct" ] && [ "$h7_pct" != "null" ]; then | |
| seven_day_pct=$(awk "BEGIN{printf \"%.0f\", $h7_pct}" 2>/dev/null) | |
| fi | |
| if [ -n "$h5_reset" ] && [ "$h5_reset" != "null" ] && [ "$h5_reset" != "0" ]; then | |
| h5r_epoch=$(date -d "@$h5_reset" +%s 2>/dev/null || date -d "$h5_reset" +%s 2>/dev/null || echo "0") | |
| if [ "$h5r_epoch" -gt "$now" ] 2>/dev/null; then | |
| five_hour_reset=$(fmt_time $(( h5r_epoch - now ))) | |
| fi | |
| fi | |
| if [ -n "$h7_reset" ] && [ "$h7_reset" != "null" ] && [ "$h7_reset" != "0" ]; then | |
| h7r_epoch=$(date -d "@$h7_reset" +%s 2>/dev/null || date -d "$h7_reset" +%s 2>/dev/null || echo "0") | |
| if [ "$h7r_epoch" -gt "$now" ] 2>/dev/null; then | |
| seven_day_reset=$(fmt_time $(( h7r_epoch - now ))) | |
| fi | |
| fi | |
| # Fallback: Haiku API probe (commented out — CC now provides rate_limits natively) | |
| # To re-enable, uncomment the block below. It probes the API every 5 minutes | |
| # and caches results to /tmp/claude-usage-cache.json. | |
| # | |
| # if [ -z "$five_hour_pct" ] && [ -z "$seven_day_pct" ]; then | |
| # usage_cache="/tmp/claude-usage-cache.json" | |
| # usage_cache_ts="/tmp/claude-usage-cache.ts" | |
| # if [ -f "$creds_file" ]; then | |
| # cache_age=9999 | |
| # [ -f "$usage_cache_ts" ] && cache_age=$(( now - $(cat "$usage_cache_ts") )) | |
| # if [ "$cache_age" -gt 300 ] || [ "$cache_age" -lt 0 ]; then | |
| # token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null) | |
| # if [ -n "$token" ]; then | |
| # headers=$(curl --silent --max-time 5 -D- -o /dev/null \ | |
| # --header "Authorization: Bearer $token" \ | |
| # --header "Content-Type: application/json" \ | |
| # --header "User-Agent: claude-code/2.1.72" \ | |
| # --header "anthropic-version: 2023-06-01" \ | |
| # --header "anthropic-beta: oauth-2025-04-20" \ | |
| # -d '{"model":"claude-haiku-4-5-20251001","max_tokens":1,"messages":[{"role":"user","content":"h"}]}' \ | |
| # "https://api.anthropic.com/v1/messages" 2>/dev/null) | |
| # if [ -n "$headers" ]; then | |
| # h5u=$(echo "$headers" | grep -i 'anthropic-ratelimit-unified-5h-utilization' | tr -d '\r' | awk '{print $2}') | |
| # h5r=$(echo "$headers" | grep -i 'anthropic-ratelimit-unified-5h-reset' | tr -d '\r' | awk '{print $2}') | |
| # h7u=$(echo "$headers" | grep -i 'anthropic-ratelimit-unified-7d-utilization' | tr -d '\r' | awk '{print $2}') | |
| # h7r=$(echo "$headers" | grep -i 'anthropic-ratelimit-unified-7d-reset' | tr -d '\r' | awk '{print $2}') | |
| # [ -n "$h5u" ] && jq -n --arg h5u "$h5u" --arg h5r "$h5r" --arg h7u "$h7u" --arg h7r "$h7r" \ | |
| # '{h5u:$h5u, h5r:$h5r, h7u:$h7u, h7r:$h7r}' > "$usage_cache" && echo "$now" > "$usage_cache_ts" | |
| # fi | |
| # fi | |
| # fi | |
| # if [ -f "$usage_cache" ]; then | |
| # h5u=$(jq -r '.h5u // empty' "$usage_cache"); h5r=$(jq -r '.h5r // empty' "$usage_cache") | |
| # h7u=$(jq -r '.h7u // empty' "$usage_cache"); h7r=$(jq -r '.h7r // empty' "$usage_cache") | |
| # [ -n "$h5u" ] && [ "$h5u" != "null" ] && five_hour_pct=$(awk "BEGIN{printf \"%.0f\", $h5u * 100}") | |
| # [ -n "$h7u" ] && [ "$h7u" != "null" ] && seven_day_pct=$(awk "BEGIN{printf \"%.0f\", $h7u * 100}") | |
| # if [ -n "$h5r" ] && [ "$h5r" != "null" ] && [ "$h5r" != "0" ]; then | |
| # h5r_epoch=$(date -d "@$h5r" +%s 2>/dev/null || date -d "$h5r" +%s 2>/dev/null || echo "0") | |
| # [ "$h5r_epoch" -gt "$now" ] && five_hour_reset=$(fmt_time $(( h5r_epoch - now ))) | |
| # fi | |
| # if [ -n "$h7r" ] && [ "$h7r" != "null" ] && [ "$h7r" != "0" ]; then | |
| # h7r_epoch=$(date -d "@$h7r" +%s 2>/dev/null || date -d "$h7r" +%s 2>/dev/null || echo "0") | |
| # [ "$h7r_epoch" -gt "$now" ] && seven_day_reset=$(fmt_time $(( h7r_epoch - now ))) | |
| # fi | |
| # fi | |
| # fi | |
| # fi | |
| # ---- Pace calculation ---- | |
| if [ -n "$seven_day_pct" ] && [ -n "$h7r_epoch" ] && [ "$h7r_epoch" -gt "$now" ] 2>/dev/null; then | |
| pace_value=$(awk -v actual="${seven_day_pct}" -v remaining="$(( h7r_epoch - now ))" ' | |
| BEGIN { | |
| window = 604800; elapsed = window - remaining | |
| if (elapsed <= 0) { print ""; exit } | |
| ideal = (elapsed / window) * 100 | |
| if (ideal < 0.01) { print ""; exit } | |
| printf "%.0f", actual / ideal * 100 | |
| }') | |
| if [ -n "$pace_value" ]; then | |
| if [ "$pace_value" -ge 80 ] && [ "$pace_value" -le 120 ] 2>/dev/null; then pace_label="on pace" | |
| elif [ "$pace_value" -lt 80 ] 2>/dev/null; then pace_label="under budget" | |
| elif [ "$pace_value" -gt 160 ] 2>/dev/null; then pace_label="over budget!" | |
| else pace_label="running hot"; fi | |
| fi | |
| fi | |
| # ---- Compose status line (II layout) ---- | |
| # ---- Terminal geometry ---- | |
| # stdin is piped (JSON), so tput/stty on stdin won't work. | |
| # Try /dev/tty first (the real terminal), then $COLUMNS, then tput, then fallback. | |
| term_cols="" | |
| if [ -z "$term_cols" ] && [ -e /dev/tty ]; then | |
| term_cols=$(tput cols < /dev/tty 2>/dev/null) | |
| fi | |
| if [ -z "$term_cols" ] && [ -n "$COLUMNS" ]; then | |
| term_cols=$COLUMNS | |
| fi | |
| if [ -z "$term_cols" ]; then | |
| term_cols=$(tput cols 2>/dev/null) | |
| fi | |
| : "${term_cols:=120}" | |
| BAR_WIDTH=14 | |
| GAUGE_WIDTH=14 | |
| # ---- Build content ---- | |
| short_cwd="" | |
| [ -n "$cwd_path" ] && short_cwd="${cwd_path/#$HOME/\~}" | |
| short_model=$(echo "$model_name" | sed 's/Claude //;s/ (.*)//') | |
| wt_plain="" | |
| if [ -n "$git_status_str" ] && [ "$git_is_worktree" = true ]; then | |
| wt_plain=" " | |
| fi | |
| git_color="${GRN}" | |
| [ "$git_dirty_count" -gt 0 ] 2>/dev/null && git_color="${YEL}" | |
| has_git=false | |
| [ -n "$git_status_str" ] && has_git=true | |
| # ---- Helpers ---- | |
| pad() { | |
| local n=$1 | |
| [ "$n" -le 0 ] 2>/dev/null && return | |
| printf "%*s" "$n" "" | |
| } | |
| truncate() { | |
| local str="$1" max=$2 | |
| if [ "${#str}" -le "$max" ]; then | |
| printf '%s' "$str" | |
| else | |
| printf '%s' "${str:0:$(( max - 1 ))}…" | |
| fi | |
| } | |
| # ---- Calculate each line's plain-text width from its segments ---- | |
| # Each line: " X " (prefix=3) + icon(1) + " " + content | |
| # Icons (,,,etc) are 1 display col each based on wcwidth | |
| # Box-drawing (╭│╰┃│) are 1 display col each | |
| ICO=1 # nerd font icon display width | |
| # Segment lengths for each line (plain text, no ANSI) | |
| if [ "$has_git" = true ]; then | |
| # Line 1: " ╭ " + ICON + " " + cwd | |
| # Line 2: " │ " + ICON + " " + git_status + wt | |
| # Line 3: " ╰ " + ICON + " " + model + " │ " + tokens + [" ctx N%"] + [" │ $cost"] | |
| seg1="${short_cwd}" | |
| seg2="${git_status_str}${wt_plain}" | |
| seg3="${short_model} ${SEP_INNER} ↓${tin_fmt} ↑${tout_fmt}" | |
| [ -n "$used_pct" ] && seg3="${seg3} ${SEP_INNER} ${ICON_CTX} ${used_pct%.*}%" | |
| if [ "$total_in" -gt 0 ] 2>/dev/null || [ "$total_out" -gt 0 ] 2>/dev/null; then | |
| seg3="${seg3} ${SEP_INNER} ${session_cost_str}" | |
| fi | |
| prefix=3 | |
| segw() { echo $(( prefix + ICO + 1 + ${#1} )); } | |
| w1=$(segw "$seg1") | |
| w2=$(segw "$seg2") | |
| w3=$(segw "$seg3") | |
| else | |
| # All 3 lines: " X " + ICON + " " + content (same structure) | |
| # Line 1: folder icon + cwd | |
| # Line 2: model icon + model │ tokens | |
| # Line 3: cost icon + [ctx N%] [│ $cost] | |
| # | |
| # Align inner │: pad left-of-│ segments to same width | |
| seg1="${short_cwd}" | |
| # Split line 2 and 3 into left│right parts | |
| s2_left="${short_model}" | |
| s2_right="↓${tin_fmt} ↑${tout_fmt}" | |
| s3_left="" | |
| s3_right="" | |
| ipct_val=${used_pct%.*} | |
| [ -n "$used_pct" ] && s3_left="${ICON_CTX} ${ipct_val}%" | |
| if [ "$total_in" -gt 0 ] 2>/dev/null || [ "$total_out" -gt 0 ] 2>/dev/null; then | |
| s3_right="${session_cost_str}" | |
| fi | |
| # Pad left parts to same width for │ alignment | |
| inner_w=${#s2_left} | |
| [ ${#s3_left} -gt $inner_w ] && inner_w=${#s3_left} | |
| s2_left_padded=$(printf "%-${inner_w}s" "$s2_left") | |
| s3_left_padded=$(printf "%-${inner_w}s" "$s3_left") | |
| # Reassemble segments | |
| if [ -n "$s2_right" ]; then | |
| seg2="${s2_left_padded} ${SEP_INNER} ${s2_right}" | |
| else | |
| seg2="${s2_left_padded}" | |
| fi | |
| if [ -n "$s3_left" ] && [ -n "$s3_right" ]; then | |
| seg3="${s3_left_padded} ${SEP_INNER} ${s3_right}" | |
| elif [ -n "$s3_left" ]; then | |
| seg3="${s3_left_padded}" | |
| elif [ -n "$s3_right" ]; then | |
| seg3="${s3_right}" | |
| else | |
| seg3="" | |
| fi | |
| # Width: " X " (3) + ICON (ICO) + " " + content | |
| prefix=3 | |
| segw() { echo $(( prefix + ICO + 1 + ${#1} )); } | |
| w1=$(segw "$seg1") | |
| w2=$(segw "$seg2") | |
| w3=$(segw "$seg3") | |
| fi | |
| # ---- Find separator column ---- | |
| sep_col=$w1 | |
| [ $w2 -gt $sep_col ] && sep_col=$w2 | |
| [ $w3 -gt $sep_col ] && sep_col=$w3 | |
| # ---- Responsive right panel ---- | |
| # Progressive degradation based on available space: | |
| # Wide: bar(14) + label(8) + annotation(20) = 42 (+ 3 for " ┃ " = 45) | |
| # Medium: bar(14) + label(8) = 22 (+ 3 = 25) | |
| # Narrow: bar(8) + label(8) = 16 (+ 3 = 19) | |
| # Tiny: bar(4) + label(8) = 12 (+ 3 = 15) | |
| # Minimal: no bars, just label(8) = 8 (+ 3 = 11) | |
| available=$(( term_cols - sep_col )) | |
| show_annotations=true | |
| bar_w=14 | |
| gauge_w=14 | |
| if [ $available -lt 45 ]; then | |
| show_annotations=false | |
| fi | |
| if [ $available -lt 25 ]; then | |
| bar_w=8 | |
| gauge_w=8 | |
| fi | |
| if [ $available -lt 19 ]; then | |
| bar_w=4 | |
| gauge_w=4 | |
| fi | |
| if [ $available -lt 15 ]; then | |
| bar_w=0 | |
| gauge_w=0 | |
| fi | |
| # ---- Truncation of left panel if still too tight ---- | |
| # Need at least 15 cols for the right panel (┃ + label + tiny bar) | |
| min_right=15 | |
| budget=$(( term_cols - min_right )) | |
| [ "$budget" -lt 15 ] && budget=15 | |
| if [ $sep_col -gt $budget ]; then | |
| overshoot=$(( sep_col - budget )) | |
| # Truncate cwd first | |
| max_seg1=$(( ${#seg1} - overshoot )) | |
| [ $max_seg1 -lt 5 ] && max_seg1=5 | |
| seg1=$(truncate "$seg1" $max_seg1) | |
| w1=$(( prefix + ICO + 1 + ${#seg1} )) | |
| # Truncate git branch if still over | |
| if [ "$has_git" = true ] && [ $w2 -gt $budget ]; then | |
| max_seg2=$(( budget - prefix - ICO - 1 )) | |
| [ $max_seg2 -lt 5 ] && max_seg2=5 | |
| seg2=$(truncate "$seg2" $max_seg2) | |
| w2=$(( prefix + ICO + 1 + ${#seg2} )) | |
| fi | |
| # Recalculate | |
| sep_col=$w1 | |
| [ $w2 -gt $sep_col ] && sep_col=$w2 | |
| [ $w3 -gt $sep_col ] && sep_col=$w3 | |
| [ $sep_col -gt $budget ] && sep_col=$budget | |
| # Recalculate available after truncation | |
| available=$(( term_cols - sep_col )) | |
| show_annotations=true | |
| bar_w=14; gauge_w=14 | |
| if [ $available -lt 45 ]; then show_annotations=false; fi | |
| if [ $available -lt 25 ]; then bar_w=8; gauge_w=8; fi | |
| if [ $available -lt 19 ]; then bar_w=4; gauge_w=4; fi | |
| if [ $available -lt 15 ]; then bar_w=0; gauge_w=0; fi | |
| fi | |
| # ---- Output helper: right panel meter ---- | |
| # Usage: emit_meter <pct> <bar_width> <color> <label> <reset_time> | |
| emit_meter() { | |
| local pct=$1 bw=$2 color=$3 label=$4 reset=$5 | |
| printf '%b' " ${GRAY}┃${RST} " | |
| if [ "$bw" -gt 0 ]; then | |
| gbar "$pct" "$bw" "$color" | |
| printf " " | |
| fi | |
| printf "${color}${label} %s%%${RST}" "$pct" | |
| if [ "$show_annotations" = true ] && [ -n "$reset" ]; then | |
| printf " ${ITALIC}${GRAY}▸ resets in %s${RST}" "$reset" | |
| fi | |
| } | |
| # ---- Output ---- | |
| # Line 1 | |
| printf '%b' " ${GRAY}╭${RST} ${BLU}${ICON_FOLDER} ${seg1}${RST}" | |
| pad $(( sep_col - w1 )) | |
| [ -n "$five_hour_pct" ] && emit_meter "$five_hour_pct" "$bar_w" "${TC_MINT}" "5h" "$five_hour_reset" | |
| printf "\n" | |
| # Line 2 | |
| if [ "$has_git" = true ]; then | |
| printf '%b' " ${GRAY}│${RST} ${git_color}${ICON_GIT} ${seg2}${RST}" | |
| else | |
| printf '%b' " ${GRAY}│${RST} ${TC_LAVENDER}${ICON_MODEL} ${s2_left_padded}${RST} ${GRAY}${SEP_INNER}${RST} ${TC_TEAL}↓${tin_fmt}${RST} ${TC_CORAL}↑${tout_fmt}${RST}" | |
| fi | |
| pad $(( sep_col - w2 )) | |
| [ -n "$seven_day_pct" ] && emit_meter "$seven_day_pct" "$bar_w" "${TC_SKY}" "7d" "$seven_day_reset" | |
| printf "\n" | |
| # Line 3 — colored version of seg3 | |
| ipct_val=${used_pct%.*} | |
| if [ "$has_git" = true ]; then | |
| c3="${TC_LAVENDER}${ICON_MODEL} ${short_model}${RST} ${GRAY}${SEP_INNER}${RST} ${TC_TEAL}↓${tin_fmt}${RST} ${TC_CORAL}↑${tout_fmt}${RST}" | |
| if [ -n "$used_pct" ]; then | |
| if [ "$ipct_val" -ge 80 ] 2>/dev/null; then c3="${c3} ${GRAY}${SEP_INNER}${RST} ${RED}${ICON_CTX} ${ipct_val}%${RST}" | |
| elif [ "$ipct_val" -ge 50 ] 2>/dev/null; then c3="${c3} ${GRAY}${SEP_INNER}${RST} ${YEL}${ICON_CTX} ${ipct_val}%${RST}" | |
| else c3="${c3} ${GRAY}${SEP_INNER}${RST} ${GRN}${ICON_CTX} ${ipct_val}%${RST}"; fi | |
| fi | |
| if [ "$total_in" -gt 0 ] 2>/dev/null || [ "$total_out" -gt 0 ] 2>/dev/null; then | |
| c3="${c3} ${GRAY}${SEP_INNER}${RST} ${YEL}${session_cost_str}${RST}" | |
| fi | |
| printf '%b' " ${GRAY}╰${RST} ${c3}" | |
| else | |
| c3_left="" | |
| if [ -n "$used_pct" ]; then | |
| if [ "$ipct_val" -ge 80 ] 2>/dev/null; then c3_left="${RED}${s3_left_padded}${RST}" | |
| elif [ "$ipct_val" -ge 50 ] 2>/dev/null; then c3_left="${YEL}${s3_left_padded}${RST}" | |
| else c3_left="${GRN}${s3_left_padded}${RST}"; fi | |
| else | |
| c3_left="${s3_left_padded}" | |
| fi | |
| if [ -n "$s3_left" ] && [ -n "$s3_right" ]; then | |
| c3="${c3_left} ${GRAY}${SEP_INNER}${RST} ${YEL}${s3_right}${RST}" | |
| elif [ -n "$s3_right" ]; then | |
| c3="${YEL}${s3_right}${RST}" | |
| else | |
| c3="${c3_left}" | |
| fi | |
| printf '%b' " ${GRAY}╰${RST} ${YEL}${ICON_COST}${RST} ${c3}" | |
| fi | |
| pad $(( sep_col - w3 )) | |
| if [ -n "$pace_value" ]; then | |
| printf '%b' " ${GRAY}┃${RST} " | |
| if [ "$gauge_w" -gt 0 ]; then | |
| pace_gauge "$pace_value" "$gauge_w" | |
| printf " " | |
| fi | |
| if [ "$pace_value" -ge 80 ] && [ "$pace_value" -le 120 ] 2>/dev/null; then pace_color="${TC_MINT}" | |
| elif [ "$pace_value" -ge 50 ] && [ "$pace_value" -le 160 ] 2>/dev/null; then pace_color="${TC_GOLD}" | |
| else pace_color="${TC_ROSE}"; fi | |
| printf "${pace_color}Pc %s%%${RST}" "$pace_value" | |
| if [ "$show_annotations" = true ] && [ -n "$pace_label" ]; then | |
| printf " ${ITALIC}${GRAY}▸ %s${RST}" "$pace_label" | |
| fi | |
| fi | |
| printf "\n" | |
| # ---- Terminal title ---- | |
| project_name="" | |
| if [ -n "$cwd_path" ]; then | |
| project_name=$(basename "$cwd_path") | |
| fi | |
| title="${project_name}" | |
| [ -n "$git_status_str" ] && title="${title} | ${git_status_str}" | |
| [ -n "$brainstorm_summary" ] && title="${title} | ${brainstorm_summary}" | |
| if [ -n "$KONSOLE_DBUS_SESSION" ]; then | |
| konsole_svc=$(qdbus6 2>/dev/null | grep konsole | head -1 | tr -d ' ') | |
| if [ -n "$konsole_svc" ]; then | |
| qdbus6 "$konsole_svc" "$KONSOLE_DBUS_SESSION" \ | |
| org.kde.konsole.Session.setTitle 1 "$title" 2>/dev/null || true | |
| fi | |
| else | |
| printf "\033]0;%s\007" "$title" >&2 | |
| fi | |
| exit 0 |
Author
N0K0
commented
Apr 6, 2026
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment