ccproxy-api 0.2.2__py3-none-any.whl → 0.2.3__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.
ccproxy/core/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.2'
32
- __version_tuple__ = version_tuple = (0, 2, 2)
31
+ __version__ = version = '0.2.3'
32
+ __version_tuple__ = version_tuple = (0, 2, 3)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -15,6 +15,77 @@ from .models import ClaudeCredentials, ClaudeProfileInfo
15
15
  logger = get_plugin_logger()
16
16
 
17
17
 
18
+ # Keychain service name used by Claude Code
19
+ KEYCHAIN_SERVICE = "Claude Code"
20
+ KEYCHAIN_ACCOUNT = "credentials"
21
+
22
+
23
+ def _is_keyring_available() -> bool:
24
+ """Check if keyring library is available."""
25
+ try:
26
+ import keyring # noqa: F401
27
+
28
+ return True
29
+ except ImportError:
30
+ return False
31
+
32
+
33
+ async def _read_from_keychain() -> dict[str, Any] | None:
34
+ """Read Claude credentials from system keychain.
35
+
36
+ Claude Code stores OAuth credentials in the system keychain and intentionally
37
+ deletes the plain text ~/.claude/.credentials.json file for security.
38
+ See: https://github.com/anthropics/claude-code/issues/1414
39
+
40
+ Uses the keyring library which supports:
41
+ - macOS Keychain
42
+ - Windows Credential Manager
43
+ - Linux Secret Service (GNOME Keyring, KDE Wallet)
44
+
45
+ Returns:
46
+ Parsed credentials dict or None if not found or keyring unavailable
47
+ """
48
+ if not _is_keyring_available():
49
+ logger.debug(
50
+ "keyring_not_available",
51
+ hint="Install keyring package for system keychain support",
52
+ category="auth",
53
+ )
54
+ return None
55
+
56
+ def read_keychain() -> dict[str, Any] | None:
57
+ try:
58
+ import keyring
59
+
60
+ password = keyring.get_password(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT)
61
+ if password:
62
+ parsed = json.loads(password)
63
+ if isinstance(parsed, dict):
64
+ return parsed
65
+ logger.debug(
66
+ "keychain_invalid_format",
67
+ expected="dict",
68
+ got=type(parsed).__name__,
69
+ category="auth",
70
+ )
71
+ except json.JSONDecodeError as e:
72
+ logger.debug(
73
+ "keychain_json_decode_error",
74
+ error=str(e),
75
+ category="auth",
76
+ )
77
+ except Exception as e:
78
+ logger.debug(
79
+ "keychain_read_error",
80
+ error=str(e),
81
+ error_type=type(e).__name__,
82
+ category="auth",
83
+ )
84
+ return None
85
+
86
+ return await asyncio.to_thread(read_keychain)
87
+
88
+
18
89
  class ClaudeOAuthStorage(BaseJsonStorage[ClaudeCredentials]):
19
90
  """Claude OAuth-specific token storage implementation."""
20
91
 
@@ -61,24 +132,48 @@ class ClaudeOAuthStorage(BaseJsonStorage[ClaudeCredentials]):
61
132
  return False
62
133
 
63
134
  async def load(self) -> ClaudeCredentials | None:
64
- """Load Claude credentials.
135
+ """Load Claude credentials from file or system keychain.
136
+
137
+ Claude Code stores credentials in the system keychain and intentionally
138
+ deletes the plain text file for security. This method tries file first,
139
+ then falls back to the system keychain (macOS Keychain, Windows Credential
140
+ Manager, or Linux Secret Service).
65
141
 
66
142
  Returns:
67
143
  Stored credentials or None
