claude-code-usage 1.0.0__py3-none-any.whl → 1.0.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-usage
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: System tray monitor for Claude Code token usage
5
5
  Author: Max
6
6
  License: MIT
@@ -28,6 +28,7 @@ Dynamic: license-file
28
28
 
29
29
  A Linux/GNOME system tray application that monitors your Claude Code token usage and alerts you before hitting limits.
30
30
 
31
+ [![PyPI version](https://img.shields.io/pypi/v/claude-code-usage.svg)](https://pypi.org/project/claude-code-usage/)
31
32
  ![Python](https://img.shields.io/badge/python-3.11+-blue.svg)
32
33
  ![Platform](https://img.shields.io/badge/platform-Linux%2FGNOME-lightgrey.svg)
33
34
  ![License](https://img.shields.io/badge/license-MIT-green.svg)
@@ -73,7 +74,20 @@ Install the [AppIndicator Support](https://extensions.gnome.org/extension/615/ap
73
74
 
74
75
  ## Installation
75
76
 
76
- ### Quick Install (recommended)
77
+ ### From PyPI (recommended)
78
+
79
+ 1. Install system dependencies (see [System Dependencies](#system-dependencies) above)
80
+
81
+ 2. Install with pipx (recommended):
82
+ ```bash
83
+ pipx install claude-code-usage --system-site-packages
84
+ ```
85
+
86
+ The `--system-site-packages` flag is required to access system GTK libraries.
87
+
88
+ 3. Run: `claude-usage`
89
+
90
+ ### From Source
77
91
 
78
92
  ```bash
79
93
  git clone https://github.com/Maex-z9/CC_Usage.git
@@ -81,32 +95,15 @@ cd CC_Usage
81
95
  ./install.sh
82
96
  ```
83
97
 
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
98
+ This installs system dependencies and the app automatically.
92
99
 
93
- # Fedora
94
- sudo dnf install python3-gobject python3-cairo libayatana-appindicator-gtk3 libnotify
100
+ ### Prerequisites
95
101
 
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
- ```
102
+ Make sure you have Claude Code CLI installed and authenticated:
103
+ ```bash
104
+ claude
105
+ # Follow the authentication flow
106
+ ```
110
107
 
111
108
  ## Usage
112
109
 
@@ -0,0 +1,15 @@
1
+ claude_code_usage-1.0.2.dist-info/licenses/LICENSE,sha256=alXcUJ2-k8eqG_Amt8y9Hhlb8fT1nABafZOqWDmdzUw,1060
2
+ src/__init__.py,sha256=rzeXYIMbNbWPF6gpjv7luGXLZA67r_5GU5ZGUNb8pKA,27
3
+ src/api.py,sha256=IymlJFR7_Ou5I_Asij8oyR2b7bx9WurBtREwcysbiJQ,6168
4
+ src/autostart.py,sha256=Xo0JcjUJ8RZ-pervQ6f8mQAxngO73Rf-IDdg0uWzlKw,3369
5
+ src/config.py,sha256=_PIPM7wfbQq1c7tHT26_L39PXbLVQWuzxaB2FMWiavw,5300
6
+ src/icon_generator.py,sha256=h1xJtzjjJp958ulmsla5DOyutOVTR1Q_0T2ymlO1IEk,3537
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.2.dist-info/METADATA,sha256=LkcegPyA4MNkqfcAmfziXL-MgppRK4TWRG_nfBkbZTM,5568
12
+ claude_code_usage-1.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
+ claude_code_usage-1.0.2.dist-info/entry_points.txt,sha256=MY_VQ3t41-QKCQuBjBsd4ug0uOUlCz1Hz-OyK78gfns,47
14
+ claude_code_usage-1.0.2.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
15
+ claude_code_usage-1.0.2.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ claude-usage = src.main:main
src/api.py CHANGED
@@ -63,7 +63,9 @@ def fetch_usage(access_token: str) -> UsageData:
63
63
  }
64
64
 
65
65
  try:
66
- response = requests.get(USAGE_ENDPOINT, headers=headers, timeout=10)
66
+ response = requests.get(
67
+ USAGE_ENDPOINT, headers=headers, timeout=10, verify=True
68
+ )
67
69
  except requests.RequestException as e:
68
70
  raise APIError(f"Network error: {e}")
69
71
 
@@ -113,9 +115,8 @@ def fetch_usage(access_token: str) -> UsageData:
113
115
  raise APIError(f"Server error: {response.status_code}")
114
116
 
115
117
  else:
116
- raise APIError(
117
- f"Unexpected response: {response.status_code} - {response.text}"
118
- )
118
+ # Don't expose response body - may contain sensitive server info
119
+ raise APIError(f"Unexpected response: {response.status_code}")
119
120
 
120
121
 
121
122
  def fetch_with_retry(access_token: str, max_retries: int = 3) -> UsageData:
src/autostart.py CHANGED
@@ -1,6 +1,8 @@
1
1
  """Autostart management for Claude Usage Overlay."""
2
2
 
3
3
  import os
4
+ import shutil
5
+ from configparser import ConfigParser
4
6
  from pathlib import Path
5
7
 
6
8
 
@@ -31,6 +33,28 @@ def get_desktop_file_path() -> Path:
31
33
  return get_autostart_path() / 'claude-usage-overlay.desktop'
32
34
 
33
35
 
36
+ def _get_exec_command() -> str:
37
+ """Determine the best Exec command for the .desktop file.
38
+
39
+ Returns:
40
+ Command string for Exec= line, preferring installed entry point
41
+ """
42
+ # Prefer the installed 'claude-usage' command if available
43
+ claude_usage_path = shutil.which("claude-usage")
44
+ if claude_usage_path:
45
+ return claude_usage_path
46
+
47
+ # Fallback to running module directly with python
48
+ project_root = Path(__file__).parent.parent.absolute()
49
+ main_py = project_root / "src" / "main.py"
50
+ if main_py.exists():
51
+ # Use sys.executable equivalent for current python
52
+ return f"python3 -m src.main"
53
+
54
+ # Last resort: assume it's installed as module
55
+ return "python3 -m src.main"
56
+
57
+
34
58
  def create_autostart_entry(enable: bool = True):
35
59
  """Create or update autostart .desktop file.
36
60
 
@@ -39,9 +63,8 @@ def create_autostart_entry(enable: bool = True):
39
63
  """
40
64
  desktop_file = get_desktop_file_path()
41
65
 
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"
66
+ # Get best exec command (prefer installed entry point)
67
+ exec_cmd = _get_exec_command()
45
68
 
46
69
  # Desktop entry content
47
70
  hidden_value = "false" if enable else "true"
@@ -50,7 +73,7 @@ Type=Application
50
73
  Version=1.0
51
74
  Name=Claude Code Usage Monitor
52
75
  Comment=System tray monitor for Claude Code token usage
53
- Exec={exec_path}
76
+ Exec={exec_cmd}
54
77
  Icon=dialog-information
55
78
  Terminal=false
56
79
  Categories=Utility;Monitor;
@@ -59,9 +82,10 @@ X-GNOME-Autostart-enabled=true
59
82
  Hidden={hidden_value}
60
83
  """
61
84
 
62
- # Write .desktop file
85
+ # Write .desktop file with secure permissions (rw-r--r--)
86
+ # .desktop files don't need execute bit - they're parsed by desktop environment
63
87
  desktop_file.write_text(content)
64
- desktop_file.chmod(0o755)
88
+ desktop_file.chmod(0o644)
65
89
 
66
90
 
67
91
  def remove_autostart_entry():
@@ -82,13 +106,14 @@ def is_autostart_enabled() -> bool:
82
106
  if not desktop_file.exists():
83
107
  return False
84
108
 
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
109
+ # Use configparser for proper INI parsing
110
+ parser = ConfigParser(interpolation=None)
111
+ try:
112
+ parser.read(desktop_file)
113
+ # Desktop files use 'Desktop Entry' section
114
+ # Hidden=true means disabled; absent or false means enabled
115
+ hidden = parser.get('Desktop Entry', 'Hidden', fallback='false')
116
+ return hidden.lower() != 'true'
117
+ except Exception:
118
+ # If parsing fails, assume enabled if file exists
119
+ return True
src/icon_generator.py CHANGED
@@ -6,8 +6,27 @@ Generates circular gauge icons using Cairo for system tray display.
6
6
  import cairo
7
7
  import math
8
8
  import os
9
+ import tempfile
10
+ from pathlib import Path
9
11
  from typing import Tuple
10
12
 
13
+ # Secure temp directory for icons (created once, reused)
14
+ _icon_temp_dir: str | None = None
15
+
16
+
17
+ def _get_secure_icon_dir() -> str:
18
+ """Get or create a secure temporary directory for icon files.
19
+
20
+ Returns:
21
+ Path to secure temp directory with restricted permissions (0o700)
22
+ """
23
+ global _icon_temp_dir
24
+ if _icon_temp_dir is None or not Path(_icon_temp_dir).exists():
25
+ # Create secure temp directory owned by current user only
26
+ _icon_temp_dir = tempfile.mkdtemp(prefix="claude-usage-icons-")
27
+ os.chmod(_icon_temp_dir, 0o700)
28
+ return _icon_temp_dir
29
+
11
30
 
12
31
  def get_color_for_percentage(percentage: float) -> Tuple[float, float, float]:
13
32
  """
@@ -85,8 +104,9 @@ def generate_gauge_icon(percentage: float, color_percentage: float, size: int =
85
104
  ctx.arc(center, center, radius, -math.pi / 2, end_angle)
86
105
  ctx.stroke()
87
106
 
88
- # Save to unique filename per percentage and color state (bust GTK icon cache)
89
- icon_path = f"/tmp/claude-usage-{int(percentage)}-{color_name}.png"
107
+ # Save to secure temp directory with unique filename (bust GTK icon cache)
108
+ icon_dir = _get_secure_icon_dir()
109
+ icon_path = os.path.join(icon_dir, f"gauge-{int(percentage)}-{color_name}.png")
90
110
  surface.write_to_png(icon_path)
91
111
 
92
112
  return os.path.abspath(icon_path)
@@ -1,15 +0,0 @@
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,,
@@ -1,5 +0,0 @@
1
- [console_scripts]
2
- claude-usage = src.main:main
3
-
4
- [gui_scripts]
5
- claude-usage-gui = src.main:main