claude-usage-widget 0.2.0__tar.gz → 0.2.2__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.
- {claude_usage_widget-0.2.0/claude_usage_widget.egg-info → claude_usage_widget-0.2.2}/PKG-INFO +8 -1
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/README.md +7 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/__init__.py +1 -1
- claude_usage_widget-0.2.2/claude_usage/__main__.py +10 -0
- claude_usage_widget-0.2.2/claude_usage/cli.py +227 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2/claude_usage_widget.egg-info}/PKG-INFO +8 -1
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage_widget.egg-info/SOURCES.txt +1 -0
- claude_usage_widget-0.2.0/claude_usage/cli.py +0 -90
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/LICENSE +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/MANIFEST.in +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/analytics.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/api_server.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/collector.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/config.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/exporter.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/forecast.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/history.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/icons/claude-tray.svg +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/notifier.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/overlay.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/overlay_macos.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/pricing.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/py.typed +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/themes.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/trends.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/updater.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/webhooks.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/widget.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage/widget_macos.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage_widget.egg-info/dependency_links.txt +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage_widget.egg-info/entry_points.txt +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage_widget.egg-info/requires.txt +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage_widget.egg-info/top_level.txt +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/config.json.example +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/README.md +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/polybar-module.ini +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/starship.toml.snippet +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/tmux.conf.snippet +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/waybar-module.json +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/waybar-style.css +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/zsh-prompt.zsh +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/pyproject.toml +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/requirements-macos.txt +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/setup.cfg +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_analytics.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_api_server.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_cli.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_collector.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_config.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_exporter.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_forecast.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_history.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_notifier.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_pricing.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_themes.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_trends.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_updater.py +0 -0
- {claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/tests/test_webhooks.py +0 -0
{claude_usage_widget-0.2.0/claude_usage_widget.egg-info → claude_usage_widget-0.2.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-usage-widget
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Desktop widget and CLI that shows real-time Claude Code usage limits and cost.
|
|
5
5
|
Author: Burak
|
|
6
6
|
License: MIT
|
|
@@ -214,6 +214,13 @@ The OSD is a transparent, borderless window rendered entirely via 2D drawing pri
|
|
|
214
214
|
### Linux: Cairo errors (`Couldn't find foreign struct converter`)
|
|
215
215
|
- Install `python3-gi-cairo`: `sudo apt install python3-gi-cairo`
|
|
216
216
|
|
|
217
|
+
### Linux: `ImportError: cannot import name '_gi' from partially initialized module 'gi'`
|
|
218
|
+
This usually means a broken user-level PyGObject install is shadowing the system package. Fix:
|
|
219
|
+
```bash
|
|
220
|
+
pip uninstall -y PyGObject pycairo
|
|
221
|
+
sudo apt install --reinstall python3-gi python3-gi-cairo gir1.2-ayatanaappindicator3-0.1
|
|
222
|
+
```
|
|
223
|
+
|
|
217
224
|
### macOS: no menu bar icon
|
|
218
225
|
- Make sure `rumps` is installed: `pip3 install rumps`
|
|
219
226
|
- If using a virtualenv, ensure `pyobjc-framework-Cocoa` is also installed.
|
|
@@ -185,6 +185,13 @@ The OSD is a transparent, borderless window rendered entirely via 2D drawing pri
|
|
|
185
185
|
### Linux: Cairo errors (`Couldn't find foreign struct converter`)
|
|
186
186
|
- Install `python3-gi-cairo`: `sudo apt install python3-gi-cairo`
|
|
187
187
|
|
|
188
|
+
### Linux: `ImportError: cannot import name '_gi' from partially initialized module 'gi'`
|
|
189
|
+
This usually means a broken user-level PyGObject install is shadowing the system package. Fix:
|
|
190
|
+
```bash
|
|
191
|
+
pip uninstall -y PyGObject pycairo
|
|
192
|
+
sudo apt install --reinstall python3-gi python3-gi-cairo gir1.2-ayatanaappindicator3-0.1
|
|
193
|
+
```
|
|
194
|
+
|
|
188
195
|
### macOS: no menu bar icon
|
|
189
196
|
- Make sure `rumps` is installed: `pip3 install rumps`
|
|
190
197
|
- If using a virtualenv, ensure `pyobjc-framework-Cocoa` is also installed.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Command-line interface for headless / scripted access to usage stats."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from dataclasses import asdict, is_dataclass
|
|
10
|
+
from typing import Sequence
|
|
11
|
+
|
|
12
|
+
from claude_usage import __version__
|
|
13
|
+
from claude_usage.collector import UsageStats, collect_all
|
|
14
|
+
from claude_usage.config import load_config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
18
|
+
"""Return the argparse parser used by the CLI dispatcher."""
|
|
19
|
+
p = argparse.ArgumentParser(
|
|
20
|
+
prog="claude-usage",
|
|
21
|
+
description="Claude Code usage tracker — GUI by default, CLI on demand.",
|
|
22
|
+
)
|
|
23
|
+
p.add_argument("--version", action="store_true", help="Print version and exit.")
|
|
24
|
+
p.add_argument("--json", action="store_true", help="Emit full stats as JSON.")
|
|
25
|
+
p.add_argument("--once", action="store_true", help="Collect once and print JSON.")
|
|
26
|
+
p.add_argument("--field", metavar="NAME", default=None,
|
|
27
|
+
help="Print a single UsageStats field by name.")
|
|
28
|
+
p.add_argument("--export", choices=("csv", "json"), default=None,
|
|
29
|
+
help="Export history as CSV or JSON to stdout.")
|
|
30
|
+
p.add_argument("--days", type=int, default=30,
|
|
31
|
+
help="Look-back window for --export (default: 30).")
|
|
32
|
+
return p
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _usage_stats_to_dict(stats: UsageStats) -> dict:
|
|
36
|
+
"""Convert a UsageStats dataclass to a JSON-serialisable dict."""
|
|
37
|
+
return asdict(stats) if is_dataclass(stats) else dict(stats)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _default_config_path() -> str:
|
|
41
|
+
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
42
|
+
cfg = os.path.join(base_dir, "config.json")
|
|
43
|
+
if not os.path.isfile(cfg):
|
|
44
|
+
cfg = os.path.join(base_dir, "config.json.example")
|
|
45
|
+
return cfg
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run_cli(argv: Sequence[str]) -> int:
|
|
49
|
+
"""Dispatch a single CLI invocation. Returns a process exit code."""
|
|
50
|
+
args = build_parser().parse_args(list(argv))
|
|
51
|
+
|
|
52
|
+
if args.version:
|
|
53
|
+
print(__version__)
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
if args.export:
|
|
57
|
+
from claude_usage.exporter import export_history
|
|
58
|
+
config = load_config(_default_config_path())
|
|
59
|
+
history_path = os.path.join(config["claude_dir"], "usage-history.jsonl")
|
|
60
|
+
count = export_history(history_path, fmt=args.export, days=args.days, out=sys.stdout)
|
|
61
|
+
print(f"# exported {count} samples", file=sys.stderr)
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
if args.json or args.once or args.field:
|
|
65
|
+
config = load_config(_default_config_path())
|
|
66
|
+
stats = collect_all(config)
|
|
67
|
+
data = _usage_stats_to_dict(stats)
|
|
68
|
+
|
|
69
|
+
if args.field is not None:
|
|
70
|
+
if args.field not in data:
|
|
71
|
+
print(f"error: unknown field {args.field!r}", file=sys.stderr)
|
|
72
|
+
return 2
|
|
73
|
+
print(data[args.field])
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
json.dump(data, sys.stdout, default=str, indent=2, sort_keys=True)
|
|
77
|
+
print()
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
# No CLI flag — caller should fall through to GUI.
|
|
81
|
+
return -1
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _launch_gui() -> None:
|
|
85
|
+
"""Launch the platform-appropriate GUI (GTK3 on Linux, AppKit on macOS)."""
|
|
86
|
+
import signal
|
|
87
|
+
|
|
88
|
+
# CLI entry is usually invoked by a console script shim (`claude-usage`),
|
|
89
|
+
# so restore the default SIGINT handler so Ctrl-C kills the GUI cleanly.
|
|
90
|
+
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
91
|
+
|
|
92
|
+
if sys.platform == "darwin":
|
|
93
|
+
from claude_usage.widget_macos import ClaudeUsageTray # noqa: WPS433
|
|
94
|
+
config = load_config(_default_config_path())
|
|
95
|
+
app = ClaudeUsageTray(config)
|
|
96
|
+
app.run()
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if sys.platform.startswith("linux"):
|
|
100
|
+
# Force XWayland; native Wayland doesn't support the override-redirect
|
|
101
|
+
# tricks needed for our borderless OSD overlay.
|
|
102
|
+
os.environ.setdefault("GDK_BACKEND", "x11")
|
|
103
|
+
|
|
104
|
+
# Guarded gi import — if the system ``python3-gi`` is broken or
|
|
105
|
+
# shadowed by a partial user-local install, bail out with a clear
|
|
106
|
+
# message instead of a confusing internal traceback.
|
|
107
|
+
try:
|
|
108
|
+
import gi # type: ignore[import-not-found]
|
|
109
|
+
except ImportError as exc:
|
|
110
|
+
_print_linux_install_instructions(exc)
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
_ensure_gi_cairo_linux()
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
gi.require_version("Gtk", "3.0")
|
|
117
|
+
from gi.repository import Gtk # type: ignore[attr-defined]
|
|
118
|
+
gi.require_version("AyatanaAppIndicator3", "0.1")
|
|
119
|
+
from gi.repository import AyatanaAppIndicator3 # noqa: F401
|
|
120
|
+
except (ValueError, ImportError) as exc:
|
|
121
|
+
_print_linux_install_instructions(exc)
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
from claude_usage.widget import ClaudeUsageTray
|
|
125
|
+
|
|
126
|
+
config = load_config(_default_config_path())
|
|
127
|
+
_tray = ClaudeUsageTray(config) # noqa: F841 — tray owns its lifecycle
|
|
128
|
+
Gtk.main()
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
print(f"ERROR: Unsupported platform: {sys.platform}", file=sys.stderr)
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _print_linux_install_instructions(exc: Exception) -> None:
|
|
136
|
+
"""Print actionable install / repair instructions for GTK-related failures."""
|
|
137
|
+
print(
|
|
138
|
+
"\nERROR: GTK / GObject Introspection stack is missing or broken.\n"
|
|
139
|
+
f" ({exc.__class__.__name__}: {exc})\n"
|
|
140
|
+
"\n"
|
|
141
|
+
"Required system packages:\n"
|
|
142
|
+
" Ubuntu/Debian:\n"
|
|
143
|
+
" sudo apt install python3-gi python3-gi-cairo python3-cairo \\\n"
|
|
144
|
+
" gir1.2-ayatanaappindicator3-0.1 gir1.2-notify-0.7\n"
|
|
145
|
+
" Fedora:\n"
|
|
146
|
+
" sudo dnf install python3-gobject python3-gobject-cairo \\\n"
|
|
147
|
+
" libappindicator-gtk3 libnotify\n"
|
|
148
|
+
" Arch:\n"
|
|
149
|
+
" sudo pacman -S python-gobject python-cairo \\\n"
|
|
150
|
+
" libappindicator-gtk3 libnotify\n"
|
|
151
|
+
"\n"
|
|
152
|
+
"If 'circular import' appears, there may be a broken user-local install:\n"
|
|
153
|
+
" pip uninstall -y PyGObject pycairo\n"
|
|
154
|
+
" sudo apt install --reinstall python3-gi # or your distro equivalent\n"
|
|
155
|
+
"\n"
|
|
156
|
+
"GNOME users: the tray icon also needs the AppIndicator extension:\n"
|
|
157
|
+
" https://extensions.gnome.org/extension/615/appindicator-support/\n",
|
|
158
|
+
file=sys.stderr,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _ensure_gi_cairo_linux() -> None:
|
|
163
|
+
"""Try system gi-cairo first, then GNOME snap fallback, else warn."""
|
|
164
|
+
import gi # noqa: WPS433
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
gi.require_foreign("cairo")
|
|
168
|
+
return
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
import glob as _glob
|
|
173
|
+
import importlib.util
|
|
174
|
+
|
|
175
|
+
ver = f"{sys.version_info.major}{sys.version_info.minor}"
|
|
176
|
+
snap_so_list = sorted(
|
|
177
|
+
_glob.glob(
|
|
178
|
+
f"/snap/gnome-*/*/usr/lib/python3/dist-packages/gi/"
|
|
179
|
+
f"_gi_cairo.cpython-{ver}*.so"
|
|
180
|
+
),
|
|
181
|
+
reverse=True,
|
|
182
|
+
)
|
|
183
|
+
for snap_so in snap_so_list:
|
|
184
|
+
try:
|
|
185
|
+
spec = importlib.util.spec_from_file_location("gi._gi_cairo", snap_so)
|
|
186
|
+
if spec is None or spec.loader is None:
|
|
187
|
+
continue
|
|
188
|
+
mod = importlib.util.module_from_spec(spec)
|
|
189
|
+
sys.modules["gi._gi_cairo"] = mod
|
|
190
|
+
spec.loader.exec_module(mod)
|
|
191
|
+
return
|
|
192
|
+
except Exception:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
print(
|
|
196
|
+
"WARNING: python3-gi-cairo not found. OSD overlay may not render.\n"
|
|
197
|
+
" Ubuntu/Debian: sudo apt install python3-gi-cairo\n"
|
|
198
|
+
" Fedora: sudo dnf install python3-gobject-cairo\n"
|
|
199
|
+
" Arch: sudo pacman -S python-gobject\n",
|
|
200
|
+
file=sys.stderr,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def main() -> int:
|
|
205
|
+
"""Entry point for the ``claude-usage`` console script.
|
|
206
|
+
|
|
207
|
+
Dispatches CLI flags first; if none were given, launches the GUI and
|
|
208
|
+
returns once the GUI exits.
|
|
209
|
+
"""
|
|
210
|
+
if sys.version_info < (3, 10):
|
|
211
|
+
print(
|
|
212
|
+
"ERROR: Python 3.10+ is required (collector.py uses str|None syntax).",
|
|
213
|
+
file=sys.stderr,
|
|
214
|
+
)
|
|
215
|
+
return 1
|
|
216
|
+
|
|
217
|
+
rc = run_cli(sys.argv[1:])
|
|
218
|
+
if rc >= 0:
|
|
219
|
+
return rc
|
|
220
|
+
|
|
221
|
+
# No CLI flag — fall through to the GUI.
|
|
222
|
+
_launch_gui()
|
|
223
|
+
return 0
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == "__main__":
|
|
227
|
+
sys.exit(main())
|
{claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2/claude_usage_widget.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-usage-widget
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Desktop widget and CLI that shows real-time Claude Code usage limits and cost.
|
|
5
5
|
Author: Burak
|
|
6
6
|
License: MIT
|
|
@@ -214,6 +214,13 @@ The OSD is a transparent, borderless window rendered entirely via 2D drawing pri
|
|
|
214
214
|
### Linux: Cairo errors (`Couldn't find foreign struct converter`)
|
|
215
215
|
- Install `python3-gi-cairo`: `sudo apt install python3-gi-cairo`
|
|
216
216
|
|
|
217
|
+
### Linux: `ImportError: cannot import name '_gi' from partially initialized module 'gi'`
|
|
218
|
+
This usually means a broken user-level PyGObject install is shadowing the system package. Fix:
|
|
219
|
+
```bash
|
|
220
|
+
pip uninstall -y PyGObject pycairo
|
|
221
|
+
sudo apt install --reinstall python3-gi python3-gi-cairo gir1.2-ayatanaappindicator3-0.1
|
|
222
|
+
```
|
|
223
|
+
|
|
217
224
|
### macOS: no menu bar icon
|
|
218
225
|
- Make sure `rumps` is installed: `pip3 install rumps`
|
|
219
226
|
- If using a virtualenv, ensure `pyobjc-framework-Cocoa` is also installed.
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""Command-line interface for headless / scripted access to usage stats."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
import json
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
from dataclasses import asdict, is_dataclass
|
|
10
|
-
from typing import Sequence
|
|
11
|
-
|
|
12
|
-
from claude_usage import __version__
|
|
13
|
-
from claude_usage.collector import UsageStats, collect_all
|
|
14
|
-
from claude_usage.config import load_config
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
18
|
-
"""Return the argparse parser used by the CLI dispatcher."""
|
|
19
|
-
p = argparse.ArgumentParser(
|
|
20
|
-
prog="claude-usage",
|
|
21
|
-
description="Claude Code usage tracker — GUI by default, CLI on demand.",
|
|
22
|
-
)
|
|
23
|
-
p.add_argument("--version", action="store_true", help="Print version and exit.")
|
|
24
|
-
p.add_argument("--json", action="store_true", help="Emit full stats as JSON.")
|
|
25
|
-
p.add_argument("--once", action="store_true", help="Collect once and print JSON.")
|
|
26
|
-
p.add_argument("--field", metavar="NAME", default=None,
|
|
27
|
-
help="Print a single UsageStats field by name.")
|
|
28
|
-
p.add_argument("--export", choices=("csv", "json"), default=None,
|
|
29
|
-
help="Export history as CSV or JSON to stdout.")
|
|
30
|
-
p.add_argument("--days", type=int, default=30,
|
|
31
|
-
help="Look-back window for --export (default: 30).")
|
|
32
|
-
return p
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _usage_stats_to_dict(stats: UsageStats) -> dict:
|
|
36
|
-
"""Convert a UsageStats dataclass to a JSON-serialisable dict."""
|
|
37
|
-
return asdict(stats) if is_dataclass(stats) else dict(stats)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _default_config_path() -> str:
|
|
41
|
-
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
42
|
-
cfg = os.path.join(base_dir, "config.json")
|
|
43
|
-
if not os.path.isfile(cfg):
|
|
44
|
-
cfg = os.path.join(base_dir, "config.json.example")
|
|
45
|
-
return cfg
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def run_cli(argv: Sequence[str]) -> int:
|
|
49
|
-
"""Dispatch a single CLI invocation. Returns a process exit code."""
|
|
50
|
-
args = build_parser().parse_args(list(argv))
|
|
51
|
-
|
|
52
|
-
if args.version:
|
|
53
|
-
print(__version__)
|
|
54
|
-
return 0
|
|
55
|
-
|
|
56
|
-
if args.export:
|
|
57
|
-
from claude_usage.exporter import export_history
|
|
58
|
-
config = load_config(_default_config_path())
|
|
59
|
-
history_path = os.path.join(config["claude_dir"], "usage-history.jsonl")
|
|
60
|
-
count = export_history(history_path, fmt=args.export, days=args.days, out=sys.stdout)
|
|
61
|
-
print(f"# exported {count} samples", file=sys.stderr)
|
|
62
|
-
return 0
|
|
63
|
-
|
|
64
|
-
if args.json or args.once or args.field:
|
|
65
|
-
config = load_config(_default_config_path())
|
|
66
|
-
stats = collect_all(config)
|
|
67
|
-
data = _usage_stats_to_dict(stats)
|
|
68
|
-
|
|
69
|
-
if args.field is not None:
|
|
70
|
-
if args.field not in data:
|
|
71
|
-
print(f"error: unknown field {args.field!r}", file=sys.stderr)
|
|
72
|
-
return 2
|
|
73
|
-
print(data[args.field])
|
|
74
|
-
return 0
|
|
75
|
-
|
|
76
|
-
json.dump(data, sys.stdout, default=str, indent=2, sort_keys=True)
|
|
77
|
-
print()
|
|
78
|
-
return 0
|
|
79
|
-
|
|
80
|
-
# No CLI flag — caller should fall through to GUI.
|
|
81
|
-
return -1
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def main() -> int:
|
|
85
|
-
"""Entry point for the ``claude-usage`` console script."""
|
|
86
|
-
return run_cli(sys.argv[1:])
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if __name__ == "__main__":
|
|
90
|
-
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage_widget.egg-info/requires.txt
RENAMED
|
File without changes
|
{claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/claude_usage_widget.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/polybar-module.ini
RENAMED
|
File without changes
|
{claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/starship.toml.snippet
RENAMED
|
File without changes
|
|
File without changes
|
{claude_usage_widget-0.2.0 → claude_usage_widget-0.2.2}/docs/integrations/waybar-module.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|