@cc-x/cc-x 0.4.10 → 0.4.12

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
@@ -178,7 +178,7 @@ cc-switch is an excellent full-featured GUI; CC-X takes the opposite, minimal ap
178
178
 
179
179
  > CC-X cares more about boundaries than features.
180
180
 
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.
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 8 managed environment variables, then let Claude Code run.
182
182
 
183
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.
184
184
 
@@ -200,7 +200,6 @@ Issues / PRs are welcome, but the direction is clear: **make switching steadier,
200
200
  | haiku → model | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | |
201
201
  | effort level | `CLAUDE_CODE_EFFORT_LEVEL` | `low`–`max`; `auto` = model default; empty = unset. Third parties may not honor it |
202
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 |
204
203
 
205
204
  > CC-X **deliberately does not set** `ANTHROPIC_MODEL`. Use `/model opus|sonnet|haiku` in-session;
206
205
  > the mapping table translates to the provider's real model name.
@@ -249,10 +248,10 @@ Issues / PRs are welcome, but the direction is clear: **make switching steadier,
249
248
  - **No Claude Code config file is ever modified.** Before third-party launches, CC-X only reads
250
249
  the onboarding field in `~/.claude.json` to decide whether to print a hint.
251
250
 
252
- CC-X only touches these 9 "managed" variables (and clears the ones a target profile doesn't use):
251
+ CC-X only touches these 8 "managed" variables (and clears the ones a target profile doesn't use):
253
252
  `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_API_KEY`, `ANTHROPIC_DEFAULT_OPUS_MODEL`,
254
253
  `ANTHROPIC_DEFAULT_SONNET_MODEL`, `ANTHROPIC_DEFAULT_HAIKU_MODEL`, `CLAUDE_CODE_EFFORT_LEVEL`,
255
- `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`, `CLAUDE_CODE_AUTO_COMPACT_WINDOW`.
254
+ `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`.
256
255
 
257
256
  > 💡 To change `settings.json`, use Claude Code's own `/update-config` and describe what you want
258
257
  > in natural language (e.g. "allow npm commands") — safer than letting an external tool rewrite it.
package/README.md CHANGED
@@ -168,7 +168,7 @@ cc-switch 是优秀的全能 GUI;CC-X 走相反的极简路线。
168
168
 
169
169
  > CC-X 的边界比功能更重要。
170
170
 
171
- Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X 不想再造一个"上层控制台",也不想把用户的配置收编进自己的数据库。它只站在 Claude Code 进程启动前的那一小步:把 9 个受管环境变量准备好,然后让 Claude Code 自己工作。
171
+ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X 不想再造一个"上层控制台",也不想把用户的配置收编进自己的数据库。它只站在 Claude Code 进程启动前的那一小步:把 8 个受管环境变量准备好,然后让 Claude Code 自己工作。
172
172
 
173
173
  所以它的取舍是有意的:不写 Claude Code 配置文件,不接管 MCP,不做自动迁移,不做后台常驻管理。能用进程环境变量解决,就不碰全局文件;能让用户显式选择,就不替用户自动决定。少做一点,是为了把风险面压到足够小。
174
174
 
@@ -190,7 +190,6 @@ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X
190
190
  | haiku → 模型 | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | |
191
191
  | effort 思考档 | `CLAUDE_CODE_EFFORT_LEVEL` | `low` ~ `max`;`auto`=模型默认;留空=不设。第三方不一定生效 |
192
192
  | 禁用非核心流量 | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | `1`=禁用 Claude Code 非核心流量;留空=不设。GLM 预置为 `1` |
193
- | 上下文窗口大小 | `CLAUDE_CODE_AUTO_COMPACT_WINDOW` | 按 Claude Code 支持的窗口值填写;留空=不设 |
194
193
 
195
194
  > CC-X **刻意不设** `ANTHROPIC_MODEL`。在会话里用 `/model opus|sonnet|haiku` 选档,映射表负责翻译成对应供应商的模型名。
196
195
 
@@ -231,8 +230,8 @@ Claude Code 已经有自己的配置系统、MCP 生态和会话状态。CC-X
231
230
  - 语义一致:**只影响新终端**;切到「官方」会清除全部受管变量
232
231
  - **不修改任何 Claude Code 配置文件。** 启动第三方前只读探测一次 `~/.claude.json` 的 onboarding 字段,用于提示。
233
232
 
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`。
233
+ CC-X 只动这 8 个「受管」环境变量,切换时清掉目标不用的:
234
+ `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`。
236
235
 
237
236
  > 💡 需要改 `settings.json`?直接用 Claude Code 的 `/update-config` 说需求(如"允许 npm 命令"),比让外部工具改可靠。
238
237
 
@@ -37,8 +37,8 @@ export const messages = {
37
37
  // —— 菜单通用 ——
38
38
  'menu.prompt': { zh: '输入序号 (q 取消): ', en: 'Enter number (q to cancel): ' },
39
39
  'menu.mainTitle': {
40
- zh: 'CC-X v{0} · Claude Code API 切换器 默认:{1}',
41
- en: 'CC-X v{0} · Claude Code API switcher Default: {1}',
40
+ zh: 'CC-X v{0} 默认:{1}',
41
+ en: 'CC-X v{0} Default: {1}',
42
42
  },
43
43
  'menu.mainHint': {
44
44
  zh: '↑↓ 选择 · Enter 进入 · e 编辑 · s 启动 · d 设默认 · Shift+↑↓ 排序 · q 退出',
package/dist/index.js CHANGED
@@ -14,9 +14,7 @@ import { loadPresets } from './config/presets.js';
14
14
  import { setDefault } from './env/default.js';
15
15
  import { providerDisplayName, resolveLang, setLang, T } from './i18n/index.js';
16
16
  import { currentTerminalLine } from './runtime-info.js';
17
- import { noteSuffix, stateLabel } from './ui/format.js';
18
- import { openMenu } from './ui/menus.js';
19
- import { padDisplay } from './utils/display.js';
17
+ import { openMenu, profileRows } from './ui/menus.js';
20
18
  const require = createRequire(import.meta.url);
21
19
  const pkg = require('../package.json');
22
20
  /** 从 argv 里轻量预读一个带值参数(`--name v` 或 `--name=v`),用于 parse 前定语言。 */
@@ -116,9 +114,8 @@ function runList(store) {
116
114
  console.log('');
117
115
  console.log(` ${T('list.default', cur ? providerDisplayName(cur) : store.current)}`);
118
116
  console.log(` ${currentTerminalLine(store)}`);
119
- for (const p of store.providers) {
120
- const mark = p.name === store.current ? '▶' : ' ';
121
- console.log(` ${mark} ${padDisplay(providerDisplayName(p), 18)}[${stateLabel(p)}]${noteSuffix(p)}`);
117
+ for (const line of profileRows(store.providers, store.current)) {
118
+ console.log(` ${line}`);
122
119
  }
123
120
  console.log('');
124
121
  }
package/dist/ui/menus.js CHANGED
@@ -7,13 +7,12 @@
7
7
  */
8
8
  import { launchSession } from '../actions.js';
9
9
  import { checkProfile } from '../check.js';
10
- import { getProviderEnvMap, getProviderState, isOfficial, reconcileCurrent, saveStore } from '../config/store.js';
10
+ import { getProviderState, isOfficial, reconcileCurrent, saveStore } from '../config/store.js';
11
11
  import { persistDefaultEnv, setDefault } from '../env/default.js';
12
12
  import { getLang, providerDisplayName, setLang, T } from '../i18n/index.js';
13
- import { currentTerminalLine, hostOf } from '../runtime-info.js';
14
13
  import { banner as updateBanner, maybeRefresh, MODE_NOTIFY, upgradeCommand } from '../update/update.js';
15
14
  import { paint } from '../utils/ansi.js';
16
- import { padDisplay } from '../utils/display.js';
15
+ import { displayWidth, padDisplay } from '../utils/display.js';
17
16
  import { editForm } from './edit.js';
18
17
  import { noteSuffix, stateLabel } from './format.js';
19
18
  import { confirmKey, selectMenu } from './select.js';
@@ -26,7 +25,7 @@ export async function openMenu(paths, store, scope, version, catalog) {
26
25
  for (;;) {
27
26
  const n = store.providers.length;
28
27
  // 更新检查(仅 notify 模式):首轮触发一次后台刷新;横幅永远读缓存(瞬时、不阻塞)。
29
- const notices = [currentTerminalLine(store)];
28
+ const notices = [];
30
29
  if (needsFirstRunHint(store))
31
30
  notices.push(T('menu.firstRunHint'));
32
31
  if (warnFlash)
@@ -42,10 +41,7 @@ export async function openMenu(paths, store, scope, version, catalog) {
42
41
  }
43
42
  const updLabel = store.update === MODE_NOTIFY ? T('menu.updateNotify') : T('menu.updateOff');
44
43
  const buildItems = () => {
45
- const labels = store.providers.map((p) => {
46
- const dft = p.name === store.current ? T('menu.default') : '';
47
- return `${padDisplay(providerDisplayName(p), 16)}${padDisplay(dft, 8)}[${stateLabel(p)}]${noteSuffix(p)}${hostSuffix(p)}`;
48
- });
44
+ const labels = profileRows(store.providers, store.current);
49
45
  return [...labels, '', T('menu.newProfile'), T('menu.language'), updLabel, '', T('menu.exit')];
50
46
  };
51
47
  const onMove = (from, to) => {
@@ -66,7 +62,7 @@ export async function openMenu(paths, store, scope, version, catalog) {
66
62
  notice: notices.join('\n'),
67
63
  ...(flash ? { status: flash } : {}),
68
64
  items: buildItems(),
69
- colors: { [n + 1]: 'yellow' },
65
+ colors: { [n + 1]: 'yellow', [n + 2]: 'dim', [n + 3]: 'dim' },
70
66
  start: sel,
71
67
  movableCount: n,
72
68
  onMove,
@@ -239,13 +235,49 @@ function applyDefault(paths, store, p, scope) {
239
235
  const name = providerDisplayName(p);
240
236
  return defaultResultMessage(defaultWarning(p), name, setDefault(paths, store, p, scope));
241
237
  }
242
- // hostSuffix 返回行尾的灰字 host(如 ` · api.deepseek.com`);无 base(官方/未填)返回空。
243
- // 超宽时由 selectMenu 的 ANSI-aware 截断从行尾裁掉,不会切坏颜色。
244
- function hostSuffix(p) {
245
- const base = (getProviderEnvMap(p).ANTHROPIC_BASE_URL ?? '').trim();
246
- if (!base)
247
- return '';
248
- return paint(` · ${hostOf(base)}`, 'dim');
238
+ const ROW_NAME_W = 13; // 名字列宽(显示宽度,CJK 2);状态/备注列宽按内容动态算
239
+ // profileRows 把所有配置格式化成三列菜单行:名字(主信息,默认项加粗、缺密钥变灰)
240
+ // + 备注(紧跟名字、dim,同名配置靠它区分)+ 状态(effort/缺密钥告警,dim,锚成最右一列)。
241
+ // 备注列宽按当前配置实际内容动态取最大值;状态列在最右,无需补宽。
242
+ // TUI 主菜单与 xx --list 共用此函数,保证两处呈现一致。与 Go 版 menu.go ProfileRows 对齐。
243
+ export function profileRows(providers, current) {
244
+ let noteW = 0;
245
+ for (const p of providers) {
246
+ noteW = Math.max(noteW, displayWidth(p.note ?? ''));
247
+ }
248
+ if (noteW > 0)
249
+ noteW += 2; // 备注列尾留出与状态列的间距
250
+ return providers.map((p) => profileRow(p, p.name === current, noteW));
251
+ }
252
+ // rowStateText 行内状态段:只保留 effort 与「缺密钥」告警。
253
+ // 「登录态/密钥已设/密钥·API_KEY」等可用态不再用文字表达——能不能用由配置名的亮/灰区分。
254
+ function rowStateText(p) {
255
+ const st = getProviderState(p);
256
+ const parts = [];
257
+ if (st.effort)
258
+ parts.push(`effort=${st.effort}`);
259
+ if (st.key === 'noKey')
260
+ parts.push(T('state.noKey'));
261
+ return parts.join(' · ');
262
+ }
263
+ // profileRow 组成一行:名字 + 备注 + 状态三列,次要信息 dim、默认项加粗、缺密钥变灰。
264
+ // 默认项只靠名字加粗标识(不再额外画 ● 符号,与光标 ▶ 重叠冗余);选中行由 selectMenu 整行绿。
265
+ // 亮/灰用加粗(1m)与 dim(2m),reset 都用 22m 不动颜色,不会破坏选中行的外层绿色。
266
+ function profileRow(p, isDefault, noteW) {
267
+ const noKey = getProviderState(p).key === 'noKey';
268
+ let name = padDisplay(providerDisplayName(p), ROW_NAME_W);
269
+ if (noKey)
270
+ name = paint(name, 'dim'); // 灰掉=当前不可用(缺密钥)
271
+ else if (isDefault)
272
+ name = paint(name, 'bold'); // 加粗=默认配置
273
+ // 空备注=定宽空白,撑出状态列前的间距
274
+ const note = (p.note ?? '').trim()
275
+ ? paint(padDisplay(p.note ?? '', noteW), 'dim')
276
+ : padDisplay('', noteW);
277
+ // 状态在最右一列,可用且无 effort 时整列省略
278
+ const seg = rowStateText(p);
279
+ const state = seg ? paint(` · ${seg}`, 'dim') : '';
280
+ return `${name}${note}${state}`;
249
281
  }
250
282
  function needsFirstRunHint(store) {
251
283
  let hasThirdParty = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cc-x/cc-x",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "Claude Code API 切换器(命令 xx):在官方账号与第三方 Anthropic 兼容 API 间切换,纯环境变量、不碰 Claude Code 配置文件。",
5
5
  "keywords": [
6
6
  "claude",