claude-pace 0.8.5 → 0.9.0

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 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`; fallback to last-known private cache when `rate_limits` is absent | Private cache root, accepted only before cached reset |
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
- Best experience is on Claude Code `2.1.80+`, where `rate_limits` is available in statusline stdin. When a later statusline run omits `rate_limits`, claude-pace can reuse the last known stdin quota snapshot from its private cache until either cached reset time expires.
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
- 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`.
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 a later statusline run omits `rate_limits`, claude-pace can reuse the last known stdin quota snapshot from its private cache as long as that snapshot's reset times are still in the future. Otherwise it falls back to `--` and may still show the local session cost.
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-04-20 · v0.8.3*
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,23 @@ 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
108
  # Fields: MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7
109
+ # Read settings into a shell var rather than process substitution so the jq
110
+ # --argjson path works on Windows Git Bash (where /proc/<pid>/fd/N used by
111
+ # <(...) is unavailable and --slurpfile silently fails, yielding empty fields).
132
112
  HAS_RL=0
113
+ _SETTINGS=$(cat "$HOME/.claude/settings.json" 2>/dev/null)
114
+ echo "$_SETTINGS" | jq -e . >/dev/null 2>&1 || _SETTINGS='{}'
133
115
  IFS=$'\t' read -r MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7 < <(
134
- jq -r --slurpfile cfg <(cat ~/.claude/settings.json 2>/dev/null || echo '{}') \
116
+ jq -r --argjson cfg "$_SETTINGS" \
135
117
  '[(.model.display_name//"?"),(.workspace.project_dir//"."),
136
118
  (.context_window.used_percentage//0|floor),(.context_window.context_window_size//0),
137
119
  (.cost.total_cost_usd//0),
138
- (.effort.level//$cfg[0].effortLevel//"default"),
120
+ (.effort.level//$cfg.effortLevel//"default"),
139
121
  (if .rate_limits then 1 else 0 end),
140
122
  (.rate_limits.five_hour.used_percentage//null|if type=="number" then floor else "--" end),
141
123
  (.rate_limits.seven_day.used_percentage//null|if type=="number" then floor else "--" end),
@@ -218,34 +200,18 @@ elif [[ "$IS_WT" == "1" ]]; then
218
200
  ((${#L1R} > 25)) && L1R="${L1R:0:25}…"
219
201
  fi
220
202
 
221
- # Usage data: read stdin rate_limits when available, otherwise show session cost.
203
+ # Usage data: read stdin rate_limits when available. When absent we show
204
+ # placeholders and the session cost instead of falling back to a possibly stale
205
+ # (or wrong-account) cached snapshot. See docs/decisions/2026-05-20-quota-cache-removal.md.
222
206
  SHOW_COST=0
223
207
  if [[ "$HAS_RL" == "1" ]]; then
224
208
  # Stdin path: real-time, no network. U5/U7 already set by jq read above.
225
209
  # Guard: resets_at=0 means field missing, leave RM empty so _usage skips it.
226
210
  RM5=$(_minutes_until "$R5")
227
211
  RM7=$(_minutes_until "$R7")
228
- if [[ -n "$QC" ]] && _valid_quota_snapshot "$U5" "$U7" "$R5" "$R7"; then
229
- _write_quota_snapshot_if_changed "$QC" "$U5" "$U7" "$R5" "$R7" || true
230
- fi
231
212
  else
232
213
  U5="--" U7="--" RM5="" RM7=""
233
214
  SHOW_COST=1
234
- if [[ -n "$QC" ]] && _load_cache_record_file "$QC"; then
235
- _CU5=${CACHE_FIELDS[0]:-}
236
- _CU7=${CACHE_FIELDS[1]:-}
237
- _CR5=${CACHE_FIELDS[2]:-}
238
- _CR7=${CACHE_FIELDS[3]:-}
239
- if _valid_quota_snapshot "$_CU5" "$_CU7" "$_CR5" "$_CR7"; then
240
- U5="$_CU5"
241
- U7="$_CU7"
242
- R5="$_CR5"
243
- R7="$_CR7"
244
- RM5=$(_minutes_until "$R5")
245
- RM7=$(_minutes_until "$R7")
246
- SHOW_COST=0
247
- fi
248
- fi
249
215
  fi
250
216
 
251
217
  # Combined usage formatter: used% [pace delta] (countdown)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-pace",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "Claude Code statusline and rate limit tracker. Pure Bash + jq, single file.",
5
5
  "bin": {
6
6
  "claude-pace": "cli.js"