feat: complete Rust port of claude-statusline
Port the entire 2236-line bash statusline script to Rust. Implements all 25 sections, 3-phase layout engine (render, priority drop, flex/justify), file-based caching with flock, 9-level terminal width detection, trend sparklines, and deep-merge JSON config. Release binary: 864K with LTO. Render time: <1ms warm. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
801
README.md
Normal file
801
README.md
Normal file
@@ -0,0 +1,801 @@
|
||||
# claude-statusline
|
||||
|
||||
A configurable, multi-line status line for Claude Code with priority-based flex layout, VCS auto-detection (git/jj), spacer-based positioning, custom command sections, and per-section formatting.
|
||||
|
||||
```
|
||||
[Opus] | Anthropic | gitlore master* +1-0
|
||||
[============----] 58% | $0.12 | +156 -23 | 14m | 7 tools (Edit)
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **bash 4+** (macOS ships bash 3 — `brew install bash`)
|
||||
- **jq** (`brew install jq` / `apt install jq`)
|
||||
- **Claude Code** with status line support
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/tayloreernisse/claude-statusline ~/projects/claude-statusline
|
||||
cd ~/projects/claude-statusline
|
||||
./install.sh
|
||||
```
|
||||
|
||||
The installer creates two symlinks:
|
||||
|
||||
```
|
||||
~/.claude/statusline.sh -> ~/projects/claude-statusline/statusline.sh
|
||||
~/.claude/statusline.json -> ~/projects/claude-statusline/statusline.json
|
||||
```
|
||||
|
||||
### Configure Claude Code
|
||||
|
||||
Add to `~/.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusLine": "~/.claude/statusline.sh"
|
||||
}
|
||||
```
|
||||
|
||||
Restart Claude Code to see the status line.
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
Claude Code (every ~300ms)
|
||||
|
|
||||
| pipes JSON to stdin
|
||||
v
|
||||
statusline.sh
|
||||
|
|
||||
+-- reads ~/.claude/statusline.json (config)
|
||||
+-- parses stdin JSON (model, cost, context, etc.)
|
||||
+-- caches expensive ops (VCS, beads, system load)
|
||||
+-- resolves layout (preset or custom array)
|
||||
+-- renders sections with ANSI colors
|
||||
+-- applies priority-based flex layout
|
||||
|
|
||||
| stdout (ANSI-colored lines)
|
||||
v
|
||||
Claude Code renders status bar
|
||||
```
|
||||
|
||||
The stdin JSON from Claude Code contains:
|
||||
- `model` — current model info (id, display name)
|
||||
- `cost` — accumulated cost, duration, lines changed, tool uses
|
||||
- `context_window` — token counts, used percentage, cache stats
|
||||
- `workspace` — project directory
|
||||
- `version` — Claude Code version
|
||||
- `output_style` — current output style
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
Config lives at `~/.claude/statusline.json` (or set `CLAUDE_STATUSLINE_CONFIG` env var).
|
||||
|
||||
### Global Options
|
||||
|
||||
```json
|
||||
{
|
||||
"global": {
|
||||
"separator": " | ",
|
||||
"justify": "left",
|
||||
"vcs": "auto",
|
||||
"width_margin": 4,
|
||||
"cache_dir": "/tmp/claude-sl-{session_id}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `separator` | `" \| "` | Text between sections. In `left` mode, used as-is. In `spread`/`space-between`, the non-space characters (e.g. `\|`) stay as visual anchors with extra padding around them. |
|
||||
| `justify` | `"left"` | How sections distribute across terminal width. See [Justify Modes](#justify-modes). |
|
||||
| `width` | (auto) | Explicit terminal width in columns. If omitted, auto-detection walks the process tree to find an ancestor with a real TTY, falling back to `stty` via `/dev/tty`, `COLUMNS`, `tput cols`, or 120. |
|
||||
| `width_margin` | `4` | Columns to subtract from the detected width. Accounts for terminal multiplexer borders (Zellij/tmux) or Claude Code UI chrome that reduce the actual visible area. |
|
||||
| `vcs` | `"auto"` | VCS detection: `auto`, `git`, `jj`, `none` |
|
||||
| `cache_dir` | `/tmp/claude-sl-{session_id}` | Cache directory template. `{session_id}` is replaced at runtime. |
|
||||
|
||||
### Layout System
|
||||
|
||||
The `layout` field controls which sections appear and on which lines.
|
||||
|
||||
**Using a preset:**
|
||||
```json
|
||||
{ "layout": "standard" }
|
||||
```
|
||||
|
||||
**Using a custom layout:**
|
||||
```json
|
||||
{
|
||||
"layout": [
|
||||
["model", "provider", "project", "spacer", "vcs"],
|
||||
["context_bar", "cost", "duration"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Each inner array is one line. Section IDs in the array are rendered left-to-right, separated by the global separator.
|
||||
|
||||
### Spacer Sections
|
||||
|
||||
Use `spacer` (or `_spacer1`, `_spacer2`, etc. for multiple spacers) as a virtual section that expands to fill remaining width. This pushes sections apart without using `justify: "spread"`.
|
||||
|
||||
```json
|
||||
{
|
||||
"layout": [
|
||||
["model", "provider", "spacer", "vcs"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output (80 columns):
|
||||
```
|
||||
[Opus] | Anthropic master* +1-0
|
||||
```
|
||||
|
||||
Spacers have `flex: true` by default and suppress the separator on adjacent sides. Use multiple unique spacer IDs (`_spacer1`, `_spacer2`) if you need more than one on a line.
|
||||
|
||||
### Built-in Presets
|
||||
|
||||
**standard** (2 lines):
|
||||
```
|
||||
Line 1: model | provider | project | spacer | vcs
|
||||
Line 2: context_bar | cost | lines_changed | duration | tools
|
||||
```
|
||||
|
||||
**dense** (1 line):
|
||||
```
|
||||
Line 1: model | provider | project | vcs | context_bar | cost | duration
|
||||
```
|
||||
|
||||
**verbose** (3 lines):
|
||||
```
|
||||
Line 1: model | provider | project | vcs | beads
|
||||
Line 2: context_bar | tokens_raw | cache_efficiency | cost | cost_velocity
|
||||
Line 3: lines_changed | duration | tools | turns | load | version
|
||||
```
|
||||
|
||||
You can define custom presets in the `presets` object and reference them by name.
|
||||
|
||||
### Section Configuration
|
||||
|
||||
Each section supports these common properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"flex": false,
|
||||
"min_width": 0,
|
||||
"prefix": "",
|
||||
"suffix": "",
|
||||
"pad": 0,
|
||||
"align": "left",
|
||||
"color": "dim"
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `enabled` | boolean | Whether the section renders at all |
|
||||
| `priority` | 1/2/3 | Display priority (see Flex Layout below) |
|
||||
| `flex` | boolean | Whether section expands to fill remaining width |
|
||||
| `min_width` | integer | Minimum character width |
|
||||
| `prefix` | string | Text prepended to section output |
|
||||
| `suffix` | string | Text appended to section output |
|
||||
| `pad` | integer | Pad output to this minimum width |
|
||||
| `align` | `left`/`right`/`center` | Alignment within padded width |
|
||||
| `color` | color name | Override the section's color (see [Color Names](#color-names))
|
||||
|
||||
### Per-Section Formatting Examples
|
||||
|
||||
**Add brackets around the project name:**
|
||||
```json
|
||||
{
|
||||
"sections": {
|
||||
"project": { "prefix": "[", "suffix": "]" }
|
||||
}
|
||||
}
|
||||
```
|
||||
Output: `[myproject]`
|
||||
|
||||
**Right-align cost in a fixed-width column:**
|
||||
```json
|
||||
{
|
||||
"sections": {
|
||||
"cost": { "pad": 8, "align": "right" }
|
||||
}
|
||||
}
|
||||
```
|
||||
Output: ` $0.12` (padded to 8 chars)
|
||||
|
||||
**Override a section's default color:**
|
||||
```json
|
||||
{
|
||||
"sections": {
|
||||
"duration": { "color": "cyan" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Flex Layout Deep Dive
|
||||
|
||||
### Priority Drop
|
||||
|
||||
When a line is wider than the terminal:
|
||||
|
||||
1. Drop `priority: 3` sections, right-to-left
|
||||
2. Still too wide? Drop `priority: 2`, right-to-left
|
||||
3. `priority: 1` sections never drop
|
||||
|
||||
**Example at 120 columns** (everything fits):
|
||||
```
|
||||
[Opus] | Anthropic | gitlore | master* | [======----] 58% | $0.12 | +156 -23 | 14m | 7 tools
|
||||
```
|
||||
|
||||
**Same content at 80 columns** (priority 3 dropped, priority 2 trimmed):
|
||||
```
|
||||
[Opus] | gitlore | master* | [======----] 58% | $0.12
|
||||
```
|
||||
|
||||
### Flex Expansion
|
||||
|
||||
If a section has `flex: true` and there's remaining width after rendering all sections, the flex section expands to fill the gap. For `context_bar`, this means a wider progress bar. For spacers, they expand to push adjacent sections apart. For other sections, it pads with spaces.
|
||||
|
||||
One flex section per line wins. Spacers take priority over non-spacer flex sections.
|
||||
|
||||
**80 columns with flex context_bar:**
|
||||
```
|
||||
[Opus] | gitlore | master* | [================----] 58% | $0.12
|
||||
```
|
||||
|
||||
**80 columns with spacer (pushes VCS to right):**
|
||||
```
|
||||
[Opus] | gitlore master* +1-0
|
||||
```
|
||||
|
||||
### Justify Modes
|
||||
|
||||
The `global.justify` setting controls how sections distribute across the full terminal width. This is independent of priority drop — sections are dropped first if needed, then the remaining sections are distributed.
|
||||
|
||||
**`"left"` (default)** — Pack sections left with fixed-width separators. Use `flex: true` on a section to fill remaining space.
|
||||
```
|
||||
[Opus] | Anthropic | gitlore | master*
|
||||
```
|
||||
|
||||
**`"spread"`** — Distribute extra space evenly into all gaps. The separator's visible characters (e.g. `|`) stay as anchors, padded with spaces on both sides. Every line fills the full terminal width.
|
||||
```
|
||||
[Opus] | Anthropic | gitlore | master*
|
||||
```
|
||||
|
||||
**`"space-between"`** — Same distribution as `spread` for terminal output (first section starts at column 0, last section ends at the terminal edge).
|
||||
|
||||
When justify is `"spread"` or `"space-between"`, the `flex` property on individual sections is ignored — the entire line spreads instead of one section expanding.
|
||||
|
||||
The separator character still matters: `" | "` produces gaps like ` | `, while `" :: "` produces ` :: `, and `" "` (pure spaces) produces invisible gaps.
|
||||
|
||||
## Section Reference
|
||||
|
||||
### model
|
||||
**Source:** stdin JSON `model.display_name` or parsed from `model.id`
|
||||
|
||||
Shows the current model name in bold brackets. Falls back to parsing "Opus"/"Sonnet"/"Haiku" from the model ID.
|
||||
|
||||
```
|
||||
[Opus]
|
||||
```
|
||||
|
||||
### provider
|
||||
**Source:** pattern match on `model.id`
|
||||
|
||||
Detects the API provider from the model identifier.
|
||||
|
||||
| Pattern | Provider |
|
||||
|---------|----------|
|
||||
| `us.anthropic.*`, `anthropic.*` | Bedrock |
|
||||
| `*@YYYYMMDD` | Vertex |
|
||||
| `claude-*` | Anthropic |
|
||||
|
||||
Empty if provider can't be determined.
|
||||
|
||||
### project
|
||||
**Source:** `basename` of `workspace.project_dir`
|
||||
|
||||
Shows the current project directory name.
|
||||
|
||||
### vcs
|
||||
**Source:** git/jj CLI (cached)
|
||||
|
||||
Auto-detects VCS by checking for `.jj/` first, then `.git/`. Override with `vcs.prefer`.
|
||||
|
||||
```json
|
||||
{
|
||||
"vcs": {
|
||||
"prefer": "auto",
|
||||
"show_ahead_behind": true,
|
||||
"show_dirty": true,
|
||||
"ttl": { "branch": 3, "dirty": 5, "ahead_behind": 30 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**git output:** `master* +1-0` (branch, dirty indicator, ahead/behind)
|
||||
|
||||
**jj output:** `feature-branch*` (bookmark or short change ID, dirty indicator)
|
||||
|
||||
Each piece has its own cache TTL — branch name barely changes, dirty status changes often, ahead/behind is expensive.
|
||||
|
||||
### beads
|
||||
**Source:** `br` CLI (cached)
|
||||
|
||||
Shows current in-progress bead and ready count. Requires `br` to be installed.
|
||||
|
||||
```json
|
||||
{
|
||||
"beads": {
|
||||
"show_wip": true,
|
||||
"show_ready_count": true,
|
||||
"ttl": 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
br-47 wip | 3 ready
|
||||
```
|
||||
|
||||
### context_bar
|
||||
**Source:** `context_window.used_percentage`
|
||||
|
||||
Visual progress bar of context window usage with color thresholds.
|
||||
|
||||
```json
|
||||
{
|
||||
"context_bar": {
|
||||
"flex": true,
|
||||
"bar_width": 10,
|
||||
"thresholds": { "warn": 50, "danger": 70, "critical": 85 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Range | Color |
|
||||
|-------|-------|
|
||||
| 0-49% | Green |
|
||||
| 50-69% | Yellow |
|
||||
| 70-84% | Red |
|
||||
| 85%+ | Bold Red |
|
||||
|
||||
### context_usage
|
||||
**Source:** `context_window.total_input_tokens`, `total_output_tokens`, `max_tokens`, `used_percentage`
|
||||
|
||||
Shows context usage as "used/total" (e.g., `125k/200k`). The "used" part is colored based on thresholds, while "total" is always dim.
|
||||
|
||||
```json
|
||||
{
|
||||
"context_usage": {
|
||||
"enabled": true,
|
||||
"thresholds": { "warn": 50, "danger": 70, "critical": 85 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output: `125k/200k` (green when below 50%, yellow 50-69%, red 70-84%, bold red 85%+)
|
||||
|
||||
If `max_tokens` is not provided in the input, it's calculated from the percentage.
|
||||
|
||||
### tokens_raw
|
||||
**Source:** `context_window.total_input_tokens`, `total_output_tokens`
|
||||
|
||||
Raw token counts in human-readable format.
|
||||
|
||||
```json
|
||||
{ "tokens_raw": { "format": "{input}in/{output}out" } }
|
||||
```
|
||||
|
||||
```
|
||||
115kin/8.5kout
|
||||
```
|
||||
|
||||
### cache_efficiency
|
||||
**Source:** `cache_read_input_tokens`, `cache_creation_input_tokens`
|
||||
|
||||
Percentage of cache reads vs total cache operations.
|
||||
|
||||
```
|
||||
cache:33%
|
||||
```
|
||||
|
||||
### cost
|
||||
**Source:** `cost.total_cost_usd`
|
||||
|
||||
Accumulated session cost with color thresholds.
|
||||
|
||||
```json
|
||||
{
|
||||
"cost": {
|
||||
"thresholds": { "warn": 0.25, "danger": 0.50, "critical": 1.00 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### cost_velocity
|
||||
**Source:** cost / duration
|
||||
|
||||
Cost per minute of session time.
|
||||
|
||||
```
|
||||
$0.08/m
|
||||
```
|
||||
|
||||
### token_velocity
|
||||
**Source:** tokens / duration
|
||||
|
||||
Total tokens (input + output) consumed per minute. Useful for understanding how fast you're burning through context.
|
||||
|
||||
```
|
||||
14.5ktok/m
|
||||
```
|
||||
|
||||
In narrow terminals, abbreviated to `14.5kt/m`.
|
||||
|
||||
### lines_changed
|
||||
**Source:** `cost.total_lines_added`, `total_lines_removed`
|
||||
|
||||
```
|
||||
+156 -23
|
||||
```
|
||||
|
||||
Green for additions, red for removals.
|
||||
|
||||
### duration
|
||||
**Source:** `cost.total_duration_ms`
|
||||
|
||||
Human-readable session duration: `14m`, `1h23m`, `45s`.
|
||||
|
||||
### tools
|
||||
**Source:** `cost.total_tool_uses`, `cost.last_tool_name`
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"show_last_name": true,
|
||||
"ttl": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
7 tools (Edit)
|
||||
```
|
||||
|
||||
### turns
|
||||
**Source:** `cost.total_turns`
|
||||
|
||||
```
|
||||
12 turns
|
||||
```
|
||||
|
||||
### load
|
||||
**Source:** system load average (macOS: `sysctl`, Linux: `/proc/loadavg`)
|
||||
|
||||
```json
|
||||
{ "load": { "ttl": 10 } }
|
||||
```
|
||||
|
||||
```
|
||||
load:2.1
|
||||
```
|
||||
|
||||
### version
|
||||
**Source:** `version` from stdin JSON
|
||||
|
||||
```
|
||||
v1.0.80
|
||||
```
|
||||
|
||||
### time
|
||||
**Source:** `date` command
|
||||
|
||||
```json
|
||||
{ "time": { "format": "%H:%M" } }
|
||||
```
|
||||
|
||||
### output_style
|
||||
**Source:** `output_style.name`
|
||||
|
||||
Shows the current Claude Code output style (e.g., "learning", "concise").
|
||||
|
||||
### hostname
|
||||
**Source:** `hostname -s`
|
||||
|
||||
Short hostname of the machine.
|
||||
|
||||
## Custom Commands
|
||||
|
||||
Add custom sections that execute shell commands and display the result.
|
||||
|
||||
```json
|
||||
{
|
||||
"custom": [
|
||||
{
|
||||
"id": "ollama",
|
||||
"label": "ollama",
|
||||
"command": "pgrep -x ollama >/dev/null && echo 'up' || echo 'down'",
|
||||
"ttl": 30,
|
||||
"priority": 3,
|
||||
"color": {
|
||||
"match": { "up": "green", "down": "dim" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then reference by `id` in any layout line:
|
||||
|
||||
```json
|
||||
{ "layout": [["model", "project", "ollama"]] }
|
||||
```
|
||||
|
||||
### Custom Command Fields
|
||||
|
||||
| Field | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `id` | yes | — | Unique identifier, used in layout arrays |
|
||||
| `label` | no | same as `id` | Display prefix before the value |
|
||||
| `command` | yes | — | Shell command. stdout is captured. |
|
||||
| `ttl` | no | 30 | Cache TTL in seconds |
|
||||
| `priority` | no | 2 | Display priority (1/2/3) |
|
||||
| `flex` | no | false | Expand to fill remaining width |
|
||||
| `min_width` | no | 4 | Minimum character width |
|
||||
| `color.match` | no | — | Map output values to color names |
|
||||
| `default_color` | no | — | Fallback color when `color.match` doesn't match |
|
||||
| `prefix` | no | — | Text prepended to output |
|
||||
| `suffix` | no | — | Text appended to output |
|
||||
| `pad` | no | — | Pad output to this minimum width |
|
||||
| `align` | no | `left` | Alignment within padded width (`left`/`right`/`center`) |
|
||||
|
||||
### Color Names
|
||||
|
||||
Available colors: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `dim`, `bold`.
|
||||
|
||||
These work in three places:
|
||||
- `sections.<name>.color` — override a built-in section's color
|
||||
- `custom[].color.match` — map output values to colors
|
||||
- `custom[].default_color` — default color for custom commands (use instead of `color` to avoid conflict with `color.match`)
|
||||
|
||||
### Recipe Ideas
|
||||
|
||||
**Dev server status:**
|
||||
```json
|
||||
{
|
||||
"id": "devserver",
|
||||
"command": "curl -so /dev/null http://localhost:3000 && echo 'up' || echo 'down'",
|
||||
"ttl": 10,
|
||||
"priority": 3,
|
||||
"color": { "match": { "up": "green", "down": "red" } }
|
||||
}
|
||||
```
|
||||
|
||||
**Disk usage:**
|
||||
```json
|
||||
{
|
||||
"id": "disk",
|
||||
"label": "disk",
|
||||
"command": "df -h / | awk 'NR==2{print $5}'",
|
||||
"ttl": 120,
|
||||
"priority": 3
|
||||
}
|
||||
```
|
||||
|
||||
**CI status (via gh):**
|
||||
```json
|
||||
{
|
||||
"id": "ci",
|
||||
"label": "CI",
|
||||
"command": "gh run list --limit 1 --json conclusion -q '.[0].conclusion' 2>/dev/null || echo '?'",
|
||||
"ttl": 60,
|
||||
"priority": 3,
|
||||
"color": { "match": { "success": "green", "failure": "red", "?": "dim" } }
|
||||
}
|
||||
```
|
||||
|
||||
**Ollama status:**
|
||||
```json
|
||||
{
|
||||
"id": "ollama",
|
||||
"label": "ollama",
|
||||
"command": "pgrep -x ollama >/dev/null && echo 'up' || echo 'down'",
|
||||
"ttl": 30,
|
||||
"priority": 3,
|
||||
"color": { "match": { "up": "green", "down": "dim" } }
|
||||
}
|
||||
```
|
||||
|
||||
**Docker status:**
|
||||
```json
|
||||
{
|
||||
"id": "docker",
|
||||
"label": "docker",
|
||||
"command": "docker info >/dev/null 2>&1 && echo 'up' || echo 'down'",
|
||||
"ttl": 60,
|
||||
"priority": 3,
|
||||
"color": { "match": { "up": "green", "down": "dim" } }
|
||||
}
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
Expensive operations (VCS commands, beads queries, custom commands, system load) are cached in files under the cache directory.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. Each cached value has a key (e.g., `vcs_branch`) and a TTL in seconds
|
||||
2. On first call, the command runs and its stdout is written to `$CACHE_DIR/$key`
|
||||
3. On subsequent calls within the TTL, the cached file is read instead
|
||||
4. After TTL expires, the command runs again
|
||||
|
||||
### Cache Location
|
||||
|
||||
Default: `/tmp/claude-sl-{session_id}/`
|
||||
|
||||
The `{session_id}` template variable is replaced with the script's PID. Configure via `global.cache_dir`.
|
||||
|
||||
### TTLs by Section
|
||||
|
||||
| Section | Key | Default TTL |
|
||||
|---------|-----|-------------|
|
||||
| vcs (branch) | `vcs_branch` | 3s |
|
||||
| vcs (dirty) | `vcs_dirty` | 5s |
|
||||
| vcs (ahead/behind) | `vcs_ab` | 30s |
|
||||
| beads (wip) | `beads_wip` | 30s |
|
||||
| beads (ready) | `beads_ready` | 30s |
|
||||
| load | `load` | 10s |
|
||||
| custom commands | `custom_{id}` | per-command `ttl` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Script doesn't run
|
||||
|
||||
- Check the shebang: `head -1 ~/.claude/statusline.sh` should be `#!/usr/bin/env bash`
|
||||
- Verify it's executable: `ls -la ~/.claude/statusline.sh` (should show `-rwxr-xr-x`)
|
||||
- Fix: `chmod +x ~/.claude/statusline.sh`
|
||||
- Verify jq is installed: `which jq`
|
||||
- Test manually: `echo '{}' | ~/.claude/statusline.sh`
|
||||
|
||||
### No output / blank status line
|
||||
|
||||
- Test with mock data: `echo '{"model":{"id":"claude-opus-4-5-20251101","display_name":"Opus"},"cost":{"total_cost_usd":0.05}}' | ~/.claude/statusline.sh`
|
||||
- Check config is valid JSON: `jq . ~/.claude/statusline.json`
|
||||
- Check symlinks: `ls -la ~/.claude/statusline.sh ~/.claude/statusline.json`
|
||||
|
||||
### Stale data
|
||||
|
||||
- VCS branch stuck? TTL is 3s by default. Increase: `sections.vcs.ttl.branch`
|
||||
- Clear cache: `rm -rf /tmp/claude-sl-*/`
|
||||
|
||||
### Performance
|
||||
|
||||
The script runs every ~300ms. Most sections are free (parsed from stdin JSON). Expensive sections:
|
||||
|
||||
| Section | Cost | Mitigation |
|
||||
|---------|------|-----------|
|
||||
| vcs | git/jj subprocess | Cached with per-field TTLs |
|
||||
| beads | br subprocess | 30s TTL |
|
||||
| load | sysctl/proc read | 10s TTL |
|
||||
| custom | arbitrary command | User-configured TTL |
|
||||
|
||||
If status line feels sluggish, increase TTLs or disable expensive sections.
|
||||
|
||||
## Provider Detection
|
||||
|
||||
The `provider` section detects the API provider from the model ID:
|
||||
|
||||
| Model ID Pattern | Detected Provider |
|
||||
|-----------------|-------------------|
|
||||
| `us.anthropic.claude-*` | Bedrock |
|
||||
| `anthropic.claude-*` | Bedrock |
|
||||
| `claude-*-*@20251101` | Vertex |
|
||||
| `claude-opus-4-5-20251101` | Anthropic (direct) |
|
||||
|
||||
Detection is pure pattern matching on `model.id` — no credentials or API calls involved.
|
||||
|
||||
## Config Validation
|
||||
|
||||
A JSON Schema is provided at `schema.json` for editor autocomplete and validation.
|
||||
|
||||
**VSCode:** Add to your `statusline.json`:
|
||||
```json
|
||||
{
|
||||
"$schema": "./schema.json",
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
**CLI validation (with ajv):**
|
||||
```bash
|
||||
npx ajv validate -s schema.json -d statusline.json
|
||||
```
|
||||
|
||||
## Terminal Width Detection
|
||||
|
||||
Claude Code runs the status line script without a TTY, which makes detecting the terminal width non-trivial. The detection priority is:
|
||||
|
||||
1. **`global.width`** in config — explicit override
|
||||
2. **`CLAUDE_STATUSLINE_WIDTH`** env var
|
||||
3. **Process tree walk** — finds an ancestor process with a real TTY and runs `stty size` on it
|
||||
4. **`stty size < /dev/tty`** — works on some systems
|
||||
5. **`COLUMNS`** env var
|
||||
6. **`tput cols`**
|
||||
7. **Fallback: 120**
|
||||
|
||||
After detection, `global.width_margin` (default: 4) is subtracted to account for terminal multiplexer borders or UI chrome.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `CLAUDE_STATUSLINE_CONFIG` | Override config file path |
|
||||
| `CLAUDE_STATUSLINE_WIDTH` | Override terminal width (second priority after `global.width`) |
|
||||
| `COLUMNS` | Standard terminal width (lower priority) |
|
||||
|
||||
## CLI Flags
|
||||
|
||||
The script supports several flags for development and debugging:
|
||||
|
||||
```bash
|
||||
# Test your config with mock data (no Claude Code needed)
|
||||
./statusline.sh --test
|
||||
|
||||
# Get path to JSON schema for IDE autocomplete
|
||||
./statusline.sh --config-schema
|
||||
|
||||
# Debug internal state (width detection, theme, VCS, etc.)
|
||||
./statusline.sh --dump-state
|
||||
|
||||
# Show help
|
||||
./statusline.sh --help
|
||||
```
|
||||
|
||||
### --test Mode
|
||||
|
||||
Renders the status line with realistic mock data. Useful for:
|
||||
- Validating config changes without restarting Claude Code
|
||||
- Rapid iteration on layout and colors
|
||||
- Debugging section visibility
|
||||
|
||||
### --dump-state Mode
|
||||
|
||||
Outputs internal computed state as JSON:
|
||||
```json
|
||||
{
|
||||
"terminal": {"detected_width": 178, "effective_width": 174, "width_tier": "wide"},
|
||||
"responsive": {"enabled": true, "layout": "verbose"},
|
||||
"theme": "dark",
|
||||
"vcs": "git",
|
||||
"paths": {"config": "~/.claude/statusline.json", "cache_dir": "/tmp/claude-sl-abc123"}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The `examples/` directory contains ready-to-use configurations:
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `dense.json` | Single-line layout with compact context bar |
|
||||
| `verbose.json` | Three-line layout with all metrics |
|
||||
| `custom-commands.json` | Custom Ollama and Docker status indicators |
|
||||
|
||||
Copy an example to your config:
|
||||
```bash
|
||||
cp examples/verbose.json ~/.claude/statusline.json
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Reference in New Issue
Block a user