claude-code-arcane 1.1.0 → 1.1.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/CHANGELOG.md +7 -0
- package/hooks/statusline.sh +57 -31
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.1.1](https://github.com/SebastianLuser/claude-code-arcane/compare/v1.1.0...v1.1.1) (2026-06-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **statusline:** derive context % from real session window, not hardcoded limit ([e9b835f](https://github.com/SebastianLuser/claude-code-arcane/commit/e9b835f457a69a9bbc371da382d7673abce10113))
|
|
7
|
+
|
|
1
8
|
# [1.1.0](https://github.com/SebastianLuser/claude-code-arcane/compare/v1.0.1...v1.1.0) (2026-06-12)
|
|
2
9
|
|
|
3
10
|
|
package/hooks/statusline.sh
CHANGED
|
@@ -37,49 +37,75 @@ try:
|
|
|
37
37
|
data = json.load(sys.stdin)
|
|
38
38
|
except Exception:
|
|
39
39
|
sys.exit(0)
|
|
40
|
-
path = data.get("transcript_path") or ""
|
|
41
|
-
# Windows python cannot resolve MSYS/Git-Bash paths (/c/Users/...); convert to C:/Users/...
|
|
42
|
-
if os.name == "nt" and len(path) > 2 and path[0] == "/" and path[2] == "/" and path[1].isalpha():
|
|
43
|
-
path = path[1].upper() + ":" + path[2:]
|
|
44
40
|
model = data.get("model") or {}
|
|
45
41
|
model_id = model.get("id", "")
|
|
46
42
|
model_name = model.get("display_name") or model_id or ""
|
|
47
43
|
# emit model on first line for bash to pick up
|
|
48
44
|
print("MODEL=" + model_name)
|
|
49
|
-
|
|
50
|
-
sys.exit(0)
|
|
51
|
-
last_usage = None
|
|
52
|
-
try:
|
|
53
|
-
with open(path, encoding="utf-8") as f:
|
|
54
|
-
for line in f:
|
|
55
|
-
try:
|
|
56
|
-
m = json.loads(line)
|
|
57
|
-
except Exception:
|
|
58
|
-
continue
|
|
59
|
-
if m.get("type") == "assistant":
|
|
60
|
-
msg = m.get("message") or {}
|
|
61
|
-
u = msg.get("usage")
|
|
62
|
-
if u:
|
|
63
|
-
last_usage = u
|
|
64
|
-
except Exception:
|
|
65
|
-
sys.exit(0)
|
|
45
|
+
|
|
66
46
|
def fmt(n):
|
|
67
47
|
if n >= 1000000:
|
|
68
48
|
return ("%.1fM" % (n / 1000000.0)).replace(".0M", "M")
|
|
69
49
|
if n >= 1000:
|
|
70
50
|
return "%dk" % int(round(n / 1000.0))
|
|
71
51
|
return str(n)
|
|
52
|
+
|
|
53
|
+
total = None
|
|
54
|
+
limit = None
|
|
55
|
+
pct = None
|
|
56
|
+
|
|
57
|
+
# Preferred: Claude Code provides the real context window for THIS session/model.
|
|
58
|
+
# context_window_size is the actual max (200k, 1M, ...) — never hardcode it.
|
|
59
|
+
cw = data.get("context_window") or {}
|
|
60
|
+
if isinstance(cw, dict) and cw:
|
|
61
|
+
limit = cw.get("context_window_size") or None
|
|
62
|
+
cu = cw.get("current_usage") or {}
|
|
63
|
+
if cu:
|
|
64
|
+
total = (cu.get("input_tokens", 0)
|
|
65
|
+
+ cu.get("cache_read_input_tokens", 0)
|
|
66
|
+
+ cu.get("cache_creation_input_tokens", 0))
|
|
67
|
+
up = cw.get("used_percentage")
|
|
68
|
+
if up is not None:
|
|
69
|
+
try:
|
|
70
|
+
pct = float(up)
|
|
71
|
+
except Exception:
|
|
72
|
+
pct = None
|
|
73
|
+
|
|
74
|
+
# Fallback (older Claude Code without context_window): scan transcript usage.
|
|
75
|
+
if total is None:
|
|
76
|
+
path = data.get("transcript_path") or ""
|
|
77
|
+
# Windows python cannot resolve MSYS/Git-Bash paths (/c/Users/...); convert to C:/Users/...
|
|
78
|
+
if os.name == "nt" and len(path) > 2 and path[0] == "/" and path[2] == "/" and path[1].isalpha():
|
|
79
|
+
path = path[1].upper() + ":" + path[2:]
|
|
80
|
+
if path and os.path.exists(path):
|
|
81
|
+
last_usage = None
|
|
82
|
+
try:
|
|
83
|
+
with open(path, encoding="utf-8") as f:
|
|
84
|
+
for line in f:
|
|
85
|
+
try:
|
|
86
|
+
m = json.loads(line)
|
|
87
|
+
except Exception:
|
|
88
|
+
continue
|
|
89
|
+
if m.get("type") == "assistant":
|
|
90
|
+
u = (m.get("message") or {}).get("usage")
|
|
91
|
+
if u:
|
|
92
|
+
last_usage = u
|
|
93
|
+
except Exception:
|
|
94
|
+
last_usage = None
|
|
95
|
+
if last_usage:
|
|
96
|
+
total = (last_usage.get("input_tokens", 0)
|
|
97
|
+
+ last_usage.get("cache_read_input_tokens", 0)
|
|
98
|
+
+ last_usage.get("cache_creation_input_tokens", 0))
|
|
99
|
+
# last-resort only: infer window from a 1M marker, else assume 200k
|
|
100
|
+
if limit is None:
|
|
101
|
+
limit = 1000000 if ("[1m]" in model_id or model_id.endswith("-1m")) else 200000
|
|
102
|
+
|
|
72
103
|
parts = []
|
|
73
|
-
if
|
|
74
|
-
total
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# pct is always total/limit so it stays consistent with the tokens shown.
|
|
79
|
-
# Under the correct limit it never exceeds 100% in normal use; if it does,
|
|
80
|
-
# the limit is wrong (broken statusline) and we want to see it, not mask it.
|
|
81
|
-
pct = int(round(total * 100 / limit)) if limit else 0
|
|
82
|
-
parts.append("\U0001F9E0 %s / %s (%d%%)" % (fmt(total), fmt(limit), pct))
|
|
104
|
+
if total is not None and limit:
|
|
105
|
+
# pct from Claude Code when available; else total/limit (consistent with tokens shown).
|
|
106
|
+
if pct is None:
|
|
107
|
+
pct = total * 100.0 / limit
|
|
108
|
+
parts.append("\U0001F9E0 %s / %s (%d%%)" % (fmt(total), fmt(limit), int(round(pct))))
|
|
83
109
|
print("EXTRA=" + " | ".join(parts))
|
|
84
110
|
' <<< "$INPUT" 2>/dev/null)
|
|
85
111
|
MODEL=$(echo "$OUT" | grep '^MODEL=' | head -1 | sed 's/^MODEL=//')
|