claude-code-usage 1.0.2__py3-none-any.whl → 2.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.
- {claude_code_usage-1.0.2.dist-info → claude_code_usage-2.0.0.dist-info}/METADATA +18 -8
- claude_code_usage-2.0.0.dist-info/RECORD +15 -0
- {claude_code_usage-1.0.2.dist-info → claude_code_usage-2.0.0.dist-info}/WHEEL +1 -1
- claude_code_usage-2.0.0.dist-info/entry_points.txt +5 -0
- src/autostart.py +268 -34
- src/config.py +35 -14
- src/icon_generator.py +66 -58
- src/notifier.py +95 -82
- src/tray.py +127 -198
- claude_code_usage-1.0.2.dist-info/RECORD +0 -15
- claude_code_usage-1.0.2.dist-info/entry_points.txt +0 -2
- {claude_code_usage-1.0.2.dist-info/licenses → claude_code_usage-2.0.0.dist-info}/LICENSE +0 -0
- {claude_code_usage-1.0.2.dist-info → claude_code_usage-2.0.0.dist-info}/top_level.txt +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
|
|
@@ -20,9 +25,14 @@ Classifier: Topic :: Utilities
|
|
|
20
25
|
Requires-Python: >=3.11
|
|
21
26
|
Description-Content-Type: text/markdown
|
|
22
27
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: requests>=2.28.0
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
|
|
28
|
+
Requires-Dist: requests >=2.28.0
|
|
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
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
src/__init__.py,sha256=rzeXYIMbNbWPF6gpjv7luGXLZA67r_5GU5ZGUNb8pKA,27
|
|
2
|
+
src/api.py,sha256=IymlJFR7_Ou5I_Asij8oyR2b7bx9WurBtREwcysbiJQ,6168
|
|
3
|
+
src/autostart.py,sha256=DJ6r0P-fuanS9bc82I3pVF1mXaXbQC3rY_JKrDs5nEY,9239
|
|
4
|
+
src/config.py,sha256=fOqNm6cDA3eLigTWbLjWPpneuS3PiZekL_mITRVvrT4,5972
|
|
5
|
+
src/icon_generator.py,sha256=o9Wm7MAsScarRkrskHajgLQkmmAiBcZZ3AWgxA_95LE,3955
|
|
6
|
+
src/main.py,sha256=A7Ej5kqbtOYWUGSuZxx6bM-7WNimjjAs--04_kvvta0,506
|
|
7
|
+
src/notifier.py,sha256=cSGlZcx8eF7VNmRVWgF156PB8bJhxflu6hbUSVhuk94,10900
|
|
8
|
+
src/tray.py,sha256=IG3uH2AYBMIUdSfavCR5lMSNB0IVe7nuIcTkvPg3JSk,8403
|
|
9
|
+
src/utils.py,sha256=ZqGvw-oyzHompxRMUeYT5FdJFaQ6uvoAthhu-3bPGJk,893
|
|
10
|
+
claude_code_usage-2.0.0.dist-info/LICENSE,sha256=alXcUJ2-k8eqG_Amt8y9Hhlb8fT1nABafZOqWDmdzUw,1060
|
|
11
|
+
claude_code_usage-2.0.0.dist-info/METADATA,sha256=5IDglmOvUTFxnPkAX-6NAA9s-94QUmnSJmEGMEY_QH4,5995
|
|
12
|
+
claude_code_usage-2.0.0.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
|
13
|
+
claude_code_usage-2.0.0.dist-info/entry_points.txt,sha256=sRUb89gemjebLTvCQVv6TJWunjYrD-i_it3A3E9bJVc,95
|
|
14
|
+
claude_code_usage-2.0.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
15
|
+
claude_code_usage-2.0.0.dist-info/RECORD,,
|
src/autostart.py
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
"""Autostart management for Claude Usage Overlay.
|
|
1
|
+
"""Autostart management for Claude Usage Overlay.
|
|
2
|
+
|
|
3
|
+
Cross-platform: supports Linux (.desktop), Windows (Registry), and macOS (LaunchAgent).
|
|
4
|
+
"""
|
|
2
5
|
|
|
3
6
|
import os
|
|
4
7
|
import shutil
|
|
8
|
+
import sys
|
|
5
9
|
from configparser import ConfigParser
|
|
6
10
|
from pathlib import Path
|
|
7
11
|
|
|
12
|
+
# Detect platform
|
|
13
|
+
IS_WINDOWS = sys.platform == 'win32'
|
|
14
|
+
IS_MACOS = sys.platform == 'darwin'
|
|
15
|
+
IS_LINUX = sys.platform.startswith('linux')
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
|
|
18
|
+
# --- Linux .desktop file support ---
|
|
19
|
+
|
|
20
|
+
def _get_linux_autostart_path() -> Path:
|
|
21
|
+
"""Get XDG-compliant autostart directory path for Linux.
|
|
11
22
|
|
|
12
23
|
Returns:
|
|
13
24
|
Path: Path to autostart directory
|
|
@@ -24,49 +35,52 @@ def get_autostart_path() -> Path:
|
|
|
24
35
|
return autostart_dir
|
|
25
36
|
|
|
26
37
|
|
|
27
|
-
def
|
|
28
|
-
"""Get path to the .desktop file.
|
|
38
|
+
def _get_linux_desktop_file_path() -> Path:
|
|
39
|
+
"""Get path to the Linux .desktop file.
|
|
29
40
|
|
|
30
41
|
Returns:
|
|
31
42
|
Path: Path to claude-usage-overlay.desktop
|
|
32
43
|
"""
|
|
33
|
-
return
|
|
44
|
+
return _get_linux_autostart_path() / 'claude-usage-overlay.desktop'
|
|
34
45
|
|
|
35
46
|
|
|
36
47
|
def _get_exec_command() -> str:
|
|
37
|
-
"""Determine the best Exec command for
|
|
48
|
+
"""Determine the best Exec command for autostart.
|
|
38
49
|
|
|
39
50
|
Returns:
|
|
40
|
-
Command string
|
|
51
|
+
Command string, preferring installed entry point.
|
|
52
|
+
On Windows, paths are quoted to handle spaces.
|
|
41
53
|
"""
|
|
42
54
|
# Prefer the installed 'claude-usage' command if available
|
|
43
55
|
claude_usage_path = shutil.which("claude-usage")
|
|
44
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}"'
|
|
45
60
|
return claude_usage_path
|
|
46
61
|
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return f"python3 -m src.main"
|
|
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
|
|
53
67
|
|
|
54
|
-
#
|
|
55
|
-
|
|
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"
|
|
56
73
|
|
|
57
74
|
|
|
58
|
-
def
|
|
59
|
-
"""Create or update autostart .desktop file.
|
|
75
|
+
def _create_linux_autostart(enable: bool = True):
|
|
76
|
+
"""Create or update autostart .desktop file on Linux.
|
|
60
77
|
|
|
61
78
|
Args:
|
|
62
79
|
enable: If True, enable autostart. If False, disable (set Hidden=true).
|
|
63
80
|
"""
|
|
64
|
-
desktop_file =
|
|
65
|
-
|
|
66
|
-
# Get best exec command (prefer installed entry point)
|
|
81
|
+
desktop_file = _get_linux_desktop_file_path()
|
|
67
82
|
exec_cmd = _get_exec_command()
|
|
68
83
|
|
|
69
|
-
# Desktop entry content
|
|
70
84
|
hidden_value = "false" if enable else "true"
|
|
71
85
|
content = f"""[Desktop Entry]
|
|
72
86
|
Type=Application
|
|
@@ -82,38 +96,258 @@ X-GNOME-Autostart-enabled=true
|
|
|
82
96
|
Hidden={hidden_value}
|
|
83
97
|
"""
|
|
84
98
|
|
|
85
|
-
# Write .desktop file with secure permissions (rw-r--r--)
|
|
86
|
-
# .desktop files don't need execute bit - they're parsed by desktop environment
|
|
87
99
|
desktop_file.write_text(content)
|
|
88
100
|
desktop_file.chmod(0o644)
|
|
89
101
|
|
|
90
102
|
|
|
91
|
-
def
|
|
92
|
-
"""Remove autostart .desktop file."""
|
|
93
|
-
desktop_file =
|
|
103
|
+
def _remove_linux_autostart():
|
|
104
|
+
"""Remove autostart .desktop file on Linux."""
|
|
105
|
+
desktop_file = _get_linux_desktop_file_path()
|
|
94
106
|
if desktop_file.exists():
|
|
95
107
|
desktop_file.unlink()
|
|
96
108
|
|
|
97
109
|
|
|
98
|
-
def
|
|
99
|
-
"""Check if autostart is enabled.
|
|
110
|
+
def _is_linux_autostart_enabled() -> bool:
|
|
111
|
+
"""Check if autostart is enabled on Linux.
|
|
100
112
|
|
|
101
113
|
Returns:
|
|
102
114
|
bool: True if autostart is enabled (file exists and Hidden!=true)
|
|
103
115
|
"""
|
|
104
|
-
desktop_file =
|
|
116
|
+
desktop_file = _get_linux_desktop_file_path()
|
|
105
117
|
|
|
106
118
|
if not desktop_file.exists():
|
|
107
119
|
return False
|
|
108
120
|
|
|
109
|
-
# Use configparser for proper INI parsing
|
|
110
121
|
parser = ConfigParser(interpolation=None)
|
|
111
122
|
try:
|
|
112
123
|
parser.read(desktop_file)
|
|
113
|
-
# Desktop files use 'Desktop Entry' section
|
|
114
|
-
# Hidden=true means disabled; absent or false means enabled
|
|
115
124
|
hidden = parser.get('Desktop Entry', 'Hidden', fallback='false')
|
|
116
125
|
return hidden.lower() != 'true'
|
|
117
126
|
except Exception:
|
|
118
|
-
# If parsing fails, assume enabled if file exists
|
|
119
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()
|
src/config.py
CHANGED
|
@@ -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
|
|
98
|
+
|
|
91
99
|
|
|
92
|
-
|
|
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,9 +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)
|
|
188
|
+
|
|
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
|