claude-select 0.1.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.
Files changed (30) hide show
  1. claude_select-0.1.0/LICENSE +22 -0
  2. claude_select-0.1.0/PKG-INFO +478 -0
  3. claude_select-0.1.0/README.md +446 -0
  4. claude_select-0.1.0/pyproject.toml +91 -0
  5. claude_select-0.1.0/setup.cfg +4 -0
  6. claude_select-0.1.0/src/claude_select/__init__.py +5 -0
  7. claude_select-0.1.0/src/claude_select/__main__.py +8 -0
  8. claude_select-0.1.0/src/claude_select/cli.py +115 -0
  9. claude_select-0.1.0/src/claude_select/exceptions.py +29 -0
  10. claude_select-0.1.0/src/claude_select/live_state.py +176 -0
  11. claude_select-0.1.0/src/claude_select/locking.py +65 -0
  12. claude_select-0.1.0/src/claude_select/manager.py +260 -0
  13. claude_select-0.1.0/src/claude_select/models.py +140 -0
  14. claude_select-0.1.0/src/claude_select/oauth.py +151 -0
  15. claude_select-0.1.0/src/claude_select/paths.py +45 -0
  16. claude_select-0.1.0/src/claude_select/store.py +136 -0
  17. claude_select-0.1.0/src/claude_select.egg-info/PKG-INFO +478 -0
  18. claude_select-0.1.0/src/claude_select.egg-info/SOURCES.txt +28 -0
  19. claude_select-0.1.0/src/claude_select.egg-info/dependency_links.txt +1 -0
  20. claude_select-0.1.0/src/claude_select.egg-info/entry_points.txt +2 -0
  21. claude_select-0.1.0/src/claude_select.egg-info/requires.txt +8 -0
  22. claude_select-0.1.0/src/claude_select.egg-info/top_level.txt +1 -0
  23. claude_select-0.1.0/tests/test_cli.py +37 -0
  24. claude_select-0.1.0/tests/test_cli_more.py +31 -0
  25. claude_select-0.1.0/tests/test_live_state.py +100 -0
  26. claude_select-0.1.0/tests/test_manager.py +95 -0
  27. claude_select-0.1.0/tests/test_oauth.py +50 -0
  28. claude_select-0.1.0/tests/test_oauth_more.py +82 -0
  29. claude_select-0.1.0/tests/test_paths.py +31 -0
  30. claude_select-0.1.0/tests/test_store.py +51 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wx.luo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,478 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-select
