@simplysm/sd-claude 13.0.80 → 13.0.82
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.
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Claude Code statusline: folder | model | context% | 5h%(time) | 7d%(time)"""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import time
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
|
|
13
|
+
_IS_WINDOWS = os.name == "nt"
|
|
14
|
+
if _IS_WINDOWS:
|
|
15
|
+
import msvcrt
|
|
16
|
+
else:
|
|
17
|
+
import fcntl
|
|
18
|
+
|
|
19
|
+
CACHE_FILE = os.path.expanduser("~/.claude/statusline-cache.json")
|
|
20
|
+
CACHE_DIR = os.path.dirname(CACHE_FILE)
|
|
21
|
+
LOCK_FILE = os.path.expanduser("~/.claude/statusline-cache.lock")
|
|
22
|
+
CREDS_FILE = os.path.expanduser("~/.claude/.credentials.json")
|
|
23
|
+
FETCH_INTERVAL = 180 # 3 minutes
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_model(model_id: str) -> str:
|
|
27
|
+
m = re.match(r"claude-(\w+)-(\d+)-(\d+)", model_id)
|
|
28
|
+
if m:
|
|
29
|
+
name = m.group(1).capitalize()
|
|
30
|
+
ver = f"{m.group(2)}.{m.group(3)}"
|
|
31
|
+
return f"{name} {ver}"
|
|
32
|
+
return model_id
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def format_remaining(reset_epoch: int) -> str:
|
|
36
|
+
delta = reset_epoch - time.time()
|
|
37
|
+
if delta <= 0:
|
|
38
|
+
return "0m"
|
|
39
|
+
total_min = int(delta / 60)
|
|
40
|
+
days = total_min // (24 * 60)
|
|
41
|
+
hours = (total_min % (24 * 60)) // 60
|
|
42
|
+
minutes = total_min % 60
|
|
43
|
+
if days > 0:
|
|
44
|
+
return f"{days}d{hours}h"
|
|
45
|
+
elif hours > 0:
|
|
46
|
+
return f"{hours}h{minutes}m"
|
|
47
|
+
else:
|
|
48
|
+
return f"{minutes}m"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def read_cache():
|
|
52
|
+
try:
|
|
53
|
+
with open(CACHE_FILE) as f:
|
|
54
|
+
return json.load(f)
|
|
55
|
+
except (json.JSONDecodeError, OSError):
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def should_fetch(cache):
|
|
60
|
+
if cache is None:
|
|
61
|
+
return True
|
|
62
|
+
last_ts = cache.get("last_fetch_ts", 0)
|
|
63
|
+
return (time.time() - last_ts) > FETCH_INTERVAL
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@contextmanager
|
|
67
|
+
def _exclusive_lock():
|
|
68
|
+
fd = os.open(LOCK_FILE, os.O_CREAT | os.O_WRONLY, 0o600)
|
|
69
|
+
try:
|
|
70
|
+
if _IS_WINDOWS:
|
|
71
|
+
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
|
|
72
|
+
else:
|
|
73
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
74
|
+
yield True
|
|
75
|
+
except (BlockingIOError, OSError):
|
|
76
|
+
yield False
|
|
77
|
+
finally:
|
|
78
|
+
if _IS_WINDOWS:
|
|
79
|
+
try:
|
|
80
|
+
msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
|
|
81
|
+
except OSError:
|
|
82
|
+
pass
|
|
83
|
+
os.close(fd)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _make_bucket(util, reset):
|
|
87
|
+
return {
|
|
88
|
+
"used_pct": round(float(util) * 100) if util is not None else None,
|
|
89
|
+
"reset_epoch": int(reset) if reset is not None else None,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def try_spawn_fetch():
|
|
94
|
+
try:
|
|
95
|
+
with _exclusive_lock() as locked:
|
|
96
|
+
if locked:
|
|
97
|
+
subprocess.Popen(
|
|
98
|
+
[sys.executable, os.path.abspath(__file__), "--fetch"],
|
|
99
|
+
stdin=subprocess.DEVNULL,
|
|
100
|
+
stdout=subprocess.DEVNULL,
|
|
101
|
+
stderr=subprocess.DEVNULL,
|
|
102
|
+
start_new_session=True,
|
|
103
|
+
)
|
|
104
|
+
except OSError:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def do_fetch():
|
|
109
|
+
try:
|
|
110
|
+
with _exclusive_lock() as locked:
|
|
111
|
+
if not locked:
|
|
112
|
+
return
|
|
113
|
+
_do_fetch_locked()
|
|
114
|
+
except OSError:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _do_fetch_locked():
|
|
119
|
+
cache = read_cache()
|
|
120
|
+
if cache and not should_fetch(cache):
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
import urllib.request
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
with open(CREDS_FILE) as f:
|
|
127
|
+
creds = json.load(f)
|
|
128
|
+
oauth = creds["claudeAiOauth"]
|
|
129
|
+
token = oauth["accessToken"]
|
|
130
|
+
expires_at = oauth.get("expiresAt", 0)
|
|
131
|
+
|
|
132
|
+
if expires_at < time.time() * 1000:
|
|
133
|
+
write_cache(cache, error="token_expired")
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
req = urllib.request.Request(
|
|
137
|
+
"https://api.anthropic.com/v1/messages",
|
|
138
|
+
data=json.dumps({
|
|
139
|
+
"model": "claude-haiku-4-5-20251001",
|
|
140
|
+
"max_tokens": 1,
|
|
141
|
+
"messages": [{"role": "user", "content": "quota"}],
|
|
142
|
+
}).encode(),
|
|
143
|
+
headers={
|
|
144
|
+
"Authorization": f"Bearer {token}",
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
"anthropic-version": "2023-06-01",
|
|
147
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
148
|
+
"x-app": "cli",
|
|
149
|
+
"User-Agent": "claude-code/2.1.72",
|
|
150
|
+
},
|
|
151
|
+
method="POST",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
resp = urllib.request.urlopen(req, timeout=15)
|
|
155
|
+
headers = resp.headers
|
|
156
|
+
resp.read()
|
|
157
|
+
|
|
158
|
+
buckets = {}
|
|
159
|
+
for key, prefix in (("five_hour", "5h"), ("seven_day", "7d")):
|
|
160
|
+
util = headers.get(f"anthropic-ratelimit-unified-{prefix}-utilization")
|
|
161
|
+
reset = headers.get(f"anthropic-ratelimit-unified-{prefix}-reset")
|
|
162
|
+
buckets[key] = _make_bucket(util, reset)
|
|
163
|
+
|
|
164
|
+
new_cache = {
|
|
165
|
+
"last_fetch_ts": time.time(),
|
|
166
|
+
**buckets,
|
|
167
|
+
"error": None,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
write_cache_atomic(new_cache)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
write_cache(cache, error=str(e))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def write_cache(old_cache, error=None):
|
|
176
|
+
data = dict(old_cache) if old_cache else {}
|
|
177
|
+
data["last_fetch_ts"] = time.time()
|
|
178
|
+
data["error"] = error
|
|
179
|
+
write_cache_atomic(data)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def write_cache_atomic(data):
|
|
183
|
+
fd, tmp_path = tempfile.mkstemp(dir=CACHE_DIR, suffix=".tmp")
|
|
184
|
+
try:
|
|
185
|
+
with os.fdopen(fd, "w") as f:
|
|
186
|
+
json.dump(data, f)
|
|
187
|
+
os.replace(tmp_path, CACHE_FILE)
|
|
188
|
+
except Exception:
|
|
189
|
+
try:
|
|
190
|
+
os.unlink(tmp_path)
|
|
191
|
+
except OSError:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def format_usage(bucket: dict) -> str:
|
|
196
|
+
pct = bucket.get("used_pct")
|
|
197
|
+
reset = bucket.get("reset_epoch")
|
|
198
|
+
if pct is not None and reset is not None:
|
|
199
|
+
return f"{pct}%({format_remaining(reset)})"
|
|
200
|
+
elif pct is not None:
|
|
201
|
+
return f"{pct}%"
|
|
202
|
+
return "?"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def main():
|
|
206
|
+
# Read stdin JSON
|
|
207
|
+
try:
|
|
208
|
+
stdin_data = json.load(sys.stdin)
|
|
209
|
+
except Exception:
|
|
210
|
+
stdin_data = {}
|
|
211
|
+
|
|
212
|
+
# Extract folder
|
|
213
|
+
cwd = stdin_data.get("workspace", {}).get("current_dir") or stdin_data.get("cwd", "")
|
|
214
|
+
folder = os.path.basename(cwd) if cwd else "?"
|
|
215
|
+
|
|
216
|
+
# Extract model
|
|
217
|
+
model_id = stdin_data.get("model", {}).get("id", "")
|
|
218
|
+
model = format_model(model_id) if model_id else "?"
|
|
219
|
+
|
|
220
|
+
# Extract context %
|
|
221
|
+
ctx_pct = stdin_data.get("context_window", {}).get("used_percentage")
|
|
222
|
+
ctx_str = f"{ctx_pct}%" if ctx_pct is not None else "?"
|
|
223
|
+
|
|
224
|
+
# Read cached usage data
|
|
225
|
+
cache = read_cache()
|
|
226
|
+
|
|
227
|
+
if cache and not cache.get("error"):
|
|
228
|
+
h5_str = format_usage(cache.get("five_hour", {}))
|
|
229
|
+
d7_str = format_usage(cache.get("seven_day", {}))
|
|
230
|
+
else:
|
|
231
|
+
h5_str = "?"
|
|
232
|
+
d7_str = "?"
|
|
233
|
+
|
|
234
|
+
# Spawn background fetch if needed
|
|
235
|
+
if should_fetch(cache):
|
|
236
|
+
try_spawn_fetch()
|
|
237
|
+
|
|
238
|
+
# Output
|
|
239
|
+
print(f"{folder} | {model} | {ctx_str} | {h5_str} | {d7_str}")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
try:
|
|
244
|
+
if "--fetch" in sys.argv:
|
|
245
|
+
do_fetch()
|
|
246
|
+
else:
|
|
247
|
+
main()
|
|
248
|
+
except Exception:
|
|
249
|
+
pass # statusline should never crash Claude Code
|
package/dist/commands/install.js
CHANGED
|
@@ -24,7 +24,7 @@ function runInstall() {
|
|
|
24
24
|
const targetDir = path.join(projectRoot, ".claude");
|
|
25
25
|
cleanSdEntries(targetDir);
|
|
26
26
|
copySdEntries(sourceDir, targetDir, sourceEntries);
|
|
27
|
-
|
|
27
|
+
setupSettings(targetDir);
|
|
28
28
|
console.log(`[@simplysm/sd-claude] Installed ${sourceEntries.length} sd-* entries.`);
|
|
29
29
|
} catch (err) {
|
|
30
30
|
console.warn("[@simplysm/sd-claude] postinstall warning:", err.message);
|
|
@@ -95,17 +95,31 @@ function copySdEntries(sourceDir, targetDir, entries) {
|
|
|
95
95
|
fs.cpSync(src, dest, { recursive: true });
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
function
|
|
98
|
+
function setupSettings(targetDir) {
|
|
99
99
|
const settingsPath = path.join(targetDir, "settings.json");
|
|
100
|
-
const sdStatusLineCommand = "node .claude/sd-statusline.js";
|
|
101
100
|
let settings = {};
|
|
102
101
|
if (fs.existsSync(settingsPath)) {
|
|
103
102
|
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
settings["statusLine"] = { type: "command", command: "python .claude/sd-statusline.py" };
|
|
105
|
+
const sdSessionEntry = {
|
|
106
|
+
matcher: "startup|resume|clear|compact",
|
|
107
|
+
hooks: [{ type: "command", command: "bash .claude/sd-session-start.sh" }]
|
|
108
|
+
};
|
|
109
|
+
const sessionStart = settings["SessionStart"];
|
|
110
|
+
if (sessionStart == null) {
|
|
111
|
+
settings["SessionStart"] = [sdSessionEntry];
|
|
112
|
+
} else {
|
|
113
|
+
const idx = sessionStart.findIndex(
|
|
114
|
+
(entry) => entry.hooks?.some((hook) => hook.command.includes("sd-session-start"))
|
|
115
|
+
);
|
|
116
|
+
if (idx >= 0) {
|
|
117
|
+
sessionStart[idx] = sdSessionEntry;
|
|
118
|
+
} else {
|
|
119
|
+
sessionStart.push(sdSessionEntry);
|
|
120
|
+
}
|
|
108
121
|
}
|
|
122
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
109
123
|
}
|
|
110
124
|
export {
|
|
111
125
|
runInstall
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/commands/install.ts"],
|
|
4
|
-
"mappings": "AAIA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAEvB,SAAS,aAAmB;AACjC,MAAI;AACF,UAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE7D,UAAM,UAAU,KAAK,QAAQ,WAAW,OAAO;AAC/C,UAAM,YAAY,KAAK,KAAK,SAAS,QAAQ;AAE7C,UAAM,cAAc,gBAAgB,SAAS;AAC7C,QAAI,eAAe,MAAM;AAEvB,cAAQ,IAAI,2EAA2E;AACvF;AAAA,IACF;AAGA,QAAI,4BAA4B,aAAa,OAAO,GAAG;AACrD;AAAA,IACF;AAGA,QAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC7B;AAAA,IACF;AAEA,UAAM,gBAAgB,iBAAiB,SAAS;AAChD,QAAI,cAAc,WAAW,GAAG;AAC9B;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,KAAK,aAAa,SAAS;AAElD,mBAAe,SAAS;AACxB,kBAAc,WAAW,WAAW,aAAa;AACjD,
|
|
4
|
+
"mappings": "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",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|
package/package.json
CHANGED
package/src/commands/install.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function runInstall(): void {
|
|
|
39
39
|
|
|
40
40
|
cleanSdEntries(targetDir);
|
|
41
41
|
copySdEntries(sourceDir, targetDir, sourceEntries);
|
|
42
|
-
|
|
42
|
+
setupSettings(targetDir);
|
|
43
43
|
|
|
44
44
|
// eslint-disable-next-line no-console
|
|
45
45
|
console.log(`[@simplysm/sd-claude] Installed ${sourceEntries.length} sd-* entries.`);
|
|
@@ -144,18 +144,40 @@ function copySdEntries(sourceDir: string, targetDir: string, entries: string[]):
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
148
|
-
function
|
|
147
|
+
/** Ensures statusLine and SessionStart hooks are configured in settings.json. */
|
|
148
|
+
function setupSettings(targetDir: string): void {
|
|
149
149
|
const settingsPath = path.join(targetDir, "settings.json");
|
|
150
|
-
const sdStatusLineCommand = "node .claude/sd-statusline.js";
|
|
151
150
|
|
|
152
151
|
let settings: Record<string, unknown> = {};
|
|
153
152
|
if (fs.existsSync(settingsPath)) {
|
|
154
153
|
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8")) as Record<string, unknown>;
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
// statusLine: always overwrite
|
|
157
|
+
settings["statusLine"] = { type: "command", command: "python .claude/sd-statusline.py" };
|
|
158
|
+
|
|
159
|
+
// SessionStart: ensure sd-session-start hook exists with correct config
|
|
160
|
+
const sdSessionEntry = {
|
|
161
|
+
matcher: "startup|resume|clear|compact",
|
|
162
|
+
hooks: [{ type: "command", command: "bash .claude/sd-session-start.sh" }],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const sessionStart = settings["SessionStart"] as
|
|
166
|
+
| Array<{ matcher?: string; hooks?: Array<{ type: string; command: string }> }>
|
|
167
|
+
| undefined;
|
|
168
|
+
|
|
169
|
+
if (sessionStart == null) {
|
|
170
|
+
settings["SessionStart"] = [sdSessionEntry];
|
|
171
|
+
} else {
|
|
172
|
+
const idx = sessionStart.findIndex((entry) =>
|
|
173
|
+
entry.hooks?.some((hook) => hook.command.includes("sd-session-start")),
|
|
174
|
+
);
|
|
175
|
+
if (idx >= 0) {
|
|
176
|
+
sessionStart[idx] = sdSessionEntry;
|
|
177
|
+
} else {
|
|
178
|
+
sessionStart.push(sdSessionEntry);
|
|
179
|
+
}
|
|
160
180
|
}
|
|
181
|
+
|
|
182
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
161
183
|
}
|