Skip to content

Instantly share code, notes, and snippets.

@nazt
Last active March 7, 2026 06:10
Show Gist options
  • Select an option

  • Save nazt/7776a85cf1c5c350336c297a23da97a7 to your computer and use it in GitHub Desktop.

Select an option

Save nazt/7776a85cf1c5c350336c297a23da97a7 to your computer and use it in GitHub Desktop.
Claude Code Status Line แบบ Starship — Step by Step setup guide
local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()
-- Font: Nerd Font for starship icons
config.font_size = 13.0
-- Catppuccin Mocha (matches starship palette)
config.color_scheme = 'Catppuccin Mocha'
config.colors = {
background = '#0a0e14',
}
config.hyperlink_rules = wezterm.default_hyperlink_rules()
-- Remote white.local paths → local sshfs mounts
table.insert(config.hyperlink_rules, {
regex = [[/home/nat/([^ \t:),'"'`]+)]],
format = 'file:///opt/white/$1',
})
table.insert(config.hyperlink_rules, {
regex = [[/home/openclaw/([^ \t:),'"'`]+)]],
format = 'file:///opt/openclaw/$1',
})
-- Local absolute paths
table.insert(config.hyperlink_rules, {
regex = [[(/Users/[^ \t:),'"'`]+)]],
format = 'file://$1',
})
-- ~/path style
table.insert(config.hyperlink_rules, {
regex = [[~/([^ \t:),'"'`]+)]],
format = 'file:///Users/nat/$1',
})
-- Relative paths — ./path and ψ/path (resolved via OSC 7 cwd on click)
table.insert(config.hyperlink_rules, {
regex = [[(\./[^ \t:),'"'`]+)]],
format = 'rel:$1',
})
table.insert(config.hyperlink_rules, {
regex = [[(?<!/)(ψ/[^ \t:),'"'`]+)]],
format = 'psi:$1',
})
-- Rewrite remote cwd to local sshfs path
local function resolve_cwd(pane)
local cwd = pane:get_current_working_dir()
if not cwd then return nil end
local p = cwd.file_path or ''
p = p:gsub('^/home/openclaw/', '/opt/openclaw/')
p = p:gsub('^/home/nat/', '/opt/white/')
return p
end
-- Intercept link clicks to resolve relative paths using OSC 7 cwd
wezterm.on('open-uri', function(window, pane, uri)
-- psi: and rel: schemes — resolve relative to cwd
local rel = uri:match('^psi:(.+)$') or uri:match('^rel:(.+)$')
if rel then
rel = rel:gsub('^%./', '') -- strip leading ./
local cwd_path = resolve_cwd(pane)
if cwd_path then
wezterm.open_with(cwd_path .. '/' .. rel)
end
return false
end
-- file:// URIs — force open with TextEdit if macOS doesn't know the type
local path = uri:match('^file://(.+)$')
if path then
local success = wezterm.run_child_process({ '/usr/bin/open', path })
if not success then
wezterm.run_child_process({ '/usr/bin/open', '-a', 'TextEdit', path })
end
return false
end
end)
-- Smart CMD+V: image in clipboard → save to sshfs + paste path, else normal paste
config.keys = {
{
key = 'v',
mods = 'CMD',
action = wezterm.action_callback(function(window, pane)
local success, stdout = wezterm.run_child_process({
'/bin/bash', '-c',
'f="/opt/white/Code/tmp/clipboard/$(date +%Y%m%d-%H%M%S).png"; '
.. 'mkdir -p /opt/white/Code/tmp/clipboard 2>/dev/null; '
.. '/opt/homebrew/bin/pngpaste "$f" 2>/dev/null && '
.. 'echo "/home/nat/Code/tmp/clipboard/$(basename $f)"'
})
if success and stdout and stdout:match('%.png') then
pane:send_text(stdout:gsub('%s+$', ''))
else
window:perform_action(act.PasteFrom 'Clipboard', pane)
end
end),
},
}
-- iTerm2 style: CMD+Click opens links, plain click selects text
config.mouse_bindings = {
{
event = { Up = { streak = 1, button = 'Left' } },
mods = 'NONE',
action = act.CompleteSelection 'ClipboardAndPrimarySelection',
},
{
event = { Up = { streak = 1, button = 'Left' } },
mods = 'CMD',
action = act.OpenLinkAtMouseCursor,
},
{
event = { Down = { streak = 1, button = 'Left' } },
mods = 'CMD',
action = act.Nop,
},
}
return config

Claude Code Status Line แบบ Starship — Step by Step

Claude Code CLI มี status line ที่ custom ได้ด้วย shell script ปกติมันแสดงแค่ model กับ token count แต่เราจะทำให้มันแสดง git branch, context %, auto-compact status, เวลา, และ working directory แบบ Starship prompt

