@cc-x/cc-x 0.4.7 → 0.4.9

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.
package/README.en.md CHANGED
@@ -29,7 +29,7 @@ different path: **switching happens purely at the environment-variable layer.**
29
29
  reads or writes any Claude Code config file. Your MCP, plugins, hooks — it won't touch them.
30
30
 
31
31
  ```text
32
- CC-X v0.4.7 · Claude Code API switcher Default: Official
32
+ CC-X v0.4.8 · Claude Code API switcher Default: Official
33
33
 
34
34
  ▶ Official (default)[Logged in]
35
35
  DeepSeek [Key set] — work
@@ -89,6 +89,19 @@ xx DeepSeek -s # Use this session, launch Claude now
89
89
  xx DeepSeek # Set as default for new terminals
90
90
  ```
91
91
 
92
+ ### Updating
93
+
94
+ Updating just means **re-running the install command** — the installer downloads the latest
95
+ release and overwrites the old binary in place, no uninstall needed. **Open a new terminal**
96
+ afterward; `xx --version` should show the new version.
97
+
98
+ - **Windows**: `irm https://github.com/becomeless/cc-x/releases/latest/download/install.ps1 | iex`
99
+ - **macOS / Linux**: `curl -fsSL https://github.com/becomeless/cc-x/releases/latest/download/install.sh | sh`
100
+ - **npm**: `npm i -g @cc-x/cc-x@latest`
101
+
102
+ > With the menu's "Update check" set to "notify", CC-X shows a banner at the top of the menu
103
+ > when a new version is out, with the matching upgrade command for your platform.
104
+
92
105
  ---
93
106
 
94
107
  ## 60-second quick start
@@ -165,7 +178,7 @@ cc-switch is an excellent full-featured GUI; CC-X takes the opposite, minimal ap
165
178
 
166
179
  > CC-X cares more about boundaries than features.
167
180
 
168
- Claude Code already has its own config system, MCP ecosystem, and session state. CC-X is not trying to become a control panel above it, or to copy user config into another database. It stands at one narrow point before Claude Code starts: prepare the 7 managed environment variables, then let Claude Code run.
181
+ Claude Code already has its own config system, MCP ecosystem, and session state. CC-X is not trying to become a control panel above it, or to copy user config into another database. It stands at one narrow point before Claude Code starts: prepare the 9 managed environment variables, then let Claude Code run.
169
182
 
170
183
  That constraint is deliberate: no writes to Claude Code config files, no MCP management, no automatic migration, no resident background controller. If process environment variables can solve it, CC-X avoids global files; if a choice matters, the user makes it explicitly. Doing less keeps the failure surface small.
171
184
 
@@ -186,6 +199,8 @@ Issues / PRs are welcome, but the direction is clear: **make switching steadier,
186
199
  | sonnet → model | `ANTHROPIC_DEFAULT_SONNET_MODEL` | |
187
200
  | haiku → model | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | |
188
201
  | effort level | `CLAUDE_CODE_EFFORT_LEVEL` | `low`–`max`; `auto` = model default; empty = unset. Third parties may not honor it |
202
+ | Disable nonessential traffic | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | `1` disables nonessential Claude Code traffic; empty = unset. Zhipu GLM defaults to `1` |
203
+ | Context window | `CLAUDE_CODE_AUTO_COMPACT_WINDOW` | Fill with a Claude Code-supported window value; empty = unset |
189
204
 
190
205
  > CC-X **deliberately does not set** `ANTHROPIC_MODEL`. Use `/model opus|sonnet|haiku` in-session;
191
206
  > the mapping table translates to the provider's real model name.
@@ -199,12 +214,12 @@ Issues / PRs are welcome, but the direction is clear: **make switching steadier,
199
214
 
200
215
  ### Pre-seeded profiles
201
216
 
202
- | Profile | BASE_URL | OPUS / SONNET | HAIKU (incl. background) | effort |
203
- |---|---|---|---|---|
204
- | Official | empty (logged-in) | — | — | — |
205
- | DeepSeek | `https://api.deepseek.com/anthropic` | `deepseek-v4-pro` | `deepseek-v4-flash` | `max` (recommended) |
206
- | Zhipu GLM | `https://open.bigmodel.cn/api/anthropic` | `GLM-4.7` | `glm-4.5-air` | — |
207
- | Xiaomi MiMo | `https://api.xiaomimimo.com/anthropic` | `mimo-v2.5-pro` | `mimo-v2.5-pro` | — |
217
+ | Profile | BASE_URL | OPUS / SONNET | HAIKU (incl. background) | effort | Extra env |
218
+ |---|---|---|---|---|---|
219
+ | Official | empty (logged-in) | — | — | — | — |
220
+ | DeepSeek | `https://api.deepseek.com/anthropic` | `deepseek-v4-pro` | `deepseek-v4-flash` | `max` (recommended) | — |
221
+ | Zhipu GLM | `https://open.bigmodel.cn/api/anthropic` | `GLM-4.7` | `glm-4.5-air` | — | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` |
222
+ | Xiaomi MiMo | `https://api.xiaomimimo.com/anthropic` | `mimo-v2.5-pro` | `mimo-v2.5-pro` | — | — |
208
223
 
