Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed
- **Statusline: staff-level overhaul.** Removed ~3,900 lines of dead code (an entire abandoned cache subsystem, superseded flat modules, rotted dev tools) and retired the background daemon — its output had no consumers and the OAuth refresh now fires directly from the render path, non-blocking. The whole tree is shellcheck-clean, usage percentages are now threshold-colored (green/yellow/red), the test suite was rebuilt against the live modules with shared assert helpers, glob discovery, and full /tmp isolation (9/9 suites), docs were consolidated into STATUSLINE.md and verified against the code, and CI gained a Linux shellcheck+test job.
- **Statusline: context moved next to the model.** Line 1 now renders the context-window percentage (`📊`) immediately after the model instead of after the git/worktree components, so token usage stays glanceable next to what's consuming it. Order is now model → context → directory → git → worktree → cost → session.

### Fixed
- **Statusline: account badge now shows without `CLAUDE_CODE_OAUTH_TOKEN`.** The `CLAUDE_STATUSLINE_ACCOUNT_LABEL` badge was gated on the OAuth-token env var also being set, so accounts switched via a separate credential store (`CLAUDE_SECURESTORAGE_CONFIG_DIR`) got no badge. The label is user-set display text and now renders whenever it's set, independent of how the account was selected. Per-account usage-cache isolation and background OAuth refresh remain keyed on `CLAUDE_CODE_OAUTH_TOKEN`.
- **Plugin manifests:** `.claude-plugin/plugin.json` still carried the pre-v5 marketing description (it had also sat at version 2.0.0 from February through July while `marketplace.json` advanced — the snapshot stale Claude Desktop installs were showing). Descriptions are aligned, and `make validate-plugins` now fails on any version or description drift between the two manifests so a partial bump can't ship again.
- **Statusline (post-review):** the background OAuth fetcher now holds a single-flight lock for the whole fetch, so overlapping renders can't stack concurrent calls against the rate-limited API; the tmux cache write reuses values the render already extracted instead of re-forking `cat`+`jq`; ISO timestamps with explicit UTC offsets parse to the correct epoch instead of being read as UTC; non-numeric values render as-is instead of `printf` garbage like `045%`; `hash_sha256` falls back to MD5 (then `default`) so per-account cache keys stay distinct on minimal systems; the error log is size-capped. The config surface was cut to the keys the code actually honors, `STATUSLINE_CONFIG_OVERRIDE` now really works, `configure_statusline.py` was rewritten to offer only working options (481→80 lines), and the dead `lib/tracking/` modules were removed (~550 lines).
- **Statusline:** `extract_json` returned success with empty output on a grep-fallback miss, making every `||` fallback chain over it unreachable (e.g. session-id lookup never tried `session_id`/`conversation_id`); `lib/display/components.sh` didn't source its own `core/json.sh` dependency; `cache_clear` could expand `rm -rf` against `/*` if its directory variable was ever empty.
Expand Down
2 changes: 1 addition & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ Real-time cost and usage tracking in your Claude Code prompt — computed fresh

**Example:**
```
🤖 Opus 4.5 │ 📁 ~/projects/cc-arsenal │ 🌿 main ● │ 📊 66% │ 💰 $3.169 │ ⏱️ 21m
🤖 Opus 4.5 │ 📊 66% │ 📁 ~/projects/cc-arsenal │ 🌿 main ● │ 💰 $3.169 │ ⏱️ 21m
🔄 5h: 16% → 21:00 │ 📅 7d: 39% → Dec 31 21:00
```

Expand Down
2 changes: 1 addition & 1 deletion integrations/claude-code/statusline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A bash+jq statusline for Claude Code showing model, git status, cost, context us
## Example

```
🤖 Opus │ 📁 cc-arsenal │ 🌿 main ● │ 📊 22% │ 💰 $0.043 │ ⏱️ 21m
🤖 Opus │ 📊 22% │ 📁 cc-arsenal │ 🌿 main ● │ 💰 $0.043 │ ⏱️ 21m
🔄 5h: 16% → 21:00 │ 📅 7d: 39% → Dec 31 21:00
```

Expand Down
14 changes: 7 additions & 7 deletions integrations/claude-code/statusline/STATUSLINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Shows model, git, cost, context, and usage-window information in your Claude Cod

The statusline renders two lines:

**Line 1** — model, directory, git, context, cost, session duration:
**Line 1** — model, context, directory, git, cost, session duration:
```
🤖 Opus │ 📁 cc-arsenal │ 🌿 main ● │ 📊 22% │ 💰 $0.043 │ ⏱️ 21m
🤖 Opus │ 📊 22% │ 📁 cc-arsenal │ 🌿 main ● │ 💰 $0.043 │ ⏱️ 21m
```

