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.
@@ -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*
@@ -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,6 @@
1
+ """cc-switch CLI entry point (``python -m cc_switch``)."""
2
+
3
+ from cc_switch.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -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