209
224
  > Model names change as providers update. Xiaomi MiMo has both pay-as-you-go and TokenPlan
210
225
  > endpoints; you pick one when selecting the provider.
@@ -215,9 +230,9 @@ Issues / PRs are welcome, but the direction is clear: **make switching steadier,
215
230
  with ` 2`, ` 3`… Use **Note** to tell them apart, shown as "Provider — Note".
216
231
  - **Custom providers**: `presets.json` is the provider catalog; add a JSON entry to offer a new
217
232
  one, no code change. Drop `~/.cc-mini/presets.json` to override the shipped catalog.
218
- - **First-launch login prompt**: third-party APIs may still show onboarding. Add
219
- `"hasCompletedOnboarding": true` to `~/.claude.json` (**only this key** don't overwrite
220
- the file; it also holds your MCP config).
233
+ - **First-launch login prompt**: before launching a third-party profile, CC-X reads
234
+ `hasCompletedOnboarding` from `~/.claude.json` and prints a non-blocking hint when onboarding
235
+ is unfinished. It does not write the file; dismiss or skip the Claude Code prompt if it appears.
221
236
  - **Update check**: toggle to "notify" in the menu — a yellow one-liner appears atop the menu
222
237
  when a new release is out. At most one check per day; never auto-upgrades.
223
238
 
@@ -231,11 +246,13 @@ Issues / PRs are welcome, but the direction is clear: **make switching steadier,
231
246
  - Windows → registry `HKCU\Environment` + one change broadcast
232
247
  - Unix → `# >>> xx >>>` … `# <<< xx <<<` marker block in shell startup file (idempotent rewrite, chosen by `$SHELL`)
233
248
  - Same semantics either way: **only affects new terminals**; switching to "Official" clears all managed vars
234
- - **No Claude Code config file is ever modified.**
249
+ - **No Claude Code config file is ever modified.** Before third-party launches, CC-X only reads
250
+ the onboarding field in `~/.claude.json` to decide whether to print a hint.
235
251
 