**Line 2** — usage-window details (only when rate-limit data is available):
Expand All @@ -28,10 +28,10 @@ The statusline renders two lines:

Line 1 components, in order:
- 🤖 **Model** — name/version, from `model.display_name` or `model.id`
- 📊 **Context** — `context_window.used_percentage`, rounded
- 📁 **Directory** — current directory, `~`-shortened
- 🌿 **Git** — branch, with `●` for uncommitted changes
- 🌳 **Worktree** — worktree name, shown only when in a worktree
- 📊 **Context** — `context_window.used_percentage`, rounded
- 💰 **Cost** — `cost.total_cost_usd` for the session
- 📝 **Lines changed** — `+added/-removed`; **disabled by default**, enable via config
- ⏱️ **Session duration** — from `cost.total_duration_ms`; hidden until a session has run
Expand Down Expand Up @@ -146,13 +146,13 @@ To point the script at a different config file entirely (e.g. for previewing a c
| `STATUSLINE_DEBUG` | `1` to log raw input JSON and context-window extraction to `/tmp/claude_statusline_debug.log` |
| `STATUSLINE_PERF` | `1` to print `[perf] <ms>ms` to stderr after each run |
| `CLAUDE_CODE_OAUTH_TOKEN` | Selects a specific account's OAuth token and enables the multi-account code path — see below |
| `CLAUDE_STATUSLINE_ACCOUNT_LABEL` | Badge text shown on line 2 when `CLAUDE_CODE_OAUTH_TOKEN` is also set; unset means no badge |
| `CLAUDE_STATUSLINE_ACCOUNT_LABEL` | Badge text shown on line 2 whenever it's set; unset means no badge. Independent of `CLAUDE_CODE_OAUTH_TOKEN` — works with any account-switch mechanism (env token, `CLAUDE_SECURESTORAGE_CONFIG_DIR`, etc.) |
| `OAUTH_USAGE_CACHE_FILE` | Overrides the OAuth usage cache path (otherwise auto-derived, see below) |
| `OAUTH_USAGE_CACHE_TTL` | OAuth usage cache TTL in seconds (default `300`) |

## Multiple accounts

The statusline supports more than one Claude account on the same machine, driven entirely by `CLAUDE_CODE_OAUTH_TOKEN`.
The statusline supports more than one Claude account on the same machine. Per-account usage isolation (cache, lock, and backoff state) is keyed on `CLAUDE_CODE_OAUTH_TOKEN`; the account badge is independent of it and renders from `CLAUDE_STATUSLINE_ACCOUNT_LABEL` alone (see [Account badge](#account-badge)).

### Precedence

Expand All @@ -170,11 +170,11 @@ This means two accounts running statuslines concurrently (e.g. in separate tmux

### Account badge

Set `CLAUDE_STATUSLINE_ACCOUNT_LABEL` (e.g. `work`, `personal`) alongside `CLAUDE_CODE_OAUTH_TOKEN` to show a badge on line 2:
Set `CLAUDE_STATUSLINE_ACCOUNT_LABEL` (e.g. `work`, `personal`) to show a badge on line 2:
```
👤 work │ 🔄 5h: 16% → 21:00
```
Both must be set — an account without a label shows no badge (it still uses that account's isolated caches, just silently).
The label is independent of how you switched accounts — it renders whenever it's set, whether you select the account via `CLAUDE_CODE_OAUTH_TOKEN` or a separate credential store (`CLAUDE_SECURESTORAGE_CONFIG_DIR=~/.claude-alt`). Unset means no badge. Note the per-account usage-cache isolation and background OAuth refresh are still keyed on `CLAUDE_CODE_OAUTH_TOKEN`; with the securestorage path, line-2 usage comes from the `rate_limits` Claude Code sends on stdin for that account.

### Refresh behavior

Expand Down
8 changes: 4 additions & 4 deletions integrations/claude-code/statusline/lib/display/builder.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ build_line_one() {
comp=$(get_model_component "$MODEL")
[[ -n "$comp" ]] && components+=("$comp")

# Context - kept right after the model so token usage stays glanceable
comp=$(get_context_component "$USED_PERCENTAGE")
[[ -n "$comp" ]] && components+=("$comp")

# Directory
comp=$(get_directory_component "$current_dir")
[[ -n "$comp" ]] && components+=("$comp")
Expand All @@ -138,10 +142,6 @@ build_line_one() {
comp=$(get_worktree_component "$NATIVE_WORKTREE_NAME")
[[ -n "$comp" ]] && components+=("$comp")

# Context
comp=$(get_context_component "$USED_PERCENTAGE")
[[ -n "$comp" ]] && components+=("$comp")

# Cost
comp=$(get_cost_component "$COST_USD")
[[ -n "$comp" ]] && components+=("$comp")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,10 @@ get_session_component() {
# Display the active account label when multi-account mode is configured
# Usage: get_account_component
get_account_component() {
if [[ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" || -z "${CLAUDE_STATUSLINE_ACCOUNT_LABEL:-}" ]]; then
# The label is user-set display text — show it whenever it's set, regardless
# of how the account was switched (CLAUDE_CODE_OAUTH_TOKEN env override, or a
# separate credential store via CLAUDE_SECURESTORAGE_CONFIG_DIR, etc.)
if [[ -z "${CLAUDE_STATUSLINE_ACCOUNT_LABEL:-}" ]]; then
return 0
fi

Expand Down
9 changes: 5 additions & 4 deletions integrations/claude-code/statusline/tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,13 @@ run_integration_test() {
echo -e "${GREEN}✅ Integration test: Statusline executed successfully${RESET}"
echo "Output: $output"

# Basic validation that output contains expected elements
if [[ "$output" == *"🤖"* && "$output" == *"📁"* && "$output" == *"📊"* ]]; then
echo -e "${GREEN}✅ Integration test: Output contains expected components${RESET}"
# Validate expected components AND their order: model → context → directory
# (context is kept right after the model on line 1)
if [[ "$output" == *"🤖"*"📊"*"📁"* ]]; then
echo -e "${GREEN}✅ Integration test: Output contains expected components in order${RESET}"
return 0
else
echo -e "${RED}❌ Integration test: Output missing expected components${RESET}"
echo -e "${RED}❌ Integration test: Output missing expected components or wrong order${RESET}"
return 1
fi
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,16 +319,17 @@ test_account_component() {

local result

# Gating: badge requires BOTH the env token and the label - any other
# combination must produce no output at all
# Gating: the badge is driven solely by the label - it renders whenever the
# label is set, independent of the OAuth token (so it works with any
# account-switch mechanism, e.g. CLAUDE_SECURESTORAGE_CONFIG_DIR)
result=$( (unset CLAUDE_CODE_OAUTH_TOKEN CLAUDE_STATUSLINE_ACCOUNT_LABEL; get_account_component) )
assert_equals "" "$result" "no badge when neither token nor label is set"

result=$( (unset CLAUDE_STATUSLINE_ACCOUNT_LABEL; export CLAUDE_CODE_OAUTH_TOKEN="tok"; get_account_component) )
assert_equals "" "$result" "no badge with token but no label"

result=$( (unset CLAUDE_CODE_OAUTH_TOKEN; export CLAUDE_STATUSLINE_ACCOUNT_LABEL="work"; get_account_component) )
assert_equals "" "$result" "no badge with label but no token"
result=$( (unset CLAUDE_CODE_OAUTH_TOKEN; export CLAUDE_STATUSLINE_ACCOUNT_LABEL="work" STATUSLINE_DISPLAY_MODE=emoji; get_account_component) )
assert_contains "👤 work" "$(strip_ansi "$result")" "badge renders with label but no token (securestorage switch)"

# Rendering: label only, never the token or a hash
result=$( (export CLAUDE_CODE_OAUTH_TOKEN="secret-tok" CLAUDE_STATUSLINE_ACCOUNT_LABEL="work" STATUSLINE_DISPLAY_MODE=emoji; get_account_component) )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ test_failsoft_render() {
echo "$stdin_json" | bash "$STATUSLINE_SH" ) 2>/dev/null )
assert_contains "testacct" "$output" "account badge renders when both token and label are set"

# Label set, NO token - securestorage/alt-account switch (CLAUDE_SECURESTORAGE_CONFIG_DIR):
# the badge is user-set display text and must render without CLAUDE_CODE_OAUTH_TOKEN
output=$( ( export PATH="$MOCK_BIN_DIR:$PATH"
unset OAUTH_USAGE_CACHE_FILE CLAUDE_CODE_OAUTH_TOKEN
export CLAUDE_STATUSLINE_ACCOUNT_LABEL="altacct"
echo "$stdin_json" | bash "$STATUSLINE_SH" ) 2>/dev/null )
assert_contains "altacct" "$output" "account badge renders with label set but no token (securestorage switch)"

# Both unset - default single-account rendering, no label
output=$( ( unset OAUTH_USAGE_CACHE_FILE CLAUDE_CODE_OAUTH_TOKEN CLAUDE_STATUSLINE_ACCOUNT_LABEL
echo "$stdin_json" | bash "$STATUSLINE_SH" ) 2>/dev/null )
Expand Down
12 changes: 6 additions & 6 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.