68
144
  """
69
145
  try:
70
- # Use parent class's read method
146
+ # Try file first (works on all platforms, manual setups)
71
147
  data = await self._read_json()
72
- if not data:
73
- return None
148
+ if data:
149
+ credentials = ClaudeCredentials.model_validate(data)
150
+ logger.debug(
151
+ "claude_oauth_credentials_loaded",
152
+ has_oauth=bool(credentials.claude_ai_oauth),
153
+ source="file",
154
+ category="auth",
155
+ )
156
+ return credentials
157
+
158
+ # Fallback to system keychain (where Claude Code stores credentials)
159
+ keychain_data = await _read_from_keychain()
160
+ if keychain_data:
161
+ credentials = ClaudeCredentials.model_validate(keychain_data)
162
+ logger.debug(
163
+ "claude_oauth_credentials_loaded",
164
+ has_oauth=bool(credentials.claude_ai_oauth),
165
+ source="keychain",
166
+ category="auth",
167
+ )
168
+ return credentials
74
169
 
75
- credentials = ClaudeCredentials.model_validate(data)
76
170
  logger.debug(
77
- "claude_oauth_credentials_loaded",
78
- has_oauth=bool(credentials.claude_ai_oauth),
171
+ "claude_oauth_credentials_not_found",
172
+ checked_file=str(self.file_path),
173
+ checked_keychain=_is_keyring_available(),
79
174
  category="auth",
80
175
  )
81
- return credentials
176
+ return None
82
177
  except Exception as e:
83
178
  logger.error(
84
179
  "claude_oauth_credentials_load_error",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccproxy-api
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: API server that provides an Anthropic and OpenAI compatible interface over Claude Code, allowing to use your Claude OAuth account or over the API.
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -20,6 +20,7 @@ Requires-Dist: typing-extensions>=4.0.0
20
20
  Requires-Dist: uvicorn>=0.34.0
21
21
  Provides-Extra: plugins-claude
22
22
  Requires-Dist: claude-agent-sdk>=0.1.0; extra == 'plugins-claude'
23
+ Requires-Dist: keyring>=25.0.0; extra == 'plugins-claude'
23
24
  Requires-Dist: qrcode>=8.2; extra == 'plugins-claude'
24
25
  Provides-Extra: plugins-codex
25
26
  Requires-Dist: pyjwt>=2.10.1; extra == 'plugins-codex'
@@ -67,7 +67,7 @@ ccproxy/config/settings.py,sha256=uva0RV4KfIvv7VApDr7w3oARft7OoBCWf7ZSM4oM2VM,19
67
67
  ccproxy/config/toml_generator.py,sha256=_txCYDHI8lXWl-mwOK8P1_TsX1TNiLgkG4iryxOruZc,10034
68
68
  ccproxy/config/utils.py,sha256=tuvOPUsMGgznz94MRwuSWw6sZi_AGkB_ri7VWKMVg8Y,11877
69
69
  ccproxy/core/__init__.py,sha256=hQgrBogZjdt8ZQlQyZtbL91I3gX9YUTWrenqTPRfwbM,236
70
- ccproxy/core/_version.py,sha256=o3ZTescp-19Z9cvBGq9dQnbppljgzdUYUf98Nov0spY,704
70
+ ccproxy/core/_version.py,sha256=kBRz0P2plw1eVdIpt70W6m1LMbEIhLY3RyOfVGdubaI,704
71
71
  ccproxy/core/async_task_manager.py,sha256=zf_mbbDwomh8q0E-oMNSPzFecHwLRi-ZPbhqsb6IPgM,16888
72
72
  ccproxy/core/async_utils.py,sha256=OFCJT8xbgZJO757iDPMAKY5c1Ildyk0PbwuktVF19UI,21676
73
73
  ccproxy/core/constants.py,sha256=FSLlbdNqCmZgZC4VAgvmovwXJh4C9WaUf_YBqDbYXXM,1837
@@ -301,7 +301,7 @@ ccproxy/plugins/oauth_claude/manager.py,sha256=0aOTVwPy3nDLhZsX5vcpWRG4kGqkdklOy
301
301
  ccproxy/plugins/oauth_claude/models.py,sha256=90BQzi0MVL9sK7NtTYZjwSWR5DGmEPsU2F8zF60Jbgw,9227
302
302
  ccproxy/plugins/oauth_claude/plugin.py,sha256=_EybVmwvigoJfuzAMETkknO3kQ-fp59earI7i1yWA-s,4918
303
303
  ccproxy/plugins/oauth_claude/provider.py,sha256=lV0RUmBthE8rmnPUNqdtcYuZL_RED95bURBhOOcyDqQ,19500
304
- ccproxy/plugins/oauth_claude/storage.py,sha256=rzMPn3uyHHe7nxA-EuQAFi-uGWS_pN60z7AMwgrYwPY,6547
304
+ ccproxy/plugins/oauth_claude/storage.py,sha256=JsSSmeGkaQHhtdczhQdQSuTEGv4N6gmXX2YIJVzO68A,9844
305
305
  ccproxy/plugins/oauth_codex/README.md,sha256=XgbCxp_JHadpYcq6JW-hC8YvBo9310CLqVbKdprY5Vc,1451
306
306
  ccproxy/plugins/oauth_codex/__init__.py,sha256=pA4sU7Ngj0xY2Ppr_G6K_H4boihe-TL7ppvvusoBKEg,345
307
307
  ccproxy/plugins/oauth_codex/client.py,sha256=Ry0uFDsdXXFUGdI5ol5i-zDgjEZtaToKdaUAlhmgyu4,7357
@@ -411,8 +411,8 @@ ccproxy/utils/id_generator.py,sha256=k6R_W40lJSPi_it4M99EVg9eRD138oC4bv_8Ua3X8ms
411
411
  ccproxy/utils/model_mapper.py,sha256=hnIWc528x8oBuk1y1HuyGHbnwe6dsSxZ2UgA1OYrcJs,3731
412
412
  ccproxy/utils/startup_helpers.py,sha256=u1okOVbm2OeSqrNrbhWco_sXBR0usNo7Wv8zvhBLPhc,7492
413
413
  ccproxy/utils/version_checker.py,sha256=cGRgjD0PUB3MDZDSAdKPQwYIyqnlzFWue0ROyfGngNE,13452
414
- ccproxy_api-0.2.2.dist-info/METADATA,sha256=5rWBdjovufqSnrAZSnIIy1HwiwWgP2a9R3xf7yV5WCE,8280
415
- ccproxy_api-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
416
- ccproxy_api-0.2.2.dist-info/entry_points.txt,sha256=bibqQtPpKZJhOY_j5TFvcYzHuR-w7tNovV2i7UcPlU4,1147
417
- ccproxy_api-0.2.2.dist-info/licenses/LICENSE,sha256=httxSCpTrEOkipisMeGXSrZhTB-4MRIorQU0hS1B6eQ,1066
418
- ccproxy_api-0.2.2.dist-info/RECORD,,
414
+ ccproxy_api-0.2.3.dist-info/METADATA,sha256=X9RSa7zriZwyjtyM4Qm-Br6D62c0u1aOeW7DfDfJl-Q,8338
415
+ ccproxy_api-0.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
416
+ ccproxy_api-0.2.3.dist-info/entry_points.txt,sha256=bibqQtPpKZJhOY_j5TFvcYzHuR-w7tNovV2i7UcPlU4,1147
417
+ ccproxy_api-0.2.3.dist-info/licenses/LICENSE,sha256=httxSCpTrEOkipisMeGXSrZhTB-4MRIorQU0hS1B6eQ,1066
418
+ ccproxy_api-0.2.3.dist-info/RECORD,,