ai:platformy:claude-code:statusline

Rozdíly

Zde můžete vidět rozdíly mezi vybranou verzí a aktuální verzí dané stránky.

Odkaz na výstup diff

Obě strany předchozí revize Předchozí verze
ai:platformy:claude-code:statusline [08.05.2026 21:45] – Zjednodušení: ctx% čteno přímo z used_percentage. Doplněny poznatky z oficiální dokumentace (total_input_tokens zahrnuje cache, used_percentage existuje). Petr Nosekai:platformy:claude-code:statusline [07.07.2026 18:08] (aktuální) – Aktualizace článku o Claude Code status line a subagentStatusLine Petr Nosek
Řádek 3: Řádek 3:
 //Vytvořeno: **** | Aktualizováno: **~~LASTMOD~~**// //Vytvořeno: **** | Aktualizováno: **~~LASTMOD~~**//
  
-[[ai:platformy:claude-code:start|Claude Code]] umí vykreslovat vlastní status line spuštěný jako externí příkaz. Tohle je moje varianta, která drží stabilní pořadí polí a na jeden pohled ukazuje pracovní adresář, model, session ID, využití kontextu, 5h 7d limity i absolutní spotřebu tokenů context window se zvýrazněním nad hranicí 200k.+[[ai:platformy:claude-code:start|Claude Code]] umí podle [[https://code.claude.com/docs/en/statusline|oficiální dokumentace]] vykreslovat vlastní status line přes externí příkaz. Tohle je moje aktuální konfigurace: hlavní ''statusLine'' ukazuje stav top-level session a samostatná ''subagentStatusLine'' přidává přehled běžících subagentů v agent panelu.
  
 ===== Co z toho získám ===== ===== Co z toho získám =====
  
-  * Aktuální pracovní adresář projektu (rootkde byl Claude Code spuštěn) zkrácený relativně k ''$HOME''takže místo celé cesty vidím jen ''~'' nebo ''~/...''. Adresář se v průběhu session neměníi když shell přejde do podadresáře+  * Hlavní status line ukazuje pracovní adresář, model, session IDvyužití context window, 5h a 7d limity a absolutní spotřebu tokenů
-  * Použitý model a session ID ímo ve status line+  * Subagent status line ukazuje každého subagenta jako vlastní řádek: agent, model, krátký popis, stav, tokeny, trend tokenů, rychlost tokenů za sekundu, běh od startu a případně pracovní adresář
-  * Využití context window v procentech, čtené ímo z předpočítaného pole ''used_percentage''+  * Model subagenta se do panelu dostává es konvenci v ''description'', protože Claude Code ho ve vstupních datech pro ''subagentStatusLine'' neposílá jako samostatné pole. 
-  * 5hodinový a 7denní limitvčetně času resetu, pokud je Claude Code pošle v JSONu. +  * Popisky subagentů jsou čitelné i při paralelním běhu více agentů.
-  * Absolutní spotřebu tokenů context window ve formátu ''XYZk/TOTALk''Hodnota celkového okna se čte dynamicky, takže funguje pro 1M context window. Červeně se rozsvítí při překročení 200k tokenů+
-  * Barevné zvýraznění podle vytížení: zelená pod 70 %, žlutá od 70 %, červená od 90 %.+
  
 ===== Zapojení do konfigurace ===== ===== Zapojení do konfigurace =====
  
-V ''~/.claude/settings.json'' mám status line zapojený takto:+V ''~/.claude/settings.json'' jsou zapojené dva skripty:
  
 <code json> <code json>
 "statusLine": { "statusLine": {
   "type": "command",   "type": "command",
-  "command": "bash /home/ubuntu/.claude/statusline-command.sh"+  "command": "bash ~/.claude/statusline-command.sh" 
 +}, 
 +"subagentStatusLine":
 +  "type": "command", 
 +  "command": "bash ~/.claude/subagent-statusline.sh"
 } }
 </code> </code>
  
-Skript čte JSON ze standardního vstupu, parsuje ho přes ''jq''složí z něj jednu barevnou řádku.+Skripty čtou JSON ze standardního vstupu, parsují ho přes ''jq''vrací barevný výstup přes ANSI sekvence.
  
-===== Jak vypadá výstup =====+===== Hlavní status line =====
  
-Když Claude Code neposílá data o ''rate_limits'', výstup vypadá zhruba takto: +Hlavní ''statusLine'' je vázaná na top-level Claude Code session. Nepřepíná se podle aktivního subagenta a nedostává data o tom, který subagent zrovna běží. K tomu slouží samostatná ''subagentStatusLine''.
- +
-<code> +
-~/scratch │ Sonnet 4.6 | sid:<id> │ ctx:23% │ 92k/400k +
-</code>+
  
-Když jsou limity v JSONu k dispozici, řádka se rozšíří o 5h a 7d přehled:+Výstup hlavní status line vypadá zhruba takto:
  
 <code> <code>
-~/project-procesniaudit-website │ Sonnet 4.6 │ ctx:55% │ 5h:4% ↺ 00:50 │ 7d:45% ↺ Po 08:00 │ 220k/400k+~/project │ Sonnet 4.6 | sid:<id> │ ctx:55% │ 5h:4% ↺ 00:50 │ 7d:45% ↺ Po 08:00 │ 220k/400k
 </code> </code>
  
Řádek 49: Řádek 47:
   * token counter ''XYZk/TOTALk'' → červeně a tučně při překročení 200k, jinak dim   * token counter ''XYZk/TOTALk'' → červeně a tučně při překročení 200k, jinak dim
  
-===== Celý skript =====+==== Skript ''statusline-command.sh'' ====
  
 <code bash> <code bash>
Řádek 124: Řádek 122:
   fi   fi
 } }
