@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.
@@ -4,4 +4,9 @@
4
4
 
5
5
  ## 코드 작성 규칙
6
6
 
7
- - YAGNI 원칙 준수
7
+ - YAGNI 원칙 준수
8
+
9
+ ## Playwright 규칙
10
+
11
+ - Playwright의 모든 output(스크린샷, PDF, 다운로드 등)은 `.tmp/playwright/` 디렉토리에 저장한다.
12
+
@@ -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
@@ -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
- setupStatusLine(targetDir);
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 setupStatusLine(targetDir) {
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
- if (settings["statusLine"] == null) {
106
- settings["statusLine"] = { type: "command", command: sdStatusLineCommand };
107
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
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": "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",
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,kBAAc,SAAS;AAGvB,YAAQ,IAAI,mCAAmC,cAAc,MAAM,gBAAgB;AAAA,EACrF,SAAS,KAAK;AAGZ,YAAQ,KAAK,8CAA+C,IAAc,OAAO;AAAA,EACnF;AACF;AAGA,SAAS,gBAAgB,SAAqC;AAC5D,MAAI,QAAQ,IAAI,UAAU,KAAK,MAAM;AACnC,WAAO,QAAQ,IAAI,UAAU;AAAA,EAC/B;AAEA,QAAM,MAAM,KAAK;AACjB,QAAM,SAAS,MAAM,iBAAiB;AACtC,QAAM,MAAM,QAAQ,QAAQ,MAAM;AAClC,SAAO,QAAQ,KAAK,QAAQ,UAAU,GAAG,GAAG,IAAI;AAClD;AAGA,SAAS,4BAA4B,aAAqB,SAA0B;AAClF,QAAM,iBAAiB,KAAK,KAAK,aAAa,cAAc;AAC5D,MAAI,CAAC,GAAG,WAAW,cAAc,EAAG,QAAO;AAE3C,QAAM,aAAa,KAAK,MAAM,GAAG,aAAa,gBAAgB,OAAO,CAAC;AAItE,MAAI,WAAW,SAAS,WAAY,QAAO;AAE3C,QAAM,kBAAkB,KAAK,KAAK,SAAS,cAAc;AACzD,MAAI,CAAC,GAAG,WAAW,eAAe,EAAG,QAAO;AAE5C,QAAM,cAAc,KAAK,MAAM,GAAG,aAAa,iBAAiB,OAAO,CAAC;AAIxE,QAAM,eAAe,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;AACrD,QAAM,gBAAgB,YAAY,SAAS,MAAM,GAAG,EAAE,CAAC;AACvD,SAAO,gBAAgB,QAAQ,iBAAiB;AAClD;AAGA,SAAS,iBAAiB,WAA6B;AACrD,QAAM,UAAoB,CAAC;AAG3B,aAAW,QAAQ,GAAG,YAAY,SAAS,GAAG;AAC5C,QAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,cAAQ,KAAK,IAAI;AAAA,IACnB;AAAA,EACF;AAGA,aAAW,UAAU,GAAG,YAAY,WAAW,EAAE,eAAe,KAAK,CAAC,GAAG;AACvE,QAAI,CAAC,OAAO,YAAY,KAAK,OAAO,KAAK,WAAW,KAAK,EAAG;AAC5D,UAAM,UAAU,KAAK,KAAK,WAAW,OAAO,IAAI;AAChD,eAAW,QAAQ,GAAG,YAAY,OAAO,GAAG;AAC1C,UAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,gBAAQ,KAAK,KAAK,KAAK,OAAO,MAAM,IAAI,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,eAAe,WAAyB;AAC/C,MAAI,CAAC,GAAG,WAAW,SAAS,EAAG;AAG/B,aAAW,QAAQ,GAAG,YAAY,SAAS,GAAG;AAC5C,QAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,SAAG,OAAO,KAAK,KAAK,WAAW,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,IAC3D;AAAA,EACF;AAGA,aAAW,UAAU,GAAG,YAAY,WAAW,EAAE,eAAe,KAAK,CAAC,GAAG;AACvE,QAAI,CAAC,OAAO,YAAY,KAAK,OAAO,KAAK,WAAW,KAAK,EAAG;AAC5D,UAAM,UAAU,KAAK,KAAK,WAAW,OAAO,IAAI;AAChD,eAAW,QAAQ,GAAG,YAAY,OAAO,GAAG;AAC1C,UAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,WAAG,OAAO,KAAK,KAAK,SAAS,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AACF;AAGA,SAAS,cAAc,WAAmB,WAAmB,SAAyB;AACpF,KAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,KAAK,KAAK,WAAW,KAAK;AACtC,UAAM,OAAO,KAAK,KAAK,WAAW,KAAK;AACvC,OAAG,UAAU,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACpD,OAAG,OAAO,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EAC1C;AACF;AAGA,SAAS,cAAc,WAAyB;AAC9C,QAAM,eAAe,KAAK,KAAK,WAAW,eAAe;AAEzD,MAAI,WAAoC,CAAC;AACzC,MAAI,GAAG,WAAW,YAAY,GAAG;AAC/B,eAAW,KAAK,MAAM,GAAG,aAAa,cAAc,OAAO,CAAC;AAAA,EAC9D;AAGA,WAAS,YAAY,IAAI,EAAE,MAAM,WAAW,SAAS,kCAAkC;AAGvF,QAAM,iBAAiB;AAAA,IACrB,SAAS;AAAA,IACT,OAAO,CAAC,EAAE,MAAM,WAAW,SAAS,mCAAmC,CAAC;AAAA,EAC1E;AAEA,QAAM,eAAe,SAAS,cAAc;AAI5C,MAAI,gBAAgB,MAAM;AACxB,aAAS,cAAc,IAAI,CAAC,cAAc;AAAA,EAC5C,OAAO;AACL,UAAM,MAAM,aAAa;AAAA,MAAU,CAAC,UAClC,MAAM,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,SAAS,kBAAkB,CAAC;AAAA,IACvE;AACA,QAAI,OAAO,GAAG;AACZ,mBAAa,GAAG,IAAI;AAAA,IACtB,OAAO;AACL,mBAAa,KAAK,cAAc;AAAA,IAClC;AAAA,EACF;AAEA,KAAG,cAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AACzE;",
5
5
  "names": []
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/sd-claude",
3
- "version": "13.0.80",
3
+ "version": "13.0.82",
4
4
  "description": "Simplysm Claude Code CLI — asset installer",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -39,7 +39,7 @@ export function runInstall(): void {
39
39
 
40
40
  cleanSdEntries(targetDir);
41
41
  copySdEntries(sourceDir, targetDir, sourceEntries);
42
- setupStatusLine(targetDir);
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
- /** Adds statusLine configuration to settings.json. */
148
- function setupStatusLine(targetDir: string): void {
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
- if (settings["statusLine"] == null) {
158
- settings["statusLine"] = { type: "command", command: sdStatusLineCommand };
159
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
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
  }