claude-code-usage 1.0.0__py3-none-any.whl

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,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-code-usage
3
+ Version: 1.0.0
4
+ Summary: System tray monitor for Claude Code token usage
5
+ Author: Max
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Maex-z9/CC_Usage
8
+ Project-URL: Repository, https://github.com/Maex-z9/CC_Usage
9
+ Project-URL: Issues, https://github.com/Maex-z9/CC_Usage/issues
10
+ Keywords: claude,anthropic,usage,monitor,tray,gnome
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: X11 Applications :: GTK
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: requests>=2.28.0
24
+ Requires-Dist: PyGObject>=3.42.0
25
+ Dynamic: license-file
26
+
27
+ # Claude Code Usage Overlay
28
+
29
+ A Linux/GNOME system tray application that monitors your Claude Code token usage and alerts you before hitting limits.
30
+
31
+ ![Python](https://img.shields.io/badge/python-3.11+-blue.svg)
32
+ ![Platform](https://img.shields.io/badge/platform-Linux%2FGNOME-lightgrey.svg)
33
+ ![License](https://img.shields.io/badge/license-MIT-green.svg)
34
+
35
+ ## Features
36
+
37
+ - **System tray icon** with color-coded gauge (green/yellow/red based on usage)
38
+ - **Panel label** showing session and weekly usage percentages
39
+ - **Popup notifications** at configurable thresholds (default: 50%, 75%, 90%)
40
+ - **Configurable settings** via JSON file or Settings menu
41
+ - **Auto-start on login** via XDG autostart
42
+
43
+ ## Screenshots
44
+
45
+ The tray icon shows your current usage with:
46
+ - Arc fill = session usage (5-hour window)
47
+ - Color = worst-case urgency (max of session/weekly)
48
+ - Panel label = "45%|67%" format
49
+
50
+ ## Requirements
51
+
52
+ - Linux with GNOME Shell (or compatible desktop with AppIndicator support)
53
+ - Python 3.11+
54
+ - [Claude Code CLI](https://claude.ai/code) installed and authenticated
55
+ - AppIndicator extension for GNOME Shell
56
+
57
+ ### System Dependencies
58
+
59
+ ```bash
60
+ # Ubuntu/Debian
61
+ sudo apt install python3-gi python3-gi-cairo gir1.2-ayatanaappindicator3-0.1 gir1.2-notify-0.7
62
+
63
+ # Fedora
64
+ sudo dnf install python3-gobject python3-cairo libayatana-appindicator-gtk3 libnotify
65
+
66
+ # Arch
67
+ sudo pacman -S python-gobject python-cairo libayatana-appindicator libnotify
68
+ ```
69
+
70
+ ### GNOME Shell Extension
71
+
72
+ Install the [AppIndicator Support](https://extensions.gnome.org/extension/615/appindicator-support/) extension for GNOME Shell to see system tray icons.
73
+
74
+ ## Installation
75
+
76
+ ### Quick Install (recommended)
77
+
78
+ ```bash
79
+ git clone https://github.com/Maex-z9/CC_Usage.git
80
+ cd CC_Usage
81
+ ./install.sh
82
+ ```
83
+
84
+ This installs system dependencies and the app. Then run with: `claude-usage`
85
+
86
+ ### Manual Install
87
+
88
+ 1. Install system dependencies:
89
+ ```bash
90
+ # Ubuntu/Debian
91
+ sudo apt install python3-gi python3-gi-cairo gir1.2-ayatanaappindicator3-0.1 gir1.2-notify-0.7
92
+
93
+ # Fedora
94
+ sudo dnf install python3-gobject python3-cairo libayatana-appindicator-gtk3 libnotify
95
+
96
+ # Arch
97
+ sudo pacman -S python-gobject python-cairo libayatana-appindicator libnotify
98
+ ```
99
+
100
+ 2. Install the app:
101
+ ```bash
102
+ pip install git+https://github.com/Maex-z9/CC_Usage.git
103
+ ```
104
+
105
+ 3. Authenticate with Claude Code (if not already done):
106
+ ```bash
107
+ claude
108
+ # Follow the authentication flow
109
+ ```
110
+
111
+ ## Usage
112
+
113
+ Run the application:
114
+
115
+ ```bash
116
+ claude-usage
117
+ ```
118
+
119
+ Or if running from source:
120
+ ```bash
121
+ python3 -m src.main
122
+ ```
123
+
124
+ ### Menu Options
125
+
126
+ Click the tray icon to access:
127
+ - Current usage percentages and reset time
128
+ - **Refresh** - Force update usage data
129
+ - **Settings** submenu:
130
+ - Pause Notifications - Temporarily disable alerts
131
+ - Autostart on Login - Enable/disable auto-start
132
+ - Edit Config File - Open config in text editor
133
+ - **Quit** - Exit the application
134
+
135
+ ### Configuration
136
+
137
+ Config file location: `~/.config/claude-usage-overlay/config.json`
138
+
139
+ ```json
140
+ {
141
+ "session_thresholds": [50, 75, 90],
142
+ "weekly_thresholds": [50, 75, 90],
143
+ "polling_interval": 300,
144
+ "pause_notifications": false,
145
+ "autostart_enabled": false
146
+ }
147
+ ```
148
+
149
+ | Setting | Description | Default |
150
+ |---------|-------------|---------|
151
+ | `session_thresholds` | Alert thresholds for 5-hour session usage | `[50, 75, 90]` |
152
+ | `weekly_thresholds` | Alert thresholds for 7-day weekly usage | `[50, 75, 90]` |
153
+ | `polling_interval` | Seconds between API checks (30-3600) | `300` (5 min) |
154
+ | `pause_notifications` | Disable popup alerts | `false` |
155
+ | `autostart_enabled` | Start on login | `false` |
156
+
157
+ ## How It Works
158
+
159
+ 1. Reads your OAuth token from `~/.claude/.credentials.json` (created by Claude Code CLI)
160
+ 2. Polls the Anthropic usage API every 5 minutes (configurable)
161
+ 3. Displays usage in the system tray with color-coded urgency
162
+ 4. Shows desktop notifications when thresholds are crossed
163
+
164
+ ## Privacy & Security
165
+
166
+ - **No API keys stored in the repo** - Uses Claude Code's existing credentials
167
+ - **Credentials stay local** - Reads from `~/.claude/.credentials.json`
168
+ - **Config stays local** - Stored in `~/.config/claude-usage-overlay/`
169
+ - **No data sent anywhere** - Only communicates with Anthropic's API
170
+
171
+ ## Known Issues
172
+
173
+ See [KNOWN_ISSUES.md](KNOWN_ISSUES.md) for details on:
174
+ - GNOME Shell tooltip limitations (using panel label instead)
175
+ - AppIndicator menu accelerator limitations
176
+
177
+ ## Contributing
178
+
179
+ Contributions welcome! Please open an issue or PR.
180
+
181
+ ## License
182
+
183
+ MIT License - See [LICENSE](LICENSE) for details.
184
+
185
+ ## Acknowledgments
186
+
187
+ Built with [Claude Code](https://claude.ai/code) using the [GSD workflow](https://github.com/get-shit-done/gsd).
@@ -0,0 +1,15 @@
1
+ claude_code_usage-1.0.0.dist-info/licenses/LICENSE,sha256=alXcUJ2-k8eqG_Amt8y9Hhlb8fT1nABafZOqWDmdzUw,1060
2
+ src/__init__.py,sha256=rzeXYIMbNbWPF6gpjv7luGXLZA67r_5GU5ZGUNb8pKA,27
3
+ src/api.py,sha256=IChRjcU8vqmiQ64YOQRXiQi8DSNxvknD4Y4mVOwH01g,6100
4
+ src/autostart.py,sha256=2Rnsr1y_yXbwwARnoYJl5_uQfMch8XsD7vgJi2CfTII,2399
5
+ src/config.py,sha256=_PIPM7wfbQq1c7tHT26_L39PXbLVQWuzxaB2FMWiavw,5300
6
+ src/icon_generator.py,sha256=Ljwhrj9ezqJSLspLu2DlmJGbfvcOLJnW50O-4J3gwKE,2861
7
+ src/main.py,sha256=A7Ej5kqbtOYWUGSuZxx6bM-7WNimjjAs--04_kvvta0,506
8
+ src/notifier.py,sha256=3j5ZmhaFMfv5XDLMCFaOgSCCw5jffn4N6dNz2R7gd8I,10267
9
+ src/tray.py,sha256=sCvQLsNdm3uBtdeNGzFmLxzLT1h1w_LH4ZsajZIlg4I,10995
10
+ src/utils.py,sha256=ZqGvw-oyzHompxRMUeYT5FdJFaQ6uvoAthhu-3bPGJk,893
11
+ claude_code_usage-1.0.0.dist-info/METADATA,sha256=tbz_u_pwUI_L31RgUWMCvrIAmkIltnjCKUimCLhyUTA,5613
12
+ claude_code_usage-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
+ claude_code_usage-1.0.0.dist-info/entry_points.txt,sha256=sRUb89gemjebLTvCQVv6TJWunjYrD-i_it3A3E9bJVc,95
14
+ claude_code_usage-1.0.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
15
+ claude_code_usage-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ claude-usage = src.main:main
3
+
4
+ [gui_scripts]
5
+ claude-usage-gui = src.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Max
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ src
src/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # overlay-CC-usage package
src/api.py ADDED
@@ -0,0 +1,202 @@
1
+ """API client for fetching Claude Code usage data."""
2
+
3
+ import sys
4
+ import time
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+ from typing import Optional
8
+
9
+ import requests
10
+
11
+
12
+ USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage"
13
+
14
+
15
+ @dataclass
16
+ class UsageData:
17
+ """Structured usage data from the Anthropic API."""
18
+
19
+ session_percent: float # from five_hour.utilization
20
+ session_resets_at: Optional[datetime] # from five_hour.resets_at
21
+ weekly_percent: float # from seven_day.utilization
22
+ weekly_resets_at: Optional[datetime] # from seven_day.resets_at
23
+
24
+
25
+ class APIError(Exception):
26
+ """Base exception for API errors."""
27
+
28
+ pass
29
+
30
+
31
+ class AuthenticationError(APIError):
32
+ """Exception for 401 authentication failures."""
33
+
34
+ pass
35
+
36
+
37
+ class RateLimitError(APIError):
38
+ """Exception for 429 rate limit errors."""
39
+
40
+ def __init__(self, message: str, retry_after: int = 60):
41
+ super().__init__(message)
42
+ self.retry_after = retry_after
43
+
44
+
45
+ def fetch_usage(access_token: str) -> UsageData:
46
+ """Fetch usage data from the Anthropic API.
47
+
48
+ Args:
49
+ access_token: OAuth access token
50
+
51
+ Returns:
52
+ UsageData: Parsed usage information
53
+
54
+ Raises:
55
+ AuthenticationError: On 401 responses
56
+ RateLimitError: On 429 responses
57
+ APIError: On other errors
58
+ """
59
+ headers = {
60
+ "Authorization": f"Bearer {access_token}",
61
+ "Accept": "application/json",
62
+ "anthropic-beta": "oauth-2025-04-20",
63
+ }
64
+
65
+ try:
66
+ response = requests.get(USAGE_ENDPOINT, headers=headers, timeout=10)
67
+ except requests.RequestException as e:
68
+ raise APIError(f"Network error: {e}")
69
+
70
+ if response.status_code == 200:
71
+ try:
72
+ data = response.json()
73
+
74
+ # Parse five_hour data
75
+ five_hour = data.get("five_hour", {})
76
+ session_percent = five_hour.get("utilization", 0.0)
77
+ session_resets_str = five_hour.get("resets_at")
78
+ session_resets_at = (
79
+ datetime.fromisoformat(session_resets_str.replace("Z", "+00:00"))
80
+ if session_resets_str
81
+ else None
82
+ )
83
+
84
+ # Parse seven_day data
85
+ seven_day = data.get("seven_day", {})
86
+ weekly_percent = seven_day.get("utilization", 0.0)
87
+ weekly_resets_str = seven_day.get("resets_at")
88
+ weekly_resets_at = (
89
+ datetime.fromisoformat(weekly_resets_str.replace("Z", "+00:00"))
90
+ if weekly_resets_str
91
+ else None
92
+ )
93
+
94
+ return UsageData(
95
+ session_percent=session_percent,
96
+ session_resets_at=session_resets_at,
97
+ weekly_percent=weekly_percent,
98
+ weekly_resets_at=weekly_resets_at,
99
+ )
100
+ except (KeyError, ValueError, TypeError) as e:
101
+ raise APIError(f"Failed to parse API response: {e}")
102
+
103
+ elif response.status_code == 401:
104
+ raise AuthenticationError("OAuth token is invalid or expired")
105
+
106
+ elif response.status_code == 429:
107
+ retry_after = int(response.headers.get("Retry-After", 60))
108
+ raise RateLimitError(
109
+ f"Rate limited, retry after {retry_after} seconds", retry_after
110
+ )
111
+
112
+ elif 500 <= response.status_code < 600:
113
+ raise APIError(f"Server error: {response.status_code}")
114
+
115
+ else:
116
+ raise APIError(
117
+ f"Unexpected response: {response.status_code} - {response.text}"
118
+ )
119
+
120
+
121
+ def fetch_with_retry(access_token: str, max_retries: int = 3) -> UsageData:
122
+ """Fetch usage data with retry logic.
123
+
124
+ Args:
125
+ access_token: OAuth access token
126
+ max_retries: Maximum number of retry attempts
127
+
128
+ Returns:
129
+ UsageData: Parsed usage information
130
+
131
+ Raises:
132
+ AuthenticationError: On 401 responses (no retry)
133
+ APIError: If all retries exhausted
134
+ """
135
+ last_exception = None
136
+
137
+ for attempt in range(max_retries):
138
+ try:
139
+ return fetch_usage(access_token)
140
+
141
+ except AuthenticationError:
142
+ # Don't retry authentication errors - user action needed
143
+ raise
144
+
145
+ except RateLimitError as e:
146
+ last_exception = e
147
+ if attempt < max_retries - 1:
148
+ print(
149
+ f"Rate limited, waiting {e.retry_after}s before retry {attempt + 2}/{max_retries}...",
150
+ file=sys.stderr,
151
+ )
152
+ time.sleep(e.retry_after)
153
+ else:
154
+ print(f"Rate limited, all retries exhausted", file=sys.stderr)
155
+
156
+ except APIError as e:
157
+ last_exception = e
158
+ if attempt < max_retries - 1:
159
+ # Exponential backoff with cap at 30 seconds
160
+ wait_time = min(30, 2**attempt)
161
+ print(
162
+ f"API error: {e}. Retrying in {wait_time}s ({attempt + 2}/{max_retries})...",
163
+ file=sys.stderr,
164
+ )
165
+ time.sleep(wait_time)
166
+ else:
167
+ print(f"API error, all retries exhausted: {e}", file=sys.stderr)
168
+
169
+ # All retries exhausted
170
+ if last_exception:
171
+ raise last_exception
172
+ else:
173
+ raise APIError("All retries exhausted")
174
+
175
+
176
+ if __name__ == "__main__":
177
+ """Integration test: fetch and display current usage."""
178
+ from src.config import get_access_token
179
+
180
+ try:
181
+ # Get OAuth token
182
+ token = get_access_token()
183
+
184
+ # Fetch usage data
185
+ usage = fetch_with_retry(token)
186
+
187
+ # Display results
188
+ print("Claude Code Usage:")
189
+ print(f" Session (5-hour): {usage.session_percent:.1f}% (resets at {usage.session_resets_at})")
190
+ print(f" Weekly (7-day): {usage.weekly_percent:.1f}% (resets at {usage.weekly_resets_at})")
191
+
192
+ except FileNotFoundError as e:
193
+ print(f"Error: {e}", file=sys.stderr)
194
+ sys.exit(1)
195
+
196
+ except AuthenticationError as e:
197
+ print(f"Authentication failed: {e}", file=sys.stderr)
198
+ sys.exit(1)
199
+
200
+ except APIError as e:
201
+ print(f"API error: {e}", file=sys.stderr)
202
+ sys.exit(1)
src/autostart.py ADDED
@@ -0,0 +1,94 @@
1
+ """Autostart management for Claude Usage Overlay."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+
7
+ def get_autostart_path() -> Path:
8
+ """Get XDG-compliant autostart directory path.
9
+
10
+ Returns:
11
+ Path: Path to autostart directory
12
+ """
13
+ config_home = os.environ.get('XDG_CONFIG_HOME')
14
+ if config_home:
15
+ base = Path(config_home)
16
+ else:
17
+ base = Path.home() / '.config'
18
+
19
+ autostart_dir = base / 'autostart'
20
+ autostart_dir.mkdir(parents=True, exist_ok=True)
21
+
22
+ return autostart_dir
23
+
24
+
25
+ def get_desktop_file_path() -> Path:
26
+ """Get path to the .desktop file.
27
+
28
+ Returns:
29
+ Path: Path to claude-usage-overlay.desktop
30
+ """
31
+ return get_autostart_path() / 'claude-usage-overlay.desktop'
32
+
33
+
34
+ def create_autostart_entry(enable: bool = True):
35
+ """Create or update autostart .desktop file.
36
+
37
+ Args:
38
+ enable: If True, enable autostart. If False, disable (set Hidden=true).
39
+ """
40
+ desktop_file = get_desktop_file_path()
41
+
42
+ # Get absolute path to main.py
43
+ project_root = Path(__file__).parent.parent.absolute()
44
+ exec_path = f"/usr/bin/python3 {project_root}/src/main.py"
45
+
46
+ # Desktop entry content
47
+ hidden_value = "false" if enable else "true"
48
+ content = f"""[Desktop Entry]
49
+ Type=Application
50
+ Version=1.0
51
+ Name=Claude Code Usage Monitor
52
+ Comment=System tray monitor for Claude Code token usage
53
+ Exec={exec_path}
54
+ Icon=dialog-information
55
+ Terminal=false
56
+ Categories=Utility;Monitor;
57
+ StartupNotify=false
58
+ X-GNOME-Autostart-enabled=true
59
+ Hidden={hidden_value}
60
+ """
61
+
62
+ # Write .desktop file
63
+ desktop_file.write_text(content)
64
+ desktop_file.chmod(0o755)
65
+
66
+
67
+ def remove_autostart_entry():
68
+ """Remove autostart .desktop file."""
69
+ desktop_file = get_desktop_file_path()
70
+ if desktop_file.exists():
71
+ desktop_file.unlink()
72
+
73
+
74
+ def is_autostart_enabled() -> bool:
75
+ """Check if autostart is enabled.
76
+
77
+ Returns:
78
+ bool: True if autostart is enabled (file exists and Hidden!=true)
79
+ """
80
+ desktop_file = get_desktop_file_path()
81
+
82
+ if not desktop_file.exists():
83
+ return False
84
+
85
+ content = desktop_file.read_text()
86
+
87
+ # Parse for Hidden= line (case insensitive value)
88
+ for line in content.splitlines():
89
+ if line.strip().startswith('Hidden='):
90
+ value = line.split('=', 1)[1].strip().lower()
91
+ return value != 'true'
92
+
93
+ # If Hidden not present, autostart is enabled
94
+ return True