@yhong91/cpac 0.1.14 → 0.1.15

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.md CHANGED
@@ -22,15 +22,6 @@ npm install -g @yhong91/cpac
22
22
  cpac
23
23
  ```
24
24
 
25
- 开发目录中安装:
26
-
27
- ```bash
28
- npm install
29
- npm run build
30
- npm install -g .
31
- cpac
32
- ```
33
-
34
25
  ## 快速上手
35
26
 
36
27
  ```bash
@@ -55,9 +46,9 @@ cpac uninstall --target codex # 字节级恢复原配置
55
46
  cpac claude # Claude Code 走 CPA
56
47
  cpac opencode # opencode 走 CPA(模型选 cpac/<模型名>)
57
48
 
58
- # 5. 可选:Claude 模型槽位配置(持久缓存,每次 cpac claude 自动生效)
49
+ # 5. 可选:Claude 模型槽位覆盖(持久缓存;默认自动选择已覆盖多数场景,见下文)
50
+ cpac claude-models # 查看当前覆盖
59
51
  cpac claude-models --haiku # 下拉选择 haiku 槽模型
60
- cpac claude-models --classifier <模型> # Auto mode 审批分类器模型
61
52
 
62
53
  # 6. 升级
63
54
  cpac upgrade --check
@@ -66,6 +57,42 @@ npm install -g @yhong91/cpac@latest
66
57
 
67
58
  各命令的完整参数与行为见下方对应章节。
68
59
 
60
+ ## Agent 自动化安装指南
61
+
62
+ 本节面向 AI agent:按顺序执行即可完成安装与配置,**全程无交互**。注意:`--v2_models` 复选框和 `claude-models` 不带值的下拉选择是 TTY-only,agent 必须走下方直接写配置 JSON 的路径。
63
+
64
+ 1. **检查前置**:`node --version` ≥ 20;确认用户需要哪些 agent(`codex`/`claude`/`pi`/`kimi`/`opencode` 在 PATH 上)。向用户索取 CPA API key。
65
+
66
+ 2. **安装与密钥**:
67
+
68
+ ```bash
69
+ npm install -g @yhong91/cpac
70
+ echo 'export CPA_API_KEY="<key>"' >> ~/.zshrc # bash 用 ~/.bash_profile 或 ~/.bashrc
71
+ source ~/.zshrc
72
+ cpac status # 验证:CPA_API_KEY 应显示 configured
73
+ ```
74
+
75
+ 3. **写入模型配置**(创建或合并 `~/.config/cpac/config.json`,已有键保留):
76
+
77
+ ```json
78
+ {
79
+ "spawn_models": ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gemini-3.7-flash-high", "grok-4.6"]
80
+ }
81
+ ```
82
+
83
+ Claude 槽位(opus/sonnet/haiku)通常不用配:cpac 启动时自动选择——同家族 claude 模型优先,否则 gemini → grok → luna → 目录第一个;opus 无匹配则不设。这个链条在常见目录上正好给出理想结果:sonnet 落在 claude-sonnet-*(它同时充当 Auto mode 分类器的替身,避免分类请求失败后回退到昂贵的会话主模型),haiku 落在快的 gemini flash。只有目录奇怪时才用 `cpac claude-models --opus/--sonnet/--haiku <模型>` 显式覆盖。codex spawn subagent 取三个 GPT + gemini + grok 五席(顺序即优先级,Codex 只广告 catalog 前 5)。候选替换:`mimo-v2.5`(haiku)、`grok-4.5`(haiku)。若上述 slug 不在目录中(`curl -s -H "Authorization: Bearer $CPA_API_KEY" "$CPA_BASE_URL/v1/models"` 可查),按家族就近替换。分类器机制细节见 [docs/claude-auto-mode-classifier.md](docs/claude-auto-mode-classifier.md)。
84
+
85
+ 4. **持久注入**(只对检测到的目标;codex 会应用步骤 3 的 spawn_models):
86
+
87
+ ```bash
88
+ cpac install --all
89
+ cpac status # 验证:各 target 显示 ready/injected
90
+ ```
91
+
92
+ 5. **临时接管类 agent 无需安装**,直接告知用户用 `cpac claude` / `cpac opencode` 启动(步骤 3 的 claude_models 自动生效)。
93
+
94
+ 失败处理:任何一步报错时先跑 `cpac status`;密钥未配置回到步骤 2;注入后异常用 `cpac uninstall --target <id>` 恢复原配置。
95
+
69
96
  ## 首次引导
70
97
 