ผลลัพธ์:

🕐 12:35 📂 ~/my-project on  main* • 🤖 Opus 4.6 • 📊 28% (56k/160k) • 🔄 auto-compact

ถ้า terminal แคบ จะ split เป็น 2 บรรทัดอัตโนมัติ:

🕐 12:35 📂 ~/my-project on  main* • 🤖 Opus 4.6
📊 28% (56k/160k) • 🔄 auto-compact

Prerequisites

brew install jq    # JSON parser (ถ้ายังไม่มี)
git --version      # ต้องมี git

Step 1: สร้าง Script

สร้างไฟล์ ~/.claude/statusline-command.sh:

#!/bin/bash

# Claude Code status line — Starship-inspired with icons
# Receives JSON on stdin from Claude Code

input=$(cat)

# Extract JSON data
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // "~"')
model=$(echo "$input" | jq -r '.model.display_name // .model.id // "Claude"')
pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
used_k=$(echo "$input" | jq -r '((.context_window.current_usage | .input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens + .output_tokens) // 0) / 1000 | floor')
max_k=$(echo "$input" | jq -r '(.context_window.context_window_size // 0) / 1000 | floor')

# Format directory (~ for home)
if [[ "$cwd" == "$HOME"* ]]; then
  display_dir="${cwd/#$HOME/~}"
else
  display_dir="$cwd"
fi

# Git branch + worktree
git_info=""
if git -C "$cwd" rev-parse --git-dir >/dev/null 2>&1; then
  branch=$(git -C "$cwd" symbolic-ref --short HEAD 2>/dev/null || git -C "$cwd" rev-parse --short HEAD 2>/dev/null)
  if [ -n "$branch" ]; then
    dirty=""
    if ! git -C "$cwd" diff-index --quiet HEAD -- 2>/dev/null; then
      dirty="*"
    fi
    # Check if in a worktree (git dir is a file pointing to main repo)
    wt=""
    git_dir=$(git -C "$cwd" rev-parse --git-dir 2>/dev/null)
    if [ -f "$git_dir" ] || echo "$input" | jq -e '.worktree' >/dev/null 2>&1; then
      wt_name=$(echo "$input" | jq -r '.worktree.name // empty' 2>/dev/null)
      [ -z "$wt_name" ] && wt_name=$(basename "$cwd")
      wt=" 🌳 ${wt_name}"
    fi
    git_info=" on  ${branch}${dirty}${wt}"
  fi
fi

# Auto-compact status
compact_val=$(jq -r 'if .autoCompactEnabled == false then "false" else "true" end' ~/.claude.json 2>/dev/null)
compact_pct=${CLAUDE_AUTOCOMPACT_PCT_OVERRIDE:-80}
if [ "$compact_val" = "false" ]; then
  compact_icon="❌ auto-compact"
else
  compact_icon="🔄 auto-compact"
  max_k=$((max_k * compact_pct / 100))
  if [ "$max_k" -gt 0 ]; then
    pct=$((used_k * 100 / max_k))
  fi
fi

# Time
now=$(date '+%H:%M')

# Build one-line version, split to two if too long
line1=$(printf '🕐 %s 📂 %s%s • 🤖 %s' "$now" "$display_dir" "$git_info" "$model")
line2=$(printf '📊 %s%% (%sk/%sk) • %s' "$pct" "$used_k" "$max_k" "$compact_icon")
oneline="$line1$line2"

# Get terminal width (fallback 80)
cols=${COLUMNS:-$(tput cols 2>/dev/null || echo 80)}

