claude-pace 0.6.2

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.
Files changed (4) hide show
  1. package/README.md +91 -0
  2. package/claude-pace.sh +275 -0
  3. package/cli.js +70 -0
  4. package/package.json +32 -0
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Claude Pace
2
+
3
+ Know your quota before you hit the wall. A statusline for Claude Code — single Bash file, zero npm.
4
+
5
+ Most statuslines show "you used 60%." That number means nothing without context. 60% with 30 minutes left? Fine, the window resets soon. 60% with 4 hours left? You're about to hit the wall. claude-pace compares your usage rate to the time remaining and shows the delta. No Node.js, no npm, no lock files. Single Bash file.
6
+
7
+ ![claude-pace statusline demo](.github/claude-pace-demo.gif)
8
+
9
+ - **⇣15%** green = you've used 15% less than expected. Headroom. Keep going.
10
+ - **⇡15%** red = you're burning 15% faster than sustainable. Slow down.
11
+ - **15%** / **20%** = used in the 5h and 7d windows. **3h** = resets in 3 hours.
12
+ - Top line: model, effort, project `(branch)`, `3f +24 -7` = git diff stats
13
+
14
+ ## Install
15
+
16
+ Requires `jq`. Node.js is only needed for install, not runtime.
17
+
18
+ ```bash
19
+ npx claude-pace
20
+ ```
21
+
22
+ Restart Claude Code. Done.
23
+
24
+ <details>
25
+ <summary>Other methods</summary>
26
+
27
+ **Plugin:**
28
+
29
+ ```bash
30
+ claude plugin marketplace add Astro-Han/claude-pace
31
+ claude plugin install claude-pace
32
+ ```
33
+
34
+ Then inside Claude Code, type `/claude-pace:setup`.
35
+
36
+ **Manual:**
37
+
38
+ ```bash
39
+ curl -o ~/.claude/statusline.sh \
40
+ https://raw.githubusercontent.com/Astro-Han/claude-pace/main/claude-pace.sh
41
+ chmod +x ~/.claude/statusline.sh
42
+ ```
43
+
44
+ Add to `~/.claude/settings.json`:
45
+
46
+ ```json
47
+ {
48
+ "statusLine": {
49
+ "type": "command",
50
+ "command": "~/.claude/statusline.sh"
51
+ }
52
+ }
53
+ ```
54
+
55
+ Restart Claude Code. Done.
56
+
57
+ </details>
58
+
59
+ To remove: delete the `statusLine` block from `~/.claude/settings.json`.
60
+
61
+ ## How It Compares
62
+
63
+ | | claude-pace | Node.js/TypeScript statuslines | Rust/Go statuslines |
64
+ |---|---|---|---|
65
+ | Runtime | `jq` | Node.js 18+ / npm | Compiled binary |
66
+ | Codebase | Single file | 1000+ lines + node_modules | Compiled, not inspectable |
67
+ | Execution | ~10ms, 3% of refresh cycle | ~90ms, 30% of refresh cycle | ~5ms (est.) |
68
+ | Memory | ~2 MB | ~57 MB | ~3 MB (est.) |
69
+ | Failure modes | Read-only, worst case prints "Claude" | Runtime dependency, package manager | Generally stable |
70
+ | Pace tracking | Usage rate vs time remaining | Trend-only or none | None |
71
+
72
+ Execution and memory measured on Apple Silicon, 300 runs, same stdin JSON. Rust/Go values are estimates.
73
+
74
+ Need themes, powerline aesthetics, or TUI config? Try [ccstatusline](https://github.com/sirmalloc/ccstatusline). The entire source of claude-pace is [one file](claude-pace.sh). Read it.
75
+
76
+ ## Under the Hood
77
+
78
+ Claude Code polls the statusline every ~300ms:
79
+
80
+ | Data | Source | Cache |
81
+ |------|--------|-------|
82
+ | Model, context, cost | stdin JSON (single `jq` call) | None needed |
83
+ | Quota (5h, 7d, pace) | stdin `rate_limits` (CC >= 2.1.80) | None needed (real-time) |
84
+ | Quota fallback | Anthropic Usage API (CC < 2.1.80) | `/tmp`, 300s TTL, async background refresh |
85
+ | Git branch + diff | `git` commands | `/tmp`, 5s TTL |
86
+
87
+ On Claude Code >= 2.1.80, usage data comes directly from stdin. No network calls. On older versions, it falls back to the Usage API in a background subshell so the statusline never blocks.
88
+
89
+ ## License
90
+
91
+ MIT
package/claude-pace.sh ADDED
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env bash
2
+ # Claude Code statusline plugin
3
+ # Line1: model (ctx) effort | project (branch) Nf +A -D
4
+ # Line2: bar PCT% CL | 5h used% [⇡⇣pace] countdown 7d used% [⇡⇣pace] countdown
5
+
6
+ # Disable glob expansion so unquoted vars with wildcards (e.g. DIR paths)
7
+ # are never accidentally expanded into filename lists.
8
+ set -f
9
+ input=$(cat)
10
+ [ -z "$input" ] && {
11
+ echo "Claude"
12
+ exit 0
13
+ }
14
+ command -v jq >/dev/null || {
15
+ echo "Claude [needs jq]"
16
+ exit 0
17
+ }
18
+
19
+ # ── Colors & Utilities ──
20
+ # C=Cyan G=Green Y=Yellow R=Red D=Dim N=Normal (reset)
21
+ C='\033[36m' G='\033[32m' Y='\033[33m' R='\033[31m' D='\033[2m' N='\033[0m'
22
+ NOW=$(date +%s)
23
+ # Returns true (exit 0) when file is missing or older than $2 seconds.
24
+ _stale() { [ ! -f "$1" ] || [ $((NOW - $(stat -f%m "$1" 2>/dev/null || stat -c%Y "$1" 2>/dev/null || echo 0))) -gt "$2" ]; }
25
+
26
+ # ── Parse stdin + settings in one jq call ──
27
+ # Fields: MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7
28
+ HAS_RL=0
29
+ IFS=$'\t' read -r MODEL DIR PCT CTX COST EFF HAS_RL U5 U7 R5 R7 < <(
30
+ jq -r --slurpfile cfg <(cat ~/.claude/settings.json 2>/dev/null || echo '{}') \
31
+ '[(.model.display_name//"?"),(.workspace.project_dir//"."),
32
+ (.context_window.used_percentage//0|floor),(.context_window.context_window_size//0),
33
+ (.cost.total_cost_usd//0),
34
+ ($cfg[0].effortLevel//"default"),
35
+ (if .rate_limits then 1 else 0 end),
36
+ (.rate_limits.five_hour.used_percentage//null|if type=="number" then floor else "--" end),
37
+ (.rate_limits.seven_day.used_percentage//null|if type=="number" then floor else "--" end),
38
+ (.rate_limits.five_hour.resets_at//0),
39
+ (.rate_limits.seven_day.resets_at//0)]|@tsv' <<<"$input"
40
+ )
41
+ case "${EFF:-default}" in high) EF='●' ;; low) EF='◔' ;; *) EF='◑' ;; esac
42
+
43
+ # ── Context label (needed by MODEL_SHORT and line 2) ──
44
+ if ((CTX >= 1000000)); then
45
+ CL="$((CTX / 1000000))M"
46
+ elif ((CTX > 0)); then
47
+ CL="$((CTX / 1000))K"
48
+ else CL=""; fi
49
+
50
+ # ── MODEL_SHORT: strip redundant context label ──
51
+ MODEL=${MODEL/ context)/)}
52
+ [[ "$CTX" -gt 0 && "$MODEL" != *"("* ]] && MODEL="${MODEL} (${CL})"
53
+ # Truncate long model names to keep padding within 0-5 chars.
54
+ _ML="${MODEL} ${EF}"
55
+ ((${#_ML} > 22)) && MODEL="${MODEL:0:$((22 - 2 - ${#EF}))}…"
56
+
57
+ # ── Progress Bar ──
58
+ F=$((PCT / 10))
59
+ ((F < 0)) && F=0
60
+ ((F > 10)) && F=10
61
+ if ((PCT >= 90)); then BC=$R; elif ((PCT >= 70)); then BC=$Y; else BC=$G; fi
62
+ BAR=""
63
+ for ((i = 0; i < F; i++)); do BAR+='█'; done
64
+ for ((i = F; i < 10; i++)); do BAR+='░'; done
65
+
66
+ # ── Git Info (5s cache, atomic write) ──
67
+ # Cache key encodes DIR so concurrent sessions in different repos don't clash.
68
+ # Atomic write: write to a temp file first, then mv to avoid partial reads.
69
+ GC="/tmp/claude-sl-git-${DIR//[^a-zA-Z0-9]/_}"
70
+ if _stale "$GC" 5; then
71
+ if git -C "$DIR" rev-parse --git-dir >/dev/null 2>&1; then
72
+ _BR=$(git -C "$DIR" --no-optional-locks branch --show-current 2>/dev/null)
73
+ _FC=0 _AD=0 _DL=0
74
+ while IFS=$'\t' read -r a d _; do
75
+ # Skip binary files (reported as "-" instead of a number).
76
+ [[ "$a" =~ ^[0-9]+$ ]] && {
77
+ _FC=$((_FC + 1))
78
+ _AD=$((_AD + a))
79
+ _DL=$((_DL + d))
80
+ }
81
+ done < <(git -C "$DIR" --no-optional-locks diff HEAD --numstat 2>/dev/null)
82
+ _TMP=$(mktemp /tmp/claude-sl-g-XXXXXX)
83
+ echo "${_BR}|${_FC}|${_AD}|${_DL}" >"$_TMP" && mv "$_TMP" "$GC"
84
+ else
85
+ echo "|||" >"$GC"
86
+ fi
87
+ fi
88
+ IFS='|' read -r BR FC AD DL <"$GC" 2>/dev/null
89
+
90
+ # ── Project Name + Line 1 Right Section ──
91
+ # Extract project name. Worktree: save repo name explicitly.
92
+ PN="${DIR##*/}"
93
+ IS_WT=0 _REPO=""
94
+ if [[ "${DIR/#$HOME/\~}" =~ /([^/]+)/\.claude/worktrees/([^/]+) ]]; then
95
+ IS_WT=1
96
+ _REPO="${BASH_REMATCH[1]}"
97
+ _WT_NAME="${BASH_REMATCH[2]}"
98
+ PN="$_REPO"
99
+ fi
100
+ ((${#PN} > 25)) && PN="${PN:0:25}…"
101
+
102
+ # Format: project (branch) [git stats]
103
+ L1R="$PN"
104
+ if [ -n "$BR" ]; then
105
+ ((${#BR} > 35)) && BR="${BR:0:35}…"
106
+ L1R+=" (${BR})"
107
+ ((FC > 0)) 2>/dev/null && L1R+=" ${FC}f ${G}+${AD}${N} ${R}-${DL}${N}"
108
+ elif [[ "$IS_WT" == "1" ]]; then
109
+ # Detached HEAD in worktree: show repo/worktree to preserve identity
110
+ L1R="${_REPO}/${_WT_NAME}"
111
+ ((${#L1R} > 25)) && L1R="${L1R:0:25}…"
112
+ fi
113
+
114
+ # Usage data: prefer stdin rate_limits (CC >=2.1.80), fall back to API polling
115
+ SHOW_COST=0
116
+ if [[ "$HAS_RL" == "1" ]]; then
117
+ # Stdin path: real-time, no network. U5/U7 already set by jq read above.
118
+ # Guard: resets_at=0 means field missing, leave RM empty so _pace/_rc skip it
119
+ RM5=""
120
+ ((R5 > 0)) && {
121
+ RM5=$(((R5 - NOW) / 60))
122
+ ((RM5 < 0)) && RM5=0
123
+ }
124
+ RM7=""
125
+ ((R7 > 0)) && {
126
+ RM7=$(((R7 - NOW) / 60))
127
+ ((RM7 < 0)) && RM7=0
128
+ }
129
+ # Extra usage (XO/XU/XL) only available via API fallback; stdin lacks this data
130
+ else
131
+ # ── API fallback (remove when CC <2.1.80 no longer supported) ──
132
+ UC="/tmp/claude-sl-usage" UL="/tmp/claude-sl-usage.lock"
133
+
134
+ # ── _get_token: credential source priority ──
135
+ # Check in order: env var → macOS Keychain → credentials file → secret-tool (Linux).
136
+ _get_token() {
137
+ [ -n "$CLAUDE_CODE_OAUTH_TOKEN" ] && {
138
+ echo "$CLAUDE_CODE_OAUTH_TOKEN"
139
+ return
140
+ }
141
+ local b=""
142
+ command -v security >/dev/null &&
143
+ b=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null)
144
+ [ -z "$b" ] && [ -f ~/.claude/.credentials.json ] && b=$(<~/.claude/.credentials.json)
145
+ [ -z "$b" ] && command -v secret-tool >/dev/null &&
146
+ b=$(timeout 2 secret-tool lookup service "Claude Code-credentials" 2>/dev/null)
147
+ [ -n "$b" ] && jq -r '.claudeAiOauth.accessToken//empty' <<<"$b" 2>/dev/null
148
+ }
149
+
150
+ # ── _fetch_usage: background stale-while-revalidate fetch ──
151
+ # Runs in a subshell (&) so the main process returns immediately with cached data.
152
+ # On API failure, touches the cache file to reset the 300s TTL and avoid a
153
+ # retry storm; placeholder "--" values leave the display unchanged.
154
+ _fetch_usage() {
155
+ (
156
+ trap 'rm -f "$UL"' EXIT
157
+ TK=$(_get_token)
158
+ [ -z "$TK" ] && return
159
+ RESP=$(curl -s --max-time 3 \
160
+ -H "Authorization: Bearer $TK" -H "anthropic-beta: oauth-2025-04-20" \
161
+ -H "Content-Type: application/json" \
162
+ "https://api.anthropic.com/api/oauth/usage" 2>/dev/null)
163
+ IFS=$'\t' read -r F5 S7 EX EU EL RM5 RM7 < <(jq -r '
164
+ def rmins: if . and . != "" then (sub("\\.[0-9]+"; "") | sub("\\+00:00$"; "Z") | fromdateiso8601) - (now|floor) | ./60|floor | if .<0 then 0 else . end else null end;
165
+ [(.five_hour.utilization|floor),(.seven_day.utilization|floor),
166
+ (if .extra_usage.is_enabled then 1 else 0 end),
167
+ (.extra_usage.used_credits//0|floor),(.extra_usage.monthly_limit//0|floor),
168
+ (.five_hour.resets_at|rmins//""),(.seven_day.resets_at|rmins//"")]|@tsv' \
169
+ <<<"$RESP" 2>/dev/null) || {
170
+ [ ! -f "$UC" ] || [[ $(head -c2 "$UC") == -- ]] && echo "--|--|0|0|0||" >"$UC"
171
+ touch "$UC"
172
+ return
173
+ }
174
+ TMP=$(mktemp /tmp/claude-sl-u-XXXXXX)
175
+ echo "${F5}|${S7}|${EX}|${EU}|${EL}|${RM5}|${RM7}" >"$TMP" && mv "$TMP" "$UC"
176
+ ) &
177
+ }
178
+
179
+ # ── Lock mechanism (noclobber mutex) ──
180
+ # `set -o noclobber` makes `>` fail atomically if the file already exists,
181
+ # providing a lock without external tools. The stale-lock check (10s) ensures
182
+ # a crashed worker can't block refreshes indefinitely.
183
+ if _stale "$UC" 300; then
184
+ if (
185
+ set -o noclobber
186
+ echo $$ >"$UL"
187
+ ) 2>/dev/null; then
188
+ _fetch_usage
189
+ elif [ -f "$UL" ] && _stale "$UL" 10; then
190
+ rm -f "$UL"
191
+ (
192
+ set -o noclobber
193
+ echo $$ >"$UL"
194
+ ) 2>/dev/null && _fetch_usage
195
+ fi
196
+ fi
197
+
198
+ # ── Read cache + drift correction ──
199
+ # The cache stores countdown minutes at write time; subtract elapsed seconds
200
+ # (in whole minutes) since the file was written to keep the countdown accurate
201
+ # between 300s refresh cycles without a network call.
202
+ U5="--" U7="--" XO=0 XU=0 XL=0 RM5="" RM7=""
203
+ [ -f "$UC" ] && IFS='|' read -r U5 U7 XO XU XL RM5 RM7 <"$UC"
204
+ U5=${U5%%.*} U7=${U7%%.*} XU=${XU%%.*} XL=${XL%%.*}
205
+ if [[ "$RM5" =~ ^[0-9]+$ ]] && [ -f "$UC" ]; then
206
+ _CA=$((NOW - $(stat -f%m "$UC" 2>/dev/null || stat -c%Y "$UC" 2>/dev/null || echo "$NOW")))
207
+ RM5=$((RM5 - _CA / 60))
208
+ ((RM5 < 0)) && RM5=0
209
+ [[ "$RM7" =~ ^[0-9]+$ ]] && {
210
+ RM7=$((RM7 - _CA / 60))
211
+ ((RM7 < 0)) && RM7=0
212
+ }
213
+ fi
214
+ [ ! -f "$UC" ] && SHOW_COST=1
215
+ # ── End API fallback ──
216
+ fi
217
+
218
+ # Combined usage formatter: used% [pace delta] (countdown)
219
+ _usage() {
220
+ local u="${1:---}" rm="$2" w="$3"
221
+ if [[ ! "$u" =~ ^[0-9]+$ ]]; then
222
+ printf "%s" "$u"
223
+ else
224
+ if ((u >= 90)); then printf "${R}%d%%${N}" "$u"; elif ((u >= 70)); then printf "${Y}%d%%${N}" "$u"; else printf "${G}%d%%${N}" "$u"; fi
225
+ if [[ "$rm" =~ ^[0-9]+$ ]] && ((rm <= w)); then
226
+ # Pace delta: positive = over pace (overspend), negative = under pace (surplus).
227
+ local d=$((u - (w - rm) * 100 / w))
228
+ ((d > 0)) && printf " ${R}⇡%d%%${N}" "$d"
229
+ ((d < 0)) && printf " ${G}⇣%d%%${N}" "${d#-}"
230
+ fi
231
+ fi
232
+ [[ "$rm" =~ ^[0-9]+$ ]] || return
233
+ ((rm >= 1440)) && {
234
+ printf " ${D}%dd${N}" $((rm / 1440))
235
+ return
236
+ }
237
+ ((rm >= 60)) && {
238
+ printf " ${D}%dh${N}" $((rm / 60))
239
+ return
240
+ }
241
+ printf " ${D}%dm${N}" "$rm"
242
+ }
243
+
244
+ # ── Output Assembly (symmetric single-pipe alignment) ──
245
+ # Default XO/XU/XL for stdin path (extra usage only available via API fallback).
246
+ : "${XO:=0}" "${XU:=0}" "${XL:=0}"
247
+
248
+ # Build plain-text left sections for width measurement (no ANSI codes).
249
+ L1_PLAIN="${MODEL} ${EF}"
250
+ L2_PLAIN="${BAR} ${PCT}% ${CL}"
251
+ # Pad shorter side so | aligns on both lines.
252
+ W1=${#L1_PLAIN} W2=${#L2_PLAIN}
253
+ PAD1="" PAD2=""
254
+ if ((W1 > W2)); then
255
+ printf -v PAD2 "%*s" $((W1 - W2)) ""
256
+ elif ((W2 > W1)); then
257
+ printf -v PAD1 "%*s" $((W2 - W1)) ""
258
+ fi
259
+
260
+ # Line 1: model (context) effort | project (branch) git-stats
261
+ L1="${C}${MODEL} ${EF}${N}${PAD1} ${D}|${N} ${L1R}"
262
+
263
+ # Line 2: bar pct% CL | 5h used% ... 7d used% ...
264
+ L2="${BC}${BAR}${N} ${PCT}% ${CL}${PAD2} ${D}|${N} 5h $(_usage "$U5" "$RM5" 300) 7d $(_usage "$U7" "$RM7" 10080)"
265
+ # Extra usage: only when enabled and has actual spending (API fallback only)
266
+ [ "$XO" = 1 ] && ((XU > 0)) &&
267
+ printf -v _XS " ${Y}\$%d.%02d${N}/\$%d.%02d" $((XU / 100)) $((XU % 100)) $((XL / 100)) $((XL % 100)) && L2+="$_XS"
268
+ # Session cost: only when /tmp/claude-sl-usage does not exist
269
+ if [[ "$SHOW_COST" == "1" ]]; then
270
+ printf -v _CS "\$%.2f" "$COST" 2>/dev/null
271
+ [[ "$_CS" != "\$0.00" ]] && L2+=" $_CS"
272
+ fi
273
+
274
+ echo -e "$L1"
275
+ echo -e "$L2"
package/cli.js ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ // One-step installer for claude-pace statusline.
4
+ // Copies the Bash script to ~/.claude/ and configures settings.json.
5
+ // After install, Node is not needed. The statusline runs as pure Bash + jq.
6
+
7
+ const { execSync } = require("child_process");
8
+ const { readFileSync, writeFileSync, copyFileSync, chmodSync, renameSync, existsSync, mkdirSync } = require("fs");
9
+ const { join } = require("path");
10
+ const { homedir, platform } = require("os");
11
+
12
+ // Platform guard: statusline is Bash, won't run on Windows
13
+ if (platform() === "win32") {
14
+ console.error("Error: claude-pace requires Bash and only works on macOS/Linux.");
15
+ process.exit(1);
16
+ }
17
+
18
+ // Check jq dependency
19
+ try {
20
+ execSync("command -v jq", { stdio: "ignore" });
21
+ } catch {
22
+ console.error("Error: jq is required but not found.");
23
+ console.error("Install it: brew install jq (macOS) or apt install jq (Linux)");
24
+ process.exit(1);
25
+ }
26
+
27
+ const claudeDir = join(homedir(), ".claude");
28
+ const dest = join(claudeDir, "statusline.sh");
29
+ const settingsPath = join(claudeDir, "settings.json");
30
+
31
+ // Ensure ~/.claude/ exists
32
+ if (!existsSync(claudeDir)) {
33
+ mkdirSync(claudeDir, { recursive: true });
34
+ }
35
+
36
+ // Copy statusline script and make it executable
37
+ copyFileSync(join(__dirname, "claude-pace.sh"), dest);
38
+ chmodSync(dest, 0o755);
39
+
40
+ // Read existing settings (empty file treated as fresh)
41
+ let settings = {};
42
+ if (existsSync(settingsPath)) {
43
+ const raw = readFileSync(settingsPath, "utf8").trim();
44
+ if (raw) {
45
+ try {
46
+ const parsed = JSON.parse(raw);
47
+ // settings.json must be a plain object
48
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
49
+ settings = parsed;
50
+ } else {
51
+ console.error("Error: ~/.claude/settings.json is not a JSON object. Fix it manually, then re-run.");
52
+ process.exit(1);
53
+ }
54
+ } catch {
55
+ console.error("Error: ~/.claude/settings.json is not valid JSON. Fix it manually, then re-run.");
56
+ process.exit(1);
57
+ }
58
+ }
59
+ }
60
+
61
+ // Merge statusLine config (spread preserves any future sub-fields)
62
+ const updating = settings.statusLine && settings.statusLine.command;
63
+ settings.statusLine = { ...settings.statusLine, type: "command", command: "~/.claude/statusline.sh" };
64
+
65
+ // Atomic write: tmp file + rename to prevent truncation on crash
66
+ const tmp = settingsPath + ".tmp";
67
+ writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n");
68
+ renameSync(tmp, settingsPath);
69
+
70
+ console.log(updating ? "claude-pace updated. Restart Claude Code." : "claude-pace installed. Restart Claude Code to see the statusline.");
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "claude-pace",
3
+ "version": "0.6.2",
4
+ "description": "A statusline for Claude Code. Pure Bash + jq, single file.",
5
+ "bin": {
6
+ "claude-pace": "cli.js"
7
+ },
8
+ "files": [
9
+ "cli.js",
10
+ "claude-pace.sh"
11
+ ],
12
+ "author": "Yuhan Lei (https://github.com/Astro-Han)",
13
+ "scripts": {
14
+ "prepublishOnly": "cp ../claude-pace.sh . && cp ../README.md ."
15
+ },
16
+ "keywords": [
17
+ "claude-code",
18
+ "statusline",
19
+ "quota",
20
+ "usage",
21
+ "pace-tracking",
22
+ "bash"
23
+ ],
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/Astro-Han/claude-pace"
28
+ },
29
+ "engines": {
30
+ "node": ">=16"
31
+ }
32
+ }