- 
  
 # ── 3) context used ──────────────────────────────────────────────────────────── # ── 3) context used ────────────────────────────────────────────────────────────
Řádek 192: Řádek 189:
  
 printf "%b\n" "$out" printf "%b\n" "$out"
 +</code>
 +
 +===== Subagent status line =====
 +
 +''subagentStatusLine'' dostává pole ''tasks'' a pro každý task musí vypsat jeden JSON řádek ve tvaru:
 +
 +<code json>
 +{"id":"<task id>","content":"<řádek s ANSI>"}
 +</code>
 +
 +V reálných datech se ukázalo, že task obsahuje zejména pole ''cwd'', ''description'', ''id'', ''label'', ''startTime'', ''status'', ''tokenCount'', ''tokenSamples'' a ''type''. Pole ''type'' je jen obecná hodnota typu ''local_agent'', nikoli název použitého subagenta. Samostatné pole pro model ani název subagenta v datech není.
 +
 +Proto se používá konvence v ''description'':
 +
 +<code>
 +<subagent_type> · <model> · <short description>
 +</code>
 +
 +Příklad:
 +
 +<code>
 +elite-prompt-architect · haiku · rešerše pricing
 +</code>
 +
 +Skript tento popisek rozparsuje a zobrazí ho jako:
 +
 +<code>
 +elite-prompt-architect │ haiku │ rešerše pricing │ running │ tok:8.1k ▁▄█ 579/s │ 14s
 +</code>
 +
 +==== Pravidlo do globálního ''CLAUDE.md'' ====
 +
 +Aby skript fungoval dobře i v dalších session, je potřeba dát do globálního ''CLAUDE.md'' pravidlo pro pojmenování dispatchů v Claude Code. Bez toho se sice subagent zobrazí, ale nebude zřejmé, který agent a model běží.
 +
 +<code markdown>
 +## `description` naming convention — Claude Code only
 +
 +In **Claude Code**, format every dispatch `description` as:
 +
 +```
 +<subagent_type> · <model> · <short description>
 +```
 +
 +Example: `elite-prompt-architect · haiku · rešerše pricing`
 +
 +Why this exact shape: the `description` is the **only** human-readable string that
 +reaches the Claude Code subagent status-line panel — the task data exposes no
 +`name` or `model` field, and `subagent_type` isn't surfaced there either. Encoding
 +them into `description` is the only way the panel can show *which agent, on which
 +model, doing what*. That visibility matters most exactly when several subagents run
 +in parallel and you need to tell them apart at a glance.
 +
 +- Separator is a spaced middot ` · ` — the status line splits the label on it.
 +- `<model>` = whatever you pass in the `model` param (`haiku`/`sonnet`/`opus`/`fable`);
 +  the status line renders it as a colored chip.
 +- Keep `<short description>` genuinely short — the panel truncates to terminal width.
 +
 +This is purely a Claude Code convenience: it relies on the `subagentStatusLine`
 +setting parsing the ` · ` format. **OpenCode** has no such status line, so use a
 +plain, natural `description` there — do **not** force this format.
 +</code>
 +
 +==== Skript ''subagent-statusline.sh'' ====
 +
 +<code bash>
 +#!/usr/bin/env bash
 +# Claude Code subagent status line
 +# Renders one custom row per active subagent in the agent panel.
 +# Row: <type> │ <status> │ tok:<n> <sparkline> <rate> │ <elapsed> │ [cwd] │ <label|desc>
 +# Input (stdin): { cwd, columns, tasks:[ {id,name,type,status,description,label,startTime,tokenCount,tokenSamples,cwd}, ... ] }
 +# Output (stdout): one JSON line per task -> {"id":"<id>","content":"<row w/ ANSI>"}
 +
 +input=$(cat)
 +
 +# ── ANSI ────────────────────────────────────────────────────────────────────────
 +RESET='\033[0m'; BOLD='\033[1m'; DIM='\033[2m'
 +RED='\033[31m'; YELLOW='\033[33m'; GREEN='\033[32m'; CYAN='\033[36m'; MAGENTA='\033[35m'; WHITE='\033[37m'; BLUE='\033[34m'
 +
 +now=$(date +%s)
 +main_cwd=$(echo "$input" | jq -r '.cwd // .workspace.current_dir // empty')
 +columns=$(echo "$input" | jq -r '.columns // 0')
 +US=$'\x1f'   # interní oddělovač pro rozklad "agent · model · popis"
 +
 +status_color() {
 +  case "$1" in
 +    running|in_progress|active) echo "$CYAN" ;;
 +    completed|done|success)     echo "$GREEN" ;;
 +    failed|error)               echo "$RED" ;;
 +    pending|queued|waiting)     echo "$YELLOW" ;;
 +    *)                          echo "$WHITE" ;;
 +  esac
 +}
 +
 +# Rozpozná model v prefixu popisku (konvence "model · popis" / "model | popis").
 +# Vrací barvu, nebo prázdno když prefix není známý model.
 +model_color() {
 +  case "$(echo "$1" | tr '[:upper:]' '[:lower:]')" in
 +    haiku*)  echo "$GREEN"   ;;
 +    sonnet*) echo "$CYAN"    ;;
 +    opus*)   echo "$MAGENTA" ;;
 +    fable*)  echo "$BLUE"    ;;
 +    *)       echo ""         ;;
 +  esac
 +}
 +
 +# elapsed seconds from startTime (epoch-s, epoch-ms, or ISO-8601) -> "2m10s"
 +elapsed_str() {
 +  local v="$1" start=""
 +  { [ -z "$v" ] || [ "$v" = "null" ]; } && return
 +  if [[ "$v" =~ ^[0-9]+$ ]]; then
 +    if [ "${#v}" -ge 13 ]; then start=$(( v / 1000 )); else start="$v"; fi
 +  else
 +    start=$(date -d "$v" +%s 2>/dev/null) || return
 +  fi
 +  local d=$(( now - start )); [ "$d" -lt 0 ] && d=0
 +  if   [ "$d" -ge 3600 ]; then printf "%dh%dm" $(( d/3600 )) $(( (d%3600)/60 ))
 +  elif [ "$d" -ge 60 ];   then printf "%dm%02ds" $(( d/60 )) $(( d%60 ))
 +  else printf "%ds" "$d"; fi
 +  return
 +}
 +elapsed_secs() {
 +  local v="$1" start=""
 +  { [ -z "$v" ] || [ "$v" = "null" ]; } && { echo 0; return; }
 +  if [[ "$v" =~ ^[0-9]+$ ]]; then
 +    if [ "${#v}" -ge 13 ]; then start=$(( v / 1000 )); else start="$v"; fi
 +  else
 +    start=$(date -d "$v" +%s 2>/dev/null) || { echo 0; return; }
 +  fi
 +  local d=$(( now - start )); [ "$d" -lt 0 ] && d=0; echo "$d"
 +}
 +
 +num_fmt() { # 39731 -> 39.7k
 +  local t="$1"
 +  [ "$t" -ge 1000 ] 2>/dev/null && python3 -c "print(f'{$t/1000:.1f}k')" 2>/dev/null || echo "$t"
 +}
 +
 +# sparkline + rate from tokenSamples (defensive: čísla i objekty)
 +# stdout: "<sparkline>\t<rate_per_s or empty>"
 +spark_and_rate() {
 +  python3 - "$1" 2>/dev/null <<'PY'
 +import sys, json
 +raw = sys.argv[1] if len(sys.argv) > 1 else "[]"
 +try:
 +    d = json.loads(raw)
 +except Exception:
 +    print("\t"); sys.exit(0)
 +vals, times = [], []
 +def num(x):
 +    if isinstance(x,(int,float)): return float(x)
 +    if isinstance(x,dict):
 +        for k in ('tokens','tokenCount','count','value','v','y'):
 +            if isinstance(x.get(k),(int,float)): return float(x[k])
 +    return None
 +def tim(x):
 +    if isinstance(x,dict):
 +        for k in ('t','time','ts','timestamp','at'):
 +            if isinstance(x.get(k),(int,float)): return float(x[k])
 +    return None
 +if isinstance(d, list):
 +    for x in d:
 +        v = num(x)
 +        if v is not None:
 +            vals.append(v); times.append(tim(x))
 +spark = ""
 +if len(vals) >= 2:
 +    blocks = "▁▂▃▄▅▆▇█"
 +    w = vals[-14:]
 +    lo, hi = min(w), max(w); rng = (hi - lo) or 1
 +    spark = "".join(blocks[min(7, int(round((v-lo)/rng*7)))] for v in w)
 +rate = ""
 +if len(vals) >= 2 and all(t is not None for t in times) and (times[-1]-times[0]) > 0:
 +    dt = times[-1]-times[0]
 +    if dt > 10_000_000_000: dt = dt/1000.0   # ms guard
 +    r = (vals[-1]-vals[0])/dt
 +    if r > 0:
 +        rate = f"{r/1000:.1f}k/s" if r >= 1000 else f"{r:.0f}/s"
 +print(f"{spark}\t{rate}")
 +PY
 +}
 +
 +short_dir() {
 +  local d="$1"
 +  { [ -z "$d" ] || [ "$d" = "null" ]; } && return
 +  if [ "$d" = "$HOME" ]; then echo "~";
 +  elif [[ "$d" == "$HOME/"* ]]; then echo "~/${d#$HOME/}";
 +  else echo "$d"; fi
 +}
 +
 +# ── build one row per task ───────────────────────────────────────────────────────
 +echo "$input" | jq -c '.tasks[]?' | while read -r task; do
 +  id=$(echo "$task" | jq -r '.id // empty'); [ -z "$id" ] && continue
 +  # Primární štítek = label|description (subagent_type ve schématu NENÍ).
 +  primary=$(echo "$task" | jq -r '(.label // "") as $l | (.description // "") as $d | (.name // "") as $n
 +    | if ($l|length)>0 then $l elif ($d|length)>0 then $d elif ($n|length)>0 then $n else "agent" end')
 +  # `type` je hrubá kategorie ("local_agent"); ukázat jen když nese info.
 +  type=$(echo "$task"    | jq -r '.type // ""')
 +  status=$(echo "$task"  | jq -r '.status // ""')
 +  tokens=$(echo "$task"  | jq -r '.tokenCount // ""')
 +  start=$(echo "$task"   | jq -r '.startTime // ""')
 +  tcwd=$(echo "$task"    | jq -r '.cwd // ""')
 +  samples=$(echo "$task" | jq -c '.tokenSamples // []')
 +
 +  sc=$(status_color "$status")
 +
 +  # Konvence "agent · model · popis" (oddělovač " · " nebo " | ") → 3 barevné části.
 +  # Zpětně kompatibilní: 2 díly "model · popis", 1 díl jen popis.
 +  agentseg=""; modelseg=""
 +  _tmp="${primary// · /$US}"; _tmp="${_tmp// | /$US}"
 +  IFS="$US" read -ra _segs <<< "$_tmp"
 +  _agent=""; _model=""; _popis=""
 +  _n=${#_segs[@]}
 +  if [ "$_n" -ge 3 ]; then
 +    _agent="${_segs[0]}"; _model="${_segs[1]}"; _popis="${_segs[2]}"
 +    for ((i=3; i<_n; i++)); do _popis="$_popis · ${_segs[$i]}"; done
 +  elif [ "$_n" -eq 2 ]; then
 +    if [ -n "$(model_color "${_segs[1]}")" ]; then _agent="${_segs[0]}"; _model="${_segs[1]}"
 +    elif [ -n "$(model_color "${_segs[0]}")" ]; then _model="${_segs[0]}"; _popis="${_segs[1]}"
 +    else _agent="${_segs[0]}"; _popis="${_segs[1]}"; fi
 +  else
 +    _popis="$primary"
 +  fi
 +  # model chip jen když je to známý model; jinak segment vrať do popisu
 +  if [ -n "$_model" ]; then
 +    _mcol=$(model_color "$_model")
 +    if [ -n "$_mcol" ]; then
 +      modelseg=$(printf "%b%b%s%b" "$_mcol" "$BOLD" "$_model" "$RESET")
 +    else
 +      _popis="${_model}${_popis:+ · $_popis}"
 +    fi
 +  fi
 +  [ -n "$_agent" ] && agentseg=$(printf "%b%s%b" "$BOLD" "$_agent" "$RESET")
 +  primary="$_popis"
 +
 +  # tokens: count + sparkline + rate
 +  tokseg=""
 +  if [ -n "$tokens" ] && [ "$tokens" != "null" ]; then
 +    disp=$(num_fmt "$tokens")
 +    if [ "$tokens" -ge 100000 ] 2>/dev/null; then tcol="${RED}${BOLD}"; else tcol="$DIM"; fi
 +    IFS=$'\t' read -r spark rate < <(spark_and_rate "$samples")
 +    # fallback rate: průměr od startu, když samples nenesou čas
 +    if [ -z "$rate" ]; then
 +      es=$(elapsed_secs "$start")
 +      [ "$es" -gt 0 ] 2>/dev/null && rate=$(python3 -c "r=$tokens/$es; print(f'{r/1000:.1f}k/s' if r>=1000 else f'{r:.0f}/s')" 2>/dev/null)
 +    fi
 +    tokseg=$(printf "%btok:%s%b" "$tcol" "$disp" "$RESET")
 +    [ -n "$spark" ] && tokseg="$tokseg $(printf "%b%s%b" "$CYAN" "$spark" "$RESET")"
 +    [ -n "$rate" ]  && tokseg="$tokseg $(printf "%b%s%b" "$DIM" "$rate" "$RESET")"
 +  fi
 +
 +  el=$(elapsed_str "$start")
 +
 +  # cwd jen když se liší od hlavní session
 +  dirseg=""
 +  if [ -n "$tcwd" ] && [ "$tcwd" != "null" ] && [ "$tcwd" != "$main_cwd" ]; then
 +    dirseg=$(printf "%b%s%b" "$YELLOW" "$(short_dir "$tcwd")" "$RESET")
 +  fi
 +
 +  # ořez primárního štítku podle columns (heuristika: fixní část ~ 44 sloupců)
 +  if [ -n "$primary" ] && [ "$columns" -gt 0 ] 2>/dev/null; then
 +    budget=$(( columns - 44 )); [ "$budget" -lt 12 ] && budget=12
 +    if [ "${#primary}" -gt "$budget" ]; then primary="${primary:0:$((budget-1))}…"; fi
 +  fi
 +
 +  # type badge jen když není běžná lokální kategorie
 +  typeseg=""
 +  case "$type" in
 +    ""|local_agent|agent) : ;;
 +    *) typeseg=$(printf "%b%s%b" "$DIM" "$type" "$RESET") ;;
 +  esac
 +
 +  parts=()
 +  if [ -n "$agentseg" ]; then
 +    parts+=("$agentseg")
 +    [ -n "$modelseg" ] && parts+=("$modelseg")
 +    [ -n "$primary" ] && parts+=("$(printf "%b%s%b" "$DIM" "$primary" "$RESET")")
 +  else
 +    [ -n "$modelseg" ] && parts+=("$modelseg")
 +    parts+=("$(printf "%b%b%s%b" "$MAGENTA" "$BOLD" "$primary" "$RESET")")
 +  fi
 +  [ -n "$status" ]  && parts+=("$(printf "%b%s%b" "$sc" "$status" "$RESET")")
 +  [ -n "$tokseg" ]  && parts+=("$tokseg")
 +  [ -n "$el" ]      && parts+=("$(printf "%b%s%b" "$DIM" "$el" "$RESET")")
 +  [ -n "$dirseg" ]  && parts+=("$dirseg")
 +  [ -n "$typeseg" ] && parts+=("$typeseg")
 +
 +  sep="$(printf "%b │ %b" "$DIM" "$RESET")"
 +  content=""
 +  for p in "${parts[@]}"; do
 +    [ -z "$content" ] && content="$p" || content="${content}${sep}${p}"
 +  done
 +
 +  content_bytes=$(printf "%b" "$content")
 +  jq -cn --arg id "$id" --arg content "$content_bytes" '{id:$id, content:$content}'
 +done
 </code> </code>
  
 ===== Poznámky k chování ===== ===== Poznámky k chování =====
  