# If one line fits, use it; otherwise split
if [ ${#oneline} -le "$cols" ]; then
  printf '%s' "$oneline"
else
  printf '%s\n%s' "$line1" "$line2"
fi

Step 2: ทำให้ Execute ได้

chmod +x ~/.claude/statusline-command.sh

Step 3: ทดสอบ Script

ก่อน enable ใน Claude Code ทดสอบด้วยตัวเองก่อน:

echo '{"workspace":{"current_dir":"'$PWD'"},"model":{"display_name":"Opus 4.6","id":"claude-opus-4-6"},"context_window":{"used_percentage":28,"context_window_size":200000,"current_usage":{"input_tokens":3,"output_tokens":94,"cache_creation_input_tokens":372,"cache_read_input_tokens":55664}}}' | ~/.claude/statusline-command.sh

ควรเห็น output ประมาณ:

🕐 12:35 📂 ~/Code/github.com/laris-co/homelab on  main • 🤖 Opus 4.6 • 📊 35% (56k/160k) • 🔄 auto-compact

ถ้าเห็น error ตรวจสอบว่า jq ติดตั้งแล้ว และ ~/.claude.json มีอยู่

Step 4: Enable ใน Claude Code Settings

เพิ่ม statusLine ใน ~/.claude/settings.json:

# ถ้ายังไม่มีไฟล์
cat ~/.claude/settings.json 2>/dev/null || echo '{}'

แก้ไข (หรือสร้างใหม่):

{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline-command.sh"
  }
}

สำคัญ: ถ้ามี settings อยู่แล้ว ให้เพิ่ม statusLine field เข้าไป อย่า overwrite ทั้งไฟล์:

# ใช้ jq เพิ่ม field (ปลอดภัยกว่า)
jq '. + {"statusLine": {"type": "command", "command": "~/.claude/statusline-command.sh"}}' ~/.claude/settings.json > /tmp/settings.json && mv /tmp/settings.json ~/.claude/settings.json

Step 5: Restart Claude Code

ปิดแล้วเปิด Claude Code ใหม่ — status line จะแสดงทันทีหลังจาก interaction แรก

มันทำงานยังไง

Claude Code pipe JSON ให้ script ทุกครั้งที่มี interaction ผ่าน stdin:

{
  "model": { "id": "claude-opus-4-6", "display_name": "Opus 4.6" },
  "workspace": { "current_dir": "/Users/nat/Code/github.com/laris-co/homelab" },
  "context_window": {
    "used_percentage": 28,
    "context_window_size": 200000,
    "current_usage": {
      "input_tokens": 3,
      "output_tokens": 94,
      "cache_creation_input_tokens": 372,
      "cache_read_input_tokens": 55664
    }
  },
  "cost": { "total_cost_usd": 1.55 },
  "version": "2.1.71"
}

Script อ่าน JSON → extract ข้อมูล → format → print ออกมา Claude Code แสดง output ของ script เป็น status line

แต่ละ Icon แสดงอะไร

Icon Info Source
🕐 เวลาปัจจุบัน date +%H:%M
📂 Working directory (ย่อ ~) .workspace.current_dir
Git branch + dirty * git symbolic-ref
🌳 Git worktree name (ถ้าอยู่ใน worktree) .worktree.name
🤖 Model name .model.display_name
📊 Context usage % + token count .context_window
🔄/❌ Auto-compact on/off ~/.claude.json

Effective Context Window — ทำไม 160k ไม่ใช่ 200k?

เรื่องนี้สำคัญ เวลา auto-compact เปิด (default) Claude Code จะ compact context ที่ ~80% ของ window หมายความว่าจริงๆ ใช้ได้แค่ 160k จาก 200k ไม่เคยเต็ม 200k

Script ปรับ max ให้สะท้อนความจริง:

  • Auto-compact on: 📊 35% (56k/160k) — แสดง effective limit
  • Auto-compact off: 📊 28% (56k/200k) — แสดง full window

ปรับ threshold ได้ด้วย env var:

export CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=75  # trigger ที่ 75% แทน 80%

Auto-compact Discovery — ของแถม

ตรงนี้หา tricky สุด auto-compact status ไม่ได้อยู่ใน JSON input ของ statusLine ต้อง dig เอง

ค้นพบจากการ strings Claude Code binary:

  1. autoCompactEnabled อ่านจาก ~/.claude.json (home root ไม่ใช่ ~/.claude/.claude.json)
  2. Key มีเฉพาะเมื่อ set เป็น false — เมื่อ enabled (default) key ไม่มี
  3. jq trap: .autoCompactEnabled // true ไม่ทำงาน! เพราะ false ถือเป็น falsy ใน jq ต้องใช้:
# ผิด — false ถูก treat เป็น falsy เลย fallback เป็น true เสมอ
jq -r '.autoCompactEnabled // true'

# ถูก — check explicitly
jq -r 'if .autoCompactEnabled == false then "false" else "true" end'

Customization Ideas

เพิ่มเข้าไปได้ตามชอบ:

# Session cost
cost=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
cost_str="💰 \$${cost}"

# Session duration
dur_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
dur_min=$((dur_ms / 60000))
dur_str="⏱️ ${dur_min}m"

# Claude Code version
version=$(echo "$input" | jq -r '.version // "?"')
ver_str="v${version}"

Troubleshooting

Status line ไม่แสดง?

  1. ตรวจสอบ script มี chmod +x
  2. ตรวจสอบ ~/.claude/settings.json syntax ถูกต้อง: jq . ~/.claude/settings.json
  3. ต้อง restart Claude Code (ไม่ใช่แค่ส่ง message ใหม่)

แสดง error หรือ blank?

  1. ทดสอบ script ด้วย test JSON ก่อน (Step 3)
  2. ตรวจสอบ jq installed: which jq
  3. ตรวจสอบ ~/.claude.json exists: cat ~/.claude.json

Git branch ไม่แสดง?

  • ต้องอยู่ใน git repo
  • git ต้องอยู่ใน PATH

Script: statusline-command.sh Config: .wezterm.lua

-- Homekeeper Oracle

#!/bin/bash
# Claude Code status line command
# Mimics Powerlevel10k style: dir + git + model + context
input=$(cat)
# Note: Previously saved to ψ/active/statusline.json but caused conflicts
# with multiple sessions. Removed - not currently used by any hooks.
# Extract JSON data
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // "~"')
model=$(echo "$input" | jq -r '.model.display_name // .model.id // "Claude"')
output_style=$(echo "$input" | jq -r '.output_style.name // "default"')
usage=$(echo "$input" | jq '.context_window.current_usage')
# Detect agent from cwd (MAW multi-agent worktree)
# Colors: 1=Yellow 2=Magenta 3=Green 4=Cyan 5=Red Main=Blue
ROOT="/Users/nat/Code/github.com/laris-co/Nat-s-Agents"
AGENT_COLOR=""
NC="\033[0m"
if [[ "$cwd" =~ $ROOT/agents/([0-9]+) ]]; then
AGENT_ID="${BASH_REMATCH[1]}"
case $AGENT_ID in
1) AGENT_COLOR="\033[0;33m" ;;
2) AGENT_COLOR="\033[0;35m" ;;
3) AGENT_COLOR="\033[0;32m" ;;
4) AGENT_COLOR="\033[0;36m" ;;
5) AGENT_COLOR="\033[0;31m" ;;
esac
elif [[ "$cwd" == "$ROOT" ]]; then
AGENT_COLOR="\033[0;36m" # Cyan for main
fi
# Format current directory (use relative path from home)
if [[ "$cwd" == "$HOME"* ]]; then
display_dir="${cwd/#$HOME/~}"
else
display_dir="$cwd"
fi
# Color Nat-s-Agents (and suffix for agents) in path
REPO_NAME=$(basename "$ROOT")
if [ -n "$AGENT_COLOR" ] && [[ "$display_dir" == *"$REPO_NAME"* ]]; then
prefix="${display_dir%$REPO_NAME*}"
suffix="${display_dir#*$REPO_NAME}"
display_dir="${prefix}${AGENT_COLOR}${REPO_NAME}${suffix}${NC}"
fi
# Get git branch if in a git repo (skip optional locks for performance)
git_branch=""
if [ -d "$cwd/.git" ] || git -C "$cwd" rev-parse --git-dir >/dev/null 2>&1; then
git_branch=$(git -C "$cwd" -c core.fileMode=false symbolic-ref --short HEAD 2>/dev/null || git -C "$cwd" -c core.fileMode=false rev-parse --short HEAD 2>/dev/null)
if [ -n "$git_branch" ]; then
# Check for uncommitted changes
if ! git -C "$cwd" -c core.fileMode=false diff-index --quiet HEAD -- 2>/dev/null; then
git_branch="$git_branch*"
fi
fi
fi
# Build status line: time dir [git:branch] model [style] context%
printf '%s' "$(date '+%H:%M')"
printf ' %b' "$display_dir"
# Add git branch after dir (color entire branch if in MAW repo)
if [ -n "$git_branch" ]; then
if [ -n "$AGENT_COLOR" ]; then
printf ' %b[%s]%b' "$AGENT_COLOR" "$git_branch" "$NC"
else
printf ' [%s]' "$git_branch"
fi
fi
printf ' • %s' "$model"
# Add output style if not default
if [[ "$output_style" != "default" ]]; then
printf ' [%s]' "$output_style"
fi
# Add context window percentage if available
# Auto-compact status + context window
compact_val=$(jq -r 'if .autoCompactEnabled == false then "false" else "true" end' ~/.claude.json 2>/dev/null)
if [ "$usage" != "null" ]; then
current=$(echo "$usage" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens')
max_k=$(echo "$input" | jq -r '(.context_window.context_window_size // 200000) / 1000 | floor')
if [ "$compact_val" != "false" ]; then
max_k=$((max_k * 80 / 100))
fi
if [ -n "$current" ] && [ "$current" != "null" ] && [ "$max_k" -gt 0 ]; then
used_k=$((current / 1000))
pct=$((used_k * 100 / max_k))
printf ' %d%% (%dk/%dk)' "$pct" "$used_k" "$max_k"
fi
fi
if [ "$compact_val" = "false" ]; then
printf ' ❌compact'
else
printf ' 🔄compact'
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment