@yhong91/cpac 0.1.23 → 0.1.25
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 +28 -47
- package/dist/cpac.js +589 -186
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,9 +47,9 @@ cpac uninstall --target codex # 字节级恢复原配置
|
|
|
47
47
|
cpac claude # Claude Code 走 CPA
|
|
48
48
|
cpac opencode # opencode 走 CPA(模型选 cpac/<模型名>)
|
|
49
49
|
|
|
50
|
-
# 5. 可选:
|
|
51
|
-
cpac
|
|
52
|
-
cpac claude
|
|
50
|
+
# 5. 可选:Agent 配置向导与模型选择
|
|
51
|
+
cpac codex setup # 进入 Codex 配置向导(Subagent 模型多选、最大上下文、Multi-Agent V2)
|
|
52
|
+
cpac claude setup # 进入 Claude Code 模型向导(Sonnet、Haiku、Opus 槽位选择)
|
|
53
53
|
|
|
54
54
|
# 6. 升级(装上新版本后自动 sync 已接入的 agent)
|
|
55
55
|
cpac upgrade
|
|
@@ -59,7 +59,7 @@ cpac upgrade
|
|
|
59
59
|
|
|
60
60
|
## Agent 自动化安装指南
|
|
61
61
|
|
|
62
|
-
本节面向 AI agent
|
|
62
|
+
本节面向 AI agent:按顺序执行即可完成安装与配置,**全程无交互**。注意:`cpac codex --config` 和 `cpac claude --config` 的交互向导是 TTY-only,agent 必须走下方直接写配置 JSON 的路径。
|
|
63
63
|
|
|
64
64
|
1. **检查前置**:`node --version` ≥ 20;确认用户需要哪些 agent(`codex`/`claude`/`pi`/`kimi`/`opencode` 在 PATH 上)。向用户索取 CPA API key。
|
|
65
65
|
|
|
@@ -72,7 +72,7 @@ source ~/.zshrc
|
|
|
72
72
|
cpac status # 验证:CPA_API_KEY 应显示 configured
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
1. **写入模型配置**(创建或合并 `~/.config/cpac/config.json`,已有键保留):
|
|
76
76
|
|
|
77
77
|
```json
|
|
78
78
|
{
|
|
@@ -80,20 +80,23 @@ cpac status # 验证:CPA_API_KEY 应显示 configured
|
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
Claude 槽位(opus/sonnet/haiku)通常不用配:cpac 启动时自动选择——同家族 claude 模型优先,否则 gemini → grok → luna → 目录第一个;opus 无匹配则不设。这个链条在常见目录上正好给出理想结果:sonnet 落在 claude-sonnet-*(它同时充当 Auto mode 分类器的替身,避免分类请求失败后回退到昂贵的会话主模型),haiku 落在快的 gemini flash。只有目录奇怪时才用 `cpac claude
|
|
83
|
+
Claude 槽位(opus/sonnet/haiku)通常不用配:cpac 启动时自动选择——同家族 claude 模型优先,否则 gemini → grok → luna → 目录第一个;opus 无匹配则不设。这个链条在常见目录上正好给出理想结果:sonnet 落在 claude-sonnet-*(它同时充当 Auto mode 分类器的替身,避免分类请求失败后回退到昂贵的会话主模型),haiku 落在快的 gemini flash。只有目录奇怪时才用 `cpac claude setup` 交互式配置。codex spawn subagent 取三个 GPT + gemini + grok 五席(顺序即优先级,Codex 只广告 catalog 前 5),可通过 `cpac codex setup` 交互式配置。候选替换:`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
84
|
|
|
85
|
-
|
|
85
|
+
1. **持久注入**(只对检测到的目标;codex 会应用步骤 3 的 spawn_models):
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
88
|
cpac install --all
|
|
89
89
|
cpac status # 验证:各 target 显示 ready/injected
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
1. **统一启动与配置**:
|
|
93
|
+
- 启动:`cpac <codex|claude|kimi|grok|pi|opencode> [args...]`(所有原生参数如 `-c` 等原样透明传递)
|
|
94
|
+
- 配置:`cpac <codex|claude> setup`(交互式配置模型与特性)
|
|
95
|
+
- 还原:`cpac <agent> clear`(还原或重置配置)
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
2. **已安装后升级**:npm 发布新版本后,等 publish 成功再执行 `cpac upgrade`。它会按需 `npm install -g`,再对已接入的 Codex / Pi / Kimi / Grok 做 `sync`。不要拆成 `npm install -g @yhong91/cpac` 再手动 `cpac sync`。只刷新配置、不升 CLI 时才用 `cpac sync`。
|
|
95
98
|
|
|
96
|
-
失败处理:任何一步报错时先跑 `cpac status`;密钥未配置回到步骤 2;注入后异常用 `cpac
|
|
99
|
+
失败处理:任何一步报错时先跑 `cpac status`;密钥未配置回到步骤 2;注入后异常用 `cpac <agent> clear` 或 `cpac restore --target <id>` 恢复原配置。
|
|
97
100
|
|
|
98
101
|
## 首次引导
|
|
99
102
|
|
|
@@ -179,34 +182,20 @@ cpac version
|
|
|
179
182
|
|
|
180
183
|
### Claude Code
|
|
181
184
|
|
|
182
|
-
通过 CPA 启动 Claude Code
|
|
185
|
+
通过 CPA 启动 Claude Code,后续所有原生参数原样传递(如 `-c` 继续会话、`--model` 等):
|
|
183
186
|
|
|
184
187
|
```bash
|
|
185
188
|
cpac claude
|
|
189
|
+
cpac claude -c # 继续上一次对话(原生 -c 原样直通)
|
|
186
190
|
cpac claude --model claude-sonnet-4-5
|
|
187
191
|
cpac claude -- -p "检查当前项目"
|
|
188
192
|
```
|
|
189
193
|
|
|
190
|
-
|
|
194
|
+
#### 配置与清除
|
|
191
195
|
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
|
|
196
|
-
CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
同时清除可能覆盖网关选择的 `ANTHROPIC_API_KEY` 和 Claude Code 云 provider 环境变量。它不会修改 `~/.claude/`,并原样返回 Claude Code 的退出码。
|
|
200
|
-
|
|
201
|
-
代理开启 Claude Code 的 gateway model discovery:`/model` 选择器会列出 CPA 目录里的全部模型。claude 系模型直接使用原 id;其它模型以 `claude-cpac--<模型名>` 别名出现(例如 `claude-cpac--gpt-5.6-sol`),请求发出时由代理还原为真实模型名。
|
|
202
|
-
|
|
203
|
-
启动时还会把模型槽位(`ANTHROPIC_DEFAULT_OPUS/SONNET/HAIKU_MODEL` 及 `ANTHROPIC_SMALL_FAST_MODEL`)指向目录里的模型,避免 haiku/subagent 任务打到网关不认识的默认 id。默认自动选择(claude 系优先:按家族名匹配 opus/sonnet/haiku,否则最大窗口给 opus/sonnet、最小窗口给 haiku),可用命令覆盖:
|
|
204
|
-
|
|
205
|
-
```sh
|
|
206
|
-
cpac claude-models # 查看当前覆盖
|
|
207
|
-
cpac claude-models --opus claude-opus-4-8 --haiku glm-5.3 # 设置/合并
|
|
208
|
-
cpac claude-models --haiku # 不带值:arrow-key 下拉选择
|
|
209
|
-
cpac claude-models --reset # 恢复自动选择
|
|
196
|
+
```bash
|
|
197
|
+
cpac claude setup # 交互式依次配置 Sonnet / Haiku / Opus
|
|
198
|
+
cpac claude clear # 重置所有覆盖,恢复自动选择
|
|
210
199
|
```
|
|
211
200
|
|
|
212
201
|
未设置的槽位启动时自动选择:claude 池内同家族模型优先,否则按 gemini → grok → luna → 目录第一个回退;opus 无匹配则不设。
|
|
@@ -223,33 +212,25 @@ Claude Code 的发现协议不携带 context 信息,claude 前缀的未知模
|
|
|
223
212
|
|
|
224
213
|
### Codex
|
|
225
214
|
|
|
226
|
-
|
|
215
|
+
启动 Codex,后续所有原生参数原样传递(如 `-c` 动态覆盖配置、`exec` 等):
|
|
227
216
|
|
|
228
217
|
```bash
|
|
229
|
-
cpac
|
|
218
|
+
cpac codex # 自动确保注入和代理后拉起 Codex
|
|
219
|
+
cpac codex -c model="gpt-5" # 动态覆盖 Codex 配置(原生 -c 原样直通)
|
|
220
|
+
cpac codex exec "帮我写一个测试"
|
|
230
221
|
```
|
|
231
222
|
|
|
232
|
-
|
|
223
|
+
#### 配置与清除
|
|
233
224
|
|
|
234
225
|
```bash
|
|
235
|
-
cpac
|
|
226
|
+
cpac codex setup # 交互式配置 Subagent 候选模型、最大上下文和 Multi-Agent V2
|
|
227
|
+
cpac codex clear # 还原 Codex 原生配置并关闭代理
|
|
236
228
|
```
|
|
237
229
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
```text
|
|
241
|
-
codex: injected proxy=http://127.0.0.1:10101/v1 sha256=...
|
|
242
|
-
claude: ready
|
|
243
|
-
pi: ready
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
- **codex**:`native`(未注入)、`injected`(已注入,附代理地址和配置摘要)、`injected (loopback proxy stopped)` 或 `injected (config missing)`
|
|
247
|
-
- **claude**:`ready`(密钥已配置)或 `not configured`
|
|
248
|
-
- **pi**:`ready`(密钥已配置)或 `not configured`
|
|
249
|
-
|
|
250
|
-
恢复注入前的原配置:
|
|
230
|
+
#### 传统注入命令
|
|
251
231
|
|
|
252
232
|
```bash
|
|
233
|
+
cpac inject [--v2_off] [--v2_models] [--max_context] [--config PATH]
|
|
253
234
|
cpac restore
|
|
254
235
|
```
|
|
255
236
|
|
package/dist/cpac.js
CHANGED
|
@@ -161,8 +161,7 @@ export function loadConfig(path, useDefaultsIfMissing = false) {
|
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
163
|
const codexHome = expandUserPath(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
|
|
164
|
-
const codexConfig = resolve(expandUserPath(value.codex_config ||
|
|
165
|
-
join(codexHome, "config.toml")));
|
|
164
|
+
const codexConfig = resolve(expandUserPath(value.codex_config || join(codexHome, "config.toml")));
|
|
166
165
|
const stateDir = resolve(expandUserPath(value.state_dir || join(homedir(), ".cpac")));
|
|
167
166
|
const stateToConfig = relative(stateDir, codexConfig);
|
|
168
167
|
if ([parse(stateDir).root, resolve(homedir()), resolve(tmpdir())].includes(stateDir) ||
|
|
@@ -248,9 +247,7 @@ export async function fetchCatalog(cpaUrl, apiKey) {
|
|
|
248
247
|
if (!Array.isArray(models) || models.length === 0) {
|
|
249
248
|
throw new CPACError("CPA rich catalog must contain a non-empty models array");
|
|
250
249
|
}
|
|
251
|
-
if (models.some((model) => !objectValue(model) ||
|
|
252
|
-
typeof model.slug !== "string" ||
|
|
253
|
-
!model.slug.trim())) {
|
|
250
|
+
if (models.some((model) => !objectValue(model) || typeof model.slug !== "string" || !model.slug.trim())) {
|
|
254
251
|
throw new CPACError("CPA catalog contains a model without a slug");
|
|
255
252
|
}
|
|
256
253
|
return {
|
|
@@ -265,10 +262,15 @@ export async function fetchCatalog(cpaUrl, apiKey) {
|
|
|
265
262
|
// set auto_compact_token_limit at 90% (Codex's own convention), keeping
|
|
266
263
|
// compaction ahead of the hard ceiling.
|
|
267
264
|
export function liftContextWindows(bytes) {
|
|
268
|
-
|
|
265
|
+
let document;
|
|
266
|
+
try {
|
|
267
|
+
document = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
throw new CPACError("invalid CPA catalog");
|
|
271
|
+
}
|
|
269
272
|
for (const model of document.models) {
|
|
270
|
-
const maxWindow = typeof model.max_context_window === "number" &&
|
|
271
|
-
model.max_context_window > 0
|
|
273
|
+
const maxWindow = typeof model.max_context_window === "number" && model.max_context_window > 0
|
|
272
274
|
? Math.floor(model.max_context_window)
|
|
273
275
|
: 0;
|
|
274
276
|
const window = typeof model.context_window === "number"
|
|
@@ -284,7 +286,13 @@ export function liftContextWindows(bytes) {
|
|
|
284
286
|
// Move `order` slugs to the front of the rich catalog (stable for the rest),
|
|
285
287
|
// so the Codex client's first-5 spawn_agent advertisement picks them.
|
|
286
288
|
export function reorderCatalog(bytes, order) {
|
|
287
|
-
|
|
289
|
+
let document;
|
|
290
|
+
try {
|
|
291
|
+
document = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
throw new CPACError("invalid CPA catalog");
|
|
295
|
+
}
|
|
288
296
|
const rank = new Map(order.map((slug, index) => [slug, index]));
|
|
289
297
|
const models = document.models.map((model, index) => ({ model, index }));
|
|
290
298
|
models.sort((left, right) => {
|
|
@@ -304,7 +312,13 @@ export function reorderCatalog(bytes, order) {
|
|
|
304
312
|
export function saveSpawnModels(configPath, models) {
|
|
305
313
|
let document = {};
|
|
306
314
|
if (existsSync(configPath)) {
|
|
307
|
-
|
|
315
|
+
let parsed;
|
|
316
|
+
try {
|
|
317
|
+
parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
throw new CPACError("config must be a valid JSON object");
|
|
321
|
+
}
|
|
308
322
|
if (!objectValue(parsed))
|
|
309
323
|
throw new CPACError("config must be a JSON object");
|
|
310
324
|
document = parsed;
|
|
@@ -360,6 +374,9 @@ async function checkboxPicker(title, items, max) {
|
|
|
360
374
|
return;
|
|
361
375
|
}
|
|
362
376
|
if (key === "\r" || key === "\n") {
|
|
377
|
+
if (max === 1 && selected.size === 0) {
|
|
378
|
+
selected.add(cursor);
|
|
379
|
+
}
|
|
363
380
|
finish();
|
|
364
381
|
return;
|
|
365
382
|
}
|
|
@@ -392,7 +409,13 @@ export async function pickSpawnModels(config) {
|
|
|
392
409
|
if (!apiKey)
|
|
393
410
|
throw new CPACError(`environment variable ${config.api_key_env} is not set`);
|
|
394
411
|
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
395
|
-
|
|
412
|
+
let document;
|
|
413
|
+
try {
|
|
414
|
+
document = JSON.parse(new TextDecoder().decode(catalog.bytes));
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
throw new CPACError("invalid CPA catalog");
|
|
418
|
+
}
|
|
396
419
|
const slugs = document.models.map((model) => model.slug);
|
|
397
420
|
const picked = await checkboxPicker("Select spawn_agent models", slugs, MAX_SPAWN_MODELS);
|
|
398
421
|
if (picked.length === 0)
|
|
@@ -604,7 +627,8 @@ function hasAgentsMaxThreads(content) {
|
|
|
604
627
|
}
|
|
605
628
|
if (table === "agents" && /^\s*max_threads\s*=/.test(line))
|
|
606
629
|
return true;
|
|
607
|
-
if (table === "root" &&
|
|
630
|
+
if (table === "root" &&
|
|
631
|
+
/^\s*(?:agents|"agents"|'agents')\s*\.\s*max_threads\s*=/.test(line))
|
|
608
632
|
return true;
|
|
609
633
|
}
|
|
610
634
|
return false;
|
|
@@ -672,7 +696,7 @@ function localBrowserOrigin(value) {
|
|
|
672
696
|
return true;
|
|
673
697
|
try {
|
|
674
698
|
const hostname = new URL(value).hostname.toLowerCase();
|
|
675
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
699
|
+
return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
|
|
676
700
|
}
|
|
677
701
|
catch {
|
|
678
702
|
return false;
|
|
@@ -702,13 +726,18 @@ function responseHeaders(headers) {
|
|
|
702
726
|
return forwarded;
|
|
703
727
|
}
|
|
704
728
|
function upstreamUrl(cpaUrl, requestUrl) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
729
|
+
try {
|
|
730
|
+
const incoming = new URL(requestUrl, "http://127.0.0.1");
|
|
731
|
+
const suffix = incoming.pathname === "/v1"
|
|
732
|
+
? ""
|
|
733
|
+
: incoming.pathname.startsWith("/v1/")
|
|
734
|
+
? incoming.pathname.slice(3)
|
|
735
|
+
: incoming.pathname;
|
|
736
|
+
return new URL(`${apiBase(cpaUrl)}${suffix}${incoming.search}`);
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
throw new CPACError(`invalid upstream request URL: ${requestUrl}`);
|
|
740
|
+
}
|
|
712
741
|
}
|
|
713
742
|
export async function createLoopbackProxy(cpaUrl, apiKey, proxyId, port) {
|
|
714
743
|
const server = createServer((request, response) => {
|
|
@@ -804,7 +833,7 @@ function shouldMarkOneMillion(id, window, compactWindow) {
|
|
|
804
833
|
return false;
|
|
805
834
|
if (window >= ONE_MILLION)
|
|
806
835
|
return true;
|
|
807
|
-
return compactWindow !== undefined && window > 200_000 && window >= compactWindow;
|
|
836
|
+
return (compactWindow !== undefined && window > 200_000 && window >= compactWindow);
|
|
808
837
|
}
|
|
809
838
|
// Auto tier defaults. Within the claude-family pool (whole catalog when no
|
|
810
839
|
// claude model exists), opus matches its family name or stays unset; sonnet
|
|
@@ -1091,7 +1120,7 @@ async function startProxyProcess(config, apiKey) {
|
|
|
1091
1120
|
});
|
|
1092
1121
|
});
|
|
1093
1122
|
}
|
|
1094
|
-
async function stopProxyProcess(proxy) {
|
|
1123
|
+
export async function stopProxyProcess(proxy) {
|
|
1095
1124
|
if (!(await proxyIsHealthy(proxy)))
|
|
1096
1125
|
return;
|
|
1097
1126
|
try {
|
|
@@ -1105,7 +1134,7 @@ async function stopProxyProcess(proxy) {
|
|
|
1105
1134
|
// The server may close the connection while shutting down.
|
|
1106
1135
|
}
|
|
1107
1136
|
}
|
|
1108
|
-
function readState(stateDir) {
|
|
1137
|
+
export function readState(stateDir) {
|
|
1109
1138
|
const path = join(stateDir, "state.json");
|
|
1110
1139
|
if (!existsSync(path))
|
|
1111
1140
|
return null;
|
|
@@ -1151,7 +1180,7 @@ function readState(stateDir) {
|
|
|
1151
1180
|
}
|
|
1152
1181
|
return value;
|
|
1153
1182
|
}
|
|
1154
|
-
function stateProxy(state) {
|
|
1183
|
+
export function stateProxy(state) {
|
|
1155
1184
|
return state.proxy_id && state.proxy_pid && state.proxy_port
|
|
1156
1185
|
? { id: state.proxy_id, pid: state.proxy_pid, port: state.proxy_port }
|
|
1157
1186
|
: null;
|
|
@@ -1226,9 +1255,7 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
1226
1255
|
existed = existsSync(config.codex_config);
|
|
1227
1256
|
try {
|
|
1228
1257
|
original = existed ? readFileSync(config.codex_config) : new Uint8Array();
|
|
1229
|
-
originalMode = existed
|
|
1230
|
-
? statSync(config.codex_config).mode & 0o7777
|
|
1231
|
-
: 0o600;
|
|
1258
|
+
originalMode = existed ? statSync(config.codex_config).mode & 0o7777 : 0o600;
|
|
1232
1259
|
}
|
|
1233
1260
|
catch (error) {
|
|
1234
1261
|
throw new CPACError(`cannot read Codex config: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1368,15 +1395,9 @@ export async function status(config) {
|
|
|
1368
1395
|
const apiKey = process.env[config.api_key_env]?.trim();
|
|
1369
1396
|
const keyConfigured = !!apiKey;
|
|
1370
1397
|
const state = readState(config.state_dir);
|
|
1371
|
-
if (
|
|
1372
|
-
console.log("codex: native");
|
|
1373
|
-
}
|
|
1374
|
-
else {
|
|
1398
|
+
if (state) {
|
|
1375
1399
|
originalBytes(config.state_dir, state, config.codex_config);
|
|
1376
|
-
if (
|
|
1377
|
-
console.log(`codex: injected (config missing: ${config.codex_config})`);
|
|
1378
|
-
}
|
|
1379
|
-
else {
|
|
1400
|
+
if (existsSync(config.codex_config)) {
|
|
1380
1401
|
const proxy = stateProxy(state);
|
|
1381
1402
|
if (!proxy || !(await proxyIsHealthy(proxy))) {
|
|
1382
1403
|
console.log(`codex: injected (loopback proxy stopped: ${config.codex_config})`);
|
|
@@ -1395,12 +1416,20 @@ export async function status(config) {
|
|
|
1395
1416
|
// Unreadable config: the injected line above already reports it.
|
|
1396
1417
|
}
|
|
1397
1418
|
}
|
|
1419
|
+
else {
|
|
1420
|
+
console.log(`codex: injected (config missing: ${config.codex_config})`);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
console.log("codex: native");
|
|
1398
1425
|
}
|
|
1399
1426
|
console.log(`claude: ${keyConfigured ? "ready" : "not configured"}`);
|
|
1400
1427
|
const piInstalled = isPiExtensionInstalled();
|
|
1401
|
-
console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac pi
|
|
1428
|
+
console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac install --target pi") : "not configured"}`);
|
|
1402
1429
|
const kimiInstalled = isKimiConfigInstalled();
|
|
1403
|
-
console.log(`kimi: ${keyConfigured ? (kimiInstalled ? "ready" : "config not installed; run: cpac kimi
|
|
1430
|
+
console.log(`kimi: ${keyConfigured ? (kimiInstalled ? "ready" : "config not installed; run: cpac install --target kimi") : "not configured"}`);
|
|
1431
|
+
const grokInstalled = isGrokConfigInstalled();
|
|
1432
|
+
console.log(`grok: ${keyConfigured ? (grokInstalled ? "ready" : "config not installed; run: cpac install --target grok") : "not configured"}`);
|
|
1404
1433
|
if (!state)
|
|
1405
1434
|
return 1;
|
|
1406
1435
|
const proxy = stateProxy(state);
|
|
@@ -1463,7 +1492,7 @@ export function saveApiKeyExport(profile, name, apiKey) {
|
|
|
1463
1492
|
atomicWrite(profile, Buffer.from(content), mode);
|
|
1464
1493
|
}
|
|
1465
1494
|
async function guide(config) {
|
|
1466
|
-
console.log(`CPA Companion\n\nCPA: ${config.cpa_url}\nCPA_API_KEY: ${process.env[config.api_key_env]?.trim() ? "configured" : "not configured"}\n\nCommands:\n cpac
|
|
1495
|
+
console.log(`CPA Companion\n\nCPA: ${config.cpa_url}\nCPA_API_KEY: ${process.env[config.api_key_env]?.trim() ? "configured" : "not configured"}\n\nCommands:\n cpac <agent> [args...] Launch an agent (codex, kimi, grok, pi, claude, opencode) through CPA\n cpac inject Inject CPA into Codex and start its loopback proxy\n cpac proxy Run the injected loopback proxy in the foreground\n cpac status Show agent support status (Codex, Claude, Pi, Kimi, Grok)\n cpac restore Restore the original Codex config\n cpac detect Show supported targets and install status\n cpac install Install CPA integration into detected targets\n cpac sync Refresh installed Codex/Pi/Kimi/Grok injections from CPA\n cpac uninstall Remove CPA integration from installed targets\n cpac upgrade Update cpac from npm and sync installed agents\n cpac --help Show command usage`);
|
|
1467
1496
|
if (process.env[config.api_key_env]?.trim())
|
|
1468
1497
|
return 0;
|
|
1469
1498
|
const apiKey = await promptSecret(config.api_key_env);
|
|
@@ -1484,7 +1513,13 @@ export async function runOpencode(config, args, executable = "opencode") {
|
|
|
1484
1513
|
if (!apiKey)
|
|
1485
1514
|
throw new CPACError(`environment variable ${config.api_key_env} is not set`);
|
|
1486
1515
|
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
1487
|
-
|
|
1516
|
+
let document;
|
|
1517
|
+
try {
|
|
1518
|
+
document = JSON.parse(new TextDecoder().decode(catalog.bytes));
|
|
1519
|
+
}
|
|
1520
|
+
catch {
|
|
1521
|
+
throw new CPACError("invalid CPA catalog");
|
|
1522
|
+
}
|
|
1488
1523
|
const models = {};
|
|
1489
1524
|
for (const row of document.models) {
|
|
1490
1525
|
const id = catalogModelId(row);
|
|
@@ -1493,14 +1528,15 @@ export async function runOpencode(config, args, executable = "opencode") {
|
|
|
1493
1528
|
const window = typeof row.context_window === "number" && row.context_window > 0
|
|
1494
1529
|
? Math.floor(row.context_window)
|
|
1495
1530
|
: 0;
|
|
1496
|
-
models[id] =
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1531
|
+
models[id] =
|
|
1532
|
+
window > 0
|
|
1533
|
+
? {
|
|
1534
|
+
limit: {
|
|
1535
|
+
context: window,
|
|
1536
|
+
output: Math.min(OPENCODE_OUTPUT_BUDGET, window),
|
|
1537
|
+
},
|
|
1538
|
+
}
|
|
1539
|
+
: {};
|
|
1504
1540
|
}
|
|
1505
1541
|
const content = JSON.stringify({
|
|
1506
1542
|
provider: {
|
|
@@ -1525,24 +1561,119 @@ export async function runOpencode(config, args, executable = "opencode") {
|
|
|
1525
1561
|
child.once("close", (code) => resolve(code ?? 1));
|
|
1526
1562
|
});
|
|
1527
1563
|
}
|
|
1528
|
-
export async function
|
|
1529
|
-
if (
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1564
|
+
export async function runCodexConfig(config, configPath, options = {}) {
|
|
1565
|
+
if (options.reset) {
|
|
1566
|
+
let raw = {};
|
|
1567
|
+
try {
|
|
1568
|
+
const file = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1569
|
+
if (!objectValue(file))
|
|
1570
|
+
throw new CPACError("config must be a JSON object");
|
|
1571
|
+
raw = file;
|
|
1572
|
+
}
|
|
1573
|
+
catch (error) {
|
|
1574
|
+
if (error.code !== "ENOENT") {
|
|
1575
|
+
if (error instanceof CPACError)
|
|
1576
|
+
throw error;
|
|
1577
|
+
throw new CPACError(`cannot read config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
delete raw.spawn_models;
|
|
1581
|
+
atomicWrite(configPath, Buffer.from(`${JSON.stringify(raw, null, 2)}\n`));
|
|
1582
|
+
await runRestore(config, ["codex"], { all: false, dryRun: false });
|
|
1583
|
+
console.log("Codex configuration reset to default");
|
|
1584
|
+
return 0;
|
|
1585
|
+
}
|
|
1586
|
+
if (options.interactive && (!process.stdin.isTTY || !process.stderr.isTTY)) {
|
|
1587
|
+
console.log(config.spawn_models
|
|
1588
|
+
? JSON.stringify(config.spawn_models, null, 2)
|
|
1589
|
+
: "spawn_models not set");
|
|
1590
|
+
return 0;
|
|
1591
|
+
}
|
|
1592
|
+
let spawnModels = config.spawn_models;
|
|
1593
|
+
let v2Off = options.v2Off ?? false;
|
|
1594
|
+
let maxContext = options.maxContext ?? false;
|
|
1595
|
+
if (options.interactive || options.v2Models) {
|
|
1596
|
+
const picked = await pickSpawnModels(config);
|
|
1597
|
+
if (picked.length > 0) {
|
|
1598
|
+
spawnModels = picked;
|
|
1599
|
+
saveSpawnModels(configPath, picked);
|
|
1600
|
+
console.log(`spawn_models saved to ${configPath}: ${picked.join(", ")}`);
|
|
1601
|
+
}
|
|
1602
|
+
if (options.interactive) {
|
|
1603
|
+
const [maxCtxChoice] = await checkboxPicker("Codex Context Window Size:", [
|
|
1604
|
+
"Standard (default input budget, e.g. 200K~272K)",
|
|
1605
|
+
"Max Context Window (lift to full upper bound, e.g. 921K~1M, 90% auto-compact limit)",
|
|
1606
|
+
], 1);
|
|
1607
|
+
maxContext = maxCtxChoice?.startsWith("Max Context") ?? false;
|
|
1608
|
+
const [v2Choice] = await checkboxPicker("Codex Multi-Agent V2 mode:", ["Enabled (Default)", "Disabled (--v2_off)"], 1);
|
|
1609
|
+
v2Off = v2Choice === "Disabled (--v2_off)";
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
const updatedConfig = { ...config, spawn_models: spawnModels };
|
|
1613
|
+
await runInstall(updatedConfig, ["codex"], {
|
|
1614
|
+
all: false,
|
|
1615
|
+
dryRun: false,
|
|
1616
|
+
force: true,
|
|
1617
|
+
v2Off,
|
|
1618
|
+
maxContext,
|
|
1619
|
+
});
|
|
1620
|
+
console.log("Codex configuration updated and synced.");
|
|
1621
|
+
return 0;
|
|
1622
|
+
}
|
|
1623
|
+
export async function runClaudeConfig(config, configPath, options = {}) {
|
|
1624
|
+
if (options.reset) {
|
|
1625
|
+
let raw = {};
|
|
1626
|
+
try {
|
|
1627
|
+
const file = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1628
|
+
if (!objectValue(file))
|
|
1629
|
+
throw new CPACError("config must be a JSON object");
|
|
1630
|
+
raw = file;
|
|
1631
|
+
}
|
|
1632
|
+
catch (error) {
|
|
1633
|
+
if (error.code !== "ENOENT") {
|
|
1634
|
+
if (error instanceof CPACError)
|
|
1635
|
+
throw error;
|
|
1636
|
+
throw new CPACError(`cannot read config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
delete raw.claude_models;
|
|
1640
|
+
atomicWrite(configPath, Buffer.from(`${JSON.stringify(raw, null, 2)}\n`));
|
|
1641
|
+
console.log("Claude Code tier models reset to CPA catalog auto-selection");
|
|
1642
|
+
return 0;
|
|
1643
|
+
}
|
|
1644
|
+
if (options.interactive && (!process.stdin.isTTY || !process.stderr.isTTY)) {
|
|
1645
|
+
console.log(config.claude_models
|
|
1646
|
+
? JSON.stringify(config.claude_models, null, 2)
|
|
1647
|
+
: "claude_models not set; cpac claude auto-picks tier models from the CPA catalog");
|
|
1648
|
+
return 0;
|
|
1649
|
+
}
|
|
1650
|
+
const models = { ...(options.models || {}) };
|
|
1651
|
+
const shouldPick = options.interactive || (options.pick && options.pick.length > 0);
|
|
1652
|
+
if (shouldPick) {
|
|
1653
|
+
const apiKey = await resolveApiKey(config.api_key_env);
|
|
1534
1654
|
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1655
|
+
let document;
|
|
1656
|
+
try {
|
|
1657
|
+
document = JSON.parse(new TextDecoder().decode(catalog.bytes));
|
|
1658
|
+
}
|
|
1659
|
+
catch {
|
|
1660
|
+
throw new CPACError("CPA catalog response is not valid JSON");
|
|
1661
|
+
}
|
|
1662
|
+
const slugs = (document.models ?? [])
|
|
1663
|
+
.map((model) => (typeof model?.slug === "string" ? model.slug : ""))
|
|
1664
|
+
.filter(Boolean);
|
|
1665
|
+
const slotsToPick = options.pick && options.pick.length > 0
|
|
1666
|
+
? options.pick
|
|
1667
|
+
: ["sonnet", "haiku", "opus"];
|
|
1668
|
+
for (const slot of slotsToPick) {
|
|
1669
|
+
const choices = slot === "sonnet" ? slugs : ["(auto-select from catalog)", ...slugs];
|
|
1670
|
+
const [picked] = await checkboxPicker(`Select Claude Code ${slot.toUpperCase()} tier model:`, choices, 1);
|
|
1671
|
+
if (picked && picked !== "(auto-select from catalog)") {
|
|
1672
|
+
models[slot] = picked;
|
|
1673
|
+
}
|
|
1542
1674
|
}
|
|
1543
1675
|
}
|
|
1544
|
-
if (!
|
|
1545
|
-
const config = loadConfig(parsed.configPath, true);
|
|
1676
|
+
if (!Object.keys(models).length && !shouldPick) {
|
|
1546
1677
|
console.log(config.claude_models
|
|
1547
1678
|
? JSON.stringify(config.claude_models, null, 2)
|
|
1548
1679
|
: "claude_models not set; cpac claude auto-picks tier models from the CPA catalog");
|
|
@@ -1550,7 +1681,7 @@ export async function runClaudeModels(parsed) {
|
|
|
1550
1681
|
}
|
|
1551
1682
|
let raw = {};
|
|
1552
1683
|
try {
|
|
1553
|
-
const file = JSON.parse(readFileSync(
|
|
1684
|
+
const file = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1554
1685
|
if (!objectValue(file))
|
|
1555
1686
|
throw new CPACError("config must be a JSON object");
|
|
1556
1687
|
raw = file;
|
|
@@ -1562,23 +1693,25 @@ export async function runClaudeModels(parsed) {
|
|
|
1562
1693
|
throw new CPACError(`cannot read config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1563
1694
|
}
|
|
1564
1695
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
raw.claude_models = existing;
|
|
1574
|
-
}
|
|
1575
|
-
atomicWrite(parsed.configPath, Buffer.from(`${JSON.stringify(raw, null, 2)}\n`));
|
|
1576
|
-
loadConfig(parsed.configPath);
|
|
1577
|
-
console.log(parsed.reset
|
|
1578
|
-
? "claude_models cleared"
|
|
1579
|
-
: `claude_models updated: ${JSON.stringify(raw.claude_models)}`);
|
|
1696
|
+
const existing = objectValue(raw.claude_models)
|
|
1697
|
+
? { ...raw.claude_models }
|
|
1698
|
+
: {};
|
|
1699
|
+
Object.assign(existing, models);
|
|
1700
|
+
raw.claude_models = existing;
|
|
1701
|
+
atomicWrite(configPath, Buffer.from(`${JSON.stringify(raw, null, 2)}\n`));
|
|
1702
|
+
loadConfig(configPath);
|
|
1703
|
+
console.log(`Claude Code tier models updated in ${configPath}:\n${JSON.stringify(raw.claude_models, null, 2)}`);
|
|
1580
1704
|
return 0;
|
|
1581
1705
|
}
|
|
1706
|
+
export async function runClaudeModels(parsed) {
|
|
1707
|
+
const config = loadConfig(parsed.configPath, true);
|
|
1708
|
+
return await runClaudeConfig(config, parsed.configPath, {
|
|
1709
|
+
models: parsed.models,
|
|
1710
|
+
pick: parsed.pick,
|
|
1711
|
+
reset: parsed.reset,
|
|
1712
|
+
interactive: false,
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1582
1715
|
export async function runClaude(config, args, executable = "claude") {
|
|
1583
1716
|
const apiKey = await resolveApiKey(config.api_key_env);
|
|
1584
1717
|
const proxy = await createClaudeProxy(config.cpa_url, apiKey);
|
|
@@ -1618,7 +1751,8 @@ export async function runClaude(config, args, executable = "claude") {
|
|
|
1618
1751
|
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
|
|
1619
1752
|
CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: "1",
|
|
1620
1753
|
};
|
|
1621
|
-
if (compactWindow !== undefined &&
|
|
1754
|
+
if (compactWindow !== undefined &&
|
|
1755
|
+
!(Number(process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW) > 0)) {
|
|
1622
1756
|
env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = String(compactWindow);
|
|
1623
1757
|
}
|
|
1624
1758
|
const setSlot = (name, value) => {
|
|
@@ -1654,6 +1788,53 @@ export async function runClaude(config, args, executable = "claude") {
|
|
|
1654
1788
|
});
|
|
1655
1789
|
});
|
|
1656
1790
|
}
|
|
1791
|
+
export async function runTargetLauncher(config, targetId, args, executable = targetId, options = {}) {
|
|
1792
|
+
const target = TARGETS.find((t) => t.id === targetId);
|
|
1793
|
+
if (!target)
|
|
1794
|
+
throw new CPACError(`unknown target: ${targetId}`);
|
|
1795
|
+
if (["kimi", "grok"].includes(targetId)) {
|
|
1796
|
+
const state = readState(config.state_dir);
|
|
1797
|
+
const proxy = state ? stateProxy(state) : null;
|
|
1798
|
+
if (!proxy || !(await proxyIsHealthy(proxy))) {
|
|
1799
|
+
const apiKey = await resolveApiKey(config.api_key_env);
|
|
1800
|
+
const started = await startProxyProcess(config, apiKey);
|
|
1801
|
+
const fingerprint = proxyFingerprint(config, apiKey);
|
|
1802
|
+
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
1803
|
+
const catalogPath = join(config.state_dir, "codex-models.json");
|
|
1804
|
+
mkdirSync(config.state_dir, { recursive: true, mode: 0o700 });
|
|
1805
|
+
atomicWrite(catalogPath, catalog.bytes);
|
|
1806
|
+
const originalMode = existsSync(config.codex_config)
|
|
1807
|
+
? statSync(config.codex_config).mode & 0o7777
|
|
1808
|
+
: 0o600;
|
|
1809
|
+
atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, state ? state.config_existed : false, state ? state.config_mode : originalMode, started, fingerprint));
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (target.installed(config)) {
|
|
1813
|
+
try {
|
|
1814
|
+
await runSync(config, [targetId], {
|
|
1815
|
+
all: false,
|
|
1816
|
+
dryRun: false,
|
|
1817
|
+
v2Off: options.v2Off,
|
|
1818
|
+
maxContext: options.maxContext,
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
catch (error) {
|
|
1822
|
+
console.warn(`warning: auto-sync failed (${error instanceof Error ? error.message : String(error)}); launching with existing config`);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
else {
|
|
1826
|
+
await target.install(config, false, options.v2Off, options.maxContext);
|
|
1827
|
+
}
|
|
1828
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
1829
|
+
const child = spawn(executable, args, { stdio: "inherit" });
|
|
1830
|
+
child.once("error", (error) => {
|
|
1831
|
+
rejectPromise(new CPACError(error instanceof Error && "code" in error && error.code === "ENOENT"
|
|
1832
|
+
? `${executable} not found`
|
|
1833
|
+
: `cannot start ${executable}: ${error.message}`));
|
|
1834
|
+
});
|
|
1835
|
+
child.once("close", (code) => resolvePromise(code ?? 1));
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1657
1838
|
export async function runProxy(config) {
|
|
1658
1839
|
const apiKey = await resolveApiKey(config.api_key_env);
|
|
1659
1840
|
const state = readState(config.state_dir);
|
|
@@ -1713,22 +1894,6 @@ export async function uninstallPiExtension() {
|
|
|
1713
1894
|
unlinkSync(target);
|
|
1714
1895
|
console.log(`Removed Pi extension: ${target}`);
|
|
1715
1896
|
}
|
|
1716
|
-
export async function runPi(config, action) {
|
|
1717
|
-
if (action === "install") {
|
|
1718
|
-
await installPiExtension(config);
|
|
1719
|
-
return 0;
|
|
1720
|
-
}
|
|
1721
|
-
if (action === "uninstall") {
|
|
1722
|
-
await uninstallPiExtension();
|
|
1723
|
-
return 0;
|
|
1724
|
-
}
|
|
1725
|
-
if (action === "status") {
|
|
1726
|
-
const installed = isPiExtensionInstalled();
|
|
1727
|
-
console.log(`Pi extension: ${installed ? "installed" : "not installed"}`);
|
|
1728
|
-
return installed ? 0 : 1;
|
|
1729
|
-
}
|
|
1730
|
-
throw new CPACError(`unknown pi action: ${action}; use install, uninstall, or status`);
|
|
1731
|
-
}
|
|
1732
1897
|
// Pre-write snapshot kept at <target>.cpac-backup (first version only) so a
|
|
1733
1898
|
// damaged config can be restored by hand even if uninstall cannot parse it.
|
|
1734
1899
|
function ensureCpacBackup(target) {
|
|
@@ -1748,7 +1913,8 @@ const KIMI_BLOCK_END = "# <<< CPAC Kimi <<<";
|
|
|
1748
1913
|
const kimiBlockRegex = new RegExp(`${KIMI_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${KIMI_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
|
|
1749
1914
|
export function isKimiConfigInstalled() {
|
|
1750
1915
|
try {
|
|
1751
|
-
|
|
1916
|
+
const content = readFileSync(kimiConfigPath(), "utf8");
|
|
1917
|
+
return (kimiBlockRegex.test(content) || /^\s*\[providers\.cpac\]\s*$/m.test(content));
|
|
1752
1918
|
}
|
|
1753
1919
|
catch {
|
|
1754
1920
|
return false;
|
|
@@ -1806,16 +1972,18 @@ const KIMI_REASONING_EFFORTS = new Set([
|
|
|
1806
1972
|
"ultra",
|
|
1807
1973
|
]);
|
|
1808
1974
|
function kimiSupportEfforts(row) {
|
|
1809
|
-
const levels = row.supported_reasoning_levels
|
|
1975
|
+
const levels = row.supported_reasoning_levels ??
|
|
1976
|
+
row.reasoning_effort_levels ??
|
|
1977
|
+
row.reasoning_levels;
|
|
1810
1978
|
if (!Array.isArray(levels))
|
|
1811
1979
|
return [];
|
|
1812
1980
|
const seen = new Set();
|
|
1813
1981
|
const efforts = [];
|
|
1814
1982
|
for (const level of levels) {
|
|
1815
1983
|
const effort = typeof level === "string"
|
|
1816
|
-
? level
|
|
1984
|
+
? level.toLowerCase()
|
|
1817
1985
|
: objectValue(level) && typeof level.effort === "string"
|
|
1818
|
-
? level.effort
|
|
1986
|
+
? level.effort.toLowerCase()
|
|
1819
1987
|
: undefined;
|
|
1820
1988
|
if (!effort || !KIMI_REASONING_EFFORTS.has(effort) || seen.has(effort))
|
|
1821
1989
|
continue;
|
|
@@ -1833,7 +2001,13 @@ function kimiInputHasImage(row) {
|
|
|
1833
2001
|
export async function installKimiConfig(config) {
|
|
1834
2002
|
const apiKey = await resolveApiKey(config.api_key_env);
|
|
1835
2003
|
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
1836
|
-
|
|
2004
|
+
let document;
|
|
2005
|
+
try {
|
|
2006
|
+
document = JSON.parse(new TextDecoder("utf-8").decode(catalog.bytes));
|
|
2007
|
+
}
|
|
2008
|
+
catch {
|
|
2009
|
+
throw new CPACError("invalid CPA catalog");
|
|
2010
|
+
}
|
|
1837
2011
|
const rows = catalogModelRows(document) ?? [];
|
|
1838
2012
|
if (rows.length === 0)
|
|
1839
2013
|
throw new CPACError("CPA catalog contains no models");
|
|
@@ -1871,9 +2045,13 @@ export async function installKimiConfig(config) {
|
|
|
1871
2045
|
}
|
|
1872
2046
|
if (efforts.length > 0) {
|
|
1873
2047
|
lines.push(`support_efforts = ${tomlStringArray(efforts)}`);
|
|
1874
|
-
const
|
|
1875
|
-
|
|
1876
|
-
|
|
2048
|
+
const rawDefaultEffort = (typeof row.default_reasoning_level === "string" &&
|
|
2049
|
+
row.default_reasoning_level) ||
|
|
2050
|
+
(typeof row.default_reasoning_effort === "string" &&
|
|
2051
|
+
row.default_reasoning_effort) ||
|
|
2052
|
+
(typeof row.default_effort === "string" && row.default_effort);
|
|
2053
|
+
const defaultEffort = rawDefaultEffort && efforts.includes(rawDefaultEffort.toLowerCase())
|
|
2054
|
+
? rawDefaultEffort.toLowerCase()
|
|
1877
2055
|
: undefined;
|
|
1878
2056
|
if (defaultEffort)
|
|
1879
2057
|
lines.push(`default_effort = ${tomlString(defaultEffort)}`);
|
|
@@ -1895,21 +2073,108 @@ export async function uninstallKimiConfig() {
|
|
|
1895
2073
|
writeKimiBlock(target, null);
|
|
1896
2074
|
console.log(`Removed Kimi Code provider config: ${target}`);
|
|
1897
2075
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2076
|
+
function grokHome() {
|
|
2077
|
+
const home = process.env.GROK_HOME?.trim() || join(homedir(), ".grok");
|
|
2078
|
+
return expandUserPath(home);
|
|
2079
|
+
}
|
|
2080
|
+
function grokConfigPath() {
|
|
2081
|
+
return join(grokHome(), "config.toml");
|
|
2082
|
+
}
|
|
2083
|
+
const GROK_BLOCK_START = "# >>> CPAC Grok >>>";
|
|
2084
|
+
const GROK_BLOCK_END = "# <<< CPAC Grok <<<";
|
|
2085
|
+
const grokBlockRegex = new RegExp(`${GROK_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${GROK_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
|
|
2086
|
+
export function isGrokConfigInstalled() {
|
|
2087
|
+
try {
|
|
2088
|
+
const content = readFileSync(grokConfigPath(), "utf8");
|
|
2089
|
+
return (grokBlockRegex.test(content) ||
|
|
2090
|
+
/^\s*\[model\.(?:"cpac\/|'cpac\/|cpac-)/m.test(content));
|
|
1902
2091
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2092
|
+
catch {
|
|
2093
|
+
return false;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
function isCpacGrokTableHeader(line) {
|
|
2097
|
+
const header = line.match(/^\[([^\]]+)\]\s*$/);
|
|
2098
|
+
if (!header)
|
|
2099
|
+
return false;
|
|
2100
|
+
const name = header[1];
|
|
2101
|
+
return /^model\.(?:"cpac\/|'cpac\/|cpac-)/.test(name);
|
|
2102
|
+
}
|
|
2103
|
+
function stripOrphanCpacGrokTables(content) {
|
|
2104
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
2105
|
+
const out = [];
|
|
2106
|
+
let skipping = false;
|
|
2107
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2108
|
+
if (/^\[[^\]]+\]\s*$/.test(line))
|
|
2109
|
+
skipping = isCpacGrokTableHeader(line);
|
|
2110
|
+
if (!skipping)
|
|
2111
|
+
out.push(line);
|
|
1906
2112
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2113
|
+
return out.join(eol).replace(/(?:\r?\n){3,}/g, `${eol}${eol}`);
|
|
2114
|
+
}
|
|
2115
|
+
function writeGrokBlock(path, block) {
|
|
2116
|
+
let content = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
2117
|
+
content = content.replace(grokBlockRegex, "");
|
|
2118
|
+
const orphan = content.indexOf(GROK_BLOCK_START);
|
|
2119
|
+
if (orphan !== -1)
|
|
2120
|
+
content = content.slice(0, orphan);
|
|
2121
|
+
content = stripOrphanCpacGrokTables(content);
|
|
2122
|
+
if (block) {
|
|
2123
|
+
content = `${content}${content && !content.endsWith("\n") ? "\n" : ""}${block}\n`;
|
|
1911
2124
|
}
|
|
1912
|
-
|
|
2125
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
2126
|
+
const mode = existsSync(path) ? statSync(path).mode & 0o7777 : 0o600;
|
|
2127
|
+
atomicWrite(path, Buffer.from(content), mode);
|
|
2128
|
+
}
|
|
2129
|
+
export async function installGrokConfig(config) {
|
|
2130
|
+
const apiKey = await resolveApiKey(config.api_key_env);
|
|
2131
|
+
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
2132
|
+
let document;
|
|
2133
|
+
try {
|
|
2134
|
+
document = JSON.parse(new TextDecoder("utf-8").decode(catalog.bytes));
|
|
2135
|
+
}
|
|
2136
|
+
catch {
|
|
2137
|
+
throw new CPACError("invalid CPA catalog");
|
|
2138
|
+
}
|
|
2139
|
+
const rows = catalogModelRows(document) ?? [];
|
|
2140
|
+
if (rows.length === 0)
|
|
2141
|
+
throw new CPACError("CPA catalog contains no models");
|
|
2142
|
+
const state = readState(config.state_dir);
|
|
2143
|
+
const recorded = state ? stateProxy(state) : null;
|
|
2144
|
+
const port = recorded?.port ?? config.codex_proxy_port;
|
|
2145
|
+
if (!port) {
|
|
2146
|
+
throw new CPACError("loopback proxy port unknown; run cpac inject first");
|
|
2147
|
+
}
|
|
2148
|
+
const lines = [GROK_BLOCK_START];
|
|
2149
|
+
for (const row of rows) {
|
|
2150
|
+
const slug = catalogModelId(row);
|
|
2151
|
+
if (!slug)
|
|
2152
|
+
continue;
|
|
2153
|
+
const context = typeof row.context_window === "number" && row.context_window > 0
|
|
2154
|
+
? Math.floor(row.context_window)
|
|
2155
|
+
: 200000;
|
|
2156
|
+
const name = typeof row.display_name === "string" && row.display_name.trim()
|
|
2157
|
+
? `${row.display_name.trim()} (CPAC)`
|
|
2158
|
+
: `CPAC ${slug}`;
|
|
2159
|
+
lines.push(`[model."cpac/${slug}"]`, `model = ${tomlString(slug)}`, `base_url = "http://127.0.0.1:${port}/v1"`, 'api_backend = "responses"', '# Placeholder: the CPAC loopback proxy replaces it with the CPA key; run "cpac inject" to start the proxy.', 'api_key = "cpac-loopback"', `name = ${tomlString(name)}`, `context_window = ${context}`, "");
|
|
2160
|
+
}
|
|
2161
|
+
if (lines[lines.length - 1] === "")
|
|
2162
|
+
lines.pop();
|
|
2163
|
+
lines.push(GROK_BLOCK_END);
|
|
2164
|
+
const target = grokConfigPath();
|
|
2165
|
+
ensureCpacBackup(target);
|
|
2166
|
+
writeGrokBlock(target, lines.join("\n"));
|
|
2167
|
+
console.log(`Installed Grok Build provider config: ${target}`);
|
|
2168
|
+
if (!recorded || !(await proxyIsHealthy(recorded))) {
|
|
2169
|
+
console.log("Loopback proxy is not running; run: cpac inject");
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
export async function uninstallGrokConfig() {
|
|
2173
|
+
const target = grokConfigPath();
|
|
2174
|
+
if (!isGrokConfigInstalled())
|
|
2175
|
+
throw new CPACError("Grok Build config is not installed");
|
|
2176
|
+
writeGrokBlock(target, null);
|
|
2177
|
+
console.log(`Removed Grok Build provider config: ${target}`);
|
|
1913
2178
|
}
|
|
1914
2179
|
function readPackageVersion() {
|
|
1915
2180
|
try {
|
|
@@ -1922,11 +2187,31 @@ function readPackageVersion() {
|
|
|
1922
2187
|
}
|
|
1923
2188
|
}
|
|
1924
2189
|
const CPAC_VERSION = readPackageVersion();
|
|
2190
|
+
export function detectClientVersion(binary) {
|
|
2191
|
+
if (!/^[\w.-]+$/.test(binary))
|
|
2192
|
+
return undefined;
|
|
2193
|
+
try {
|
|
2194
|
+
const res = spawnSync(binary, ["--version"], {
|
|
2195
|
+
encoding: "utf8",
|
|
2196
|
+
timeout: 2000,
|
|
2197
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2198
|
+
});
|
|
2199
|
+
if (res.status === 0 && typeof res.stdout === "string") {
|
|
2200
|
+
const match = res.stdout.match(/v?(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
|
|
2201
|
+
return match ? match[1] : res.stdout.trim().split(/\s+/)[0] || undefined;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
catch {
|
|
2205
|
+
// ignored
|
|
2206
|
+
}
|
|
2207
|
+
return undefined;
|
|
2208
|
+
}
|
|
1925
2209
|
function binaryOnPath(name) {
|
|
1926
2210
|
if (!/^[\w.-]+$/.test(name))
|
|
1927
2211
|
return false;
|
|
1928
2212
|
try {
|
|
1929
|
-
return spawnSync(`command -v ${name}`, { stdio: "ignore", shell: true }).status ===
|
|
2213
|
+
return (spawnSync(`command -v ${name}`, { stdio: "ignore", shell: true }).status ===
|
|
2214
|
+
0);
|
|
1930
2215
|
}
|
|
1931
2216
|
catch {
|
|
1932
2217
|
return false;
|
|
@@ -1947,6 +2232,7 @@ const TARGETS = [
|
|
|
1947
2232
|
return false;
|
|
1948
2233
|
}
|
|
1949
2234
|
},
|
|
2235
|
+
version: () => detectClientVersion("codex"),
|
|
1950
2236
|
install: async (config, dryRun, v2Off, maxContext) => {
|
|
1951
2237
|
if (dryRun) {
|
|
1952
2238
|
console.log("would inject CPA catalog and loopback proxy into Codex");
|
|
@@ -1967,6 +2253,7 @@ const TARGETS = [
|
|
|
1967
2253
|
home: () => piExtensionsDir(),
|
|
1968
2254
|
detected: () => binaryOnPath("pi") || existsSync(piExtensionsDir()),
|
|
1969
2255
|
installed: () => isPiExtensionInstalled(),
|
|
2256
|
+
version: () => detectClientVersion("pi"),
|
|
1970
2257
|
install: async (config, dryRun) => {
|
|
1971
2258
|
if (dryRun) {
|
|
1972
2259
|
console.log(`would write Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
|
|
@@ -1987,6 +2274,7 @@ const TARGETS = [
|
|
|
1987
2274
|
home: () => dirname(kimiConfigPath()),
|
|
1988
2275
|
detected: () => binaryOnPath("kimi") || existsSync(dirname(kimiConfigPath())),
|
|
1989
2276
|
installed: () => isKimiConfigInstalled(),
|
|
2277
|
+
version: () => detectClientVersion("kimi"),
|
|
1990
2278
|
install: async (config, dryRun) => {
|
|
1991
2279
|
if (dryRun) {
|
|
1992
2280
|
console.log(`would write CPA provider block: ${kimiConfigPath()}`);
|
|
@@ -2002,6 +2290,27 @@ const TARGETS = [
|
|
|
2002
2290
|
await uninstallKimiConfig();
|
|
2003
2291
|
},
|
|
2004
2292
|
},
|
|
2293
|
+
{
|
|
2294
|
+
id: "grok",
|
|
2295
|
+
home: () => grokHome(),
|
|
2296
|
+
detected: () => binaryOnPath("grok") || existsSync(grokHome()),
|
|
2297
|
+
installed: () => isGrokConfigInstalled(),
|
|
2298
|
+
version: () => detectClientVersion("grok"),
|
|
2299
|
+
install: async (config, dryRun) => {
|
|
2300
|
+
if (dryRun) {
|
|
2301
|
+
console.log(`would write CPA provider block: ${grokConfigPath()}`);
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
await installGrokConfig(config);
|
|
2305
|
+
},
|
|
2306
|
+
uninstall: async (_config, dryRun) => {
|
|
2307
|
+
if (dryRun) {
|
|
2308
|
+
console.log(`would remove CPA provider block: ${grokConfigPath()}`);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
await uninstallGrokConfig();
|
|
2312
|
+
},
|
|
2313
|
+
},
|
|
2005
2314
|
];
|
|
2006
2315
|
export async function detectTargets(config) {
|
|
2007
2316
|
return TARGETS.map((target) => ({
|
|
@@ -2009,6 +2318,7 @@ export async function detectTargets(config) {
|
|
|
2009
2318
|
detected: target.detected(),
|
|
2010
2319
|
installed: target.installed(config),
|
|
2011
2320
|
path: target.home(),
|
|
2321
|
+
version: target.version?.(),
|
|
2012
2322
|
}));
|
|
2013
2323
|
}
|
|
2014
2324
|
export async function runDetect(config, asJson) {
|
|
@@ -2027,7 +2337,11 @@ function selectTargets(requested, all, eligible) {
|
|
|
2027
2337
|
if (unknown.length > 0) {
|
|
2028
2338
|
throw new CPACError(`unknown target(s): ${unknown.join(", ")}; use ${TARGETS.map((target) => target.id).join(",")}`);
|
|
2029
2339
|
}
|
|
2030
|
-
const ids = requested.length > 0
|
|
2340
|
+
const ids = requested.length > 0
|
|
2341
|
+
? requested
|
|
2342
|
+
: all
|
|
2343
|
+
? TARGETS.map((target) => target.id)
|
|
2344
|
+
: eligible;
|
|
2031
2345
|
return TARGETS.filter((target) => ids.includes(target.id));
|
|
2032
2346
|
}
|
|
2033
2347
|
export async function runInstall(config, requested, options) {
|
|
@@ -2087,6 +2401,17 @@ export async function runUninstall(config, requested, options) {
|
|
|
2087
2401
|
}
|
|
2088
2402
|
return 0;
|
|
2089
2403
|
}
|
|
2404
|
+
export async function runRestore(config, requested, options) {
|
|
2405
|
+
if (requested.length > 0 || options.all) {
|
|
2406
|
+
return runUninstall(config, requested, options);
|
|
2407
|
+
}
|
|
2408
|
+
if (options.dryRun) {
|
|
2409
|
+
console.log("would restore the original Codex config");
|
|
2410
|
+
return 0;
|
|
2411
|
+
}
|
|
2412
|
+
await restore(config);
|
|
2413
|
+
return 0;
|
|
2414
|
+
}
|
|
2090
2415
|
function compareVersions(a, b) {
|
|
2091
2416
|
const left = a.split(".").map(Number);
|
|
2092
2417
|
const right = b.split(".").map(Number);
|
|
@@ -2128,7 +2453,10 @@ export async function runUpgrade(config, checkOnly) {
|
|
|
2128
2453
|
console.log("Update available. Run: cpac upgrade");
|
|
2129
2454
|
return 0;
|
|
2130
2455
|
}
|
|
2131
|
-
if (
|
|
2456
|
+
if (upToDate) {
|
|
2457
|
+
console.log("Already up to date.");
|
|
2458
|
+
}
|
|
2459
|
+
else {
|
|
2132
2460
|
console.log(`Installing @yhong91/cpac@${latest}`);
|
|
2133
2461
|
const result = spawnSync("npm", ["install", "-g", `@yhong91/cpac@${latest}`], { stdio: "inherit" });
|
|
2134
2462
|
if (result.status !== 0) {
|
|
@@ -2136,9 +2464,6 @@ export async function runUpgrade(config, checkOnly) {
|
|
|
2136
2464
|
return result.status ?? 1;
|
|
2137
2465
|
}
|
|
2138
2466
|
}
|
|
2139
|
-
else {
|
|
2140
|
-
console.log("Already up to date.");
|
|
2141
|
-
}
|
|
2142
2467
|
const installed = TARGETS.filter((target) => target.installed(config)).map((target) => target.id);
|
|
2143
2468
|
if (installed.length === 0) {
|
|
2144
2469
|
console.log("No installed agents to sync.");
|
|
@@ -2150,23 +2475,21 @@ export async function runUpgrade(config, checkOnly) {
|
|
|
2150
2475
|
function usage() {
|
|
2151
2476
|
return [
|
|
2152
2477
|
"Usage: cpac",
|
|
2478
|
+
" cpac <codex|kimi|grok|pi|claude|opencode> [args...]",
|
|
2479
|
+
" cpac <codex|claude> setup",
|
|
2480
|
+
" cpac <codex|claude|kimi|grok|pi> clear",
|
|
2153
2481
|
" cpac inject [--v2_off] [--v2_models] [--max_context] [--config PATH]",
|
|
2154
|
-
" cpac
|
|
2482
|
+
" cpac restore [--target codex,pi,kimi,grok] [--all] [--dry-run] [--config PATH]",
|
|
2483
|
+
" cpac status [--config PATH]",
|
|
2155
2484
|
" cpac proxy [--config PATH]",
|
|
2156
|
-
" cpac claude [--config PATH] [--] [claude args...]",
|
|
2157
|
-
" cpac opencode [--config PATH] [--] [opencode args...]",
|
|
2158
|
-
" cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--reset] [--config PATH]",
|
|
2159
|
-
" cpac pi <install|uninstall|status> [--config PATH]",
|
|
2160
|
-
" cpac kimi <install|uninstall|status> [--config PATH]",
|
|
2161
2485
|
" cpac detect [--json] [--home PATH]",
|
|
2162
|
-
" cpac install [--target codex,pi,kimi] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
|
|
2163
|
-
" cpac sync [--target codex,pi,kimi] [--all] [--dry-run] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
|
|
2164
|
-
" cpac uninstall [--target codex,pi,kimi] [--all] [--dry-run] [--home PATH]",
|
|
2486
|
+
" cpac install [--target codex,pi,kimi,grok] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
|
|
2487
|
+
" cpac sync [--target codex,pi,kimi,grok] [--all] [--dry-run] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
|
|
2165
2488
|
" cpac upgrade [--check]",
|
|
2166
2489
|
" cpac version",
|
|
2167
2490
|
"",
|
|
2168
|
-
"
|
|
2169
|
-
"
|
|
2491
|
+
"launch an agent directly with `cpac <agent> [args...]` (all args forwarded).",
|
|
2492
|
+
"configure an agent with `cpac <agent> setup` or restore with `cpac <agent> clear`.",
|
|
2170
2493
|
].join("\n");
|
|
2171
2494
|
}
|
|
2172
2495
|
function parseArgs(args) {
|
|
@@ -2177,6 +2500,7 @@ function parseArgs(args) {
|
|
|
2177
2500
|
args[0] === "install" ||
|
|
2178
2501
|
args[0] === "sync" ||
|
|
2179
2502
|
args[0] === "uninstall" ||
|
|
2503
|
+
args[0] === "restore" ||
|
|
2180
2504
|
args[0] === "upgrade" ||
|
|
2181
2505
|
args[0] === "version") {
|
|
2182
2506
|
const parsed = {
|
|
@@ -2240,23 +2564,100 @@ function parseArgs(args) {
|
|
|
2240
2564
|
}
|
|
2241
2565
|
return parsed;
|
|
2242
2566
|
}
|
|
2243
|
-
if (
|
|
2567
|
+
if (["codex", "kimi", "grok", "pi", "claude", "opencode"].includes(args[0])) {
|
|
2568
|
+
const targetId = args[0];
|
|
2244
2569
|
let configPath = defaultConfigPath();
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2570
|
+
if (args[1] === "clear" || args[1] === "--clear") {
|
|
2571
|
+
for (let i = 2; i < args.length; i++) {
|
|
2572
|
+
if (args[i] === "--config") {
|
|
2573
|
+
const val = args[++i];
|
|
2574
|
+
if (val)
|
|
2575
|
+
configPath = resolve(expandUserPath(val));
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
if (targetId === "claude") {
|
|
2579
|
+
return {
|
|
2580
|
+
command: "claude-config",
|
|
2581
|
+
configPath,
|
|
2582
|
+
models: {},
|
|
2583
|
+
pick: [],
|
|
2584
|
+
reset: true,
|
|
2585
|
+
interactive: false,
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
return {
|
|
2589
|
+
command: "restore",
|
|
2590
|
+
configPath,
|
|
2591
|
+
json: false,
|
|
2592
|
+
targets: [targetId],
|
|
2593
|
+
all: false,
|
|
2594
|
+
dryRun: false,
|
|
2595
|
+
force: false,
|
|
2596
|
+
check: false,
|
|
2597
|
+
v2Off: false,
|
|
2598
|
+
v2Models: false,
|
|
2599
|
+
maxContext: false,
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
if (args[1] === "setup" || args[1] === "--setup") {
|
|
2603
|
+
for (let i = 2; i < args.length; i++) {
|
|
2604
|
+
if (args[i] === "--config") {
|
|
2605
|
+
const val = args[++i];
|
|
2606
|
+
if (val)
|
|
2607
|
+
configPath = resolve(expandUserPath(val));
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
if (targetId === "codex") {
|
|
2611
|
+
return {
|
|
2612
|
+
command: "codex-config",
|
|
2613
|
+
configPath,
|
|
2614
|
+
v2Off: false,
|
|
2615
|
+
v2Models: false,
|
|
2616
|
+
maxContext: false,
|
|
2617
|
+
reset: false,
|
|
2618
|
+
interactive: true,
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
if (targetId === "claude") {
|
|
2622
|
+
return {
|
|
2623
|
+
command: "claude-config",
|
|
2624
|
+
configPath,
|
|
2625
|
+
models: {},
|
|
2626
|
+
pick: [],
|
|
2627
|
+
reset: false,
|
|
2628
|
+
interactive: true,
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
throw new CPACError(`${targetId} does not require setup (all CPA models are auto-synced). Run 'cpac ${targetId}' or 'cpac install --target ${targetId}'.`);
|
|
2632
|
+
}
|
|
2633
|
+
const passArgs = args.slice(1);
|
|
2634
|
+
if (targetId === "codex") {
|
|
2635
|
+
return {
|
|
2636
|
+
command: "codex",
|
|
2637
|
+
configPath,
|
|
2638
|
+
args: passArgs,
|
|
2639
|
+
v2Off: false,
|
|
2640
|
+
v2Models: false,
|
|
2641
|
+
maxContext: false,
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
if (targetId === "claude") {
|
|
2645
|
+
return {
|
|
2646
|
+
command: "claude",
|
|
2647
|
+
configPath,
|
|
2648
|
+
args: passArgs,
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
return {
|
|
2652
|
+
command: targetId,
|
|
2653
|
+
configPath,
|
|
2654
|
+
args: passArgs,
|
|
2655
|
+
};
|
|
2255
2656
|
}
|
|
2256
2657
|
if (args[0] === "claude-models") {
|
|
2257
2658
|
if (args.includes("-h") || args.includes("--help")) {
|
|
2258
2659
|
console.log([
|
|
2259
|
-
"Usage: cpac claude
|
|
2660
|
+
"Usage: cpac claude --config [--opus [M]] [--sonnet [M]] [--haiku [M]] [--reset] [--config PATH]",
|
|
2260
2661
|
"",
|
|
2261
2662
|
"Sets the model Claude Code uses per task tier. Saved to the config file",
|
|
2262
2663
|
"and applied on every `cpac claude` launch:",
|
|
@@ -2288,9 +2689,7 @@ function parseArgs(args) {
|
|
|
2288
2689
|
else if (arg === "--reset") {
|
|
2289
2690
|
reset = true;
|
|
2290
2691
|
}
|
|
2291
|
-
else if (arg === "--opus" ||
|
|
2292
|
-
arg === "--sonnet" ||
|
|
2293
|
-
arg === "--haiku") {
|
|
2692
|
+
else if (arg === "--opus" || arg === "--sonnet" || arg === "--haiku") {
|
|
2294
2693
|
const slot = arg.slice(2);
|
|
2295
2694
|
const value = args[index + 1];
|
|
2296
2695
|
if (value !== undefined && !value.startsWith("-")) {
|
|
@@ -2307,35 +2706,14 @@ function parseArgs(args) {
|
|
|
2307
2706
|
throw new CPACError(`unknown option: ${arg}`);
|
|
2308
2707
|
}
|
|
2309
2708
|
}
|
|
2310
|
-
return {
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
throw new CPACError("--config requires a path");
|
|
2319
|
-
configPath = resolve(expandUserPath(value));
|
|
2320
|
-
index += 1;
|
|
2321
|
-
}
|
|
2322
|
-
if (args[index] === "--")
|
|
2323
|
-
index += 1;
|
|
2324
|
-
return { command: "claude", configPath, args: args.slice(index) };
|
|
2325
|
-
}
|
|
2326
|
-
if (args[0] === "opencode") {
|
|
2327
|
-
let configPath = defaultConfigPath();
|
|
2328
|
-
let index = 1;
|
|
2329
|
-
if (args[index] === "--config") {
|
|
2330
|
-
const value = args[++index];
|
|
2331
|
-
if (!value)
|
|
2332
|
-
throw new CPACError("--config requires a path");
|
|
2333
|
-
configPath = resolve(expandUserPath(value));
|
|
2334
|
-
index += 1;
|
|
2335
|
-
}
|
|
2336
|
-
if (args[index] === "--")
|
|
2337
|
-
index += 1;
|
|
2338
|
-
return { command: "opencode", configPath, args: args.slice(index) };
|
|
2709
|
+
return {
|
|
2710
|
+
command: "claude-config",
|
|
2711
|
+
configPath,
|
|
2712
|
+
models,
|
|
2713
|
+
pick,
|
|
2714
|
+
reset,
|
|
2715
|
+
interactive: !reset && Object.keys(models).length === 0 && pick.length === 0,
|
|
2716
|
+
};
|
|
2339
2717
|
}
|
|
2340
2718
|
if (args.includes("-h") || args.includes("--help")) {
|
|
2341
2719
|
console.log(usage());
|
|
@@ -2359,7 +2737,8 @@ function parseArgs(args) {
|
|
|
2359
2737
|
else if (args[index] === "--v2_models" || args[index] === "--v2-models") {
|
|
2360
2738
|
v2Models = true;
|
|
2361
2739
|
}
|
|
2362
|
-
else if (args[index] === "--max_context" ||
|
|
2740
|
+
else if (args[index] === "--max_context" ||
|
|
2741
|
+
args[index] === "--max-context") {
|
|
2363
2742
|
maxContext = true;
|
|
2364
2743
|
}
|
|
2365
2744
|
else if (args[index].startsWith("-")) {
|
|
@@ -2369,7 +2748,7 @@ function parseArgs(args) {
|
|
|
2369
2748
|
positional.push(args[index]);
|
|
2370
2749
|
}
|
|
2371
2750
|
if (positional.length !== 1 ||
|
|
2372
|
-
!["inject", "status", "
|
|
2751
|
+
!["inject", "status", "proxy"].includes(positional[0])) {
|
|
2373
2752
|
throw new CPACError(usage());
|
|
2374
2753
|
}
|
|
2375
2754
|
if (positional[0] !== "inject" && (v2Off || v2Models || maxContext)) {
|
|
@@ -2432,8 +2811,8 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
2432
2811
|
maxContext: parsed.maxContext,
|
|
2433
2812
|
});
|
|
2434
2813
|
}
|
|
2435
|
-
if (parsed.command === "uninstall")
|
|
2436
|
-
return await
|
|
2814
|
+
if (parsed.command === "uninstall" || parsed.command === "restore")
|
|
2815
|
+
return await runRestore(config, parsed.targets, {
|
|
2437
2816
|
all: parsed.all,
|
|
2438
2817
|
dryRun: parsed.dryRun,
|
|
2439
2818
|
});
|
|
@@ -2445,16 +2824,42 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
2445
2824
|
}
|
|
2446
2825
|
if (parsed.command === "claude")
|
|
2447
2826
|
return await runClaude(config, parsed.args);
|
|
2827
|
+
if (parsed.command === "claude-config")
|
|
2828
|
+
return await runClaudeConfig(config, parsed.configPath, {
|
|
2829
|
+
models: parsed.models,
|
|
2830
|
+
pick: parsed.pick,
|
|
2831
|
+
reset: parsed.reset,
|
|
2832
|
+
interactive: parsed.interactive,
|
|
2833
|
+
});
|
|
2834
|
+
if (parsed.command === "codex-config")
|
|
2835
|
+
return await runCodexConfig(config, parsed.configPath, {
|
|
2836
|
+
v2Off: parsed.v2Off,
|
|
2837
|
+
v2Models: parsed.v2Models,
|
|
2838
|
+
maxContext: parsed.maxContext,
|
|
2839
|
+
reset: parsed.reset,
|
|
2840
|
+
interactive: parsed.interactive,
|
|
2841
|
+
});
|
|
2448
2842
|
if (parsed.command === "opencode")
|
|
2449
2843
|
return await runOpencode(config, parsed.args);
|
|
2844
|
+
if (parsed.command === "codex") {
|
|
2845
|
+
let launcherConfig = config;
|
|
2846
|
+
if (parsed.v2Models) {
|
|
2847
|
+
const picked = await pickSpawnModels(config);
|
|
2848
|
+
saveSpawnModels(parsed.configPath, picked);
|
|
2849
|
+
console.log(`spawn_models saved to ${parsed.configPath}: ${picked.join(", ")}`);
|
|
2850
|
+
launcherConfig = { ...config, spawn_models: picked };
|
|
2851
|
+
}
|
|
2852
|
+
return await runTargetLauncher(launcherConfig, "codex", parsed.args, "codex", { v2Off: parsed.v2Off, maxContext: parsed.maxContext });
|
|
2853
|
+
}
|
|
2854
|
+
if (parsed.command === "kimi" ||
|
|
2855
|
+
parsed.command === "grok" ||
|
|
2856
|
+
parsed.command === "pi") {
|
|
2857
|
+
return await runTargetLauncher(config, parsed.command, parsed.args);
|
|
2858
|
+
}
|
|
2450
2859
|
if (parsed.command === "claude-models")
|
|
2451
2860
|
return await runClaudeModels(parsed);
|
|
2452
2861
|
if (parsed.command === "proxy")
|
|
2453
2862
|
return await runProxy(config);
|
|
2454
|
-
if (parsed.command === "pi")
|
|
2455
|
-
return await runPi(config, parsed.action);
|
|
2456
|
-
if (parsed.command === "kimi")
|
|
2457
|
-
return await runKimi(config, parsed.action);
|
|
2458
2863
|
if (parsed.command === "inject") {
|
|
2459
2864
|
let injectConfig = config;
|
|
2460
2865
|
if (parsed.v2Models) {
|
|
@@ -2465,8 +2870,6 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
2465
2870
|
}
|
|
2466
2871
|
await inject(injectConfig, parsed.v2Off, parsed.maxContext);
|
|
2467
2872
|
}
|
|
2468
|
-
else if (parsed.command === "restore")
|
|
2469
|
-
await restore(config);
|
|
2470
2873
|
else
|
|
2471
2874
|
return await status(config);
|
|
2472
2875
|
return 0;
|