claude-code-usage 1.0.3__tar.gz → 2.0.0__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_code_usage-1.0.3 → claude_code_usage-2.0.0}/PKG-INFO +17 -7
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/claude_code_usage.egg-info/PKG-INFO +17 -7
- claude_code_usage-2.0.0/claude_code_usage.egg-info/entry_points.txt +5 -0
- claude_code_usage-2.0.0/claude_code_usage.egg-info/requires.txt +9 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/pyproject.toml +24 -6
- claude_code_usage-2.0.0/src/autostart.py +353 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/src/config.py +34 -16
- claude_code_usage-2.0.0/src/icon_generator.py +120 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/src/notifier.py +95 -82
- claude_code_usage-2.0.0/src/tray.py +231 -0
- claude_code_usage-1.0.3/claude_code_usage.egg-info/entry_points.txt +0 -2
- claude_code_usage-1.0.3/claude_code_usage.egg-info/requires.txt +0 -2
- claude_code_usage-1.0.3/src/autostart.py +0 -119
- claude_code_usage-1.0.3/src/icon_generator.py +0 -112
- claude_code_usage-1.0.3/src/tray.py +0 -302
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/LICENSE +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/README.md +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/claude_code_usage.egg-info/SOURCES.txt +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/claude_code_usage.egg-info/dependency_links.txt +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/claude_code_usage.egg-info/top_level.txt +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/setup.cfg +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/src/__init__.py +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/src/api.py +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/src/main.py +0 -0
- {claude_code_usage-1.0.3 → claude_code_usage-2.0.0}/src/utils.py +0 -0
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: claude-code-usage
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Cross-platform system tray monitor for Claude Code token usage
|
|
5
5
|
Author: Max
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Maex-z9/CC_Usage
|
|
8
8
|
Project-URL: Repository, https://github.com/Maex-z9/CC_Usage
|
|
9
9
|
Project-URL: Issues, https://github.com/Maex-z9/CC_Usage/issues
|
|
10
|
-
Keywords: claude,anthropic,usage,monitor,tray,
|
|
10
|
+
Keywords: claude,anthropic,usage,monitor,tray,cross-platform
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Environment ::
|
|
12
|
+
Classifier: Environment :: MacOS X
|
|
13
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
14
|
+
Classifier: Environment :: X11 Applications
|
|
13
15
|
Classifier: Intended Audience :: Developers
|
|
14
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: MacOS
|
|
15
20
|
Classifier: Operating System :: POSIX :: Linux
|
|
16
21
|
Classifier: Programming Language :: Python :: 3
|
|
17
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -21,8 +26,13 @@ Requires-Python: >=3.11
|
|
|
21
26
|
Description-Content-Type: text/markdown
|
|
22
27
|
License-File: LICENSE
|
|
23
28
|
Requires-Dist: requests>=2.28.0
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
|
|
29
|
+
Requires-Dist: pystray>=0.19.0
|
|
30
|
+
Requires-Dist: Pillow>=9.0.0
|
|
31
|
+
Requires-Dist: desktop-notifier>=3.5.0
|
|
32
|
+
Requires-Dist: platformdirs>=3.0.0
|
|
33
|
+
Requires-Dist: humanize>=4.0.0
|
|
34
|
+
Provides-Extra: linux
|
|
35
|
+
Requires-Dist: PyGObject>=3.42.0; extra == "linux"
|
|
26
36
|
|
|
27
37
|
# Claude Code Usage Overlay
|
|
28
38
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: claude-code-usage
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Cross-platform system tray monitor for Claude Code token usage
|
|
5
5
|
Author: Max
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Maex-z9/CC_Usage
|
|
8
8
|
Project-URL: Repository, https://github.com/Maex-z9/CC_Usage
|
|
9
9
|
Project-URL: Issues, https://github.com/Maex-z9/CC_Usage/issues
|
|
10
|
-
Keywords: claude,anthropic,usage,monitor,tray,
|
|
10
|
+
Keywords: claude,anthropic,usage,monitor,tray,cross-platform
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Environment ::
|
|
12
|
+
Classifier: Environment :: MacOS X
|
|
13
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
14
|
+
Classifier: Environment :: X11 Applications
|
|
13
15
|
Classifier: Intended Audience :: Developers
|
|
14
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: MacOS
|
|
15
20
|
Classifier: Operating System :: POSIX :: Linux
|
|
16
21
|
Classifier: Programming Language :: Python :: 3
|
|
17
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -21,8 +26,13 @@ Requires-Python: >=3.11
|
|
|
21
26
|
Description-Content-Type: text/markdown
|
|
22
27
|
License-File: LICENSE
|
|
23
28
|
Requires-Dist: requests>=2.28.0
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
|
|
29
|
+
Requires-Dist: pystray>=0.19.0
|
|
30
|
+
Requires-Dist: Pillow>=9.0.0
|
|
31
|
+
Requires-Dist: desktop-notifier>=3.5.0
|
|
32
|
+
Requires-Dist: platformdirs>=3.0.0
|
|
33
|
+
Requires-Dist: humanize>=4.0.0
|
|
34
|
+
Provides-Extra: linux
|
|
35
|
+
Requires-Dist: PyGObject>=3.42.0; extra == "linux"
|
|
26
36
|
|
|
27
37
|
# Claude Code Usage Overlay
|
|
28
38
|
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.0"]
|
|
2
|
+
requires = ["setuptools>=61.0,<74"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "claude-code-usage"
|
|
7
|
-
version = "
|
|
8
|
-
description = "
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "Cross-platform system tray monitor for Claude Code token usage"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
11
11
|
authors = [
|
|
12
12
|
{name = "Max"}
|
|
13
13
|
]
|
|
14
|
-
keywords = ["claude", "anthropic", "usage", "monitor", "tray", "
|
|
14
|
+
keywords = ["claude", "anthropic", "usage", "monitor", "tray", "cross-platform"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
|
-
"Environment ::
|
|
17
|
+
"Environment :: MacOS X",
|
|
18
|
+
"Environment :: Win32 (MS Windows)",
|
|
19
|
+
"Environment :: X11 Applications",
|
|
18
20
|
"Intended Audience :: Developers",
|
|
19
21
|
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Operating System :: Microsoft :: Windows",
|
|
24
|
+
"Operating System :: MacOS",
|
|
20
25
|
"Operating System :: POSIX :: Linux",
|
|
21
26
|
"Programming Language :: Python :: 3",
|
|
22
27
|
"Programming Language :: Python :: 3.11",
|
|
@@ -26,9 +31,19 @@ classifiers = [
|
|
|
26
31
|
requires-python = ">=3.11"
|
|
27
32
|
dependencies = [
|
|
28
33
|
"requests>=2.28.0",
|
|
29
|
-
"
|
|
34
|
+
"pystray>=0.19.0",
|
|
35
|
+
"Pillow>=9.0.0",
|
|
36
|
+
"desktop-notifier>=3.5.0",
|
|
37
|
+
"platformdirs>=3.0.0",
|
|
38
|
+
"humanize>=4.0.0",
|
|
30
39
|
]
|
|
31
40
|
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
# Linux/GNOME: pystray needs PyGObject for AppIndicator menu support
|
|
43
|
+
# Install system packages: sudo apt install python3-gi gir1.2-ayatanaappindicator3-0.1
|
|
44
|
+
# Then use: pip install -e . --system-site-packages
|
|
45
|
+
linux = ["PyGObject>=3.42.0"]
|
|
46
|
+
|
|
32
47
|
[project.urls]
|
|
33
48
|
Homepage = "https://github.com/Maex-z9/CC_Usage"
|
|
34
49
|
Repository = "https://github.com/Maex-z9/CC_Usage"
|
|
@@ -37,6 +52,9 @@ Issues = "https://github.com/Maex-z9/CC_Usage/issues"
|
|
|
37
52
|
[project.scripts]
|
|
38
53
|
claude-usage = "src.main:main"
|
|
39
54
|
|
|
55
|
+
[project.gui-scripts]
|
|
56
|
+
claude-usage-gui = "src.main:main"
|
|
57
|
+
|
|
40
58
|
[tool.setuptools.packages.find]
|
|
41
59
|
where = ["."]
|
|
42
60
|
include = ["src*"]
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""Autostart management for Claude Usage Overlay.
|
|
2
|
+
|
|
3
|
+
Cross-platform: supports Linux (.desktop), Windows (Registry), and macOS (LaunchAgent).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import sys
|
|
9
|
+
from configparser import ConfigParser
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Detect platform
|
|
13
|
+
IS_WINDOWS = sys.platform == 'win32'
|
|
14
|
+
IS_MACOS = sys.platform == 'darwin'
|
|
15
|
+
IS_LINUX = sys.platform.startswith('linux')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# --- Linux .desktop file support ---
|
|
19
|
+
|
|
20
|
+
def _get_linux_autostart_path() -> Path:
|
|
21
|
+
"""Get XDG-compliant autostart directory path for Linux.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Path: Path to autostart directory
|
|
25
|
+
"""
|
|
26
|
+
config_home = os.environ.get('XDG_CONFIG_HOME')
|
|
27
|
+
if config_home:
|
|
28
|
+
base = Path(config_home)
|
|
29
|
+
else:
|
|
30
|
+
base = Path.home() / '.config'
|
|
31
|
+
|
|
32
|
+
autostart_dir = base / 'autostart'
|
|
33
|
+
autostart_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
|
|
35
|
+
return autostart_dir
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _get_linux_desktop_file_path() -> Path:
|
|
39
|
+
"""Get path to the Linux .desktop file.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Path: Path to claude-usage-overlay.desktop
|
|
43
|
+
"""
|
|
44
|
+
return _get_linux_autostart_path() / 'claude-usage-overlay.desktop'
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_exec_command() -> str:
|
|
48
|
+
"""Determine the best Exec command for autostart.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Command string, preferring installed entry point.
|
|
52
|
+
On Windows, paths are quoted to handle spaces.
|
|
53
|
+
"""
|
|
54
|
+
# Prefer the installed 'claude-usage' command if available
|
|
55
|
+
claude_usage_path = shutil.which("claude-usage")
|
|
56
|
+
if claude_usage_path:
|
|
57
|
+
# Quote path on Windows in case of spaces
|
|
58
|
+
if IS_WINDOWS and ' ' in claude_usage_path:
|
|
59
|
+
return f'"{claude_usage_path}"'
|
|
60
|
+
return claude_usage_path
|
|
61
|
+
|
|
62
|
+
# For PyInstaller frozen executable
|
|
63
|
+
if getattr(sys, 'frozen', False):
|
|
64
|
+
if IS_WINDOWS and ' ' in sys.executable:
|
|
65
|
+
return f'"{sys.executable}"'
|
|
66
|
+
return sys.executable
|
|
67
|
+
|
|
68
|
+
# Fallback to running module directly with python
|
|
69
|
+
if IS_WINDOWS:
|
|
70
|
+
# Quote Python path on Windows (often in "Program Files")
|
|
71
|
+
return f'"{sys.executable}" -m src.main'
|
|
72
|
+
return f"{sys.executable} -m src.main"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _create_linux_autostart(enable: bool = True):
|
|
76
|
+
"""Create or update autostart .desktop file on Linux.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
enable: If True, enable autostart. If False, disable (set Hidden=true).
|
|
80
|
+
"""
|
|
81
|
+
desktop_file = _get_linux_desktop_file_path()
|
|
82
|
+
exec_cmd = _get_exec_command()
|
|
83
|
+
|
|
84
|
+
hidden_value = "false" if enable else "true"
|
|
85
|
+
content = f"""[Desktop Entry]
|
|
86
|
+
Type=Application
|
|
87
|
+
Version=1.0
|
|
88
|
+
Name=Claude Code Usage Monitor
|
|
89
|
+
Comment=System tray monitor for Claude Code token usage
|
|
90
|
+
Exec={exec_cmd}
|
|
91
|
+
Icon=dialog-information
|
|
92
|
+
Terminal=false
|
|
93
|
+
Categories=Utility;Monitor;
|
|
94
|
+
StartupNotify=false
|
|
95
|
+
X-GNOME-Autostart-enabled=true
|
|
96
|
+
Hidden={hidden_value}
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
desktop_file.write_text(content)
|
|
100
|
+
desktop_file.chmod(0o644)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _remove_linux_autostart():
|
|
104
|
+
"""Remove autostart .desktop file on Linux."""
|
|
105
|
+
desktop_file = _get_linux_desktop_file_path()
|
|
106
|
+
if desktop_file.exists():
|
|
107
|
+
desktop_file.unlink()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _is_linux_autostart_enabled() -> bool:
|
|
111
|
+
"""Check if autostart is enabled on Linux.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
bool: True if autostart is enabled (file exists and Hidden!=true)
|
|
115
|
+
"""
|
|
116
|
+
desktop_file = _get_linux_desktop_file_path()
|
|
117
|
+
|
|
118
|
+
if not desktop_file.exists():
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
parser = ConfigParser(interpolation=None)
|
|
122
|
+
try:
|
|
123
|
+
parser.read(desktop_file)
|
|
124
|
+
hidden = parser.get('Desktop Entry', 'Hidden', fallback='false')
|
|
125
|
+
return hidden.lower() != 'true'
|
|
126
|
+
except Exception:
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --- Windows Registry support ---
|
|
131
|
+
|
|
132
|
+
WINDOWS_RUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
|
|
133
|
+
WINDOWS_APP_NAME = "ClaudeUsageOverlay"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _create_windows_autostart(enable: bool = True):
|
|
137
|
+
"""Create or remove autostart Registry entry on Windows.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
enable: If True, enable autostart. If False, remove entry.
|
|
141
|
+
"""
|
|
142
|
+
if not IS_WINDOWS:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
import winreg
|
|
146
|
+
|
|
147
|
+
exec_cmd = _get_exec_command()
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
key = winreg.OpenKey(
|
|
151
|
+
winreg.HKEY_CURRENT_USER,
|
|
152
|
+
WINDOWS_RUN_KEY,
|
|
153
|
+
0,
|
|
154
|
+
winreg.KEY_SET_VALUE
|
|
155
|
+
)
|
|
156
|
+
try:
|
|
157
|
+
if enable:
|
|
158
|
+
winreg.SetValueEx(key, WINDOWS_APP_NAME, 0, winreg.REG_SZ, exec_cmd)
|
|
159
|
+
else:
|
|
160
|
+
try:
|
|
161
|
+
winreg.DeleteValue(key, WINDOWS_APP_NAME)
|
|
162
|
+
except FileNotFoundError:
|
|
163
|
+
pass # Already removed
|
|
164
|
+
finally:
|
|
165
|
+
winreg.CloseKey(key)
|
|
166
|
+
except OSError as e:
|
|
167
|
+
print(f"Failed to modify Windows autostart: {e}", file=sys.stderr)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _remove_windows_autostart():
|
|
171
|
+
"""Remove autostart Registry entry on Windows."""
|
|
172
|
+
_create_windows_autostart(enable=False)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _is_windows_autostart_enabled() -> bool:
|
|
176
|
+
"""Check if autostart is enabled on Windows.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
bool: True if Registry entry exists
|
|
180
|
+
"""
|
|
181
|
+
if not IS_WINDOWS:
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
import winreg
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
key = winreg.OpenKey(
|
|
188
|
+
winreg.HKEY_CURRENT_USER,
|
|
189
|
+
WINDOWS_RUN_KEY,
|
|
190
|
+
0,
|
|
191
|
+
winreg.KEY_READ
|
|
192
|
+
)
|
|
193
|
+
try:
|
|
194
|
+
winreg.QueryValueEx(key, WINDOWS_APP_NAME)
|
|
195
|
+
return True
|
|
196
|
+
except FileNotFoundError:
|
|
197
|
+
return False
|
|
198
|
+
finally:
|
|
199
|
+
winreg.CloseKey(key)
|
|
200
|
+
except OSError:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# --- macOS LaunchAgent support ---
|
|
205
|
+
|
|
206
|
+
def _get_macos_launch_agent_path() -> Path:
|
|
207
|
+
"""Get path to macOS LaunchAgent plist file.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Path: Path to com.claude-usage.overlay.plist
|
|
211
|
+
"""
|
|
212
|
+
launch_agents_dir = Path.home() / "Library" / "LaunchAgents"
|
|
213
|
+
launch_agents_dir.mkdir(parents=True, exist_ok=True)
|
|
214
|
+
return launch_agents_dir / "com.claude-usage.overlay.plist"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _create_macos_autostart(enable: bool = True):
|
|
218
|
+
"""Create or update LaunchAgent plist on macOS.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
enable: If True, enable autostart. If False, remove plist.
|
|
222
|
+
"""
|
|
223
|
+
if not IS_MACOS:
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
plist_path = _get_macos_launch_agent_path()
|
|
227
|
+
|
|
228
|
+
if not enable:
|
|
229
|
+
if plist_path.exists():
|
|
230
|
+
plist_path.unlink()
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
exec_cmd = _get_exec_command()
|
|
234
|
+
|
|
235
|
+
# For module execution, split into program and arguments
|
|
236
|
+
if " -m " in exec_cmd:
|
|
237
|
+
parts = exec_cmd.split()
|
|
238
|
+
program = parts[0]
|
|
239
|
+
args = parts[1:]
|
|
240
|
+
args_xml = "\n".join(f" <string>{arg}</string>" for arg in args)
|
|
241
|
+
program_args = f""" <key>ProgramArguments</key>
|
|
242
|
+
<array>
|
|
243
|
+
<string>{program}</string>
|
|
244
|
+
{args_xml}
|
|
245
|
+
</array>"""
|
|
246
|
+
else:
|
|
247
|
+
program_args = f""" <key>ProgramArguments</key>
|
|
248
|
+
<array>
|
|
249
|
+
<string>{exec_cmd}</string>
|
|
250
|
+
</array>"""
|
|
251
|
+
|
|
252
|
+
plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
253
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
254
|
+
<plist version="1.0">
|
|
255
|
+
<dict>
|
|
256
|
+
<key>Label</key>
|
|
257
|
+
<string>com.claude-usage.overlay</string>
|
|
258
|
+
{program_args}
|
|
259
|
+
<key>RunAtLoad</key>
|
|
260
|
+
<true/>
|
|
261
|
+
<key>KeepAlive</key>
|
|
262
|
+
<false/>
|
|
263
|
+
</dict>
|
|
264
|
+
</plist>
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
plist_path.write_text(plist_content)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _remove_macos_autostart():
|
|
271
|
+
"""Remove LaunchAgent plist on macOS."""
|
|
272
|
+
_create_macos_autostart(enable=False)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _is_macos_autostart_enabled() -> bool:
|
|
276
|
+
"""Check if autostart is enabled on macOS.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
bool: True if LaunchAgent plist exists
|
|
280
|
+
"""
|
|
281
|
+
if not IS_MACOS:
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
return _get_macos_launch_agent_path().exists()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# --- Cross-platform public API ---
|
|
288
|
+
|
|
289
|
+
def get_autostart_path() -> Path:
|
|
290
|
+
"""Get platform-specific autostart path.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Path: Path to autostart configuration location
|
|
294
|
+
"""
|
|
295
|
+
if IS_WINDOWS:
|
|
296
|
+
# Return a conceptual path for documentation purposes
|
|
297
|
+
return Path("HKCU") / WINDOWS_RUN_KEY / WINDOWS_APP_NAME
|
|
298
|
+
elif IS_MACOS:
|
|
299
|
+
return _get_macos_launch_agent_path()
|
|
300
|
+
else:
|
|
301
|
+
return _get_linux_autostart_path()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def get_desktop_file_path() -> Path:
|
|
305
|
+
"""Get path to the autostart configuration file.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Path: Platform-specific autostart file path
|
|
309
|
+
"""
|
|
310
|
+
if IS_WINDOWS:
|
|
311
|
+
return Path("HKCU") / WINDOWS_RUN_KEY / WINDOWS_APP_NAME
|
|
312
|
+
elif IS_MACOS:
|
|
313
|
+
return _get_macos_launch_agent_path()
|
|
314
|
+
else:
|
|
315
|
+
return _get_linux_desktop_file_path()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def create_autostart_entry(enable: bool = True):
|
|
319
|
+
"""Create or update autostart entry for current platform.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
enable: If True, enable autostart. If False, disable.
|
|
323
|
+
"""
|
|
324
|
+
if IS_WINDOWS:
|
|
325
|
+
_create_windows_autostart(enable)
|
|
326
|
+
elif IS_MACOS:
|
|
327
|
+
_create_macos_autostart(enable)
|
|
328
|
+
else:
|
|
329
|
+
_create_linux_autostart(enable)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def remove_autostart_entry():
|
|
333
|
+
"""Remove autostart entry for current platform."""
|
|
334
|
+
if IS_WINDOWS:
|
|
335
|
+
_remove_windows_autostart()
|
|
336
|
+
elif IS_MACOS:
|
|
337
|
+
_remove_macos_autostart()
|
|
338
|
+
else:
|
|
339
|
+
_remove_linux_autostart()
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def is_autostart_enabled() -> bool:
|
|
343
|
+
"""Check if autostart is enabled for current platform.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
bool: True if autostart is enabled
|
|
347
|
+
"""
|
|
348
|
+
if IS_WINDOWS:
|
|
349
|
+
return _is_windows_autostart_enabled()
|
|
350
|
+
elif IS_MACOS:
|
|
351
|
+
return _is_macos_autostart_enabled()
|
|
352
|
+
else:
|
|
353
|
+
return _is_linux_autostart_enabled()
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
"""Configuration module for loading Claude OAuth credentials.
|
|
1
|
+
"""Configuration module for loading Claude OAuth credentials.
|
|
2
|
+
|
|
3
|
+
Cross-platform: uses platformdirs for config directory resolution.
|
|
4
|
+
"""
|
|
2
5
|
|
|
3
6
|
import json
|
|
4
7
|
import os
|
|
@@ -6,9 +9,15 @@ import time
|
|
|
6
9
|
from dataclasses import asdict, dataclass
|
|
7
10
|
from pathlib import Path
|
|
8
11
|
|
|
12
|
+
import platformdirs
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
CREDENTIALS_PATH = Path.home() / ".claude" / ".credentials.json"
|
|
11
16
|
|
|
17
|
+
# Application info for platformdirs
|
|
18
|
+
APP_NAME = "claude-usage-overlay"
|
|
19
|
+
APP_AUTHOR = "claude-usage" # Used on Windows
|
|
20
|
+
|
|
12
21
|
|
|
13
22
|
def load_credentials() -> dict:
|
|
14
23
|
"""Load Claude OAuth credentials from ~/.claude/.credentials.json.
|
|
@@ -74,22 +83,27 @@ def get_access_token() -> str:
|
|
|
74
83
|
return credentials["accessToken"]
|
|
75
84
|
|
|
76
85
|
|
|
77
|
-
def
|
|
78
|
-
"""Get
|
|
86
|
+
def get_config_dir() -> Path:
|
|
87
|
+
"""Get cross-platform config directory.
|
|
79
88
|
|
|
80
89
|
Returns:
|
|
81
|
-
Path:
|
|
90
|
+
Path: Config directory path
|
|
91
|
+
- Linux: ~/.config/claude-usage-overlay/
|
|
92
|
+
- Windows: %APPDATA%/claude-usage/claude-usage-overlay/
|
|
93
|
+
- macOS: ~/Library/Application Support/claude-usage-overlay/
|
|
82
94
|
"""
|
|
83
|
-
|
|
84
|
-
if config_home:
|
|
85
|
-
base = Path(config_home)
|
|
86
|
-
else:
|
|
87
|
-
base = Path.home() / '.config'
|
|
88
|
-
|
|
89
|
-
config_dir = base / 'claude-usage-overlay'
|
|
95
|
+
config_dir = Path(platformdirs.user_config_dir(APP_NAME, APP_AUTHOR))
|
|
90
96
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
return config_dir
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
|
|
100
|
+
def get_config_path() -> Path:
|
|
101
|
+
"""Get cross-platform config file path.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Path: Path to config.json in platform-specific config directory
|
|
105
|
+
"""
|
|
106
|
+
return get_config_dir() / 'config.json'
|
|
93
107
|
|
|
94
108
|
|
|
95
109
|
@dataclass
|
|
@@ -140,7 +154,7 @@ class UserConfig:
|
|
|
140
154
|
|
|
141
155
|
@classmethod
|
|
142
156
|
def load(cls) -> 'UserConfig':
|
|
143
|
-
"""Load configuration from
|
|
157
|
+
"""Load configuration from config file.
|
|
144
158
|
|
|
145
159
|
Returns:
|
|
146
160
|
UserConfig: Configuration object with values from file or defaults
|
|
@@ -165,12 +179,16 @@ class UserConfig:
|
|
|
165
179
|
raise ValueError(f"Invalid config file structure: {e}")
|
|
166
180
|
|
|
167
181
|
def save(self):
|
|
168
|
-
"""Save configuration to
|
|
182
|
+
"""Save configuration to config file."""
|
|
169
183
|
config_path = get_config_path()
|
|
170
184
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
171
185
|
|
|
172
186
|
with open(config_path, 'w') as f:
|
|
173
187
|
json.dump(asdict(self), f, indent=2)
|
|
174
188
|
|
|
175
|
-
# Set secure permissions (owner read/write only)
|
|
176
|
-
|
|
189
|
+
# Set secure permissions (owner read/write only) on Unix
|
|
190
|
+
try:
|
|
191
|
+
os.chmod(config_path, 0o600)
|
|
192
|
+
except (OSError, AttributeError):
|
|
193
|
+
# Windows doesn't support chmod the same way, skip
|
|
194
|
+
pass
|