3
+ Version: 0.1.0
4
+ Summary: Manage multiple Claude auth profiles for the Claude Code CLI and Python Agent SDK usage.
5
+ Author: wx.luo
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Nomia/claude-select
8
+ Project-URL: Repository, https://github.com/Nomia/claude-select
9
+ Project-URL: Issues, https://github.com/Nomia/claude-select/issues
10
+ Project-URL: Changelog, https://github.com/Nomia/claude-select/blob/main/CHANGELOG.md
11
+ Keywords: claude,claude-code,oauth,sdk,cli
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: dev
25
+ Requires-Dist: build>=1.2.2; extra == "dev"
26
+ Requires-Dist: mypy>=1.11.0; extra == "dev"
27
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
29
+ Requires-Dist: ruff>=0.11.0; extra == "dev"
30
+ Requires-Dist: twine>=5.1.1; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # claude-select
34
+
35
+ `claude-select` is a local SDK and CLI design for managing multiple Claude authentication profiles across:
36
+
37
+ - the global Claude Code CLI login state
38
+ - Python programs using the Claude Agent SDK
39
+
40
+ This repository now contains a working first implementation of the design described below:
41
+
42
+ - file-backed profile storage
43
+ - CLI profile capture, switch, sync, inspect, remove, and default SDK selection
44
+ - Python `build_sdk_env()` support for direct `ClaudeAgentOptions(env=...)` usage
45
+ - OAuth refresh handling for stored profiles
46
+
47
+ The README still documents the intended architecture so the implementation can evolve without losing the original design constraints.
48
+
49
+ ## Status
50
+
51
+ Current implementation status:
52
+
53
+ - `ProfileManager` and top-level `build_sdk_env()` are implemented
54
+ - CLI commands are implemented for local single-user usage
55
+ - OAuth refresh is implemented for stored profiles
56
+ - unit tests are in place
57
+ - lint, type-check, build, and CI configuration are included
58
+
59
+ ## Goals
60
+
61
+ - Let a user capture multiple Claude accounts/profiles on one machine.
62
+ - Let the global Claude Code CLI switch between stored profiles.
63
+ - Let Python programs select a profile explicitly for each Claude Agent SDK call.
64
+ - Share one profile store between CLI switching and Python SDK usage.
65
+ - Avoid coupling Python SDK requests to the current global CLI account.
66
+
67
+ ## Non-goals
68
+
69
+ - Replacing Claude's official login flow.
70
+ - Building a hosted multi-user auth product.
71
+ - Relying on global process-wide environment mutation for Python SDK calls.
72
+ - Treating Agent SDK auth and CLI live auth as the same runtime state.
73
+
74
+ ## Core Model
75
+
76
+ The design separates three concepts:
77
+
78
+ 1. `profiles`
79
+ Stored account/auth profiles shared by CLI and Python SDK usage.
80
+ 2. `current_cli_profile`
81
+ The profile currently written into Claude Code's live login state.
82
+ 3. `default_sdk_profile`
83
+ The fallback profile used by Python helpers when the caller does not explicitly choose one.
84
+
85
+ This separation is intentional:
86
+
87
+ - CLI switching is global and mutates Claude's live state.
88
+ - Agent SDK switching is per-call and should be isolated through `env`.
89
+
90
+ ## Authentication Model
91
+
92
+ ### CLI
93
+
94
+ For the Claude Code CLI, switching works by reading and writing Claude's live login state:
95
+
96
+ - `~/.claude.json` or `~/.claude/.config.json`
97
+ - `~/.claude/.credentials.json`
98
+ - platform-specific secure storage where applicable
99
+ - `CLAUDE_CONFIG_DIR` when set
100
+
101
+ The SDK captures the current live state into a reusable profile, then later writes a chosen profile back into the live Claude files when the user switches.
102
+
103
+ ### Python Agent SDK
104
+
105
+ For Python, the preferred model is explicit per-call environment injection:
106
+
107
+ ```python
108
+ env = manager.build_sdk_env("work")
109
+ options = ClaudeAgentOptions(env=env)
110
+ ```
111
+
112
+ This avoids:
113
+
114
+ - mutating global `os.environ`
115
+ - leaking one profile into another concurrent request
116
+ - forcing Python usage to follow whatever the CLI currently uses
117
+
118
+ ## Shared Store Design
119
+
120
+ The SDK owns a profile store separate from Claude's live runtime files.
121
+
122
+ Recommended structure:
123
+
124
+ ```text
125
+ ~/.config/claude-select/
126
+ state.json
127
+ secrets/
128
+ work.json
129
+ personal.json
130
+ ```
131
+
132
+ ### `state.json`
133
+
134
+ Holds non-sensitive metadata and pointers:
135
+
136
+ ```json
137
+ {
138
+ "version": 1,
139
+ "current_cli_profile": "personal",
140
+ "default_sdk_profile": "work",
141
+ "profiles": {
142
+ "work": {
143
+ "id": "work",
144
+ "kind": "oauth",
145
+ "label": "work",
146
+ "email": "user@example.com",
147
+ "organization_id": "org_xxx",
148
+ "organization_name": "Example Org",
149
+ "auth_state": "ok",
150
+ "expires_at": 1760000000000,
151
+ "secret_ref": "work",
152
+ "updated_at": "2026-04-19T00:00:00Z"
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### `secrets/<profile>.json`
159
+
160
+ Holds sensitive auth material:
161
+
162
+ ```json
163
+ {
164
+ "oauthAccount": {
165
+ "emailAddress": "user@example.com"
166
+ },
167
+ "credentials": {
168
+ "claudeAiOauth": {
169
+ "accessToken": "...",
170
+ "refreshToken": "...",
171
+ "expiresAt": 1760000000000,
172
+ "scopes": ["user:profile"]
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ Future versions may replace file-based secret storage with platform keychains while keeping the same `secret_ref` abstraction.
179
+
180
+ ## OAuth Expiry Strategy
181
+
182
+ When using OAuth-backed profiles:
183
+
184
+ - Access token near expiry: refresh automatically if `refreshToken` exists.
185
+ - Refresh succeeds: update the profile store before returning.
186
+ - Refresh fails: mark the profile as `reauth_required`.
187
+
188
+ For CLI switching:
189
+
190
+ - switching should still be allowed
191
+ - the tool should clearly report that the selected profile now requires `claude` `/login`
192
+ - after re-login, the user runs `capture` or `sync` to update the stored profile
193
+
194
+ For Python SDK usage:
195
+
196
+ - `build_sdk_env(profile)` should attempt refresh first
197
+ - if refresh fails, raise a clear exception such as `ProfileReauthRequired`
198
+
199
+ ## CLI Design
200
+
201
+ Planned commands:
202
+
203
+ ```bash
204
+ claude-select capture <profile>
205
+ claude-select sync [<profile>]
206
+ claude-select list
207
+ claude-select current
208
+ claude-select use <profile>
209
+ claude-select remove <profile>
210
+ claude-select set-default-sdk <profile>
211
+ ```
212
+
213
+ ### Command behavior
214
+
215
+ - `capture <profile>`
216
+ Reads Claude's current live login state and stores it as a named profile.
217
+ - `sync [<profile>]`
218
+ Updates an existing stored profile from the current live login state.
219
+ - `list`
220
+ Shows all stored profiles and auth state.
221
+ - `current`
222
+ Shows the current CLI profile and default SDK profile.
223
+ - `use <profile>`
224
+ Switches Claude's global live login state to the chosen profile.
225
+ - `remove <profile>`
226
+ Removes a stored profile from the SDK store.
227
+ - `set-default-sdk <profile>`
228
+ Updates `default_sdk_profile`.
229
+
230
+ ## Python SDK Design
231
+
232
+ Planned primary interface:
233
+
234
+ ```python
235
+ from claude_select import ProfileManager
236
+
237
+ manager = ProfileManager()
238
+ env = manager.build_sdk_env("work")
239
+ ```
240
+
241
+ Planned convenience function:
242
+
243
+ ```python
244
+ from claude_select import build_sdk_env
245
+
246
+ env = build_sdk_env("work")
247
+ ```
248
+
249
+ ### Intended `ProfileManager` surface
250
+
251
+ ```python
252
+ class ProfileManager:
253
+ def list_profiles(self) -> list[str]: ...
254
+ def capture_cli_profile(self, name: str) -> None: ...
255
+ def sync_cli_profile(self, name: str | None = None) -> None: ...
256
+ def switch_cli(self, name: str) -> None: ...
257
+ def set_default_sdk_profile(self, name: str) -> None: ...
258
+ def get_default_sdk_profile(self) -> str | None: ...
259
+ def inspect_profile(self, name: str) -> dict: ...
260
+ def build_sdk_env(self, name: str | None = None) -> dict[str, str]: ...
261
+ ```
262
+
263
+ ## Agent SDK Environment Injection
264
+
265
+ The direct usage style for Python is the intended default:
266
+
267
+ ```python
268
+ from claude_select import ProfileManager
269
+ from claude_code_sdk import ClaudeAgentOptions, query
270
+
271
+ manager = ProfileManager()
272
+
273
+ env = manager.build_sdk_env("work")
274
+ options = ClaudeAgentOptions(env=env)
275
+
276
+ async for message in query(
277
+ prompt="Analyze this repository",
278
+ options=options,
279
+ ):
280
+ print(message)
281
+ ```
282
+
283
+ ### Why this is the default
284
+
285
+ - explicit per-call behavior
286
+ - works with async and concurrent tasks
287
+ - no hidden global mutation
288
+ - lets one process use multiple profiles safely
289
+
290
+ ## Environment Variables by Profile Type
291
+
292
+ `build_sdk_env()` will return a clean environment map for exactly one auth mode.
293
+
294
+ ### OAuth profile
295
+
296
+ Expected output:
297
+
298
+ ```python
299
+ {
300
+ "CLAUDE_CODE_OAUTH_TOKEN": "...",
301
+ "CLAUDE_CODE_OAUTH_REFRESH_TOKEN": "..."
302
+ }
303
+ ```
304
+
305
+ ### API key profile
306
+
307
+ Expected output:
308
+
309
+ ```python
310
+ {
311
+ "ANTHROPIC_API_KEY": "..."
312
+ }
313
+ ```
314
+
315
+ ### Important rule
316
+
317
+ Conflicting auth env vars should be removed from the returned environment. For example, an OAuth profile should not leave these active:
318
+
319
+ - `ANTHROPIC_API_KEY`
320
+ - `ANTHROPIC_AUTH_TOKEN`
321
+ - `CLAUDE_CODE_USE_BEDROCK`
322
+ - `CLAUDE_CODE_USE_VERTEX`
323
+ - `CLAUDE_CODE_USE_FOUNDRY`
324
+
325
+ ## How Users Get Started
326
+
327
+ Recommended first-run flow:
328
+
329
+ 1. Log in with Claude's official CLI flow.
330
+
331
+ ```bash
332
+ claude
333
+ ```
334
+
335
+ Then complete `/login`.
336
+
337
+ 2. Capture the current account into a named profile.
338
+
339
+ ```bash
340
+ claude-select capture work
341
+ ```
342
+
343
+ 3. Log in with another account if needed, then capture again.
344
+
345
+ ```bash
346
+ claude-select capture personal
347
+ ```
348
+
349
+ 4. Switch the global CLI account when needed.
350
+
351
+ ```bash
352
+ claude-select use personal
353
+ ```
354
+
355
+ 5. Use a chosen profile from Python.
356
+
357
+ ```python
358
+ from claude_select import ProfileManager
359
+ from claude_code_sdk import ClaudeAgentOptions
360
+
361
+ manager = ProfileManager()
362
+ env = manager.build_sdk_env("work")
363
+ options = ClaudeAgentOptions(env=env)
364
+ ```
365
+
366
+ ## Development
367
+
368
+ Set up a local environment:
369
+
370
+ ```bash
371
+ python3 -m venv .venv
372
+ source .venv/bin/activate
373
+ python3 -m pip install -e ".[dev]"
374
+ ```
375
+
376
+ Run the full local quality suite:
377
+
378
+ ```bash
379
+ ruff check .
380
+ ruff format --check .
381
+ mypy
382
+ python3 -m pytest
383
+ python3 -m build
384
+ python3 -m twine check dist/*
385
+ ```
386
+
387
+ You can also run the CLI as:
388
+
389
+ ```bash
390
+ python3 -m claude_select --help
391
+ ```
392
+
393
+ ## Runtime Relationship
394
+
395
+ The intended data flow is:
396
+
397
+ - CLI capture: Claude live state -> SDK profile store
398
+ - CLI switch: SDK profile store -> Claude live state
399
+ - Python SDK usage: SDK profile store -> `env`
400
+
401
+ The Python SDK should not depend on the current global CLI account unless the caller explicitly chooses the same profile.
402
+
403
+ ## Safety and Concurrency
404
+
405
+ Implementation should include:
406
+
407
+ - file locking during capture, sync, switch, and refresh
408
+ - atomic writes for state and secret files
409
+ - backups before mutating Claude live files
410
+ - detection of running Claude CLI / IDE instances
411
+ - clear messaging when restart or re-login is required
412
+
413
+ Current status:
414
+
415
+ - file locking is implemented for the SDK profile store
416
+ - atomic file writes are implemented for profile and live-state file writes
417
+ - live-state backups are implemented before Claude auth mutation
418
+ - full Claude process detection is not implemented yet
419
+
420
+ ## Status Values
421
+
422
+ Each profile should expose a simple auth status:
423
+
424
+ - `ok`
425
+ - `expiring_soon`
426
+ - `refreshable`
427
+ - `reauth_required`
428
+ - `invalid`
429
+
430
+ These statuses should be visible in `list` and `inspect_profile`.
431
+
432
+ ## Scope of First Implementation
433
+
434
+ The first implementation should prioritize:
435
+
436
+ 1. file-based profile store
437
+ 2. OAuth profile capture from Claude CLI live state
438
+ 3. CLI switching between stored OAuth profiles
439
+ 4. `build_sdk_env(profile)` for Python Agent SDK usage
440
+ 5. token refresh for OAuth-backed profiles
441
+
442
+ The first implementation should not prioritize:
443
+
444
+ - hosted sync
445
+ - multi-user remote storage
446
+ - GUI
447
+ - deep plugin integrations
448
+
449
+ ## Current Limitations
450
+
451
+ - The primary supported profile type today is OAuth captured from Claude CLI live state.
452
+ - macOS keychain reading and writing is implemented, but broader secure-store coverage is still incomplete.
453
+ - Full pre-switch detection of running Claude sessions and IDE integrations is not implemented yet.
454
+ - This project is designed for local single-user machines, not shared multi-user hosts.
455
+
456
+ ## Release Checklist
457
+
458
+ Before publishing a release:
459
+
460
+ 1. Run the full local quality suite.
461
+ 2. Verify `claude-select capture`, `claude-select use`, and `build_sdk_env()` against a real local Claude setup.
462
+ 3. Update `CHANGELOG.md`.
463
+ 4. Create a version tag and publish artifacts built from CI-verified sources.
464
+
465
+ ## References
466
+
467
+ This design is informed by the following projects:
468
+
469
+ - [Leuconoe/ClaudeCodeMultiAccounts](https://github.com/Leuconoe/ClaudeCodeMultiAccounts)
470
+ - [realiti4/claude-swap](https://github.com/realiti4/claude-swap)
471
+
472
+ ## Current State
473
+
474
+ The repository contains a tested first implementation aimed at local single-user usage. Future work can harden:
475
+
476
+ - platform keychain integration beyond the current file-backed path and macOS keychain support
477
+ - richer process detection before CLI switching
478
+ - broader profile kinds beyond OAuth-backed profiles