I adapted Matt Pocock’s article to replicate my zsh git prompt in Claude Code’s status line.
Implementation
Create the file statusline.sh in ~/.claude/ and copy the code below.
statusline.sh
statusline.sh
#!/bin/bash
set -euo pipefail
# Input: {"workspace":{"current_dir":"/abs/path"}}
# Color codes
reset=$'\033[00m'
green=$'\033[38;5;70m'
blue=$'\033[38;5;67m'
cyan=$'\033[38;5;32m'
red=$'\033[31m'
black=$'\033[38;5;0m'
bold=$'\033[1m'
normal=$'\033[22m'
default_fg=$'\033[39m'
context_black=$'\033[38;5;16m'
input=$(cat)
# Extract current_dir from JSON
cwd=""
if command -v jq >/dev/null 2>&1; then
if ! cwd=$(printf '%s' "$input" | jq -er '.workspace.current_dir' 2>/dev/null); then
cwd=""
fi
fi
if [[ -z $cwd ]] && command -v node >/dev/null 2>&1; then
if ! cwd=$(printf '%s' "$input" | node -e '(() => { const fs=require("fs"); try { const data=JSON.parse(fs.readFileSync(0,"utf-8")); const dir=data && data.workspace && data.workspace.current_dir; if (typeof dir === "string") { process.stdout.write(dir); } } catch (_) {} })()' 2>/dev/null); then
cwd=""
fi
fi
if [[ -z "$cwd" || "$cwd" == "null" || "$cwd" == "undefined" || ! -d "$cwd" ]]; then
printf '%s[error: no current_dir]%s' "$green" "$reset"
exit 0
fi
# Git information
if git -C "$cwd" rev-parse --git-dir >/dev/null 2>&1; then
status_output=$(git -C "$cwd" --no-optional-locks status --porcelain=v2 --branch 2>/dev/null)
branch=$(echo "$status_output" | grep '^# branch.head ' | cut -d' ' -f3)
if [[ -z "$branch" ]]; then
branch="HEAD"
fi
ahead=0
behind=0
ab_line=$(echo "$status_output" | grep '^# branch.ab ' || true)
if [[ -n "$ab_line" ]]; then
ahead=$(echo "$ab_line" | awk '{print $3}' | tr -d '+')
behind=$(echo "$ab_line" | awk '{print $4}' | tr -d '-')
fi
staged=$(echo "$status_output" | grep -c '^[12] [MADRCT].' || true)
unstaged=$(echo "$status_output" | grep -c '^[12] .[MADRCT]' || true)
untracked=$(echo "$status_output" | grep -c '^?' || true)
conflicts=$(echo "$status_output" | grep -c '^u ' || true)
# Format path
if [[ "$cwd" == "$HOME/Repos/github.com/"* ]]; then
repo_name="${cwd#"$HOME/Repos/github.com/"}"
elif [[ "$cwd" == "$HOME" ]]; then
repo_name="~"
elif [[ "$cwd" == "$HOME/"* ]]; then
# shellcheck disable=SC2088
repo_name="~/${cwd#"$HOME/"}"
else
repo_name="$cwd"
fi
# Commit delta
commit_delta=""
[[ $ahead -gt 0 ]] && commit_delta+="${black}↑${ahead}"
[[ $behind -gt 0 ]] && commit_delta+="${black}↓${behind}"
# Status indicators
indicators=""
[[ $untracked -gt 0 ]] && indicators+="${red}${untracked}…${reset}"
[[ $staged -gt 0 ]] && indicators+="${green}●${staged}${reset}"
[[ $conflicts -gt 0 ]] && indicators+="${red}✗${conflicts}${reset}"
[[ $unstaged -gt 0 ]] && indicators+="${cyan}✚${unstaged}${reset}"
git_info=$(printf '%s%s%s | %s(%s%s' "$green" "$repo_name" "$reset" "$blue" "$branch" "$commit_delta")
if [[ -n "$indicators" ]]; then
git_info+=$(printf '%s|%s%s)%s' "$reset" "$indicators" "$blue" "$reset")
else
git_info+=$(printf '%s)%s' "$blue" "$reset")
fi
else
# Not a git repo
if [[ "$cwd" == "$HOME" ]]; then
display_path="~"
elif [[ "$cwd" == "$HOME/"* ]]; then
# shellcheck disable=SC2088
display_path="~/${cwd#"$HOME/"}"
else
display_path="$cwd"
fi
git_info=$(printf '%s%s%s' "$green" "$display_path" "$reset")
fi
# Context window limit constant
readonly CONTEXT_LIMIT=200000
# Context percentage calculation
calculate_context_percentage() {
local json_input="$1"
local transcript_path
local context_prefix="${reset}${bold}${context_black}"
local context_suffix="${default_fg}${normal}"
local zero_context="${context_prefix}0.0%${context_suffix}"
# Extract transcript_path from input
transcript_path=$(printf '%s' "$json_input" | jq -r '.transcript_path // empty' 2>/dev/null)
# Validate path safety
if [[ "$transcript_path" == *".."* ]]; then
printf '%s' "$zero_context"
return
fi
# Validate file exists
if [[ -z "$transcript_path" ]] || [[ ! -f "$transcript_path" ]]; then
printf '%s' "$zero_context"
return
fi
# Find most recent entry with usage data (no type filter, matches ccstatusline)
local most_recent
most_recent=$(jq -Rc 'fromjson? | select(. != null) | select(.message.usage and (.isSidechain | not) and (.isApiErrorMessage | not) and .timestamp)' \
"$transcript_path" 2>/dev/null | tail -1 || true)
if [[ -z "$most_recent" ]]; then
printf '%s' "$zero_context"
return
fi
# Extract token values without executing shell code
local usage_values
usage_values=$(printf '%s' "$most_recent" | jq -er '
def to_num:
if type == "number" then .
elif type == "string" then (try (tonumber) catch 0)
else 0
end;
[
(.message.usage.input_tokens // 0 | to_num),
(.message.usage.cache_read_input_tokens // 0 | to_num),
(.message.usage.cache_creation_input_tokens // 0 | to_num),
(.message.usage.output_tokens // 0 | to_num)
] | @tsv
' 2>/dev/null) || usage_values=$'0\t0\t0\t0'
local input_tokens cache_read cache_creation output_tokens
IFS=$'\t' read -r input_tokens cache_read cache_creation output_tokens <<< "$usage_values"
local var_name
for var_name in input_tokens cache_read cache_creation output_tokens; do
if [[ ! ${!var_name} =~ ^-?[0-9]+$ ]]; then
printf -v "$var_name" '0'
fi
done
# Calculate context length
local context_length=$((input_tokens + cache_read + cache_creation + output_tokens))
# Calculate percentage (capped at 100)
local percentage
percentage=$(awk -v context_length="$context_length" -v limit="$CONTEXT_LIMIT" 'BEGIN {
pct = (context_length / limit) * 100;
if (pct > 100) pct = 100;
if (pct < 0) pct = 0;
pct += 1e-9;
printf "%.1f%%", pct;
}')
# Format with black color (matching ccstatusline)
printf '%s%s%s' "$context_prefix" "$percentage" "$context_suffix"
}
# Try native bash implementation first
context_pct=""
if command -v jq >/dev/null 2>&1; then
context_pct=$(calculate_context_percentage "$input")
fi
# Fallback if native implementation unavailable
if [[ -z "$context_pct" ]]; then
if command -v ccstatusline >/dev/null 2>&1; then
alt_pct=$(printf '%s' "$input" | ccstatusline 2>/dev/null || true)
[[ -n "$alt_pct" ]] && context_pct="$alt_pct"
elif command -v npx >/dev/null 2>&1; then
context_output=$(printf '%s' "$input" | npx --yes --quiet ccstatusline 2>/dev/null || true)
if [[ -n $context_output ]]; then
while IFS= read -r line; do
[[ -n $line ]] && context_pct=$line
done <<< "$context_output"
fi
fi
fi
# Output
if [[ -n "$context_pct" ]]; then
printf '%s | %s' "$git_info" "$context_pct"
else
printf '%s' "$git_info"
fiChange $HOME/Repos/github.com/ to your repositories directory.
settings.json
Only change the command for the status line in ~/.claude/settings.json:
Make the Script Executable
Context Calculation
This script includes output_tokens in the calculation, unlike ccstatusline. Output tokens from the current response become input tokens in the next request, so including them shows context growing in real-time as the agent works.
Claude Code has documented bugs causing inflated warnings. Issue #8792 reported 99% usage when actual usage was 55%. Issue #6616 warned “7% remaining” with 86% free. Use /context for accurate measurements.
