diff --git a/scripts/claude-statusline-command.sh b/scripts/claude-statusline-command.sh new file mode 100755 index 0000000..671dc06 --- /dev/null +++ b/scripts/claude-statusline-command.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +# CANONICAL COPY of the Claude Code statusline. Deployed to (and read from): +# +# ~/.claude/statusline-command.sh <- the LIVE path CC actually runs +# +# Install / update after editing here: +# cp scripts/claude-statusline-command.sh ~/.claude/statusline-command.sh +# +# Copies, not symlinks โ€” same rule as stacks/: this tree is intent, the live +# path is reality, and they diverge until someone deploys. Diff them with +# diff -u scripts/claude-statusline-command.sh ~/.claude/statusline-command.sh +# +# It is version-controlled here because the althing v3 cutover broke it in a way +# that was invisible: the segment gated on `command -v althing-cli`, a binary the +# cutover deleted, so the ๐Ÿ“ฌ badge and ๐Ÿ”” bell silently vanished for every session +# on the box. With 71 of 73 handles pull-only, that badge is the ONLY out-of-band +# signal telling a session with no armed waiter that it has mail โ€” a dead +# statusline made a working bus look like an empty one. +# +# Smoke test (CC pipes session JSON on stdin): +# echo '{"model":{"display_name":"opus"},"workspace":{"current_dir":"/tmp"},"cwd":"/tmp"}' \ +# | ALTHING_HANDLE= bash scripts/claude-statusline-command.sh +# want: a leading `๐Ÿ“ฌ N`; and with an unreachable post office, degradation in +# ~2s rather than a hang. +# Claude Code statusline. Layout: +# [๐Ÿ“ฌN] [๐Ÿ””/๐Ÿ”•] | โއ * โ†‘ | | ctx: | $ | 5h:% 7d:% +# ctx% and rate-limit %s are threshold-colored: green <60, yellow 60-90, red >90. +# All segments degrade gracefully (missing tool / non-git dir / no handle => segment omitted). +input=$(cat) + +# --- threshold color: $1=numeric pct, $2=display text -> colored text --- +color_pct() { + local p="$1" txt="$2" c + if awk "BEGIN{exit !($p < 60)}"; then c=$'\033[32m' # green <60 + elif awk "BEGIN{exit !($p > 90)}"; then c=$'\033[31m' # red >90 + else c=$'\033[33m' # yellow 60-90 + fi + printf '%s%s\033[0m' "$c" "$txt" +} + +# --- reset countdown: $1=unix ts -> "1d3h"/"3h20m"/"45m" (2-unit; "now"/empty edge) --- +reset_in() { + local ts="$1" now delta d h m + [ -z "$ts" ] && return + now=$(date +%s) + delta=$(( ts - now )) + [ "$delta" -le 0 ] && { printf 'now'; return; } + if [ "$delta" -ge 86400 ]; then + d=$(( delta / 86400 )); h=$(( (delta % 86400) / 3600 )) + if [ "$h" -gt 0 ]; then printf '%dd%dh' "$d" "$h"; else printf '%dd' "$d"; fi + elif [ "$delta" -ge 3600 ]; then + h=$(( delta / 3600 )); m=$(( (delta % 3600) / 60 )) + if [ "$m" -gt 0 ]; then printf '%dh%dm' "$h" "$m"; else printf '%dh' "$h"; fi + else + printf '%dm' $(( delta / 60 )) + fi +} + +# --- one jq pass for every payload field --- +# \x1f (unit separator) delimiter, NOT tab: tab is IFS-whitespace so `read` would +# collapse consecutive tabs and shift every field after an empty one (e.g. a +# session with no rate_limits). \x1f is non-whitespace -> empty fields preserved. +IFS=$'\x1f' read -r model used_pct input_tok five_pct week_pct cwd fast cost_usd model_id five_reset week_reset < <( + printf '%s' "$input" | jq -r '[ + (.model.display_name // "unknown"), + (.context_window.used_percentage // ""), + (.context_window.total_input_tokens // 0), + (.rate_limits.five_hour.used_percentage // ""), + (.rate_limits.seven_day.used_percentage // ""), + (.cwd // .workspace.current_dir // ""), + (.fast_mode // false), + (.cost.total_cost_usd // 0), + (.model.id // ""), + (.rate_limits.five_hour.resets_at // ""), + (.rate_limits.seven_day.resets_at // "") + ] | map(tostring) | join("")' +) +[ -z "$model" ] && model="unknown" + +# --- model (compact) + fast-mode flag --- +model="${model%% (*}" # "Opus 4.8 (1M context)" -> "Opus 4.8" +[ "$fast" = "true" ] && model="โšก$model" + +# --- context % (colored) + absolute input tokens --- +if [ -n "$used_pct" ]; then + ctx_seg=$(color_pct "$used_pct" "ctx:$(printf '%.0f%%' "$used_pct")") +else + ctx_seg="ctx:--" +fi +if [ "${input_tok:-0}" -ge 1000 ] 2>/dev/null; then + toks=$(awk "BEGIN{printf \"%.0fk\", ${input_tok}/1000}") +else + toks="${input_tok:-0}" +fi + +# --- per-session cost (Claude Code's own cache/model-aware accounting) --- +# adaptive precision: whole dollars once it's real money, cents when small. +cost=$(awk "BEGIN{c=${cost_usd:-0}; if(c>=100) printf \"%.0f\",c; else if(c>=10) printf \"%.1f\",c; else printf \"%.2f\",c}") + +# --- rate limits (each % colored on the same thresholds, + reset countdown) --- +_rl() { # $1=pct $2=label $3=reset_ts -> "