ccusage 0.1.1__tar.gz → 0.1.4__tar.gz
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.
- {ccusage-0.1.1 → ccusage-0.1.4}/PKG-INFO +1 -1
- {ccusage-0.1.1 → ccusage-0.1.4}/pyproject.toml +1 -1
- {ccusage-0.1.1 → ccusage-0.1.4}/src/ccusage/__init__.py +58 -14
- {ccusage-0.1.1 → ccusage-0.1.4}/uv.lock +2 -2
- {ccusage-0.1.1 → ccusage-0.1.4}/.github/workflows/publish.yml +0 -0
- {ccusage-0.1.1 → ccusage-0.1.4}/.gitignore +0 -0
- {ccusage-0.1.1 → ccusage-0.1.4}/.python-version +0 -0
- {ccusage-0.1.1 → ccusage-0.1.4}/README.md +0 -0
|
@@ -16,15 +16,58 @@ Usage:
|
|
|
16
16
|
import argparse
|
|
17
17
|
import json
|
|
18
18
|
import signal
|
|
19
|
+
import subprocess
|
|
19
20
|
import sys
|
|
20
21
|
import time
|
|
21
22
|
import urllib.request
|
|
22
23
|
from datetime import datetime, timezone
|
|
23
24
|
from pathlib import Path
|
|
24
25
|
|
|
26
|
+
_TTY = sys.stdout.isatty()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _resolve_claude_path(relative: str) -> Path:
|
|
30
|
+
"""Return the first existing path for a file inside ~/.claude/.
|
|
31
|
+
|
|
32
|
+
On Windows, if the native path doesn't exist, also checks WSL distros
|
|
33
|
+
so the tool works from a Windows terminal against a WSL-based Claude Code
|
|
34
|
+
installation.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
relative: path relative to the .claude directory, e.g. ".credentials.json"
|
|
38
|
+
"""
|
|
39
|
+
native = Path.home() / ".claude" / relative
|
|
40
|
+
if native.exists() or sys.platform != "win32":
|
|
41
|
+
return native
|
|
42
|
+
|
|
43
|
+
# Windows: try WSL paths
|
|
44
|
+
try:
|
|
45
|
+
out = subprocess.run(
|
|
46
|
+
["wsl", "-l", "-q"],
|
|
47
|
+
capture_output=True, timeout=5,
|
|
48
|
+
)
|
|
49
|
+
decoded = out.stdout.decode("utf-16-le", errors="ignore")
|
|
50
|
+
distros = [d.strip() for d in decoded.splitlines() if d.strip()]
|
|
51
|
+
except Exception:
|
|
52
|
+
return native
|
|
53
|
+
|
|
54
|
+
for distro in distros:
|
|
55
|
+
wsl_base = Path(f"//wsl$/{distro}/home")
|
|
56
|
+
try:
|
|
57
|
+
users = [p.name for p in wsl_base.iterdir() if p.is_dir()]
|
|
58
|
+
except OSError:
|
|
59
|
+
continue
|
|
60
|
+
for user in users:
|
|
61
|
+
candidate = wsl_base / user / ".claude" / relative
|
|
62
|
+
if candidate.exists():
|
|
63
|
+
return candidate
|
|
64
|
+
|
|
65
|
+
return native
|
|
66
|
+
|
|
67
|
+
|
|
25
68
|
CLAUDE_DIR = Path.home() / ".claude"
|
|
26
|
-
CREDENTIALS_FILE =
|
|
27
|
-
USAGE_FILE =
|
|
69
|
+
CREDENTIALS_FILE = _resolve_claude_path(".credentials.json")
|
|
70
|
+
USAGE_FILE = _resolve_claude_path("usage-limits.json")
|
|
28
71
|
DAEMON_INTERVAL = 300 # 5 minutes
|
|
29
72
|
|
|
30
73
|
|
|
@@ -131,11 +174,11 @@ def cmd_status(raw_json=False):
|
|
|
131
174
|
print(json.dumps(data, indent=2))
|
|
132
175
|
return
|
|
133
176
|
|
|
134
|
-
R = "\033[0;31m"
|
|
135
|
-
Y = "\033[0;33m"
|
|
136
|
-
G = "\033[0;32m"
|
|
137
|
-
D = "\033[0;90m"
|
|
138
|
-
RST = "\033[0m"
|
|
177
|
+
R = "\033[0;31m" if _TTY else ""
|
|
178
|
+
Y = "\033[0;33m" if _TTY else ""
|
|
179
|
+
G = "\033[0;32m" if _TTY else ""
|
|
180
|
+
D = "\033[0;90m" if _TTY else ""
|
|
181
|
+
RST = "\033[0m" if _TTY else ""
|
|
139
182
|
|
|
140
183
|
def color_pct(pct):
|
|
141
184
|
p = int(pct)
|
|
@@ -181,7 +224,8 @@ def cmd_status(raw_json=False):
|
|
|
181
224
|
def cmd_daemon(interval: int = DAEMON_INTERVAL):
|
|
182
225
|
"""Run in foreground, refresh every `interval` seconds."""
|
|
183
226
|
signal.signal(signal.SIGINT, lambda *_: sys.exit(0))
|
|
184
|
-
|
|
227
|
+
if hasattr(signal, "SIGTERM"):
|
|
228
|
+
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
|
|
185
229
|
|
|
186
230
|
print(f"ccusage daemon started (refreshing every {interval}s)")
|
|
187
231
|
print(f"Writing to {USAGE_FILE}")
|
|
@@ -238,12 +282,12 @@ def _get_cached_usage(max_age: int = DAEMON_INTERVAL) -> dict:
|
|
|
238
282
|
|
|
239
283
|
def cmd_statusline():
|
|
240
284
|
"""Claude Code statusline command. Reads Claude's JSON from stdin + cached usage."""
|
|
241
|
-
R = "\033[0;31m"
|
|
242
|
-
Y = "\033[0;33m"
|
|
243
|
-
G = "\033[0;32m"
|
|
244
|
-
C = "\033[0;36m"
|
|
245
|
-
D = "\033[0;90m"
|
|
246
|
-
RST = "\033[0m"
|
|
285
|
+
R = "\033[0;31m" if _TTY else ""
|
|
286
|
+
Y = "\033[0;33m" if _TTY else ""
|
|
287
|
+
G = "\033[0;32m" if _TTY else ""
|
|
288
|
+
C = "\033[0;36m" if _TTY else ""
|
|
289
|
+
D = "\033[0;90m" if _TTY else ""
|
|
290
|
+
RST = "\033[0m" if _TTY else ""
|
|
247
291
|
|
|
248
292
|
def color_pct(pct: int) -> str:
|
|
249
293
|
c = R if pct >= 70 else Y if pct >= 50 else G
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|