71
98
  无参数运行 `cpac` 会显示当前 CPA、密钥状态和可用命令:
@@ -175,11 +202,14 @@ CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1
175
202
  cpac claude-models # 查看当前覆盖
176
203
  cpac claude-models --opus claude-opus-4-8 --haiku glm-5.3 # 设置/合并
177
204
  cpac claude-models --haiku # 不带值:arrow-key 下拉选择
178
- cpac claude-models --classifier claude-sonnet-4-6 # Auto mode 审批分类器模型
179
205
  cpac claude-models --reset # 恢复自动选择
180
206
  ```
181
207
 
182
- 所有设定写入配置文件持久缓存,每次 `cpac claude` 启动自动生效。`--classifier` 对应 `CLAUDE_CODE_AUTO_MODE_MODEL`(Auto mode 的审批分类器,默认跟随 Claude Code 内置选择);不设置则不注入。
208
+ 未设置的槽位启动时自动选择:claude 池内同家族模型优先,否则按 gemini grok luna 目录第一个回退;opus 无匹配则不设。
209
+
210
+ 所有设定写入配置文件持久缓存,每次 `cpac claude` 启动自动生效。
211
+
212
+ `--opus` / `--sonnet` / `--haiku` 对应 `ANTHROPIC_DEFAULT_*_MODEL`(haiku 同时写入 `ANTHROPIC_SMALL_FAST_MODEL`)。旧版的 `--classifier` 已移除:`CLAUDE_CODE_AUTO_MODE_MODEL` 在 Claude Code 2.1.224 上不被读取,Auto mode 分类器由官方自选(默认 Sonnet 5);现版本唯一能影响分类器替身的旋钮是 `--sonnet`(副作用:同时改 `sonnet` 别名)。完整选择顺序、回退规则与出处见 [docs/claude-auto-mode-classifier.md](docs/claude-auto-mode-classifier.md)。
183
213
 
184
214
  你自己 export 同名环境变量时以你为准。
185
215
 
@@ -354,40 +384,3 @@ export CPAC_CONFIG=/path/to/cpac.json
354
384
  - shell 启动文件和 Codex 配置均通过同目录临时文件原子替换。
355
385
  - CPA 请求失败、catalog 无效或 key 缺失时,首次注入不会修改 Codex 配置。
356
386
  - `restore` 只删除 CPAC 自有的 state、backup 和 catalog,不删除 `state_dir` 中的其他文件。
357
-
358
- ## 开发
359
-
360
- ```bash
361
- npm install
362
- npm test
363
- npm run check
364
- npm run build
365
- npm run check:pi
366
- npm run pack:check
367
- ```
368
-
369
- 发布包的 CLI 入口为 `dist/cpac.js`。`dist/` 是构建产物,不应手工编辑。
370
-
371
- ## npm 发布
372
-
373
- `.github/workflows/publish.yml` 在推送到 `main` 或手动触发时执行测试、类型检查、构建并发布到 npm。发布前会确认 `package.json` 中的版本尚未存在;因此每次计划发布的 push 都必须先提升版本。普通 PR 仅运行 CI。
374
-
375
- 首次发布前:
376
-
377
- 1. 在 npm 创建账号并启用 2FA。
378
- 2. 因为新包尚不存在,先创建允许 publish 且可绕过 2FA 的 npm granular access token,在仓库的 **Settings → Secrets and variables → Actions** 添加 `NPM_TOKEN`,然后手动运行 `Publish to npm` workflow 完成首次发布。
379
- 3. 首次发布后,在 npm 包 `@yhong91/cpac` 的设置中添加 Trusted Publisher:GitHub 用户 `yhong91`、仓库 `cpac`、workflow 文件 `publish.yml`,允许 `npm publish`。
380
- 4. Trusted Publisher 生效后删除 GitHub 的 `NPM_TOKEN`;后续 workflow 自动使用 OIDC。
381
-
382
- 正常发布新版本:
383
-
384
- ```bash
385
- npm version patch --no-git-tag-version # 或 minor / major
386
- git add package.json package-lock.json
387
- git commit -m 'Release 0.1.1'
388
- git push
389
- ```
390
-
391
- 也可以手工修改并提交两个版本文件。push 到 `main` 后 Action 自动发布;若忘记提升版本,发布 workflow 会明确失败,不会覆盖 npm 已发布版本。
392
-
393
- 仓库当前为 private,因此 npm Trusted Publishing 可以使用,但 npm 不会为发布包生成 provenance。
package/cpac.example.json CHANGED
@@ -5,8 +5,7 @@
5
5
  "claude_models": {
6
6
  "opus": "claude-opus-4-8",
7
7
  "sonnet": "claude-sonnet-4-6",
8
- "haiku": "glm-5.3",
9
- "classifier": "claude-sonnet-4-6"
8
+ "haiku": "glm-5.3"
10
9
  },
11
10
  "spawn_models": ["gpt-5.5", "claude-opus-4-8"]
12
11
  }
package/dist/cpac.js CHANGED
@@ -25,7 +25,7 @@ const CONFIG_KEYS = new Set([
25
25
  // Codex advertises only the first 5 picker-visible catalog models as
26
26
  // spawn_agent overrides (codex-rs MAX_SPAWN_AGENT_MODEL_OVERRIDES).
27
27
  const MAX_SPAWN_MODELS = 5;
28
- const CLAUDE_SLOT_KEYS = ["opus", "sonnet", "haiku", "classifier"];
28
+ const CLAUDE_SLOT_KEYS = ["opus", "sonnet", "haiku"];
29
29
  const REQUIRED_STATE_KEYS = new Set([
30
30
  "config_path",
31
31
  "config_existed",
@@ -131,7 +131,9 @@ export function loadConfig(path, useDefaultsIfMissing = false) {
131
131
  if (!objectValue(claudeModelsRaw))
132
132
  throw new CPACError("claude_models must be an object");
133
133
  const unknownSlots = Object.keys(claudeModelsRaw)
134
- .filter((key) => !CLAUDE_SLOT_KEYS.includes(key))
134
+ // "classifier" was a shipped slot; its env var turned out unread by
135
+ // Claude Code, so the key is dropped on load instead of erroring.
136
+ .filter((key) => key !== "classifier" && !CLAUDE_SLOT_KEYS.includes(key))
135
137
  .sort();
136
138
  if (unknownSlots.length)
137
139
  throw new CPACError(`unknown claude_models slots: ${unknownSlots.join(", ")}`);
@@ -778,25 +780,29 @@ function shouldMarkOneMillion(id, window, compactWindow) {
778
780
  return true;
779
781
  return compactWindow !== undefined && window > 200_000 && window >= compactWindow;
780
782
  }
781
- // Auto tier defaults: prefer claude-family models; within the pool match by
782
- // family keyword first (opus/sonnet/haiku) so equal context windows don't
783
- // collapse every slot onto the first catalog entry, then fall back to
784
- // biggest-window (opus/sonnet) and smallest-window (haiku).
783
+ // Auto tier defaults. Within the claude-family pool (whole catalog when no
784
+ // claude model exists), opus matches its family name or stays unset; sonnet
785
+ // and haiku match their family name first, then fall back through
786
+ // gemini grok luna → first catalog row.
785
787
  function defaultClaudeSlots(models) {
786
788
  if (!models.length)
787
789
  return undefined;
788
790
  const claudeRows = models.filter((model) => model.id.startsWith("claude"));
789
791
  const pool = claudeRows.length ? claudeRows : models;
790
- const biggest = [...pool].sort((a, b) => b.window - a.window)[0];
791
- const smallest = pool
792
- .filter((model) => model.window > 0)
793
- .sort((a, b) => a.window - b.window)[0];
794
792
  const family = (keyword) => pool.find((model) => model.id.includes(keyword));
795
- return {
796
- opus: (family("opus") ?? biggest).id,
797
- sonnet: (family("sonnet") ?? biggest).id,
798
- haiku: (family("haiku") ?? smallest ?? biggest).id,
793
+ const fallback = (keyword) => family(keyword) ??
794
+ models.find((model) => model.id.includes("gemini")) ??
795
+ models.find((model) => model.id.includes("grok")) ??
796
+ models.find((model) => model.id.includes("luna")) ??
797
+ models[0];
798
+ const slots = {
799
+ sonnet: fallback("sonnet").id,
800
+ haiku: fallback("haiku").id,
799
801
  };
802
+ const opus = family("opus");
803
+ if (opus)
804
+ slots.opus = opus.id;
805
+ return slots;
800
806
  }
801
807
  // Resolve the tier-model env values: config overrides win over catalog
802
808
  // defaults; values are [1m]-marked per the predicate above (Claude Code
@@ -810,11 +816,14 @@ export function claudeTierSlots(override, models, compactWindow) {
810
816
  shouldMarkOneMillion(id, windowById.get(id) ?? 0, compactWindow)
811
817
  ? `${id}[1m]`
812
818
  : id;
813
- return {
814
- opus: mark(override?.opus ?? defaults.opus),
819
+ const slots = {
815
820
  sonnet: mark(override?.sonnet ?? defaults.sonnet),
816
821
  haiku: mark(override?.haiku ?? defaults.haiku),
817
822
  };
823
+ const opus = override?.opus ?? defaults.opus;
824
+ if (opus)
825
+ slots.opus = mark(opus);
826
+ return slots;
818
827
  }
819
828
  function catalogModelRows(document) {
820
829
  if (!objectValue(document))
@@ -1590,14 +1599,14 @@ export async function runClaude(config, args, executable = "claude") {
1590
1599
  };
1591
1600
  if (slots) {
1592
1601
  // Route haiku/subagent tier traffic at catalog models instead of the
1593
- // stock claude-haiku-* ids the gateway does not carry.
1594
- setSlot("ANTHROPIC_DEFAULT_OPUS_MODEL", slots.opus);
1602
+ // stock claude-haiku-* ids the gateway does not carry. The opus slot
1603
+ // stays unset when the catalog has no opus-family model.
1604
+ if (slots.opus)
1605
+ setSlot("ANTHROPIC_DEFAULT_OPUS_MODEL", slots.opus);
1595
1606
  setSlot("ANTHROPIC_DEFAULT_SONNET_MODEL", slots.sonnet);
1596
1607
  setSlot("ANTHROPIC_DEFAULT_HAIKU_MODEL", slots.haiku);
1597
1608
  setSlot("ANTHROPIC_SMALL_FAST_MODEL", slots.haiku);
1598
1609
  }
1599
- if (config.claude_models?.classifier)
1600
- setSlot("CLAUDE_CODE_AUTO_MODE_MODEL", config.claude_models.classifier);
1601
1610
  delete env.ANTHROPIC_API_KEY;
1602
1611
  delete env.CLAUDE_CODE_USE_ANTHROPIC_AWS;
1603
1612
  delete env.CLAUDE_CODE_USE_BEDROCK;
@@ -1994,7 +2003,7 @@ function usage() {
1994
2003
  " cpac proxy [--config PATH]",
1995
2004
  " cpac claude [--config PATH] [--] [claude args...]",
1996
2005
  " cpac opencode [--config PATH] [--] [opencode args...]",
1997
- " cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--classifier [M]] [--reset] [--config PATH]",
2006
+ " cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--reset] [--config PATH]",
1998
2007
  " cpac pi <install|uninstall|status> [--config PATH]",
1999
2008
  " cpac kimi <install|uninstall|status> [--config PATH]",
2000
2009
  " cpac detect [--json] [--home PATH]",
@@ -2089,7 +2098,7 @@ function parseArgs(args) {
2089
2098
  if (args[0] === "claude-models") {
2090
2099
  if (args.includes("-h") || args.includes("--help")) {
2091
2100
  console.log([
2092
- "Usage: cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--classifier [M]] [--reset] [--config PATH]",
2101
+ "Usage: cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--reset] [--config PATH]",
2093
2102
  "",
2094
2103
  "Sets the model Claude Code uses per task tier. Saved to the config file",
2095
2104
  "and applied on every `cpac claude` launch:",
@@ -2097,8 +2106,9 @@ function parseArgs(args) {
2097
2106
  " --sonnet main conversation default tier (ANTHROPIC_DEFAULT_SONNET_MODEL)",
2098
2107
  " --haiku high-frequency small tasks: titles, summaries, background",
2099
2108
  " (ANTHROPIC_DEFAULT_HAIKU_MODEL + ANTHROPIC_SMALL_FAST_MODEL)",
2100
- " --classifier auto-mode approval classifier (CLAUDE_CODE_AUTO_MODE_MODEL);",
2101
- " unset follows Claude Code's built-in default",
2109
+ "",
2110
+ "Unset slots auto-select from the catalog: same-family claude model first,",
2111
+ "then gemini → grok → luna → first model (opus stays unset without a match).",
2102
2112
  "",
2103
2113
  "A slot without a value opens an arrow-key picker over the CPA catalog;",
2104
2114
  "--reset clears all overrides back to catalog auto-selection.",
@@ -2122,8 +2132,7 @@ function parseArgs(args) {
2122
2132
  }
2123
2133
  else if (arg === "--opus" ||
2124
2134
  arg === "--sonnet" ||
2125
- arg === "--haiku" ||
2126
- arg === "--classifier") {
2135
+ arg === "--haiku") {
2127
2136
  const slot = arg.slice(2);
2128
2137
  const value = args[index + 1];
2129
2138
  if (value !== undefined && !value.startsWith("-")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {