One-time setup for the cc-proxy plugin. Writes API keys (all optional — GLM_API_KEY, OPENROUTER_API_KEY, DEEPSEEK_API_KEY, DASHSCOPE_API_KEY, LMSTUDIO_BASE_URL, LMSTUDIO_API_KEY) to ~/.env, and configures ANTHROPIC_BASE_URL and the glm-5.3[1m] custom model option in ~/.claude/settings.json so the SessionStart hook can auto-start the proxy and /model can route to GLM. Invoke via /cc-proxy:setup.
Scanned 9/4/2026
Install to Claude Code
npx -y skills add betmoar/cc-proxy-plugin --skill setup --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Setup?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/betmoar-setup)More formats (shields.io, HTML) on the badges page.
---
name: setup
description: One-time setup for the cc-proxy plugin. Writes API keys (all optional — GLM_API_KEY, OPENROUTER_API_KEY, DEEPSEEK_API_KEY, DASHSCOPE_API_KEY, LMSTUDIO_BASE_URL, LMSTUDIO_API_KEY) to ~/.env, and configures ANTHROPIC_BASE_URL and the glm-5.3[1m] custom model option in ~/.claude/settings.json so the SessionStart hook can auto-start the proxy and /model can route to GLM. Invoke via /cc-proxy:setup.
---
# cc-proxy setup
One-time configuration of `~/.claude/settings.json` so the proxy runs automatically on every Claude Code session.
The proxy binary needs **no configuration**: the SessionStart hook and `scripts/start-proxy.js` resolve `bin/cc-proxy.js` from their own plugin tree, which is always the currently-installed version. Do **not** write `PROXY_PATH` into settings.json — a version-pinned path there is exactly how users used to get stuck on stale proxies after plugin updates. If a `PROXY_PATH` already exists in settings.json `env`, **remove it** during step 3 (it is a legacy pin; the tree's own bin outranks it anyway).
## What to do
Follow these steps **exactly**. Do not skip any.
### 1. Locate the plugin tree (for the statusline path only)
Check these locations in order and use the first one that exists:
1. `~/.claude/plugins/cache/betmoar/cc-proxy/*/` (marketplace install — the normal case; glob the `*` version segment and take the newest if there is more than one)
2. `~/dev/cc-proxy-plugin/` (dev-repo fallback, if the user cloned source)
This concrete path is needed **only** for the optional statusline command in step 4 (which runs outside plugin context). It is *not* written as `PROXY_PATH`.
### 2. Collect provider API keys (written to `~/.env`)
API keys live in `~/.env` — the single source of truth the proxy reads at startup. Do **not** put keys in `~/.claude/settings.json` `env`; it keeps only non-secret plumbing (step 3).
Read `~/.env` first (create the file if absent). For each key, reuse a value already present rather than re-asking.
**Z.ai / GLM — optional, but the one this plugin is built around.** It is the model wired into the `/model` picker, so without it that picker entry routes nowhere useful. If `GLM_API_KEY` is missing or empty in `~/.env`, **ask explicitly**:
> "Enter your Z.ai API key (https://z.ai → Dashboard → API Keys), or press Enter to skip. It will be stored in ~/.env:"
If the user skips, continue setup — do not stop and do not re-ask. The proxy
starts without it and routes to Claude; a backend with no key is simply not
registered. Say once that `glm-5.3[1m]` will appear in `/model` but won't route
until a key is added, then move on.
**OpenRouter — optional.** Ask the user whether they also want OpenRouter routing. If yes and `OPENROUTER_API_KEY` is missing or empty in `~/.env`, ask:
> "Enter your OpenRouter API key (https://openrouter.ai/settings/keys). It will be stored in ~/.env:"
**DeepSeek — optional.** Ask the user whether they also want DeepSeek routing. If yes and `DEEPSEEK_API_KEY` is missing or empty in `~/.env`, ask:
> "Enter your DeepSeek API key (https://platform.deepseek.com/api_keys). It will be stored in ~/.env:"
**Qwen — optional.** Ask the user whether they also want Qwen (QwenCloud Token Plan) routing. If yes and `DASHSCOPE_API_KEY` is missing or empty in `~/.env`, ask:
> "Enter your QwenCloud Token Plan API key (ANTHROPIC_AUTH_TOKEN from the QwenCloud console). It will be stored in ~/.env:"
**LM Studio — optional (self-hosted).** Ask the user whether they also run an LM Studio server they want reachable. If yes and `LMSTUDIO_BASE_URL` is missing or empty in `~/.env`, ask:
> "Enter your LM Studio server base URL, INCLUDING the http:// scheme (e.g. http://192.168.1.50:1234 — LM Studio's own UI shows the address without it, and a scheme-less value is refused. The server must also have 'Serve on Local Network' enabled to be reachable from another machine). It will be stored in ~/.env:"
If the user's server has "Require Authentication" enabled and `LMSTUDIO_API_KEY` is missing or empty, also ask:
> "Enter your LM Studio API key. It will be stored in ~/.env:"
(Auth-off servers need no key; a dummy token is sent and ignored. `LMSTUDIO_BASE_URL` is the opt-in — the key alone registers nothing.)
Write each collected key to `~/.env` as a `KEY=value` line, one per line (e.g. `GLM_API_KEY=<value>`). If `~/.env` already exists, **merge** — update only the key lines you collected and preserve every other line unchanged. If it does not exist, create it with just the key line(s).
The proxy only registers OpenRouter when `OPENROUTER_API_KEY` is set, and routes any model id containing a slash to it (e.g. `z-ai/glm-4.7`, `deepseek/deepseek-v4-pro` — never an `anthropic/…` id: that routes Claude traffic through a metered reseller, which invariants 3 and 4 exist to prevent, and discovery deliberately omits those ids). It only registers DeepSeek when `DEEPSEEK_API_KEY` is set, and routes any bare `deepseek-*` id to it (e.g. `deepseek-v4-pro`, `deepseek-v4-flash`). It only registers Qwen when `DASHSCOPE_API_KEY` is set, and routes any bare `qwen`-prefixed id to it (e.g. `qwen3.7-max`, `qwen3.6-flash`). It only registers LM Studio when `LMSTUDIO_BASE_URL` is set, and routes **only** explicit `lmstudio:<model-id>` selectors to it — no bare id routes there by shape (local model names would collide with the GLM/Qwen/OpenRouter predicates above); `DEFAULT_BACKEND=lmstudio` additionally makes it the unmatched-id fallback, which is an explicit user choice like `=openrouter`. **Tell the user this constraint:** Claude Code allows only **one** custom `/model` picker entry, and GLM uses it — so OpenRouter, DeepSeek, Qwen, and LM Studio models do **not** appear in the `/model` picker. They are reached only by (a) setting `DEFAULT_BACKEND=openrouter` (or `deepseek`, `qwen`, `lmstudio`) so unmatched requests fall through to it, (b) a subagent/slash-command whose frontmatter pins the model id (which the proxy then routes verbatim), or — for LM Studio only — (c) any `/model lmstudio:<model-id>`-style selector, since bare local ids cannot route by shape anyway.
**Migrate existing keys (one source of truth).** Read `~/.claude/settings.json`. If its `env` block contains `GLM_API_KEY`, `OPENROUTER_API_KEY`, `DEEPSEEK_API_KEY`, `DASHSCOPE_API_KEY`, `LMSTUDIO_BASE_URL`, or `LMSTUDIO_API_KEY` (legacy setups), move them to `~/.env`: if `~/.env` already has the key, keep the `~/.env` value and just drop the settings.json copy; otherwise copy the value over then **remove** the key from settings.json `env`. After setup, keys must exist **only** in `~/.env`.
### 3. Update `~/.claude/settings.json` (plumbing only — no keys)
Read the current file, then merge the following into the `env` object (create `env` if missing). Preserve every other existing key unchanged, **except `PROXY_PATH`: delete it if present** (legacy version-pinned path; the hook resolves the binary from its own tree now). **Do not add `GLM_API_KEY`, `OPENROUTER_API_KEY`, `DEEPSEEK_API_KEY`, `DASHSCOPE_API_KEY`, `LMSTUDIO_BASE_URL`, or `LMSTUDIO_API_KEY` here** — they go in `~/.env` (step 2).
```json
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:4000"
}
}
```
**Then, ONLY if a `GLM_API_KEY` was collected in step 2**, also merge the picker
entry:
```json
{
"env": {
"ANTHROPIC_CUSTOM_MODEL_OPTION": "glm-5.3[1m]",
"ANTHROPIC_CUSTOM_MODEL_OPTION_NAME": "GLM-5.3 (1M)",
"ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION": "Z.ai GLM-5.3 1M-context (routed via cc-proxy)"
}
}
```
This registers `glm-5.3[1m]` in the `/model` picker (Claude Code allows exactly
one custom model option). If `ANTHROPIC_CUSTOM_MODEL_OPTION` is already set to a
different value, ask the user before overwriting it.
**Why the condition.** The GLM key is skippable (issue #20), and the picker slot
holds exactly one entry. Writing `glm-5.3[1m]` for a user who skipped it puts a
model in their picker that cannot route — it resolves to the default backend
instead, and the only warning was spoken once during setup and never persisted,
so weeks later the entry fails with nothing on disk explaining why. If the user
skipped GLM, say plainly that the picker entry was skipped too, and that adding
`GLM_API_KEY` to `~/.env` and re-running `/cc-proxy:setup` will add it.
Write the file back with 2-space indentation, matching the existing formatting.
**PROXY_AUTH_TOKEN — only if the user says the proxy must be reachable off this
machine** (e.g. another device on the LAN, `PROXY_HOST=0.0.0.0`). Ask:
> "You want the proxy reachable from other machines. It injects API keys, so it needs a shared token. Enter one (any long random string), or press Enter to skip and keep loopback-only:"
If given: write `PROXY_AUTH_TOKEN=<value>` to `~/.env` **and** merge
`"ANTHROPIC_AUTH_TOKEN": "<same value>"` into settings.json `env` (that is how
Claude Code presents it to the proxy). Also warn, once, in plain words: with the
token in place, Claude Code's single credential slot is the proxy token, so
`claude-*` model requests no longer carry OAuth — this mode is for GLM/OpenRouter/
DeepSeek/Qwen/LM Studio routing. If the user did not ask for off-host access, do
NOT configure any of this; the loopback default needs no token.
### 4. Optional: enable the statusline
Ask the user whether they want the quota/credits statusline. It shows Claude 5-hour usage, GLM coding quota, OpenRouter credits (when `OPENROUTER_API_KEY` is set), DeepSeek balance (when `DEEPSEEK_API_KEY` is set), Qwen presence (when `DASHSCOPE_API_KEY` is set), and a bold-red `proxy down` when the local proxy is unreachable.
If yes, merge this **top-level** key into `~/.claude/settings.json` (it is *not* under `env`):
```json
{
"statusLine": {
"type": "command",
"command": "node <PROXY_DIR>/scripts/statusline.js"
}
}
```
`<PROXY_DIR>` is the plugin tree located in step 1 (e.g. `~/.claude/plugins/cache/betmoar/cc-proxy/<version>`). The statusline command runs outside plugin context, so `${CLAUDE_PLUGIN_ROOT}` is unavailable — an absolute path is required here, and it *is* version-pinned (a statusline pointing at an older cache dir still renders; it does not affect which proxy runs). If the user already has a `statusLine` configured, show them the command and let them decide rather than overwriting it.
### 5. Start the proxy now
Spawn the proxy so it is already up when `ANTHROPIC_BASE_URL` takes effect, eliminating the first-run `ECONNREFUSED`. Run, verbatim:
```
node "$CLAUDE_PLUGIN_ROOT/scripts/start-proxy.js"
```
`scripts/start-proxy.js` reuses the SessionStart hook's `ensureProxyRunning()`: it probes `PROXY_PORT` first (idempotent — a same-version proxy is left running; a stale-version one is gracefully replaced), then spawns its own tree's `bin/cc-proxy.js` detached + `unref`'d so it survives this turn. It reads the `env` block you just wrote to `~/.claude/settings.json` and passes it to the spawn, because the proxy reads config from env (not settings.json) and nothing has injected those vars into this process yet on a first-run setup.
Interpret the script's stdout/stderr:
- `cc-proxy already up`, `cc-proxy started`, or `cc-proxy restarted` → success. Proceed to step 6.
- `PROXY_PATH is unset` → the plugin tree has no `bin/cc-proxy.js` (hand-rolled install) and no legacy `PROXY_PATH` exists. Ask the user where `cc-proxy.js` is and put that absolute path in settings.json `env` as `PROXY_PATH` — the one case where it is still legitimate.
- `did not become reachable in time` → spawn fired but readiness timed out. Treat as a fallback: keep `/exit` + `/resume` as the path to recovery (step 6 covers this). Show the user the `~/.claude/cc-proxy/cc-proxy.log` tail if they ask.
### 6. Inform the user
Tell the user, verbatim:
> Setup complete. The proxy is running (step 5 started it). Claude Code re-applies `ANTHROPIC_BASE_URL` to running sessions immediately, so any open `claude` may still fail until it re-reads env — `/exit` and `/resume` any open session if you hit an error.
>
> To confirm, check `~/.claude/cc-proxy/cc-proxy.log` after your next prompt — you should see routing lines like `claude-sonnet-4-6 -> claude` or `glm-5.3 -> glm`.
## Important constraints
- **Do not** overwrite unrelated keys in `settings.json` or unrelated lines in `~/.env`. Use a merge strategy for both, not a full rewrite from template.
- **Do not** commit the user's API key anywhere. API keys stay only in `~/.env` (it is gitignored). They must **not** appear in `~/.claude/settings.json`.
- **Do not** start the proxy by hand with `node bin/cc-proxy.js` or similar — use `scripts/start-proxy.js` (step 5), which is idempotent and passes settings.json's plumbing env to the spawn. Raw starts risk duplicate proxies on the port.
- If `~/.claude/settings.json` does not exist, create it with just the `env` block above (and valid JSON structure).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!