Squash al branch-ului de lucru plan13-s2. Clase: ofacturare (nucleul formularului unificat), ofacturare_comun, ferestre_cere_date, ocomenzi, caut_ora (lista de preturi in combo-urile de cautare), omodificari. Programe: ofacturare impartit - ofacturare_antet, ofacturare_rutare_scriere si ogrid_latimi sunt fisiere noi; oproceduri_facturare primeste discountul pe linie si cota standard de TVA cand articolul nu are cota pe politica. Documentatie: capcana SQLExec no_data_found, conventia de encoding, depanarea testelor VFP si regulile de lucru - conflictele cu modificarile venite din alte proiecte sunt rezolvate pastrand ambele parti. Loguri de rulare a testelor scoase din versionare; testele raman. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PkyjGyrV2S7932om4kfSiK
247 lines
10 KiB
Markdown
247 lines
10 KiB
Markdown
# Prompt pentru alta sesiune: recreeaza statusline-ul Claude Code
|
|
|
|
Copiaza tot ce urmeaza si trimite-l ca prompt intr-o sesiune Claude Code noua
|
|
(pe alta masina/user), ca sa recreeze exact statusline-ul curent (Windows / Git Bash).
|
|
|
|
---
|
|
|
|
Vreau sa configurezi un statusline custom pentru Claude Code, pe Windows cu Git Bash.
|
|
|
|
1. Verifica ca exista `jq.exe` in `~/.claude/bin/jq.exe`. Daca nu exista, descarca
|
|
binarul oficial jq pentru Windows (jq-win64.exe de pe github.com/jqlang/jq/releases)
|
|
si salveaza-l acolo ca `jq.exe`.
|
|
|
|
2. Creeaza fisierul `~/.claude/statusline.sh` (bash, LF, executabil) cu continutul de mai jos.
|
|
|
|
3. In `~/.claude/settings.json`, adauga/actualizeaza cheia de nivel radacina:
|
|
```json
|
|
"statusLine": {
|
|
"type": "command",
|
|
"command": "C:/Users/<USER>/.claude/statusline.sh"
|
|
}
|
|
```
|
|
(inlocuieste `<USER>` cu userul curent; foloseste slash-uri, nu backslash).
|
|
|
|
Ce face scriptul:
|
|
- Linia 1: model | ctx folosit/total si % din fereastra | proiect (branch git, cu `*` daca e dirty)
|
|
- Linia 2: trei bare de puncte (10 puncte fiecare) pentru consum curent (5h), saptamanal (7 zile)
|
|
si sonnet (7 zile) — procent + timp pana la reset, in format `●●●○○○○○○○ 30% ↺ 2hr 15min`
|
|
- Cele trei procente vin din `/api/oauth/usage` (acelasi endpoint ca ecranul `/usage`),
|
|
cu cache de 10 minute in `~/.claude/.statusline-usage.json` si backoff la 429/eroare
|
|
(20 min, respectiv 5 min), ca sa nu loveasca rate-limit-ul la fiecare prompt.
|
|
- Pana la primul fetch reusit, current/weekly cad back pe `rate_limits` din JSON-ul primit pe stdin.
|
|
- Token-ul OAuth se ia din `~/.claude/.credentials.json` (`claudeAiOauth.accessToken`).
|
|
- La inceput reface PATH-ul (`$HOME/.claude/bin:/usr/bin:/bin:$PATH`), pentru ca Claude Code
|
|
lanseaza scriptul ca shell non-login/non-interactiv, deci profilul Git Bash nu se incarca
|
|
si PATH-ul nu are coreutils.
|
|
|
|
Continutul complet al `~/.claude/statusline.sh`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# Claude Code Statusline (Windows / Git Bash)
|
|
# Line 1: Model | ctx 33K/1M 3% | project (branch*)
|
|
# Line 2: current <bar> 24% ↺ 3hr │ weekly <bar> 4% ↺ Thu 7pm │ sonnet <bar> 2% ↺ Thu 7pm
|
|
#
|
|
# All three usage figures come from /api/oauth/usage -- the same source as the
|
|
# `/usage` screen -- so the numbers match it exactly. That endpoint is rate-
|
|
# limited, so the result is cached for 10 min; on failure the last good values
|
|
# are kept. Until the first successful fetch, current/weekly fall back to the
|
|
# `rate_limits` object on stdin (slightly different snapshot, but never blank).
|
|
|
|
set -o pipefail
|
|
|
|
# Claude Code spawns this as a non-login, non-interactive shell, so Git Bash's
|
|
# profile is never sourced and PATH has no coreutils. Restore them explicitly,
|
|
# plus the bundled jq.exe in ~/.claude/bin.
|
|
export PATH="$HOME/.claude/bin:/usr/bin:/bin:$PATH"
|
|
|
|
input=$(cat)
|
|
|
|
# Validate JSON
|
|
if ! echo "$input" | jq -e . >/dev/null 2>&1; then
|
|
printf '\033[90mLoading...\033[0m'
|
|
exit 0
|
|
fi
|
|
|
|
# --- Context / project (line 1) ---
|
|
model=$(echo "$input" | jq -r '.model.display_name // "?"')
|
|
workdir=$(echo "$input" | jq -r '.workspace.current_dir // ""')
|
|
project=$(basename "$workdir" 2>/dev/null) || project="?"
|
|
|
|
ctx_pct=$(echo "$input" | jq -r '(.context_window.used_percentage // 0) | floor')
|
|
[[ "$ctx_pct" =~ ^[0-9]+$ ]] || ctx_pct=0
|
|
|
|
ctx_size=$(echo "$input" | jq -r '.context_window.context_window_size // 0')
|
|
ctx_input=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
|
|
ctx_output=$(echo "$input" | jq -r '.context_window.current_usage.output_tokens // 0')
|
|
ctx_cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
|
|
ctx_cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
|
|
ctx_used=$(( ctx_input + ctx_output + ctx_cache_create + ctx_cache_read ))
|
|
|
|
fmt_tokens() {
|
|
local n=$1
|
|
if [ "$n" -ge 1000000 ]; then printf "%dM" $(( n / 1000000 ))
|
|
elif [ "$n" -ge 1000 ]; then printf "%dK" $(( n / 1000 ))
|
|
else printf "%d" "$n"; fi
|
|
}
|
|
ctx_used_fmt=$(fmt_tokens "$ctx_used")
|
|
ctx_size_fmt=$(fmt_tokens "$ctx_size")
|
|
|
|
# Git branch with dirty marker
|
|
git_branch=""
|
|
if [ -n "$workdir" ] && [ -d "$workdir" ]; then
|
|
git_branch=$(cd "$workdir" 2>/dev/null && git -c gc.auto=0 rev-parse --abbrev-ref HEAD 2>/dev/null) || git_branch=""
|
|
if [ -n "$git_branch" ]; then
|
|
git_dirty=$(cd "$workdir" 2>/dev/null && git -c gc.auto=0 status --porcelain 2>/dev/null | head -1)
|
|
[ -n "$git_dirty" ] && git_branch="${git_branch}*"
|
|
fi
|
|
fi
|
|
|
|
# --- Usage: all three figures from /api/oauth/usage, cached ---
|
|
USAGE_CACHE="$HOME/.claude/.statusline-usage.json"
|
|
USAGE_REFRESH=600 # refresh at most every 10 min
|
|
USAGE_BACKOFF_429=1200 # after a rate-limit, wait 20 min before retrying
|
|
USAGE_BACKOFF_ERR=300 # after any other error, wait 5 min
|
|
|
|
now=$(date +%s)
|
|
c5p=""; c5r=""; c7p=""; c7r=""; csp=""; csr=""; fetched=0; retry=0
|
|
|
|
read_usage_cache() {
|
|
[ -f "$USAGE_CACHE" ] || return
|
|
c5p=$(jq -r '.five_hour_pct // ""' "$USAGE_CACHE" 2>/dev/null)
|
|
c5r=$(jq -r '.five_hour_reset // ""' "$USAGE_CACHE" 2>/dev/null)
|
|
c7p=$(jq -r '.seven_day_pct // ""' "$USAGE_CACHE" 2>/dev/null)
|
|
c7r=$(jq -r '.seven_day_reset // ""' "$USAGE_CACHE" 2>/dev/null)
|
|
csp=$(jq -r '.sonnet_pct // ""' "$USAGE_CACHE" 2>/dev/null)
|
|
csr=$(jq -r '.sonnet_reset // ""' "$USAGE_CACHE" 2>/dev/null)
|
|
fetched=$(jq -r '(.fetched_at // 0) | floor' "$USAGE_CACHE" 2>/dev/null || echo 0)
|
|
retry=$(jq -r '(.retry_until // 0) | floor' "$USAGE_CACHE" 2>/dev/null || echo 0)
|
|
}
|
|
read_usage_cache
|
|
[[ "$fetched" =~ ^[0-9]+$ ]] || fetched=0
|
|
[[ "$retry" =~ ^[0-9]+$ ]] || retry=0
|
|
|
|
write_usage_cache() { # 5p 5r 7p 7r sp sr fetched_at retry_until
|
|
printf '{"five_hour_pct":"%s","five_hour_reset":"%s","seven_day_pct":"%s","seven_day_reset":"%s","sonnet_pct":"%s","sonnet_reset":"%s","fetched_at":%s,"retry_until":%s}\n' \
|
|
"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" > "$USAGE_CACHE"
|
|
}
|
|
|
|
refresh_usage() {
|
|
local creds="$HOME/.claude/.credentials.json" token tmp http
|
|
[ -f "$creds" ] || return
|
|
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds" 2>/dev/null)
|
|
[ -n "$token" ] || return
|
|
tmp="$HOME/.claude/.statusline-usage.tmp.$$"
|
|
http=$(curl -s -o "$tmp" -w '%{http_code}' --max-time 3 \
|
|
-H "Authorization: Bearer $token" \
|
|
-H "anthropic-beta: oauth-2025-04-20" \
|
|
-H "Content-Type: application/json" \
|
|
"https://api.anthropic.com/api/oauth/usage" 2>/dev/null)
|
|
if [ "$http" = "200" ]; then
|
|
local p5 r5 p7 r7 ps rs
|
|
p5=$(jq -r 'if .five_hour.utilization!=null then (.five_hour.utilization|floor) else "" end' "$tmp" 2>/dev/null)
|
|
r5=$(jq -r '.five_hour.resets_at // ""' "$tmp" 2>/dev/null)
|
|
p7=$(jq -r 'if .seven_day.utilization!=null then (.seven_day.utilization|floor) else "" end' "$tmp" 2>/dev/null)
|
|
r7=$(jq -r '.seven_day.resets_at // ""' "$tmp" 2>/dev/null)
|
|
ps=$(jq -r 'if .seven_day_sonnet.utilization!=null then (.seven_day_sonnet.utilization|floor) else "" end' "$tmp" 2>/dev/null)
|
|
rs=$(jq -r '.seven_day_sonnet.resets_at // ""' "$tmp" 2>/dev/null)
|
|
write_usage_cache "$p5" "$r5" "$p7" "$r7" "$ps" "$rs" "$now" 0
|
|
elif [ "$http" = "429" ]; then
|
|
write_usage_cache "$c5p" "$c5r" "$c7p" "$c7r" "$csp" "$csr" "$fetched" "$(( now + USAGE_BACKOFF_429 ))"
|
|
else
|
|
write_usage_cache "$c5p" "$c5r" "$c7p" "$c7r" "$csp" "$csr" "$fetched" "$(( now + USAGE_BACKOFF_ERR ))"
|
|
fi
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
if [ "$(( now - fetched ))" -gt "$USAGE_REFRESH" ] && [ "$now" -gt "$retry" ]; then
|
|
refresh_usage
|
|
read_usage_cache
|
|
fi
|
|
|
|
# current / weekly: use API cache; fall back to stdin rate_limits if not yet fetched
|
|
if [ -n "$c5p" ]; then
|
|
five_hr_pct=$c5p; five_hr_reset=$c5r
|
|
else
|
|
five_hr_pct=$(echo "$input" | jq -r '(.rate_limits.five_hour.used_percentage // 0) | floor')
|
|
five_hr_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // ""')
|
|
fi
|
|
if [ -n "$c7p" ]; then
|
|
weekly_pct=$c7p; weekly_reset=$c7r
|
|
else
|
|
weekly_pct=$(echo "$input" | jq -r '(.rate_limits.seven_day.used_percentage // 0) | floor')
|
|
weekly_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // ""')
|
|
fi
|
|
# sonnet: only available from the API
|
|
sonnet_pct=${csp:-0}; sonnet_reset=$csr
|
|
|
|
[[ "$five_hr_pct" =~ ^[0-9]+$ ]] || five_hr_pct=0
|
|
[[ "$weekly_pct" =~ ^[0-9]+$ ]] || weekly_pct=0
|
|
[[ "$sonnet_pct" =~ ^[0-9]+$ ]] || sonnet_pct=0
|
|
|
|
# --- reset-time formatting (accepts epoch seconds or ISO string) ---
|
|
to_epoch() {
|
|
local v="${1%%.*}"
|
|
if [ -z "$v" ] || [ "$v" = "null" ]; then return 1; fi
|
|
if [[ "$v" =~ ^[0-9]+$ ]]; then printf '%s' "$v"
|
|
else date -d "$1" +%s 2>/dev/null || return 1; fi
|
|
}
|
|
|
|
fmt_reset() {
|
|
# No TZ= : Git Bash's date has no zoneinfo DB, but with TZ unset it uses the
|
|
# Windows system timezone correctly. Setting TZ=Europe/... falls back to UTC.
|
|
local e
|
|
e=$(to_epoch "$1") || { printf '%s' "--"; return; }
|
|
date -d "@$e" "+%a %-I:%M%P" 2>/dev/null || printf '%s' "--"
|
|
}
|
|
|
|
fmt_remaining() {
|
|
local e now_ts rem rh rm rd
|
|
e=$(to_epoch "$1") || { printf '%s' "--"; return; }
|
|
now_ts=$(date +%s)
|
|
if [ "$e" -gt "$now_ts" ]; then
|
|
rem=$(( e - now_ts )); rh=$(( rem / 3600 )); rm=$(( (rem % 3600) / 60 ))
|
|
if [ "$rh" -gt 24 ]; then
|
|
rd=$(( rh / 24 )); rh=$(( rh % 24 )); printf "%dd %dh" "$rd" "$rh"
|
|
elif [ "$rh" -gt 0 ]; then printf "%dhr %dmin" "$rh" "$rm"
|
|
else printf "%dmin" "$rm"; fi
|
|
else printf "resetting"; fi
|
|
}
|
|
|
|
five_hr_remain=$(fmt_remaining "$five_hr_reset")
|
|
weekly_reset_str=$(fmt_reset "$weekly_reset")
|
|
sonnet_reset_str=$(fmt_reset "$sonnet_reset")
|
|
|
|
# --- dot bar ---
|
|
DOT_TOTAL=10
|
|
make_dots() {
|
|
local pct=$1 filled i dots=""
|
|
filled=$(( pct * DOT_TOTAL / 100 ))
|
|
[ "$filled" -gt "$DOT_TOTAL" ] && filled=$DOT_TOTAL
|
|
[ "$filled" -lt 0 ] && filled=0
|
|
for (( i=0; i<DOT_TOTAL; i++ )); do
|
|
if [ "$i" -lt "$filled" ]; then dots="${dots}●"; else dots="${dots}○"; fi
|
|
done
|
|
printf "%s" "$dots"
|
|
}
|
|
|
|
# --- build & output ---
|
|
if [ -n "$git_branch" ]; then
|
|
proj_display="${project} (${git_branch})"
|
|
else
|
|
proj_display="${project}"
|
|
fi
|
|
line1=$(printf "%s │ ctx %s/%s %d%% │ %s" "$model" "$ctx_used_fmt" "$ctx_size_fmt" "$ctx_pct" "$proj_display")
|
|
|
|
line2=$(printf "current %s %d%% ↺ %s │ weekly %s %d%% ↺ %s │ sonnet %s %d%% ↺ %s" \
|
|
"$(make_dots "$five_hr_pct")" "$five_hr_pct" "$five_hr_remain" \
|
|
"$(make_dots "$weekly_pct")" "$weekly_pct" "$weekly_reset_str" \
|
|
"$(make_dots "$sonnet_pct")" "$sonnet_pct" "$sonnet_reset_str")
|
|
|
|
printf "%s\n%s" "$line1" "$line2"
|
|
```
|
|
|
|
Dupa ce salvezi fisierul si actualizezi `settings.json`, deschide o sesiune noua Claude
|
|
Code (sau `/statusline` refresh) ca sa vezi statusline-ul aplicat.
|