cc-switch 0.9.6__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.
- cc_switch-0.9.6/.gitignore +244 -0
- cc_switch-0.9.6/PKG-INFO +138 -0
- cc_switch-0.9.6/README.md +119 -0
- cc_switch-0.9.6/cc_switch/__init__.py +50 -0
- cc_switch-0.9.6/cc_switch/__main__.py +6 -0
- cc_switch-0.9.6/cc_switch/_activate.py +142 -0
- cc_switch-0.9.6/cc_switch/_api.py +105 -0
- cc_switch-0.9.6/cc_switch/_defaults.py +55 -0
- cc_switch-0.9.6/cc_switch/cli.py +288 -0
- cc_switch-0.9.6/pyproject.toml +51 -0
- cc_switch-0.9.6/tests/__init__.py +1 -0
- cc_switch-0.9.6/tests/conftest.py +29 -0
- cc_switch-0.9.6/tests/test_activate.py +163 -0
- cc_switch-0.9.6/tests/test_api.py +92 -0
- cc_switch-0.9.6/tests/test_cli.py +144 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Runtime config — personal per-user settings; template is slife.template.json5.
|
|
2
|
+
slife.json5
|
|
3
|
+
|
|
4
|
+
# Playwright MCP browser traces (test artifacts)
|
|
5
|
+
.playwright-mcp/
|
|
6
|
+
|
|
7
|
+
# Credential store encrypted backup
|
|
8
|
+
credentials.crypt
|
|
9
|
+
|
|
10
|
+
# WeChat session files (contain auth tokens)
|
|
11
|
+
wechat_*.json5
|
|
12
|
+
|
|
13
|
+
# Runtime logs
|
|
14
|
+
logs/
|
|
15
|
+
|
|
16
|
+
# User-saved files (memfiles plugin storage — per-agent runtime data)
|
|
17
|
+
*.files/
|
|
18
|
+
|
|
19
|
+
# Byte-compiled / optimized / DLL files
|
|
20
|
+
__pycache__/
|
|
21
|
+
*.py[codz]
|
|
22
|
+
*$py.class
|
|
23
|
+
|
|
24
|
+
# C extensions
|
|
25
|
+
*.so
|
|
26
|
+
|
|
27
|
+
# Distribution / packaging
|
|
28
|
+
.Python
|
|
29
|
+
build/
|
|
30
|
+
develop-eggs/
|
|
31
|
+
dist/
|
|
32
|
+
downloads/
|
|
33
|
+
eggs/
|
|
34
|
+
.eggs/
|
|
35
|
+
lib/
|
|
36
|
+
lib64/
|
|
37
|
+
parts/
|
|
38
|
+
sdist/
|
|
39
|
+
var/
|
|
40
|
+
wheels/
|
|
41
|
+
share/python-wheels/
|
|
42
|
+
*.egg-info/
|
|
43
|
+
.installed.cfg
|
|
44
|
+
*.egg
|
|
45
|
+
MANIFEST
|
|
46
|
+
|
|
47
|
+
# PyInstaller
|
|
48
|
+
# Usually these files are written by a python script from a template
|
|
49
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
50
|
+
*.manifest
|
|
51
|
+
*.spec
|
|
52
|
+
|
|
53
|
+
# Installer logs
|
|
54
|
+
pip-log.txt
|
|
55
|
+
pip-delete-this-directory.txt
|
|
56
|
+
|
|
57
|
+
# Unit test / coverage reports
|
|
58
|
+
htmlcov/
|
|
59
|
+
.tox/
|
|
60
|
+
.nox/
|
|
61
|
+
.coverage
|
|
62
|
+
.coverage.*
|
|
63
|
+
.cache
|
|
64
|
+
nosetests.xml
|
|
65
|
+
coverage.xml
|
|
66
|
+
*.cover
|
|
67
|
+
*.py.cover
|
|
68
|
+
.hypothesis/
|
|
69
|
+
.pytest_cache/
|
|
70
|
+
cover/
|
|
71
|
+
|
|
72
|
+
# Translations
|
|
73
|
+
*.mo
|
|
74
|
+
*.pot
|
|
75
|
+
|
|
76
|
+
# Django stuff:
|
|
77
|
+
*.log
|
|
78
|
+
local_settings.py
|
|
79
|
+
db.sqlite3
|
|
80
|
+
db.sqlite3-journal
|
|
81
|
+
|
|
82
|
+
# Flask stuff:
|
|
83
|
+
instance/
|
|
84
|
+
.webassets-cache
|
|
85
|
+
|
|
86
|
+
# Scrapy stuff:
|
|
87
|
+
.scrapy
|
|
88
|
+
|
|
89
|
+
# Sphinx documentation
|
|
90
|
+
docs/_build/
|
|
91
|
+
|
|
92
|
+
# PyBuilder
|
|
93
|
+
.pybuilder/
|
|
94
|
+
target/
|
|
95
|
+
|
|
96
|
+
# Jupyter Notebook
|
|
97
|
+
.ipynb_checkpoints
|
|
98
|
+
|
|
99
|
+
# IPython
|
|
100
|
+
profile_default/
|
|
101
|
+
ipython_config.py
|
|
102
|
+
|
|
103
|
+
# pyenv
|
|
104
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
105
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
106
|
+
# .python-version
|
|
107
|
+
|
|
108
|
+
# pipenv
|
|
109
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
110
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
111
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
112
|
+
# install all needed dependencies.
|
|
113
|
+
# Pipfile.lock
|
|
114
|
+
|
|
115
|
+
# UV
|
|
116
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
117
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
118
|
+
# commonly ignored for libraries.
|
|
119
|
+
# uv.lock
|
|
120
|
+
|
|
121
|
+
# poetry
|
|
122
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
123
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
124
|
+
# commonly ignored for libraries.
|
|
125
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
126
|
+
# poetry.lock
|
|
127
|
+
# poetry.toml
|
|
128
|
+
|
|
129
|
+
# pdm
|
|
130
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
131
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
132
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
133
|
+
# pdm.lock
|
|
134
|
+
# pdm.toml
|
|
135
|
+
.pdm-python
|
|
136
|
+
.pdm-build/
|
|
137
|
+
|
|
138
|
+
# pixi
|
|
139
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
140
|
+
# pixi.lock
|
|
141
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
142
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
143
|
+
.pixi
|
|
144
|
+
|
|
145
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
146
|
+
__pypackages__/
|
|
147
|
+
|
|
148
|
+
# Celery stuff
|
|
149
|
+
celerybeat-schedule
|
|
150
|
+
celerybeat.pid
|
|
151
|
+
|
|
152
|
+
# Redis
|
|
153
|
+
*.rdb
|
|
154
|
+
*.aof
|
|
155
|
+
*.pid
|
|
156
|
+
|
|
157
|
+
# RabbitMQ
|
|
158
|
+
mnesia/
|
|
159
|
+
rabbitmq/
|
|
160
|
+
rabbitmq-data/
|
|
161
|
+
|
|
162
|
+
# ActiveMQ
|
|
163
|
+
activemq-data/
|
|
164
|
+
|
|
165
|
+
# SageMath parsed files
|
|
166
|
+
*.sage.py
|
|
167
|
+
|
|
168
|
+
# Environments
|
|
169
|
+
.env
|
|
170
|
+
.envrc
|
|
171
|
+
.venv
|
|
172
|
+
env/
|
|
173
|
+
venv/
|
|
174
|
+
ENV/
|
|
175
|
+
env.bak/
|
|
176
|
+
venv.bak/
|
|
177
|
+
|
|
178
|
+
# Spyder project settings
|
|
179
|
+
.spyderproject
|
|
180
|
+
.spyproject
|
|
181
|
+
|
|
182
|
+
# Rope project settings
|
|
183
|
+
.ropeproject
|
|
184
|
+
|
|
185
|
+
# mkdocs documentation
|
|
186
|
+
/site
|
|
187
|
+
|
|
188
|
+
# mypy
|
|
189
|
+
.mypy_cache/
|
|
190
|
+
.dmypy.json
|
|
191
|
+
dmypy.json
|
|
192
|
+
|
|
193
|
+
# Pyre type checker
|
|
194
|
+
.pyre/
|
|
195
|
+
|
|
196
|
+
# pytype static type analyzer
|
|
197
|
+
.pytype/
|
|
198
|
+
|
|
199
|
+
# Cython debug symbols
|
|
200
|
+
cython_debug/
|
|
201
|
+
|
|
202
|
+
# PyCharm
|
|
203
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
204
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
205
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
206
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
207
|
+
# .idea/
|
|
208
|
+
|
|
209
|
+
# Abstra
|
|
210
|
+
# Abstra is an AI-powered process automation framework.
|
|
211
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
212
|
+
# Learn more at https://abstra.io/docs
|
|
213
|
+
.abstra/
|
|
214
|
+
|
|
215
|
+
# Visual Studio Code
|
|
216
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
217
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
218
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
219
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
220
|
+
# .vscode/
|
|
221
|
+
# Temporary file for partial code execution
|
|
222
|
+
tempCodeRunnerFile.py
|
|
223
|
+
|
|
224
|
+
# Ruff stuff:
|
|
225
|
+
.ruff_cache/
|
|
226
|
+
|
|
227
|
+
# PyPI configuration file
|
|
228
|
+
.pypirc
|
|
229
|
+
|
|
230
|
+
# Marimo
|
|
231
|
+
marimo/_static/
|
|
232
|
+
marimo/_lsp/
|
|
233
|
+
__marimo__/
|
|
234
|
+
|
|
235
|
+
# Streamlit
|
|
236
|
+
.streamlit/secrets.toml
|
|
237
|
+
|
|
238
|
+
# SQLite database files (may appear anywhere in the project)
|
|
239
|
+
*.db
|
|
240
|
+
*.db-shm
|
|
241
|
+
*.db-wal
|
|
242
|
+
|
|
243
|
+
# local DB backups (never committed)
|
|
244
|
+
slife.db.bak*
|
cc_switch-0.9.6/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: cc-switch
|
|
3
|
+
Version: 0.9.6
|
|
4
|
+
Summary: Generate ~/.claude/settings.json from saved provider/model configs
|
|
5
|
+
Project-URL: Homepage, https://github.com/juzcn/slife
|
|
6
|
+
Project-URL: Repository, https://github.com/juzcn/slife
|
|
7
|
+
Author-email: juzcn <zhangjun@cueb.edu.cn>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Terminals
|
|
16
|
+
Requires-Python: >=3.13
|
|
17
|
+
Requires-Dist: credstore
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# cc-switch
|
|
21
|
+
|
|
22
|
+
Generate `~/.claude/settings.json` from saved provider/model configs —
|
|
23
|
+
a small CLI that mirrors the [credstore](../credstore/README.md) pattern.
|
|
24
|
+
|
|
25
|
+
Non-secret provider *shape* lives in `~/.claude/cc-switch.json`. API
|
|
26
|
+
keys are **never** stored there — the config keeps the key's *name*,
|
|
27
|
+
and the value is read from credstore at activate time and injected into
|
|
28
|
+
the current process environment as `ANTHROPIC_AUTH_TOKEN`. The
|
|
29
|
+
generated `settings.json` contains no credential line.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
cc-switch is a standalone PyPI package — installed independently of
|
|
34
|
+
slife (installing slife does **not** bring cc-switch, and vice versa).
|
|
35
|
+
Both depend on [credstore](../credstore/README.md), which is pulled in
|
|
36
|
+
automatically.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install cc-switch
|
|
40
|
+
# or, in this repo:
|
|
41
|
+
uv sync
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
### `cc-switch set <provider-name>`
|
|
47
|
+
|
|
48
|
+
Create or edit a provider. Prompts for:
|
|
49
|
+
|
|
50
|
+
- **Base URL** (required)
|
|
51
|
+
- **API key name** — the credstore key holding the secret (required)
|
|
52
|
+
- **Supported models** (optional; comma, space, or semicolon separated)
|
|
53
|
+
|
|
54
|
+
If the provider already exists this *edits* it; otherwise it *adds* it.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cc-switch set deepseek
|
|
58
|
+
# Base URL [..]: https://api.deepseek.com/anthropic
|
|
59
|
+
# API key name (credstore key): DEEPSEEK_API_KEY
|
|
60
|
+
# Supported models (comma separated): deepseek-chat,deepseek-reasoner
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
No secret value is ever asked for or written — store it first with:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
credstore set DEEPSEEK_API_KEY
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `cc-switch activate <provider-name/model-name>`
|
|
70
|
+
|
|
71
|
+
Writes `~/.claude/settings.json` with stock Claude Code defaults, and
|
|
72
|
+
injects the API key from credstore into the system environment as
|
|
73
|
+
`ANTHROPIC_AUTH_TOKEN` (mirroring `credstore inject` — registry on
|
|
74
|
+
Windows, shell profile on Unix), so a freshly launched Claude Code
|
|
75
|
+
session inherits it. The settings file contains **no** credential.
|
|
76
|
+
|
|
77
|
+
If the provider's API key is not in credstore, `activate` fails loudly
|
|
78
|
+
with a `credstore set <key>` hint instead of writing an unusable setup.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cc-switch activate deepseek/deepseek-chat
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If the model is omitted and the provider has exactly one model, it is
|
|
85
|
+
used; if several, you are prompted to pick one.
|
|
86
|
+
|
|
87
|
+
### `cc-switch activate <provider-name/model-name> --custom`
|
|
88
|
+
|
|
89
|
+
Like `activate`, but lets you override every model slot first —
|
|
90
|
+
`ANTHROPIC_DEFAULT_HAIKU_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`,
|
|
91
|
+
`ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_CLAUDE_CODE_SUBAGENT_MODEL`,
|
|
92
|
+
`ANTHROPIC_MODEL`, `ANTHROPIC_CLAUDE_CODE_EFFORT_LEVEL` — one at a time.
|
|
93
|
+
Enter a value to override, or Enter to keep the default. Overrides are
|
|
94
|
+
one-shot: they never touch the stored provider config.
|
|
95
|
+
|
|
96
|
+
### `cc-switch list`
|
|
97
|
+
|
|
98
|
+
Lists every configured provider/model pair, one per line:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
deepseek/deepseek-chat
|
|
102
|
+
deepseek/deepseek-reasoner
|
|
103
|
+
scnet/scnet-1m
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `cc-switch list-providers`
|
|
107
|
+
|
|
108
|
+
Shows provider metadata (base URL, API key name, models) without the
|
|
109
|
+
model-per-line layout of `list`.
|
|
110
|
+
|
|
111
|
+
### `cc-switch remove <provider-name>`
|
|
112
|
+
|
|
113
|
+
Deletes a provider config. Does not touch credstore or settings.json.
|
|
114
|
+
|
|
115
|
+
## Files
|
|
116
|
+
|
|
117
|
+
| Path | Purpose |
|
|
118
|
+
|------|---------|
|
|
119
|
+
| `~/.claude/cc-switch.json` | Provider/model shapes (no secrets) |
|
|
120
|
+
| `~/.claude/settings.json` | Generated by `activate` |
|
|
121
|
+
| credstore (`DEEPSEEK_API_KEY`, …) | The actual API key values |
|
|
122
|
+
|
|
123
|
+
`CC_SWITCH_FILE` overrides the config path; the settings path can be
|
|
124
|
+
overridden for tests.
|
|
125
|
+
|
|
126
|
+
## Security notes
|
|
127
|
+
|
|
128
|
+
- The config file holds only metadata — the API key is referenced by name.
|
|
129
|
+
- `activate` reads the secret from credstore and injects it into the
|
|
130
|
+
system environment as `ANTHROPIC_AUTH_TOKEN` (registry on Windows /
|
|
131
|
+
shell profile on Unix), mirroring `credstore inject`; it is never
|
|
132
|
+
written to settings.json.
|
|
133
|
+
- On a TTY, `activate` prints an activation hint without echoing the
|
|
134
|
+
secret; when stdout is piped it emits the shell export line for `eval`.
|
|
135
|
+
- Restart your shell (or start a new terminal) after activating for the
|
|
136
|
+
env change to take effect in the current session.
|
|
137
|
+
- Secrets are immutable Python `str` — cc-switch follows credstore's
|
|
138
|
+
practice of `del`-ing the reference immediately after use.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# cc-switch
|
|
2
|
+
|
|
3
|
+
Generate `~/.claude/settings.json` from saved provider/model configs —
|
|
4
|
+
a small CLI that mirrors the [credstore](../credstore/README.md) pattern.
|
|
5
|
+
|
|
6
|
+
Non-secret provider *shape* lives in `~/.claude/cc-switch.json`. API
|
|
7
|
+
keys are **never** stored there — the config keeps the key's *name*,
|
|
8
|
+
and the value is read from credstore at activate time and injected into
|
|
9
|
+
the current process environment as `ANTHROPIC_AUTH_TOKEN`. The
|
|
10
|
+
generated `settings.json` contains no credential line.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
cc-switch is a standalone PyPI package — installed independently of
|
|
15
|
+
slife (installing slife does **not** bring cc-switch, and vice versa).
|
|
16
|
+
Both depend on [credstore](../credstore/README.md), which is pulled in
|
|
17
|
+
automatically.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv tool install cc-switch
|
|
21
|
+
# or, in this repo:
|
|
22
|
+
uv sync
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
### `cc-switch set <provider-name>`
|
|
28
|
+
|
|
29
|
+
Create or edit a provider. Prompts for:
|
|
30
|
+
|
|
31
|
+
- **Base URL** (required)
|
|
32
|
+
- **API key name** — the credstore key holding the secret (required)
|
|
33
|
+
- **Supported models** (optional; comma, space, or semicolon separated)
|
|
34
|
+
|
|
35
|
+
If the provider already exists this *edits* it; otherwise it *adds* it.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cc-switch set deepseek
|
|
39
|
+
# Base URL [..]: https://api.deepseek.com/anthropic
|
|
40
|
+
# API key name (credstore key): DEEPSEEK_API_KEY
|
|
41
|
+
# Supported models (comma separated): deepseek-chat,deepseek-reasoner
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
No secret value is ever asked for or written — store it first with:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
credstore set DEEPSEEK_API_KEY
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `cc-switch activate <provider-name/model-name>`
|
|
51
|
+
|
|
52
|
+
Writes `~/.claude/settings.json` with stock Claude Code defaults, and
|
|
53
|
+
injects the API key from credstore into the system environment as
|
|
54
|
+
`ANTHROPIC_AUTH_TOKEN` (mirroring `credstore inject` — registry on
|
|
55
|
+
Windows, shell profile on Unix), so a freshly launched Claude Code
|
|
56
|
+
session inherits it. The settings file contains **no** credential.
|
|
57
|
+
|
|
58
|
+
If the provider's API key is not in credstore, `activate` fails loudly
|
|
59
|
+
with a `credstore set <key>` hint instead of writing an unusable setup.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cc-switch activate deepseek/deepseek-chat
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If the model is omitted and the provider has exactly one model, it is
|
|
66
|
+
used; if several, you are prompted to pick one.
|
|
67
|
+
|
|
68
|
+
### `cc-switch activate <provider-name/model-name> --custom`
|
|
69
|
+
|
|
70
|
+
Like `activate`, but lets you override every model slot first —
|
|
71
|
+
`ANTHROPIC_DEFAULT_HAIKU_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`,
|
|
72
|
+
`ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_CLAUDE_CODE_SUBAGENT_MODEL`,
|
|
73
|
+
`ANTHROPIC_MODEL`, `ANTHROPIC_CLAUDE_CODE_EFFORT_LEVEL` — one at a time.
|
|
74
|
+
Enter a value to override, or Enter to keep the default. Overrides are
|
|
75
|
+
one-shot: they never touch the stored provider config.
|
|
76
|
+
|
|
77
|
+
### `cc-switch list`
|
|
78
|
+
|
|
79
|
+
Lists every configured provider/model pair, one per line:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
deepseek/deepseek-chat
|
|
83
|
+
deepseek/deepseek-reasoner
|
|
84
|
+
scnet/scnet-1m
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `cc-switch list-providers`
|
|
88
|
+
|
|
89
|
+
Shows provider metadata (base URL, API key name, models) without the
|
|
90
|
+
model-per-line layout of `list`.
|
|
91
|
+
|
|
92
|
+
### `cc-switch remove <provider-name>`
|
|
93
|
+
|
|
94
|
+
Deletes a provider config. Does not touch credstore or settings.json.
|
|
95
|
+
|
|
96
|
+
## Files
|
|
97
|
+
|
|
98
|
+
| Path | Purpose |
|
|
99
|
+
|------|---------|
|
|
100
|
+
| `~/.claude/cc-switch.json` | Provider/model shapes (no secrets) |
|
|
101
|
+
| `~/.claude/settings.json` | Generated by `activate` |
|
|
102
|
+
| credstore (`DEEPSEEK_API_KEY`, …) | The actual API key values |
|
|
103
|
+
|
|
104
|
+
`CC_SWITCH_FILE` overrides the config path; the settings path can be
|
|
105
|
+
overridden for tests.
|
|
106
|
+
|
|
107
|
+
## Security notes
|
|
108
|
+
|
|
109
|
+
- The config file holds only metadata — the API key is referenced by name.
|
|
110
|
+
- `activate` reads the secret from credstore and injects it into the
|
|
111
|
+
system environment as `ANTHROPIC_AUTH_TOKEN` (registry on Windows /
|
|
112
|
+
shell profile on Unix), mirroring `credstore inject`; it is never
|
|
113
|
+
written to settings.json.
|
|
114
|
+
- On a TTY, `activate` prints an activation hint without echoing the
|
|
115
|
+
secret; when stdout is piped it emits the shell export line for `eval`.
|
|
116
|
+
- Restart your shell (or start a new terminal) after activating for the
|
|
117
|
+
env change to take effect in the current session.
|
|
118
|
+
- Secrets are immutable Python `str` — cc-switch follows credstore's
|
|
119
|
+
practice of `del`-ing the reference immediately after use.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""cc-switch — generate ~/.claude/settings.json from saved provider/model configs.
|
|
2
|
+
|
|
3
|
+
A standalone CLI (mirroring the credstore pattern) that keeps the
|
|
4
|
+
non-secret *shape* of a Claude Code provider setup in
|
|
5
|
+
``~/.claude/cc-switch.json`` and materialises it into
|
|
6
|
+
``~/.claude/settings.json``. Secrets never touch settings.json — the
|
|
7
|
+
API key is read from credstore at activate time and injected only into
|
|
8
|
+
the system environment as ``ANTHROPIC_AUTH_TOKEN``.
|
|
9
|
+
|
|
10
|
+
Modules::
|
|
11
|
+
|
|
12
|
+
cc_switch Package API (get/save/remove/list/activate)
|
|
13
|
+
cc_switch.cli Command-line interface (entry point ``cc-switch``)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from cc_switch._api import (
|
|
17
|
+
CONFIG_PATH,
|
|
18
|
+
add_provider,
|
|
19
|
+
list_providers,
|
|
20
|
+
remove_provider,
|
|
21
|
+
load_config,
|
|
22
|
+
save_config,
|
|
23
|
+
update_provider,
|
|
24
|
+
)
|
|
25
|
+
from cc_switch._defaults import (
|
|
26
|
+
DEFAULT_ENV,
|
|
27
|
+
DEFAULT_OVERRIDE_KEYS,
|
|
28
|
+
DEFAULT_SETTINGS,
|
|
29
|
+
MAIN_MODEL_SLOT_KEYS,
|
|
30
|
+
default_value,
|
|
31
|
+
list_default_override_keys,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__version__ = "0.9.6"
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"CONFIG_PATH",
|
|
38
|
+
"DEFAULT_ENV",
|
|
39
|
+
"DEFAULT_OVERRIDE_KEYS",
|
|
40
|
+
"DEFAULT_SETTINGS",
|
|
41
|
+
"MAIN_MODEL_SLOT_KEYS",
|
|
42
|
+
"add_provider",
|
|
43
|
+
"default_value",
|
|
44
|
+
"list_default_override_keys",
|
|
45
|
+
"list_providers",
|
|
46
|
+
"load_config",
|
|
47
|
+
"remove_provider",
|
|
48
|
+
"save_config",
|
|
49
|
+
"update_provider",
|
|
50
|
+
]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Activation — materialise a provider/model into ``~/.claude/settings.json``.
|
|
2
|
+
|
|
3
|
+
Plain ``activate`` fills every model slot with the stock Claude Code
|
|
4
|
+
defaults from :mod:`cc_switch._defaults`; ``--custom`` lets the user
|
|
5
|
+
override each env key interactively before writing.
|
|
6
|
+
|
|
7
|
+
The secret is read from credstore (by the *api_key_name* referenced in
|
|
8
|
+
the provider config) and injected into the **system environment** as
|
|
9
|
+
``ANTHROPIC_AUTH_TOKEN`` — mirroring ``credstore inject`` (registry on
|
|
10
|
+
Windows, shell profile on Unix). The generated settings.json never
|
|
11
|
+
contains a credential line, and the env injection survives the transient
|
|
12
|
+
cc-switch process so a new Claude Code session picks it up.
|
|
13
|
+
|
|
14
|
+
If the secret is missing from credstore, activation fails loudly instead
|
|
15
|
+
of writing an unusable settings.json.
|
|
16
|
+
|
|
17
|
+
Memory safety: the keyring value is fetched, used, then ``del``-ed
|
|
18
|
+
immediately.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
from cc_switch import _defaults
|
|
27
|
+
|
|
28
|
+
# Output path — overridable for tests via monkeypatch.
|
|
29
|
+
SETTINGS_PATH = os.path.expanduser("~/.claude/settings.json")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SecretNotFoundError(RuntimeError):
|
|
33
|
+
"""Raised when a provider's api_key_name is not stored in credstore."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resolve_secret(api_key_name: str) -> str | None:
|
|
37
|
+
"""Read *api_key_name* from credstore (system keyring).
|
|
38
|
+
|
|
39
|
+
Returns the secret, or None when the key is not stored or credstore
|
|
40
|
+
is unavailable. The caller must ``del`` the returned value after
|
|
41
|
+
use. This is the only place cc-switch touches secret material.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
from credstore import get_credential
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
return get_credential(api_key_name)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_env(provider: dict, model_name: str, overrides: dict | None = None) -> dict:
|
|
51
|
+
"""Build the env block for settings.json.
|
|
52
|
+
|
|
53
|
+
*overrides* maps env key → value and is used by ``--custom``; a
|
|
54
|
+
missing key falls back to ``default_value`` — the main-model slots
|
|
55
|
+
inherit *model_name* (never empty), the rest keep their static
|
|
56
|
+
default. Always + ``ANTHROPIC_BASE_URL`` + model — never a secret.
|
|
57
|
+
"""
|
|
58
|
+
env: dict[str, str] = {}
|
|
59
|
+
# 1. Every override slot (with the override value where provided)
|
|
60
|
+
for key in _defaults.DEFAULT_OVERRIDE_KEYS:
|
|
61
|
+
if overrides and key in overrides:
|
|
62
|
+
env[key] = str(overrides[key])
|
|
63
|
+
else:
|
|
64
|
+
env[key] = _defaults.default_value(key, model_name)
|
|
65
|
+
# 2. Provider-specific env overrides (applied after the slots)
|
|
66
|
+
env.update(provider.get("extra_env") or {})
|
|
67
|
+
# 3. Provider base URL + chosen model
|
|
68
|
+
env["ANTHROPIC_BASE_URL"] = provider["base_url"]
|
|
69
|
+
env["ANTHROPIC_MODEL"] = model_name
|
|
70
|
+
return env
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_settings(provider: dict, model_name: str, overrides: dict | None = None) -> dict:
|
|
74
|
+
"""Return the full settings.json dict (no secret material)."""
|
|
75
|
+
settings = dict(_defaults.DEFAULT_SETTINGS)
|
|
76
|
+
settings["env"] = build_env(provider, model_name, overrides)
|
|
77
|
+
return settings
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def write_settings(settings: dict) -> None:
|
|
81
|
+
"""Write the settings dict to ``~/.claude/settings.json`` (overrides path)."""
|
|
82
|
+
import json
|
|
83
|
+
|
|
84
|
+
target = SETTINGS_PATH
|
|
85
|
+
parent = os.path.dirname(target)
|
|
86
|
+
if parent:
|
|
87
|
+
os.makedirs(parent, exist_ok=True)
|
|
88
|
+
with open(target, "w", encoding="utf-8") as fh:
|
|
89
|
+
json.dump(settings, fh, ensure_ascii=False, indent=2)
|
|
90
|
+
fh.write("\n")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def inject_token(secret: str, shell: str = "auto", output=None) -> None:
|
|
94
|
+
"""Persist *secret* as ``ANTHROPIC_AUTH_TOKEN`` in the system environment.
|
|
95
|
+
|
|
96
|
+
Mirrors ``credstore inject``: writes the value to the system env via
|
|
97
|
+
``persist_key`` (registry on Windows, shell profile on Unix) so a
|
|
98
|
+
freshly launched Claude Code session inherits it. When *output* is
|
|
99
|
+
a TTY it prints an activation hint (no secret); otherwise it prints
|
|
100
|
+
the export line for the current shell.
|
|
101
|
+
"""
|
|
102
|
+
from credstore._shell import format_export, persist_key
|
|
103
|
+
|
|
104
|
+
persist_key("ANTHROPIC_AUTH_TOKEN", secret, shell)
|
|
105
|
+
|
|
106
|
+
out = output if output is not None else sys.stdout
|
|
107
|
+
if out.isatty():
|
|
108
|
+
print(
|
|
109
|
+
"Injected ANTHROPIC_AUTH_TOKEN into the system environment.",
|
|
110
|
+
file=out,
|
|
111
|
+
)
|
|
112
|
+
print(
|
|
113
|
+
"Restart your shell (or start a new terminal) for it to take effect.",
|
|
114
|
+
file=out,
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
print(format_export("ANTHROPIC_AUTH_TOKEN", secret, shell), file=out)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def activate(provider: dict, model_name: str, overrides: dict | None = None,
|
|
121
|
+
shell: str = "auto", output=None) -> dict:
|
|
122
|
+
"""Generate, write settings.json, and inject the secret into the system env.
|
|
123
|
+
|
|
124
|
+
Writes settings.json first (so the config shape is in place even if
|
|
125
|
+
the token lookup then fails), then resolves the API key from
|
|
126
|
+
credstore. A missing key raises :class:`SecretNotFoundError` —
|
|
127
|
+
never a silent skip. Returns the settings dict that was written.
|
|
128
|
+
"""
|
|
129
|
+
settings = build_settings(provider, model_name, overrides)
|
|
130
|
+
write_settings(settings)
|
|
131
|
+
|
|
132
|
+
secret = resolve_secret(provider["api_key_name"])
|
|
133
|
+
if secret is None:
|
|
134
|
+
raise SecretNotFoundError(
|
|
135
|
+
f"'{provider['api_key_name']}' is not in credstore.\n"
|
|
136
|
+
f"Store it first: credstore set {provider['api_key_name']}"
|
|
137
|
+
)
|
|
138
|
+
try:
|
|
139
|
+
inject_token(secret, shell=shell, output=output)
|
|
140
|
+
finally:
|
|
141
|
+
del secret
|
|
142
|
+
return settings
|