Use go-git for native git operations
Replace exec-based git calls with go-git/v6 library for ~5.6x speedup over shell version. Only pgrep remains as external call.
This commit is contained in:
265
statusline.md
Normal file
265
statusline.md
Normal file
@ -0,0 +1,265 @@
|
||||
# Status line configuration
|
||||
|
||||
> Create a custom status line for Claude Code to display contextual information
|
||||
|
||||
Make Claude Code your own with a custom status line that displays at the bottom
|
||||
of the Claude Code interface, similar to how terminal prompts (PS1) work in
|
||||
shells like Oh-my-zsh.
|
||||
|
||||
## Create a custom status line
|
||||
|
||||
You can either:
|
||||
|
||||
- Run `/statusline` to ask Claude Code to help you set up a custom status line.
|
||||
By default, it will try to reproduce your terminal's prompt, but you can
|
||||
provide additional instructions about the behavior you want to Claude Code,
|
||||
such as `/statusline show the model name in orange`
|
||||
|
||||
- Directly add a `statusLine` command to your `.claude/settings.json`:
|
||||
|
||||
```json theme={null}
|
||||
{
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh",
|
||||
"padding": 0 // Optional: set to 0 to let status line go to edge
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How it Works
|
||||
|
||||
- The status line is updated when the conversation messages update
|
||||
- Updates run at most every 300 ms
|
||||
- The first line of stdout from your command becomes the status line text
|
||||
- ANSI color codes are supported for styling your status line
|
||||
- Claude Code passes contextual information about the current session (model,
|
||||
directories, etc.) as JSON to your script via stdin
|
||||
|
||||
## JSON Input Structure
|
||||
|
||||
Your status line command receives structured data via stdin in JSON format:
|
||||
|
||||
```json theme={null}
|
||||
{
|
||||
"hook_event_name": "Status",
|
||||
"session_id": "abc123...",
|
||||
"transcript_path": "/path/to/transcript.json",
|
||||
"cwd": "/current/working/directory",
|
||||
"model": {
|
||||
"id": "claude-opus-4-1",
|
||||
"display_name": "Opus"
|
||||
},
|
||||
"workspace": {
|
||||
"current_dir": "/current/working/directory",
|
||||
"project_dir": "/original/project/directory"
|
||||
},
|
||||
"version": "1.0.80",
|
||||
"output_style": {
|
||||
"name": "default"
|
||||
},
|
||||
"cost": {
|
||||
"total_cost_usd": 0.01234,
|
||||
"total_duration_ms": 45000,
|
||||
"total_api_duration_ms": 2300,
|
||||
"total_lines_added": 156,
|
||||
"total_lines_removed": 23
|
||||
},
|
||||
"context_window": {
|
||||
"total_input_tokens": 15234,
|
||||
"total_output_tokens": 4521,
|
||||
"context_window_size": 200000,
|
||||
"current_usage": {
|
||||
"input_tokens": 8500,
|
||||
"output_tokens": 1200,
|
||||
"cache_creation_input_tokens": 5000,
|
||||
"cache_read_input_tokens": 2000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Example Scripts
|
||||
|
||||
### Simple Status Line
|
||||
|
||||
```bash theme={null}
|
||||
#!/bin/bash
|
||||
# Read JSON input from stdin
|
||||
input=$(cat)
|
||||
|
||||
# Extract values using jq
|
||||
MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name')
|
||||
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')
|
||||
|
||||
echo "[$MODEL_DISPLAY] 📁 ${CURRENT_DIR##*/}"
|
||||
```
|
||||
|
||||
### Git-Aware Status Line
|
||||
|
||||
```bash theme={null}
|
||||
#!/bin/bash
|
||||
# Read JSON input from stdin
|
||||
input=$(cat)
|
||||
|
||||
# Extract values using jq
|
||||
MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name')
|
||||
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir')
|
||||
|
||||
# Show git branch if in a git repo
|
||||
GIT_BRANCH=""
|
||||
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
BRANCH=$(git branch --show-current 2>/dev/null)
|
||||
if [ -n "$BRANCH" ]; then
|
||||
GIT_BRANCH=" | 🌿 $BRANCH"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[$MODEL_DISPLAY] 📁 ${CURRENT_DIR##*/}$GIT_BRANCH"
|
||||
```
|
||||
|
||||
### Python Example
|
||||
|
||||
```python theme={null}
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Read JSON from stdin
|
||||
data = json.load(sys.stdin)
|
||||
|
||||
# Extract values
|
||||
model = data['model']['display_name']
|
||||
current_dir = os.path.basename(data['workspace']['current_dir'])
|
||||
|
||||
# Check for git branch
|
||||
git_branch = ""
|
||||
if os.path.exists('.git'):
|
||||
try:
|
||||
with open('.git/HEAD', 'r') as f:
|
||||
ref = f.read().strip()
|
||||
if ref.startswith('ref: refs/heads/'):
|
||||
git_branch = f" | 🌿 {ref.replace('ref: refs/heads/', '')}"
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"[{model}] 📁 {current_dir}{git_branch}")
|
||||
```
|
||||
|
||||
### Node.js Example
|
||||
|
||||
```javascript theme={null}
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Read JSON from stdin
|
||||
let input = "";
|
||||
process.stdin.on("data", (chunk) => input += chunk);
|
||||
process.stdin.on("end", () => {
|
||||
const data = JSON.parse(input);
|
||||
|
||||
// Extract values
|
||||
const model = data.model.display_name;
|
||||
const currentDir = path.basename(data.workspace.current_dir);
|
||||
|
||||
// Check for git branch
|
||||
let gitBranch = "";
|
||||
try {
|
||||
const headContent = fs.readFileSync(".git/HEAD", "utf8").trim();
|
||||
if (headContent.startsWith("ref: refs/heads/")) {
|
||||
gitBranch = ` | 🌿 ${headContent.replace("ref: refs/heads/", "")}`;
|
||||
}
|
||||
} catch (e) {
|
||||
// Not a git repo or can't read HEAD
|
||||
}
|
||||
|
||||
console.log(`[${model}] 📁 ${currentDir}${gitBranch}`);
|
||||
});
|
||||
```
|
||||
|
||||
### Helper Function Approach
|
||||
|
||||
For more complex bash scripts, you can create helper functions:
|
||||
|
||||
```bash theme={null}
|
||||
#!/bin/bash
|
||||
# Read JSON input once
|
||||
input=$(cat)
|
||||
|
||||
# Helper functions for common extractions
|
||||
get_model_name() { echo "$input" | jq -r '.model.display_name'; }
|
||||
get_current_dir() { echo "$input" | jq -r '.workspace.current_dir'; }
|
||||
get_project_dir() { echo "$input" | jq -r '.workspace.project_dir'; }
|
||||
get_version() { echo "$input" | jq -r '.version'; }
|
||||
get_cost() { echo "$input" | jq -r '.cost.total_cost_usd'; }
|
||||
get_duration() { echo "$input" | jq -r '.cost.total_duration_ms'; }
|
||||
get_lines_added() { echo "$input" | jq -r '.cost.total_lines_added'; }
|
||||
get_lines_removed() { echo "$input" | jq -r '.cost.total_lines_removed'; }
|
||||
get_input_tokens() { echo "$input" | jq -r '.context_window.total_input_tokens'; }
|
||||
get_output_tokens() { echo "$input" | jq -r '.context_window.total_output_tokens'; }
|
||||
get_context_window_size() { echo "$input" | jq -r '.context_window.context_window_size'; }
|
||||
|
||||
# Use the helpers
|
||||
MODEL=$(get_model_name)
|
||||
DIR=$(get_current_dir)
|
||||
echo "[$MODEL] 📁 ${DIR##*/}"
|
||||
```
|
||||
|
||||
### Context Window Usage
|
||||
|
||||
Display the percentage of context window consumed. The `context_window` object
|
||||
contains:
|
||||
|
||||
- `total_input_tokens` / `total_output_tokens`: Cumulative totals across the
|
||||
entire session
|
||||
- `current_usage`: Current context window usage from the last API call (may be
|
||||
`null` if no messages yet)
|
||||
- `input_tokens`: Input tokens in current context
|
||||
- `output_tokens`: Output tokens generated
|
||||
- `cache_creation_input_tokens`: Tokens written to cache
|
||||
- `cache_read_input_tokens`: Tokens read from cache
|
||||
|
||||
For accurate context percentage, use `current_usage` which reflects the actual
|
||||
context window state:
|
||||
|
||||
```bash theme={null}
|
||||
#!/bin/bash
|
||||
input=$(cat)
|
||||
|
||||
MODEL=$(echo "$input" | jq -r '.model.display_name')
|
||||
CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size')
|
||||
USAGE=$(echo "$input" | jq '.context_window.current_usage')
|
||||
|
||||
if [ "$USAGE" != "null" ]; then
|
||||
# Calculate current context from current_usage fields
|
||||
CURRENT_TOKENS=$(echo "$USAGE" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens')
|
||||
PERCENT_USED=$((CURRENT_TOKENS * 100 / CONTEXT_SIZE))
|
||||
echo "[$MODEL] Context: ${PERCENT_USED}%"
|
||||
else
|
||||
echo "[$MODEL] Context: 0%"
|
||||
fi
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- Keep your status line concise - it should fit on one line
|
||||
- Use emojis (if your terminal supports them) and colors to make information
|
||||
scannable
|
||||
- Use `jq` for JSON parsing in Bash (see examples above)
|
||||
- Test your script by running it manually with mock JSON input:
|
||||
`echo '{"model":{"display_name":"Test"},"workspace":{"current_dir":"/test"}}' | ./statusline.sh`
|
||||
- Consider caching expensive operations (like git status) if needed
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If your status line doesn't appear, check that your script is executable
|
||||
(`chmod +x`)
|
||||
- Ensure your script outputs to stdout (not stderr)
|
||||
|
||||
---
|
||||
|
||||
> To find navigation and other pages in this documentation, fetch the llms.txt
|
||||
> file at: https://code.claude.com/docs/llms.txt
|
||||
Reference in New Issue
Block a user