@yhong91/cpac 0.1.13 → 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
@@ -10,7 +10,7 @@ CPAC 将 Codex 和 Claude Code 接入远端 CLIProxyAPI(CPA):
10
10
  ## 要求
11
11
 
12
12
  - [Node.js](https://nodejs.org/) 20 或更新版本(npm 随 Node 安装)
13
- - 已安装需要使用的 agent:`codex` 或 `claude`
13
+ - 已安装需要使用的 agent:`codex`、`claude`、`pi`、`kimi` 或 `opencode`
14
14
  - 有权访问的 CPA API key
15
15
 
16
16
  ## 安装
@@ -22,15 +22,77 @@ npm install -g @yhong91/cpac
22
22
  cpac
23
23
  ```
24
24
 
25
- 开发目录中安装:
25
+ ## 快速上手
26
26
 
27
27
  ```bash
28
- npm install
29
- npm run build
30
- npm install -g .
28
+ # 1. 安装
29
+ npm install -g @yhong91/cpac
30
+
31
+ # 2. 配置密钥:无参数运行进入交互引导,自动写入 shell 启动文件;
32
+ # 也可手动写入(zsh 示例):
31
33
  cpac
34
+ echo 'export CPA_API_KEY="<你的key>"' >> ~/.zshrc && source ~/.zshrc
35
+
36
+ # 3. 验证
37
+ cpac status
38
+
39
+ # 4a. 持久注入(长期生效,写入 agent 配置,可完整恢复)
40
+ cpac install --all # 注入所有检测到的目标(codex/pi/kimi)
41
+ cpac install --target codex # 只注入 Codex(默认开启 multi-agent v2)
42
+ cpac install --target codex --v2_models # 先多选 spawn_agent 模型再注入
43
+ cpac uninstall --target codex # 字节级恢复原配置
44
+
45
+ # 4b. 临时接管(退出无痕,不写任何配置文件)
46
+ cpac claude # Claude Code 走 CPA
47
+ cpac opencode # opencode 走 CPA(模型选 cpac/<模型名>)
48
+
49
+ # 5. 可选:Claude 模型槽位覆盖(持久缓存;默认自动选择已覆盖多数场景,见下文)
50
+ cpac claude-models # 查看当前覆盖
51
+ cpac claude-models --haiku # 下拉选择 haiku 槽模型
52
+
53
+ # 6. 升级
54
+ cpac upgrade --check
55
+ npm install -g @yhong91/cpac@latest
56
+ ```
57
+
58
+ 各命令的完整参数与行为见下方对应章节。
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
+ }
32
81
  ```
33
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
+
34
96
  ## 首次引导
35
97
 
36
98
  无参数运行 `cpac` 会显示当前 CPA、密钥状态和可用命令:
@@ -140,11 +202,14 @@ CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1
140
202
  cpac claude-models # 查看当前覆盖
141
203
  cpac claude-models --opus claude-opus-4-8 --haiku glm-5.3 # 设置/合并
142
204
  cpac claude-models --haiku # 不带值:arrow-key 下拉选择
143
- cpac claude-models --classifier claude-sonnet-4-6 # Auto mode 审批分类器模型
144
205
  cpac claude-models --reset # 恢复自动选择
145
206
  ```
146
207
 
147
- 所有设定写入配置文件持久缓存,每次 `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)。
148
213
 
149
214
  你自己 export 同名环境变量时以你为准。
150
215
 
@@ -319,40 +384,3 @@ export CPAC_CONFIG=/path/to/cpac.json
319
384
  - shell 启动文件和 Codex 配置均通过同目录临时文件原子替换。
320
385
  - CPA 请求失败、catalog 无效或 key 缺失时,首次注入不会修改 Codex 配置。
321
386
  - `restore` 只删除 CPAC 自有的 state、backup 和 catalog,不删除 `state_dir` 中的其他文件。
322
-
323
- ## 开发
324
-
325
- ```bash
326
- npm install
327
- npm test
328
- npm run check
329
- npm run build
330
- npm run check:pi
331
- npm run pack:check
332
- ```
333
-
334
- 发布包的 CLI 入口为 `dist/cpac.js`。`dist/` 是构建产物,不应手工编辑。
335
-
336
- ## npm 发布
337
-
338
- `.github/workflows/publish.yml` 在推送到 `main` 或手动触发时执行测试、类型检查、构建并发布到 npm。发布前会确认 `package.json` 中的版本尚未存在;因此每次计划发布的 push 都必须先提升版本。普通 PR 仅运行 CI。
339
-
340
- 首次发布前:
341
-
342
- 1. 在 npm 创建账号并启用 2FA。
343
- 2. 因为新包尚不存在,先创建允许 publish 且可绕过 2FA 的 npm granular access token,在仓库的 **Settings → Secrets and variables → Actions** 添加 `NPM_TOKEN`,然后手动运行 `Publish to npm` workflow 完成首次发布。
344
- 3. 首次发布后,在 npm 包 `@yhong91/cpac` 的设置中添加 Trusted Publisher:GitHub 用户 `yhong91`、仓库 `cpac`、workflow 文件 `publish.yml`,允许 `npm publish`。
345
- 4. Trusted Publisher 生效后删除 GitHub 的 `NPM_TOKEN`;后续 workflow 自动使用 OIDC。
346
-
347
- 正常发布新版本:
348
-
349
- ```bash
350
- npm version patch --no-git-tag-version # 或 minor / major
351
- git add package.json package-lock.json
352
- git commit -m 'Release 0.1.1'
353
- git push
354
- ```
355
-
356
- 也可以手工修改并提交两个版本文件。push 到 `main` 后 Action 自动发布;若忘记提升版本,发布 workflow 会明确失败,不会覆盖 npm 已发布版本。
357
-
358
- 仓库当前为 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.13",
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": {