claude-pace 0.8.6 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/claude-pace.sh +22 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,12 +108,12 @@ Claude Code polls the statusline every ~300ms:
|
|
|
108
108
|
| Data | Source | Cache |
|
|
109
109
|
|------|--------|-------|
|
|
110
110
|
| Model, context, cost | stdin JSON (single `jq` call) | None needed |
|
|
111
|
-
| Quota (5h, 7d, pace) | stdin `rate_limits
|
|
111
|
+
| Quota (5h, 7d, pace) | stdin `rate_limits` (live, no fallback) | None |
|
|
112
112
|
| Git branch + diff | `git` commands | Private cache dir, 5s TTL |
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
Requires Claude Code `2.1.80+`, where `rate_limits` is available in statusline stdin. When stdin omits `rate_limits` (older Claude Code, or providers that do not surface the field), claude-pace shows `--` for 5h/7d quota and the session cost if available. No cached or stale quota is ever shown, because a cached account-level snapshot cannot be proven to belong to the current provider/account.
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
Git cache files live in a private per-user directory (`$XDG_RUNTIME_DIR/claude-pace` or `~/.cache/claude-pace`, mode 700). All cache reads are validated before use. No files are ever written to shared `/tmp`.
|
|
117
117
|
|
|
118
118
|
## Claude Code Statusline FAQ
|
|
119
119
|
|
|
@@ -124,7 +124,7 @@ No. Only `jq` (available via `brew install jq` or your package manager). No npm,
|
|
|
124
124
|
claude-pace compares your current usage percentage to the fraction of time elapsed in each window (5-hour and 7-day). If you've used 40% of your quota but only 30% of the time has passed, the pace delta shows ⇡10% (red, burning too fast). If you've used 30% with 40% of time elapsed, it shows ⇣10% (green, headroom).
|
|
125
125
|
|
|
126
126
|
**Does it make network calls?**
|
|
127
|
-
No. Quota data comes from stdin `rate_limits` on Claude Code `2.1.80+`. If
|
|
127
|
+
No. Quota data comes from stdin `rate_limits` on Claude Code `2.1.80+`. If `rate_limits` is absent (older Claude Code, or providers that omit it), claude-pace shows `--` for 5h/7d and the local session cost when present. No stale quota fallback — see [the removal decision](docs/decisions/2026-05-20-quota-cache-removal.md).
|
|
128
128
|
|
|
129
129
|
**Can I inspect the source?**
|
|
130
130
|
The entire tool is [one Bash file](claude-pace.sh). Read it before you install it.
|
|
@@ -137,4 +137,4 @@ The entire tool is [one Bash file](claude-pace.sh). Read it before you install i
|
|
|
137
137
|
|
|
138
138
|
MIT
|
|
139
139
|
|
|
140
|
-
*Last updated: 2026-
|
|
140
|
+
*Last updated: 2026-05-20 · v0.9.0*
|
package/claude-pace.sh
CHANGED
|
@@ -65,17 +65,6 @@ _write_cache_record() {
|
|
|
65
65
|
printf '%s\n' "$*"
|
|
66
66
|
) >"$tmp" && mv "$tmp" "$path"
|
|
67
67
|
}
|
|
68
|
-
# Avoids rewriting the quota cache when the live snapshot is unchanged.
|
|
69
|
-
_write_quota_snapshot_if_changed() {
|
|
70
|
-
local path="$1" u5="$2" u7="$3" r5="$4" r7="$5"
|
|
71
|
-
if [ -f "$path" ] && [ ! -L "$path" ] && [ -r "$path" ] && _load_cache_record_file "$path"; then
|
|
72
|
-
[[ "${CACHE_FIELDS[0]:-}" == "$u5" ]] &&
|
|
73
|
-
[[ "${CACHE_FIELDS[1]:-}" == "$u7" ]] &&
|
|
74
|
-
[[ "${CACHE_FIELDS[2]:-}" == "$r5" ]] &&
|
|
75
|
-
[[ "${CACHE_FIELDS[3]:-}" == "$r7" ]] && return 0
|
|
76
|
-
fi
|
|
77
|
-
_write_cache_record "$path" "$u5" "$u7" "$r5" "$r7"
|
|
78
|
-
}
|
|
79
68
|
# Computes remaining whole minutes until a future epoch. Missing or expired
|
|
80
69
|
# timestamps return an empty string so callers can skip countdown formatting.
|
|
81
70
|
_minutes_until() {
|
|
@@ -85,16 +74,6 @@ _minutes_until() {
|
|
|
85
74
|
((mins < 0)) && mins=0
|
|
86
75
|
printf '%s\n' "$mins"
|
|
87
76
|
}
|
|
88
|
-
# Valid quota snapshots must contain integer usage values and future reset
|
|
89
|
-
# epochs for both windows. Partial or expired snapshots never enter the cache.
|
|
90
|
-
_valid_quota_snapshot() {
|
|
91
|
-
local u5="$1" u7="$2" r5="$3" r7="$4"
|
|
92
|
-
[[ "$u5" =~ ^[0-9]+$ ]] || return 1
|
|
93
|
-
[[ "$u7" =~ ^[0-9]+$ ]] || return 1
|
|
94
|
-
[[ "$r5" =~ ^[0-9]+$ ]] || return 1
|
|
95
|
-
[[ "$r7" =~ ^[0-9]+$ ]] || return 1
|
|
96
|
-
((r5 > NOW && r7 > NOW))
|
|
97
|
-
}
|
|
98
77
|
# Collects live Git metadata for DIR. On non-repos, leaves defaults in place
|
|
99
78
|
# and returns non-zero so callers can decide whether to cache the empty result.
|
|
100
79
|
_collect_git_info() {
|
|
@@ -122,20 +101,18 @@ for _BASE in "${XDG_RUNTIME_DIR:-}" "${HOME}/.cache"; do
|
|
|
122
101
|
CACHE_OK=1
|
|
123
102
|
break
|
|
124
103
|
done
|
|
125
|
-
QC=""
|
|
126
|
-
[[ "$CACHE_OK" == "1" ]] && QC="${_CD}/claude-sl-quota"
|
|
127
104
|
# Returns true (exit 0) when file is missing or older than $2 seconds.
|
|
128
105
|
_stale() { [ ! -f "$1" ] || [ $((NOW - $(stat -f%m "$1" 2>/dev/null || stat -c%Y "$1" 2>/dev/null || echo 0))) -gt "$2" ]; }
|
|
129
106
|
|
|
130
107
|
# ── Parse stdin + settings in one jq call ──
|
|
131
|
-
# Fields: MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7
|
|
108
|
+
# Fields: MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7 TIN
|
|
132
109
|
# Read settings into a shell var rather than process substitution so the jq
|
|
133
110
|
# --argjson path works on Windows Git Bash (where /proc/<pid>/fd/N used by
|
|
134
111
|
# <(...) is unavailable and --slurpfile silently fails, yielding empty fields).
|
|
135
112
|
HAS_RL=0
|
|
136
113
|
_SETTINGS=$(cat "$HOME/.claude/settings.json" 2>/dev/null)
|
|
137
114
|
echo "$_SETTINGS" | jq -e . >/dev/null 2>&1 || _SETTINGS='{}'
|
|
138
|
-
IFS=$'\t' read -r MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7 < <(
|
|
115
|
+
IFS=$'\t' read -r MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7 TIN < <(
|
|
139
116
|
jq -r --argjson cfg "$_SETTINGS" \
|
|
140
117
|
'[(.model.display_name//"?"),(.workspace.project_dir//"."),
|
|
141
118
|
(.context_window.used_percentage//0|floor),(.context_window.context_window_size//0),
|
|
@@ -145,10 +122,26 @@ IFS=$'\t' read -r MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7 < <(
|
|
|
145
122
|
(.rate_limits.five_hour.used_percentage//null|if type=="number" then floor else "--" end),
|
|
146
123
|
(.rate_limits.seven_day.used_percentage//null|if type=="number" then floor else "--" end),
|
|
147
124
|
(.rate_limits.five_hour.resets_at//0),
|
|
148
|
-
(.rate_limits.seven_day.resets_at//0)
|
|
125
|
+
(.rate_limits.seven_day.resets_at//0),
|
|
126
|
+
(.context_window.total_input_tokens//0|floor)]|@tsv' <<<"$input"
|
|
149
127
|
)
|
|
150
128
|
case "${EFF:-default}" in low) EF='low' ;; high) EF='high' ;; xhigh) EF='xhigh' ;; max) EF='max' ;; *) EF='medium' ;; esac
|
|
151
129
|
|
|
130
|
+
# ── Auto-compact window: track usage against the compaction threshold ──
|
|
131
|
+
# When CLAUDE_CODE_AUTO_COMPACT_WINDOW is set, compaction fires at that token
|
|
132
|
+
# count, not the model's full window. Recompute PCT against it and relabel CTX
|
|
133
|
+
# so the bar measures "distance to compaction", matching the desktop app's bar
|
|
134
|
+
# (e.g. 49.8k / 400.0k). Falls back to the full window when unset or when token
|
|
135
|
+
# data is missing (early session, before the first API response).
|
|
136
|
+
ACW="${CLAUDE_CODE_AUTO_COMPACT_WINDOW:-0}"
|
|
137
|
+
if [[ "$ACW" =~ ^[0-9]+$ ]] && ((ACW > 0)) && [[ "$TIN" =~ ^[0-9]+$ ]] && ((TIN > 0)); then
|
|
138
|
+
# Compaction can't exceed the real window; clamp so the label stays honest.
|
|
139
|
+
((CTX > 0 && ACW > CTX)) && ACW=$CTX
|
|
140
|
+
PCT=$((TIN * 100 / ACW))
|
|
141
|
+
((PCT > 100)) && PCT=100
|
|
142
|
+
CTX=$ACW
|
|
143
|
+
fi
|
|
144
|
+
|
|
152
145
|
# ── Context label (needed by MODEL_SHORT and line 2) ──
|
|
153
146
|
if ((CTX >= 1000000)); then
|
|
154
147
|
CL="$((CTX / 1000000))M"
|
|
@@ -223,34 +216,18 @@ elif [[ "$IS_WT" == "1" ]]; then
|
|
|
223
216
|
((${#L1R} > 25)) && L1R="${L1R:0:25}…"
|
|
224
217
|
fi
|
|
225
218
|
|
|
226
|
-
# Usage data: read stdin rate_limits when available
|
|
219
|
+
# Usage data: read stdin rate_limits when available. When absent we show
|
|
220
|
+
# placeholders and the session cost instead of falling back to a possibly stale
|
|
221
|
+
# (or wrong-account) cached snapshot. See docs/decisions/2026-05-20-quota-cache-removal.md.
|
|
227
222
|
SHOW_COST=0
|
|
228
223
|
if [[ "$HAS_RL" == "1" ]]; then
|
|
229
224
|
# Stdin path: real-time, no network. U5/U7 already set by jq read above.
|
|
230
225
|
# Guard: resets_at=0 means field missing, leave RM empty so _usage skips it.
|
|
231
226
|
RM5=$(_minutes_until "$R5")
|
|
232
227
|
RM7=$(_minutes_until "$R7")
|
|
233
|
-
if [[ -n "$QC" ]] && _valid_quota_snapshot "$U5" "$U7" "$R5" "$R7"; then
|
|
234
|
-
_write_quota_snapshot_if_changed "$QC" "$U5" "$U7" "$R5" "$R7" || true
|
|
235
|
-
fi
|
|
236
228
|
else
|
|
237
229
|
U5="--" U7="--" RM5="" RM7=""
|
|
238
230
|
SHOW_COST=1
|
|
239
|
-
if [[ -n "$QC" ]] && _load_cache_record_file "$QC"; then
|
|
240
|
-
_CU5=${CACHE_FIELDS[0]:-}
|
|
241
|
-
_CU7=${CACHE_FIELDS[1]:-}
|
|
242
|
-
_CR5=${CACHE_FIELDS[2]:-}
|
|
243
|
-
_CR7=${CACHE_FIELDS[3]:-}
|
|
244
|
-
if _valid_quota_snapshot "$_CU5" "$_CU7" "$_CR5" "$_CR7"; then
|
|
245
|
-
U5="$_CU5"
|
|
246
|
-
U7="$_CU7"
|
|
247
|
-
R5="$_CR5"
|
|
248
|
-
R7="$_CR7"
|
|
249
|
-
RM5=$(_minutes_until "$R5")
|
|
250
|
-
RM7=$(_minutes_until "$R7")
|
|
251
|
-
SHOW_COST=0
|
|
252
|
-
fi
|
|
253
|
-
fi
|
|
254
231
|
fi
|
|
255
232
|
|
|
256
233
|
# Combined usage formatter: used% [pace delta] (countdown)
|