claude-code-cache-fix 3.3.0 → 3.5.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.ko.md +3 -3
- package/README.md +58 -3
- package/README.zh.md +3 -3
- package/package.json +2 -2
- package/proxy/extensions/cache-telemetry.mjs +139 -17
- package/proxy/extensions/identity-normalization.mjs +1 -1
- package/proxy/extensions/image-strip.mjs +7 -2
- package/proxy/extensions/messages-cache-breakpoint.mjs +314 -0
- package/proxy/extensions/microcompact-stability.mjs +429 -0
- package/proxy/extensions/ttl-management.mjs +2 -1
- package/proxy/extensions/ttl-tier-detect.mjs +33 -0
- package/proxy/extensions.json +3 -0
- package/tools/cache-test.sh +19 -11
- package/tools/cross-version-cache-test.sh +4 -4
- package/tools/quota-statusline.sh +75 -19
|
@@ -1,25 +1,82 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Status line: show quota % and burn rate from quota-status.
|
|
2
|
+
# Status line: show quota % and burn rate from per-session quota-status files.
|
|
3
3
|
# Written by cache-fix proxy's cache-telemetry extension on every API call.
|
|
4
|
+
#
|
|
5
|
+
# Layout (post-v3.5.0):
|
|
6
|
+
# ~/.claude/quota-status/account.json — global quota fields (5h/7d, status, overage)
|
|
7
|
+
# ~/.claude/quota-status/sessions/<filename>.json — per-session cache fields (ttl_tier, hit_rate)
|
|
8
|
+
#
|
|
9
|
+
# CC pipes hook input as JSON on stdin including `session_id`, which we map to
|
|
10
|
+
# the per-session filename via the canonical rule (matches the writer in
|
|
11
|
+
# proxy/extensions/cache-telemetry.mjs:sessionFilename).
|
|
4
12
|
|
|
5
13
|
input=$(cat)
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
ACCOUNT="$HOME/.claude/quota-status/account.json"
|
|
16
|
+
SESSIONS_DIR="$HOME/.claude/quota-status/sessions"
|
|
8
17
|
|
|
9
|
-
if
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
# Show quota even if no per-session file exists yet (fresh session, first
|
|
19
|
+
# request hasn't fired). Per-session block just gets blank.
|
|
20
|
+
if [ ! -f "$ACCOUNT" ]; then
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
result=$(python3 -c "
|
|
25
|
+
import sys, json, os, re, hashlib
|
|
12
26
|
from datetime import datetime, timezone, timedelta
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
home = os.path.expanduser('~')
|
|
29
|
+
account_path = os.path.join(home, '.claude', 'quota-status', 'account.json')
|
|
30
|
+
sessions_dir = os.path.join(home, '.claude', 'quota-status', 'sessions')
|
|
31
|
+
|
|
32
|
+
# Parse stdin JSON (CC hook input) for session_id. Pass the raw value
|
|
33
|
+
# (including null / "" / whitespace) through session_filename so the
|
|
34
|
+
# canonical rule decides — the writer maps all those to 'unknown',
|
|
35
|
+
# the reader must do the same to keep the contract identical.
|
|
36
|
+
try:
|
|
37
|
+
stdin_data = json.loads('''$input''') if '''$input''' else {}
|
|
38
|
+
except Exception:
|
|
39
|
+
stdin_data = {}
|
|
40
|
+
sess_id_raw = stdin_data.get('session_id')
|
|
41
|
+
|
|
42
|
+
# Canonical filename derivation — must match cache-telemetry.mjs:sessionFilename.
|
|
43
|
+
# Allowlist: [A-Za-z0-9_-]{1,128}; else inv-<sha256(s)[:16]>; null/empty/whitespace -> 'unknown'.
|
|
44
|
+
SAFE = re.compile(r'^[A-Za-z0-9_-]{1,128}\$')
|
|
45
|
+
def session_filename(raw):
|
|
46
|
+
if raw is None:
|
|
47
|
+
return 'unknown'
|
|
48
|
+
s = str(raw).strip()
|
|
49
|
+
if not s:
|
|
50
|
+
return 'unknown'
|
|
51
|
+
if SAFE.match(s):
|
|
52
|
+
return s
|
|
53
|
+
return 'inv-' + hashlib.sha256(s.encode('utf-8')).hexdigest()[:16]
|
|
15
54
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
55
|
+
# Read account.json (account-global fields).
|
|
56
|
+
try:
|
|
57
|
+
acc = json.load(open(account_path))
|
|
58
|
+
except Exception:
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
|
|
61
|
+
# Read this session's per-session file (cache fields). Apply the rule
|
|
62
|
+
# unconditionally — null/empty/whitespace land at sessions/unknown.json,
|
|
63
|
+
# matching where the writer would have placed them. If the file doesn't
|
|
64
|
+
# exist (e.g. unknown.json never written, or this is a fresh session
|
|
65
|
+
# whose first request hasn't fired), statusline still shows quota % —
|
|
66
|
+
# just no TTL/hit-rate block.
|
|
67
|
+
sess_filename = session_filename(sess_id_raw)
|
|
68
|
+
try:
|
|
69
|
+
sess = json.load(open(os.path.join(sessions_dir, sess_filename + '.json')))
|
|
70
|
+
except Exception:
|
|
71
|
+
sess = {}
|
|
72
|
+
|
|
73
|
+
q5h = acc.get('five_hour', {}).get('pct', 0)
|
|
74
|
+
q7d = acc.get('seven_day', {}).get('pct', 0)
|
|
75
|
+
q5h_reset = acc.get('five_hour', {}).get('resets_at', 0)
|
|
76
|
+
q7d_reset = acc.get('seven_day', {}).get('resets_at', 0)
|
|
77
|
+
status = acc.get('status', '')
|
|
78
|
+
overage = acc.get('overage_status', '')
|
|
79
|
+
ts = sess.get('timestamp') or acc.get('timestamp', '')
|
|
23
80
|
|
|
24
81
|
now = datetime.fromisoformat(ts.replace('Z', '+00:00')) if ts else datetime.now(timezone.utc)
|
|
25
82
|
|
|
@@ -48,9 +105,9 @@ if rate7:
|
|
|
48
105
|
if overage == 'active':
|
|
49
106
|
label += ' | OVERAGE'
|
|
50
107
|
|
|
51
|
-
# TTL and cache stats
|
|
52
|
-
ttl =
|
|
53
|
-
hit =
|
|
108
|
+
# Per-session TTL and cache stats
|
|
109
|
+
ttl = sess.get('cache', {}).get('ttl_tier', '')
|
|
110
|
+
hit = sess.get('cache', {}).get('hit_rate', '')
|
|
54
111
|
if ttl:
|
|
55
112
|
if ttl == '5m':
|
|
56
113
|
label += ' | \033[31mTTL:5m\033[0m'
|
|
@@ -59,12 +116,11 @@ if ttl:
|
|
|
59
116
|
if hit and hit != 'N/A':
|
|
60
117
|
label += ' ' + hit + '%'
|
|
61
118
|
|
|
62
|
-
peak =
|
|
119
|
+
peak = acc.get('peak_hour', False)
|
|
63
120
|
if peak:
|
|
64
121
|
label += ' | \033[33mPEAK\033[0m'
|
|
65
122
|
|
|
66
123
|
print(label)
|
|
67
124
|
" 2>/dev/null)
|
|
68
125
|
|
|
69
|
-
|
|
70
|
-
fi
|
|
126
|
+
[ -n "$result" ] && echo "$result"
|