-  * Pole ''5h'' a ''7d'' se zobrazí jen když Claude Code skutečně pošle ''rate_limits''+  * Hlavní ''statusLine'' a ''subagentStatusLine'' jsou oddělené. Hlavní řádek zůstává pro top-level session, subagenti se zobrazují v agent panelu
-  * Funkce ''fmt_reset_short()'' a ''fmt_reset_long()'' zvládnou jak unix epoch, tak ISO-8601 string. +  * ''subagentStatusLine'' nedostává samostatně model ani skutečný ''subagent_type''. Proto je nutná konvence v ''description''
-  * Pro cwd se primárně čte ''.workspace.project_dir'' — pole, které Claude Code nastavuje na root projektu (kde byl spuštěn) a nemění se průběhu session, i když shell přejde do podadresáře. Pole ''.workspace.current_dir''''.cwd'' jsou jen fallback+  * ''tokenSamples'' se používá pro sparkline trendu tokenů. Když vzorky neobsahují časrychlost se dopočítá jako průměr od ''startTime''. 
-  * Procento využití context window se čte přímo z ''context_window.used_percentage'' — předpočítané polekteré Claude Code posílá od verze 2.1.132. Pokrývá vstupní tokeny včetně cache: ''used_percentage'' = (''input_tokens'' + ''cache_creation_input_tokens'' + ''cache_read_input_tokens'') / ''context_window_size''Výstupní tokeny do procenta nezahrnuje, ale ani do ''XYZk'' v token counteru (tam se čte ''total_input_tokens''které je součtem stejných tří složek)+  * ''columns'' se používá pro ořez popiskuaby se řádky v úzkém terminálu nelámaly
-  * ''total_input_tokens'' zahrnuje všechny vstupní tokeny včetně cache a od v2.1.132 reprezentuje aktuální stav context window, nikoli kumulativní součet přes celou session. +  * ''cwd'' se zobrazí jen tehdy, když se liší od hlavní session. 
-  * Skript záměrně nepoužívá příznak ''exceeds_200k_tokens'' — ten vychází z tokenů aktuálního API volání (input + output + cache) a má pevnou hranici 200k bez ohledu na skutečnou velikost okna. Místo toho skript ukazuje aktuální stav v segmentu ''XYZk/TOTALk''+  * Skripty vyžadují ''jq''''python3''běžné unixové ''date''.
-  * Když některé pole v JSONu chybí, skript se nerozbije prostě ho přeskočí. +
-  * Pořadí polí je pevné: ''cwd'' → ''model + sid'' → ''ctx'' → ''5h'' → ''7d'' → ''XYZk/TOTALk''.+
  
 ===== Zdroje ===== ===== Zdroje =====
  
   * [[https://code.claude.com/docs/en/statusline|Claude Code – Customize your status line (oficiální dokumentace)]]   * [[https://code.claude.com/docs/en/statusline|Claude Code – Customize your status line (oficiální dokumentace)]]
  • ai/platformy/claude-code/statusline.txt
  • Poslední úprava: 07.07.2026 18:08
  • autor: Petr Nosek