236
- CC-X only touches these 7 "managed" variables (and clears the ones a target profile doesn't use):
252
+ CC-X only touches these 9 "managed" variables (and clears the ones a target profile doesn't use):
237
253
  `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_API_KEY`, `ANTHROPIC_DEFAULT_OPUS_MODEL`,
238
- `ANTHROPIC_DEFAULT_SONNET_MODEL`, `ANTHROPIC_DEFAULT_HAIKU_MODEL`, `CLAUDE_CODE_EFFORT_LEVEL`.
254
+ `ANTHROPIC_DEFAULT_SONNET_MODEL`, `ANTHROPIC_DEFAULT_HAIKU_MODEL`, `CLAUDE_CODE_EFFORT_LEVEL`,
255
+ `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`, `CLAUDE_CODE_AUTO_COMPACT_WINDOW`.
239
256
 
240
257
  > 💡 To change `settings.json`, use Claude Code's own `/update-config` and describe what you want
241
258
  > in natural language (e.g. "allow npm commands") — safer than letting an external tool rewrite it.
package/README.md CHANGED
@@ -28,7 +28,7 @@ CC-X 把这事儿做到了最简——切换只在环境变量层,**不读写
28
28
  你的 MCP、插件、hooks,它碰都不会碰。
29
29
 
30
30
  ```text
31
- CC-X v0.4.7 · Claude Code API 切换器 默认:官方
31
+ CC-X v0.4.8 · Claude Code API 切换器 默认:官方
32
32
 
33
33
  ▶ 官方 (默认)[登录态]
34
34
  DeepSeek [密钥已设] — 公司
@@ -87,6 +87,16 @@ xx DeepSeek -s # 本次启用,立即启动 Claude
87
87
  xx DeepSeek # 设为默认,以后新终端自动生效
88
88
  ```
89
89
 
90
+ ### 更新到新版本
91
+
92
+ 更新就是**重新跑一遍安装命令**——安装器会下载最新版覆盖旧的,不用先卸载。跑完**新开一个终端**,`xx --version` 即为新版本。
93
+
94
+ - **Windows**:`irm https://github.com/becomeless/cc-x/releases/latest/download/install.ps1 | iex`
95
+ - **macOS / Linux**:`curl -fsSL https://github.com/becomeless/cc-x/releases/latest/download/install.sh | sh`
96
+ - **npm**:`npm i -g @cc-x/cc-x@latest`
97
+
98
+ > 把菜单里的「更新检查」开到「提醒」后,有新版时 CC-X 会在菜单顶部横幅提示,并直接给出上面对应平台的升级命令。
99
+
90
100
  ---
91
101
 
92
102
  ## 60 秒上手
@@ -158,7 +168,7 @@ cc-switch 是优秀的全能 GUI;CC-X 走相反的极简路线。
158
168
 
159
169
  > CC-X 的边界比功能更重要。
160
170
 
161
- Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X 不想再造一个"上层控制台",也不想把用户的配置收编进自己的数据库。它只站在 Claude Code 进程启动前的那一小步:把 7 个受管环境变量准备好,然后让 Claude Code 自己工作。
171
+ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X 不想再造一个"上层控制台",也不想把用户的配置收编进自己的数据库。它只站在 Claude Code 进程启动前的那一小步:把 9 个受管环境变量准备好,然后让 Claude Code 自己工作。
162
172
 
163
173
  所以它的取舍是有意的:不写 Claude Code 配置文件,不接管 MCP,不做自动迁移,不做后台常驻管理。能用进程环境变量解决,就不碰全局文件;能让用户显式选择,就不替用户自动决定。少做一点,是为了把风险面压到足够小。
164
174
 
@@ -179,6 +189,8 @@ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X
179
189
  | sonnet → 模型 | `ANTHROPIC_DEFAULT_SONNET_MODEL` | |
180
190
  | haiku → 模型 | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | |
181
191
  | effort 思考档 | `CLAUDE_CODE_EFFORT_LEVEL` | `low` ~ `max`;`auto`=模型默认;留空=不设。第三方不一定生效 |
192
+ | 禁用非核心流量 | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | `1`=禁用 Claude Code 非核心流量;留空=不设。GLM 预置为 `1` |
193
+ | 上下文窗口大小 | `CLAUDE_CODE_AUTO_COMPACT_WINDOW` | 按 Claude Code 支持的窗口值填写;留空=不设 |
182
194
 
183
195
  > CC-X **刻意不设** `ANTHROPIC_MODEL`。在会话里用 `/model opus|sonnet|haiku` 选档,映射表负责翻译成对应供应商的模型名。
184
196
 
@@ -191,12 +203,12 @@ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X
191
203
 
192
204
  ### 预置配置
193
205
 
194
- | 配置 | BASE_URL | OPUS / SONNET | HAIKU(含后台任务) | effort |
195
- |---|---|---|---|---|
196
- | 官方 | 留空=登录态 | — | — | — |
197
- | DeepSeek | `https://api.deepseek.com/anthropic` | `deepseek-v4-pro` | `deepseek-v4-flash` | `max`(官方推荐) |
198
- | 智谱GLM | `https://open.bigmodel.cn/api/anthropic` | `GLM-4.7` | `glm-4.5-air` | — |
199
- | 小米MiMo | `https://api.xiaomimimo.com/anthropic` | `mimo-v2.5-pro` | `mimo-v2.5-pro` | — |
206
+ | 配置 | BASE_URL | OPUS / SONNET | HAIKU(含后台任务) | effort | 额外 env |
207
+ |---|---|---|---|---|---|
208
+ | 官方 | 留空=登录态 | — | — | — | — |
209
+ | DeepSeek | `https://api.deepseek.com/anthropic` | `deepseek-v4-pro` | `deepseek-v4-flash` | `max`(官方推荐) | — |
210
+ | 智谱GLM | `https://open.bigmodel.cn/api/anthropic` | `GLM-4.7` | `glm-4.5-air` | — | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` |
211
+ | 小米MiMo | `https://api.xiaomimimo.com/anthropic` | `mimo-v2.5-pro` | `mimo-v2.5-pro` | — | — |
200
212
 
201
213
  > 模型名随各家更新而变,以供应商官方接入文档为准。小米有按量付费和 TokenPlan 两个地址,选供应商时会让你挑。
202
214
 
@@ -204,7 +216,7 @@ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X
204
216
 
205
217
  - **多账号**:同一家建多份配置,名称自动追加「 2」「 3」…用**备注**区分,列表显示为「供应商 — 备注」。
206
218
  - **自定义供应商**:`presets.json` 是供应商目录,加一个 JSON 条目就多一个供应商,无需改代码。可在 `~/.cc-mini/presets.json` 放自定义版覆盖随工具发布的版本。
207
- - **第三方首次弹登录**:在 `~/.claude.json` 最外层加 `"hasCompletedOnboarding": true`(**只加这个键**,别覆盖整个文件——里面还有你的 MCP 配置)。
219
+ - **第三方首次弹登录**:CC-X 启动第三方前会只读检测 `~/.claude.json` `hasCompletedOnboarding`,未完成时只打印提示,不阻断、不写文件;若 Claude Code 仍显示引导,按提示跳过或关闭即可。
208
220
  - **更新检查**:主菜单可切「提醒」模式,新版本出现时菜单顶部黄字提示升级命令。每天最多查一次,不自动升级。
209
221
 
210
222
  ---
@@ -217,10 +229,10 @@ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X
217
229
  - Windows → 注册表 `HKCU\Environment` + 广播一次变更
218
230
  - Unix → shell 启动文件 `# >>> xx >>>` … `# <<< xx <<<` 标记块(幂等重写,按 `$SHELL` 选文件)
219
231
  - 语义一致:**只影响新终端**;切到「官方」会清除全部受管变量
220
- - **不修改任何 Claude Code 配置文件。**
232
+ - **不修改任何 Claude Code 配置文件。** 启动第三方前只读探测一次 `~/.claude.json` 的 onboarding 字段,用于提示。
221
233
 
222
- CC-X 只动这 7 个「受管」环境变量,切换时清掉目标不用的:
223
- `ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN`、`ANTHROPIC_API_KEY`、`ANTHROPIC_DEFAULT_OPUS_MODEL`、`ANTHROPIC_DEFAULT_SONNET_MODEL`、`ANTHROPIC_DEFAULT_HAIKU_MODEL`、`CLAUDE_CODE_EFFORT_LEVEL`。
234
+ CC-X 只动这 9 个「受管」环境变量,切换时清掉目标不用的:
235
+ `ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN`、`ANTHROPIC_API_KEY`、`ANTHROPIC_DEFAULT_OPUS_MODEL`、`ANTHROPIC_DEFAULT_SONNET_MODEL`、`ANTHROPIC_DEFAULT_HAIKU_MODEL`、`CLAUDE_CODE_EFFORT_LEVEL`、`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`、`CLAUDE_CODE_AUTO_COMPACT_WINDOW`。
224
236
 
225
237
  > 💡 需要改 `settings.json`?直接用 Claude Code 的 `/update-config` 说需求(如"允许 npm 命令"),比让外部工具改可靠。
226
238
 
package/dist/actions.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * 共享动作:本次启用(CLI 路径与菜单都用,避免 index ↔ menus 循环依赖)。
3
3
  */
4
- import { getProviderState } from './config/store.js';
4
+ import { hasCompletedOnboarding } from './claudecfg.js';
5
+ import { isOfficial, getProviderState } from './config/store.js';
5
6
  import { sessionLaunch } from './env/session.js';
6
7
  import { providerDisplayName, T } from './i18n/index.js';
7
8
  /** 非官方且未填密钥时给黄字提示(对齐现版)。 */
@@ -13,6 +14,9 @@ export function warnIfNoKey(p) {
13
14
  /** 本次启用:缺密钥提示 + banner + 套环境启动 claude,阻塞至其退出。 */
14
15
  export function launchSession(p) {
15
16
  warnIfNoKey(p);
17
+ if (!isOfficial(p) && !hasCompletedOnboarding()) {
18
+ console.log(` ${T('session.onboardingHint')}`);
19
+ }
16
20
  console.log('');
17
21
  console.log(` ${T('session.launch', providerDisplayName(p))}`);
18
22
  console.log(` ${T('session.starting')}`);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 对 ~/.claude.json 的只读访问。
3
+ * 铁律:只读不写,任何写操作都在 ccx 范围之外。
4
+ */
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ /**
9
+ * 只读检测 ~/.claude.json 的 hasCompletedOnboarding 字段。
10
+ * 文件不存在、解析失败或字段缺失,均返回 false(视为未完成)。
11
+ */
12
+ export function hasCompletedOnboarding() {
13
+ const path = join(homedir(), '.claude.json');
14
+ if (!existsSync(path))
15
+ return false;
16
+ try {
17
+ const data = JSON.parse(readFileSync(path, 'utf-8'));
18
+ return data.hasCompletedOnboarding === true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
@@ -26,6 +26,10 @@ export const BUILTIN_PRESETS = [
26
26
  auth: 'AUTH_TOKEN',
27
27
  urls: [{ label: 'Anthropic 兼容', url: 'https://open.bigmodel.cn/api/anthropic' }],
28
28
  models: { opus: 'GLM-4.7', sonnet: 'GLM-4.7', haiku: 'glm-4.5-air' },
29
+ env: {
30
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
31
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: '1000000',
32
+ },
29
33
  },
30
34
  {
31
35
  name: '小米MiMo',
@@ -59,6 +63,16 @@ function normalizeModels(raw) {
59
63
  models.haiku = m.haiku;
60
64
  return models;
61
65
  }
66
+ const PRESET_ENV_KEYS = ['CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC', 'CLAUDE_CODE_AUTO_COMPACT_WINDOW'];
67
+ function normalizeEnv(raw) {
68
+ const m = (raw && typeof raw === 'object' ? raw : {});
69
+ const env = {};
70
+ for (const key of PRESET_ENV_KEYS) {
71
+ if (typeof m[key] === 'string')
72
+ env[key] = m[key];
73
+ }
74
+ return Object.keys(env).length > 0 ? env : undefined;
75
+ }
62
76
  function normalizePreset(raw) {
63
77
  const p = (raw && typeof raw === 'object' ? raw : {});
64
78
  const name = asString(p.name).trim();
@@ -69,6 +83,9 @@ function normalizePreset(raw) {
69
83
  const preset = { name, auth, urls, models: normalizeModels(p.models) };
70
84
  if (typeof p.effort === 'string' && p.effort.trim())
71
85
  preset.effort = p.effort.trim();
86
+ const env = normalizeEnv(p.env);
87
+ if (env)
88
+ preset.env = env;
72
89
  return preset;
73
90
  }
74
91
  /** 把任意解析结果规整为 Preset[];非数组或全空则返回 undefined(让调用方跌落兜底)。 */
@@ -44,6 +44,7 @@ export function defaultStore() {
44
44
  ANTHROPIC_DEFAULT_OPUS_MODEL: 'GLM-4.7',
45
45
  ANTHROPIC_DEFAULT_SONNET_MODEL: 'GLM-4.7',
46
46
  ANTHROPIC_DEFAULT_HAIKU_MODEL: 'glm-4.5-air',
47
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
47
48
  },
48
49
  },
49
50
  {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 数据格式必须与现版 PowerShell 完全兼容(老用户的 ~/.cc-mini/providers.json 能直接读)。
5
5
  */
6
- /** 受管的 7 个环境变量(工具只动这些,其它一律不碰)。详见 plan §2。 */
6
+ /** 受管的 9 个环境变量(工具只动这些,其它一律不碰)。详见 plan §2。 */
7
7
  export const KNOWN_KEYS = [
8
8
  'ANTHROPIC_BASE_URL',
9
9
  'ANTHROPIC_AUTH_TOKEN',
@@ -12,4 +12,6 @@ export const KNOWN_KEYS = [
12
12
  'ANTHROPIC_DEFAULT_SONNET_MODEL',
13
13
  'ANTHROPIC_DEFAULT_HAIKU_MODEL',
14
14
  'CLAUDE_CODE_EFFORT_LEVEL',
15
+ 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
16
+ 'CLAUDE_CODE_AUTO_COMPACT_WINDOW',
15
17
  ];
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 设为默认(Set-Default)—— 持久化用户环境变量,仅影响新开终端、不动运行中会话。
3
3
  *
4
- * 对 7 个受管键:目标配置有值的写值,没值的清除(vals 里记 null)。然后 store.current=name 并存盘。
4
+ * 对 9 个受管键:目标配置有值的写值,没值的清除(vals 里记 null)。然后 store.current=name 并存盘。
5
5
  * 平台分叉是唯一有平台差异的地方:Windows 走注册表+广播,Unix 走 rc 文件 marker 块。
6
6
  * `--default-scope process` = 不落盘 dry-run(评审⑥):照常算 vals、更新 store,但跳过系统持久化。
7
7
  */
@@ -18,23 +18,33 @@ export function computeManagedVals(p) {
18
18
  }
19
19
  return vals;
20
20
  }
21
- export function setDefault(paths, store, p, scope) {
21
+ export function persistDefaultEnv(p, scope) {
22
22
  const vals = computeManagedVals(p);
23
23
  const dryRun = scope === 'process';
24
24
  const result = { scope, dryRun };
25
- let persisted = true;
26
25
  if (!dryRun) {
27
26
  if (process.platform === 'win32') {
28
27
  result.windows = persistWindows(vals);
29
- persisted = result.windows.ok;
30
28
  }
31
29
  else {
32
30
  result.unix = persistUnix(vals);
33
- persisted = !result.unix.unsupported; // fish 未写入 → 不算持久化成功
34
31
  }
35
32
  }
33
+ return result;
34
+ }
35
+ function envPersisted(result) {
36
+ if (result.dryRun)
37
+ return true;
38
+ if (result.windows)
39
+ return result.windows.ok;
40
+ if (result.unix)
41
+ return !result.unix.unsupported; // fish 未写入 → 不算持久化成功
42
+ return false;
43
+ }
44
+ export function setDefault(paths, store, p, scope) {
45
+ const result = persistDefaultEnv(p, scope);
36
46
  // [P1] 仅当持久化成功(或 dry-run)才改默认指向并落盘,避免「报失败却已改默认」的不一致。
37
- if (dryRun || persisted) {
47
+ if (envPersisted(result)) {
38
48
  store.current = p.name;
39
49
  saveStore(paths, store);
40
50
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 评审③:钉 `powershell.exe`(5.1,Windows 必然存在),不用 `pwsh`(7+,可能没装)。
5
5
  * 逻辑搬自现版 Set-UserEnv-Fast + Invoke-EnvBroadcast:注册表瞬时写入、最后只广播一次(100ms 短超时、
6
- * SMTO_ABORTIFHUNG 跳过挂死窗口),避免「逐个 setx 广播 7 次、每窗口等 1s」的拖慢。
6
+ * SMTO_ABORTIFHUNG 跳过挂死窗口),避免「逐个 setx 广播 9 次、每窗口等 1s」的拖慢。
7
7
  *
8
8
  * 键值通过环境变量传 JSON 给子进程、用 ConvertFrom-Json 解析 —— 彻底避开命令行注入/引号转义。
9
9
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 本次启用(Session-Launch)—— 进程级、阅后即焚。
3
3
  *
4
- * 1) 对 7 个受管键:有值 set,没值 delete(只动这 7 个)。
4
+ * 1) 对 9 个受管键:有值 set,没值 delete(只动这 9 个)。
5
5
  * 2) 找到 claude(which;Windows 上是 claude.cmd,评审②)。
6
6
  * 3) spawn 且 stdio:inherit —— 子进程继承真实控制台句柄,天然没有现版 PowerShell 的
7
7
  * 「stdin 被包成管道 → claude 误判非交互」问题。
@@ -10,7 +10,7 @@
10
10
  import { spawnSync } from 'node:child_process';
11
11
  import which from 'which';
12
12
  import { KNOWN_KEYS, getProviderEnvMap } from '../config/store.js';
13
- /** 把目标配置的受管环境变量套到当前进程(有值 set、没值 delete,只动这 7 个)。 */
13
+ /** 把目标配置的受管环境变量套到当前进程(有值 set、没值 delete,只动这 9 个)。 */
14
14
  export function applyManagedEnv(p) {
15
15
  const map = getProviderEnvMap(p);
16
16
  for (const key of KNOWN_KEYS) {
@@ -90,6 +90,8 @@ export const messages = {
90
90
  'edit.field.sonnet': { zh: 'sonnet→ 模型 ', en: 'sonnet→ model ' },
91
91
  'edit.field.haiku': { zh: 'haiku → 模型 ', en: 'haiku → model ' },
92
92
  'edit.field.effort': { zh: 'effort 思考档 ', en: 'effort level ' },
93
+ 'edit.field.disableTraffic': { zh: '禁用非核心流量 ', en: 'Disable traffic ' },
94
+ 'edit.field.autoCompact': { zh: '上下文窗口大小 ', en: 'Context window ' },
93
95
  'edit.toggleSecretShow': { zh: '显示密钥明文(当前隐藏)', en: 'Show key in plaintext (now hidden)' },
94
96
  'edit.toggleSecretHide': { zh: '隐藏密钥(当前明文)', en: 'Hide key (now shown)' },
95
97
  'edit.save': { zh: '保存并返回', en: 'Save & back' },
@@ -121,6 +123,7 @@ export const messages = {
121
123
  'error.storeRead': { zh: '配置文件读取失败:{0}', en: 'Failed to read config file: {0}' },
122
124
  'error.storeCorrupt': { zh: '配置文件解析失败(JSON 语法错误):{0}', en: 'Failed to parse config file (invalid JSON): {0}' },
123
125
  'error.storeFormat': { zh: '配置文件结构不正确(顶层须为对象、providers 须为数组且条目结构合法):{0}', en: 'Config file has invalid structure (top-level must be an object, providers must be an array with valid profile entries): {0}' },
126
+ 'error.storeSave': { zh: '配置文件保存失败:{0}', en: 'Failed to save config file: {0}' },
124
127
  'error.storeCorruptHint': {
125
128
  zh: '为避免误删,未对它做任何改动。请修复后重试;或删除该文件以重新生成默认配置(会丢失已填的密钥)。',
126
129
  en: 'Left untouched to avoid data loss. Fix it and retry; or delete the file to regenerate defaults (loses any saved keys).',
@@ -138,6 +141,10 @@ export const messages = {
138
141
  },
139
142
  'session.noClaude': { zh: '未找到 claude 命令,请确认它在 PATH 中。', en: 'claude not found on PATH.' },
140
143
  'session.noClaudeHint': { zh: '下一步:安装 Claude Code 后新开终端;npm 用户可运行 `npm install -g @anthropic-ai/claude-code`。', en: 'Next: install Claude Code and open a new terminal; npm users can run `npm install -g @anthropic-ai/claude-code`.' },
144
+ 'session.onboardingHint': {
145
+ zh: '提示:若 Claude Code 首次启动出现登录引导,选择跳过或关闭引导后第三方 API 会自动生效。',
146
+ en: 'Tip: if Claude Code shows a login prompt on first launch, dismiss or skip it — the third-party API will take effect regardless.',
147
+ },
141
148
  // —— 配置自检(只读网络探测)——
142
149
  'check.noUrl': { zh: '官方登录态 / 未设置 API 地址,无需自检。', en: 'Official login / no API URL set; nothing to probe.' },
143
150
  'check.noKey': { zh: '还没填密钥,无法自检。', en: 'No key set; cannot probe.' },
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * 入口:解析 CLI 参数 → 分派到 CLI 路径(--list / xx <name> / -s)或交互菜单(TUI)。
6
6
  *
7
- * 铁律:绝不写 Claude Code 配置文件;API 切换只动 7 个受管环境变量。详见 CLAUDE.md / plan §2。
7
+ * 铁律:绝不写 Claude Code 配置文件;API 切换只动 9 个受管环境变量。详见 CLAUDE.md / plan §2。
8
8
  */
9
9
  import { createRequire } from 'node:module';
10
10
  import { Command, Option } from 'commander';
package/dist/ui/edit.js CHANGED
@@ -22,6 +22,8 @@ function fromProvider(p) {
22
22
  sonnet: m.ANTHROPIC_DEFAULT_SONNET_MODEL ?? '',
23
23
  haiku: m.ANTHROPIC_DEFAULT_HAIKU_MODEL ?? '',
24
24
  effort: m.CLAUDE_CODE_EFFORT_LEVEL ?? '',
25
+ disableTraffic: m.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC ?? '',
26
+ autoCompact: m.CLAUDE_CODE_AUTO_COMPACT_WINDOW ?? '',
25
27
  };
26
28
  }
27
29
  /**
@@ -46,6 +48,8 @@ export async function editForm(prov, store, catalog, focusKey = false) {
46
48
  { action: 'sonnet', label: `${T('edit.field.sonnet')}: ${v(W.sonnet)}` },
47
49
  { action: 'haiku', label: `${T('edit.field.haiku')}: ${v(W.haiku)}` },
48
50
  { action: 'effort', label: `${T('edit.field.effort')}: ${v(W.effort)}` },
51
+ { action: 'disableTraffic', label: `${T('edit.field.disableTraffic')}: ${v(W.disableTraffic)}` },
52
+ { action: 'autoCompact', label: `${T('edit.field.autoCompact')}: ${v(W.autoCompact)}` },
49
53
  { action: 'sep', label: '' },
50
54
  { action: 'toggle', label: showSecret ? T('edit.toggleSecretHide') : T('edit.toggleSecretShow') },
51
55
  { action: 'sep', label: '' },
@@ -76,6 +80,12 @@ export async function editForm(prov, store, catalog, focusKey = false) {
76
80
  W.haiku = pp.models.haiku;
77
81
  if (pp.effort)
78
82
  W.effort = pp.effort;
83
+ if (pp.env && Object.prototype.hasOwnProperty.call(pp.env, 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC')) {
84
+ W.disableTraffic = pp.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC ?? '';
85
+ }
86
+ if (pp.env && Object.prototype.hasOwnProperty.call(pp.env, 'CLAUDE_CODE_AUTO_COMPACT_WINDOW')) {
87
+ W.autoCompact = pp.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW ?? '';
88
+ }
79
89
  }
80
90
  break;
81
91
  }
@@ -120,6 +130,18 @@ export async function editForm(prov, store, catalog, focusKey = false) {
120
130
  case 'effort':
121
131
  W.effort = await pickEffort(W.effort);
122
132
  break;
133
+ case 'disableTraffic': {
134
+ const r = await readValue(T('edit.field.disableTraffic').trim(), W.disableTraffic);
135
+ if (r.changed)
136
+ W.disableTraffic = r.value;
137
+ break;
138
+ }
139
+ case 'autoCompact': {
140
+ const r = await readValue(T('edit.field.autoCompact').trim(), W.autoCompact);
141
+ if (r.changed)
142
+ W.autoCompact = r.value;
143
+ break;
144
+ }
123
145
  case 'toggle':
124
146
  showSecret = !showSecret; // 仅切换显示,不改数据、不持久化
125
147
  break;
@@ -134,6 +156,8 @@ export async function editForm(prov, store, catalog, focusKey = false) {
134
156
  ANTHROPIC_DEFAULT_SONNET_MODEL: W.sonnet,
135
157
  ANTHROPIC_DEFAULT_HAIKU_MODEL: W.haiku,
136
158
  CLAUDE_CODE_EFFORT_LEVEL: W.effort,
159
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: W.disableTraffic,
160
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: W.autoCompact,
137
161
  };
138
162
  if (W.auth === 'API_KEY')
139
163
  fields.ANTHROPIC_API_KEY = W.token;
package/dist/ui/menus.js CHANGED
@@ -8,7 +8,7 @@
8
8
  import { launchSession } from '../actions.js';
9
9
  import { checkProfile } from '../check.js';
10
10
  import { getProviderEnvMap, getProviderState, isOfficial, reconcileCurrent, saveStore } from '../config/store.js';
11
- import { setDefault } from '../env/default.js';
11
+ import { persistDefaultEnv, setDefault } from '../env/default.js';
12
12
  import { getLang, providerDisplayName, setLang, T } from '../i18n/index.js';
13
13
  import { currentTerminalLine, hostOf } from '../runtime-info.js';
14
14
  import { banner as updateBanner, maybeRefresh, MODE_NOTIFY, upgradeCommand } from '../update/update.js';
@@ -91,9 +91,7 @@ export async function openMenu(paths, store, scope, version, catalog) {
91
91
  if (shortcut === 'e') {
92
92
  const old = target.name;
93
93
  if (await editForm(target, store, catalog)) {
94
- if (store.current === old)
95
- store.current = target.name;
96
- saveStore(paths, store);
94
+ ({ warn: warnFlash, toast: flash } = saveEditedProfile(paths, store, target, old, scope));
97
95
  }
98
96
  }
99
97
  else if (shortcut === 's') {
@@ -137,9 +135,7 @@ export async function openMenu(paths, store, scope, version, catalog) {
137
135
  // #9:无密钥的第三方配置,Enter 直达编辑并聚焦密钥行(铺平首次成功路径)。
138
136
  const old = target.name;
139
137
  if (await editForm(target, store, catalog, true)) {
140
- if (store.current === old)
141
- store.current = target.name;
142
- saveStore(paths, store);
138
+ ({ warn: warnFlash, toast: flash } = saveEditedProfile(paths, store, target, old, scope));
143
139
  }
144
140
  }
145
141
  else {
@@ -187,9 +183,7 @@ async function actionMenu(paths, store, p, scope, catalog) {
187
183
  else if (sel === 3) {
188
184
  const old = p.name;
189
185
  if (await editForm(p, store, catalog)) {
190
- if (store.current === old)
191
- store.current = p.name; // 改了名/供应商时同步默认指向
192
- saveStore(paths, store);
186
+ ({ warn: warnFlash, toast: flash } = saveEditedProfile(paths, store, p, old, scope));
193
187
  }
194
188
  }
195
189
  else if (sel === 4) {
@@ -212,14 +206,19 @@ function defaultDisplayName(store) {
212
206
  return '—';
213
207
  return providerDisplayName(store.providers.find((p) => p.name === store.current) ?? { name: store.current, env: {} });
214
208
  }
215
- /**
216
- * 设为默认,返回 { warn, toast }:warn 为黄字警告(缺密钥),toast 为绿色结果。
217
- * 分开返回让调用方各自上色,避免警告被染成「成功」绿。
218
- */
219
- function applyDefault(paths, store, p, scope) {
220
- const name = providerDisplayName(p);
221
- const warn = getProviderState(p).key === 'noKey' ? T('default.noKey', name) : '';
222
- const r = setDefault(paths, store, p, scope);
209
+ function saveEditedProfile(paths, store, p, oldName, scope) {
210
+ const wasDefault = store.current === oldName;
211
+ if (wasDefault)
212
+ store.current = p.name; // 改了名/供应商时同步默认指向
213
+ saveStore(paths, store);
214
+ if (wasDefault)
215
+ return syncDefaultEnv(p, scope);
216
+ return {};
217
+ }
218
+ function defaultWarning(p) {
219
+ return getProviderState(p).key === 'noKey' ? T('default.noKey', providerDisplayName(p)) : '';
220
+ }
221
+ function defaultResultMessage(warn, name, r) {
223
222
  if (r.dryRun)
224
223
  return { warn, toast: `${T('default.done', name)} ${T('default.dryRun')}` };
225
224
  if (r.windows && !r.windows.ok)
@@ -228,6 +227,18 @@ function applyDefault(paths, store, p, scope) {
228
227
  return { warn, toast: T('default.fishUnsupported') };
229
228
  return { warn, toast: T('default.done', name) };
230
229
  }
230
+ function syncDefaultEnv(p, scope) {
231
+ const name = providerDisplayName(p);
232
+ return defaultResultMessage(defaultWarning(p), name, persistDefaultEnv(p, scope));
233
+ }
234
+ /**
235
+ * 设为默认,返回 { warn, toast }:warn 为黄字警告(缺密钥),toast 为绿色结果。
236
+ * 分开返回让调用方各自上色,避免警告被染成「成功」绿。
237
+ */
238
+ function applyDefault(paths, store, p, scope) {
239
+ const name = providerDisplayName(p);
240
+ return defaultResultMessage(defaultWarning(p), name, setDefault(paths, store, p, scope));
241
+ }
231
242
  // hostSuffix 返回行尾的灰字 host(如 ` · api.deepseek.com`);无 base(官方/未填)返回空。
232
243
  // 超宽时由 selectMenu 的 ANSI-aware 截断从行尾裁掉,不会切坏颜色。
233
244
  function hostSuffix(p) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cc-x/cc-x",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Claude Code API 切换器(命令 xx):在官方账号与第三方 Anthropic 兼容 API 间切换,纯环境变量、不碰 Claude Code 配置文件。",
5
5
  "keywords": [
6
6
  "claude",
package/presets.json CHANGED
@@ -14,7 +14,11 @@
14
14
  "urls": [
15
15
  { "label": "Anthropic 兼容", "url": "https://open.bigmodel.cn/api/anthropic" }
16
16
  ],
17
- "models": { "opus": "GLM-4.7", "sonnet": "GLM-4.7", "haiku": "glm-4.5-air" }
17
+ "models": { "opus": "GLM-4.7", "sonnet": "GLM-4.7", "haiku": "glm-4.5-air" },
18
+ "env": {
19
+ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
20
+ "CLAUDE_CODE_AUTO_COMPACT_WINDOW": "1000000"
21
+ }
18
22
  },
19
23
  {
20
24
  "name": "小米MiMo",