claude-code-usage 1.0.1__tar.gz → 1.0.3__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.
Files changed (20) hide show
  1. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/PKG-INFO +1 -1
  2. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/claude_code_usage.egg-info/PKG-INFO +1 -1
  3. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/pyproject.toml +1 -1
  4. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/api.py +5 -4
  5. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/autostart.py +41 -16
  6. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/config.py +3 -0
  7. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/icon_generator.py +22 -2
  8. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/LICENSE +0 -0
  9. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/README.md +0 -0
  10. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/claude_code_usage.egg-info/SOURCES.txt +0 -0
  11. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/claude_code_usage.egg-info/dependency_links.txt +0 -0
  12. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/claude_code_usage.egg-info/entry_points.txt +0 -0
  13. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/claude_code_usage.egg-info/requires.txt +0 -0
  14. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/claude_code_usage.egg-info/top_level.txt +0 -0
  15. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/setup.cfg +0 -0
  16. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/__init__.py +0 -0
  17. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/main.py +0 -0
  18. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/notifier.py +0 -0
  19. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/tray.py +0 -0
  20. {claude_code_usage-1.0.1 → claude_code_usage-1.0.3}/src/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-usage
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: System tray monitor for Claude Code token usage
5
5
  Author: Max
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-usage
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: System tray monitor for Claude Code token usage
5
5
  Author: Max
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claude-code-usage"
7
- version = "1.0.1"
7
+ version = "1.0.3"
8
8
  description = "System tray monitor for Claude Code token usage"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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:
@@ -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
@@ -171,3 +171,6 @@ class UserConfig:
171
171
 
172
172
  with open(config_path, 'w') as f:
173
173
  json.dump(asdict(self), f, indent=2)
174
+
175
+ # Set secure permissions (owner read/write only)
176
+ os.chmod(config_path, 0o600)
@@ -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)