@yhong91/cpac 0.1.4 → 0.1.5
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 +36 -0
- package/dist/cpac.js +399 -5
- package/dist/pi-extension.template +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,24 @@ cpac
|
|
|
91
91
|
cpac --help
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### 统一管理(detect / install / uninstall / upgrade)
|
|
95
|
+
|
|
96
|
+
参考 vibetime 的目标式命令,对 `codex`、`claude`、`pi`、`kimi` 四个目标统一管理:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cpac detect [--json] [--home PATH]
|
|
100
|
+
cpac install [--target codex,claude,pi,kimi] [--all] [--dry-run] [--force] [--home PATH]
|
|
101
|
+
cpac uninstall [--target codex,claude,pi,kimi] [--all] [--dry-run] [--home PATH]
|
|
102
|
+
cpac upgrade [--check]
|
|
103
|
+
cpac version
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `detect` 列出每个目标是否被检测到、是否已接入 CPAC;`--json` 输出机器可读结果。
|
|
107
|
+
- `install` 默认作用于检测到的目标;`--target` 指定目标、`--all` 全部。各目标映射到既有逻辑:`codex` → `cpac inject`,`pi` → `cpac pi install`,`kimi` → `cpac kimi install`;`claude` 无持久化安装(用 `cpac claude` 启动)。`--dry-run` 只打印将要做的事,`--force` 允许重装已安装目标。
|
|
108
|
+
- `uninstall` 默认作用于已安装目标,映射到 `cpac restore` / `cpac pi uninstall` / `cpac kimi uninstall`。
|
|
109
|
+
- `--home PATH` 临时把 `HOME` 指向给定目录,便于在隔离环境中试验。
|
|
110
|
+
- `upgrade` 检查 npm 上是否有新版本,`--check` 只报告不提示安装命令。
|
|
111
|
+
|
|
94
112
|
### Claude Code
|
|
95
113
|
|
|
96
114
|
通过 CPA 启动 Claude Code,后续参数原样传递:
|
|
@@ -196,6 +214,23 @@ cpac pi status
|
|
|
196
214
|
|
|
197
215
|
Pi 扩展尊重 `PI_CODING_AGENT_DIR` 环境变量。
|
|
198
216
|
|
|
217
|
+
### Kimi Code
|
|
218
|
+
|
|
219
|
+
写入 CPA provider 配置到 Kimi Code:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cpac kimi install
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
它在 `~/.kimi-code/config.toml` 追加一个由 CPAC 管理的块,注册 `cpac` OpenAI 兼容 provider,并把 CPA 目录里的每个模型映射为一个 `cpac/<模型名>` 条目。`base_url` 指向 Codex 注入使用的 loopback 代理,因此密钥不落盘;代理未运行时 install 会提示先执行 `cpac inject`。卸载与状态:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
cpac kimi uninstall
|
|
229
|
+
cpac kimi status
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Kimi Code 配置目录尊重 `KIMI_CODE_HOME` 环境变量。首次写入前若 `config.toml` 已存在,会在同目录保留一份原始快照 `config.toml.cpac-backup`(只存最早版本),万一文件损坏可手动复制回去。
|
|
233
|
+
|
|
199
234
|
## 环境变量
|
|
200
235
|
|
|
201
236
|
| 变量 | 默认值 | 作用 |
|
|
@@ -203,6 +238,7 @@ Pi 扩展尊重 `PI_CODING_AGENT_DIR` 环境变量。
|
|
|
203
238
|
| `CPA_API_KEY` | 无 | CPA Bearer key;可由 `cpac` 首次引导写入 shell 启动文件 |
|
|
204
239
|
| `CPA_BASE_URL` | `https://cpa.vibetime.cc` | 覆盖内置 CPA 地址 |
|
|
205
240
|
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | 覆盖 Pi agent 目录(影响 `cpac pi install` 写入位置) |
|
|
241
|
+
| `KIMI_CODE_HOME` | `~/.kimi-code` | 覆盖 Kimi Code 目录(影响 `cpac kimi install` 写入位置) |
|
|
206
242
|
| `CPAC_CONFIG` | `~/.config/cpac/config.json` | 指定可选 JSON 配置路径 |
|
|
207
243
|
| `CODEX_HOME` | `~/.codex` | Codex home 目录 |
|
|
208
244
|
|
package/dist/cpac.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
import { chmodSync, closeSync, copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
import { createServer, request as httpRequest, } from "node:http";
|
|
6
6
|
import { request as httpsRequest } from "node:https";
|
|
@@ -1016,6 +1016,8 @@ export async function status(config) {
|
|
|
1016
1016
|
console.log(`claude: ${keyConfigured ? "ready" : "not configured"}`);
|
|
1017
1017
|
const piInstalled = isPiExtensionInstalled();
|
|
1018
1018
|
console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac pi install") : "not configured"}`);
|
|
1019
|
+
const kimiInstalled = isKimiConfigInstalled();
|
|
1020
|
+
console.log(`kimi: ${keyConfigured ? (kimiInstalled ? "ready" : "config not installed; run: cpac kimi install") : "not configured"}`);
|
|
1019
1021
|
if (!state)
|
|
1020
1022
|
return 1;
|
|
1021
1023
|
const proxy = stateProxy(state);
|
|
@@ -1078,7 +1080,7 @@ export function saveApiKeyExport(profile, name, apiKey) {
|
|
|
1078
1080
|
atomicWrite(profile, Buffer.from(content), mode);
|
|
1079
1081
|
}
|
|
1080
1082
|
async function guide(config) {
|
|
1081
|
-
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 claude [args...] Launch Claude Code 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)\n cpac restore Restore the original Codex config\n cpac pi install Install CPA provider extension for Pi\n cpac pi uninstall Remove the Pi CPA provider extension\n cpac pi status Show Pi extension install status\n cpac --help Show command usage`);
|
|
1083
|
+
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 claude [args...] Launch Claude Code 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)\n cpac restore Restore the original Codex config\n cpac pi install Install CPA provider extension for Pi\n cpac pi uninstall Remove the Pi CPA provider extension\n cpac pi status Show Pi extension install status\n cpac kimi install Write CPA provider config for Kimi Code\n cpac kimi uninstall Remove the Kimi Code CPA provider config\n cpac kimi status Show Kimi Code config install status\n cpac detect Show supported targets and install status\n cpac install Install CPA integration into detected targets\n cpac uninstall Remove CPA integration from installed targets\n cpac upgrade Check for cpac updates on npm\n cpac --help Show command usage`);
|
|
1082
1084
|
if (process.env[config.api_key_env]?.trim())
|
|
1083
1085
|
return 0;
|
|
1084
1086
|
const apiKey = await promptSecret(config.api_key_env);
|
|
@@ -1169,6 +1171,7 @@ export async function installPiExtension(config) {
|
|
|
1169
1171
|
const dir = piExtensionsDir();
|
|
1170
1172
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
1171
1173
|
const target = join(dir, "cpac.ts");
|
|
1174
|
+
ensureCpacBackup(target);
|
|
1172
1175
|
atomicWrite(target, Buffer.from(piExtensionContent(config.cpa_url)), 0o644);
|
|
1173
1176
|
console.log(`Installed Pi extension: ${target}`);
|
|
1174
1177
|
}
|
|
@@ -1195,6 +1198,314 @@ export async function runPi(config, action) {
|
|
|
1195
1198
|
}
|
|
1196
1199
|
throw new CPACError(`unknown pi action: ${action}; use install, uninstall, or status`);
|
|
1197
1200
|
}
|
|
1201
|
+
// Pre-write snapshot kept at <target>.cpac-backup (first version only) so a
|
|
1202
|
+
// damaged config can be restored by hand even if uninstall cannot parse it.
|
|
1203
|
+
function ensureCpacBackup(target) {
|
|
1204
|
+
if (!existsSync(target))
|
|
1205
|
+
return;
|
|
1206
|
+
const backup = `${target}.cpac-backup`;
|
|
1207
|
+
if (existsSync(backup))
|
|
1208
|
+
return;
|
|
1209
|
+
copyFileSync(target, backup);
|
|
1210
|
+
}
|
|
1211
|
+
function kimiConfigPath() {
|
|
1212
|
+
const home = process.env.KIMI_CODE_HOME?.trim() || join(homedir(), ".kimi-code");
|
|
1213
|
+
return join(expandUserPath(home), "config.toml");
|
|
1214
|
+
}
|
|
1215
|
+
const KIMI_BLOCK_START = "# >>> CPAC Kimi >>>";
|
|
1216
|
+
const KIMI_BLOCK_END = "# <<< CPAC Kimi <<<";
|
|
1217
|
+
const kimiBlockRegex = new RegExp(`${KIMI_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${KIMI_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
|
|
1218
|
+
export function isKimiConfigInstalled() {
|
|
1219
|
+
try {
|
|
1220
|
+
return kimiBlockRegex.test(readFileSync(kimiConfigPath(), "utf8"));
|
|
1221
|
+
}
|
|
1222
|
+
catch {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
function writeKimiBlock(path, block) {
|
|
1227
|
+
let content = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
1228
|
+
content = content.replace(kimiBlockRegex, "");
|
|
1229
|
+
if (block) {
|
|
1230
|
+
content = `${content}${content && !content.endsWith("\n") ? "\n" : ""}${block}\n`;
|
|
1231
|
+
}
|
|
1232
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
1233
|
+
const mode = existsSync(path) ? statSync(path).mode & 0o7777 : 0o600;
|
|
1234
|
+
atomicWrite(path, Buffer.from(content), mode);
|
|
1235
|
+
}
|
|
1236
|
+
export async function installKimiConfig(config) {
|
|
1237
|
+
const apiKey = await resolveApiKey(config.api_key_env);
|
|
1238
|
+
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
1239
|
+
const document = JSON.parse(new TextDecoder("utf-8").decode(catalog.bytes));
|
|
1240
|
+
const rows = catalogModelRows(document) ?? [];
|
|
1241
|
+
if (rows.length === 0)
|
|
1242
|
+
throw new CPACError("CPA catalog contains no models");
|
|
1243
|
+
const state = readState(config.state_dir);
|
|
1244
|
+
const recorded = state ? stateProxy(state) : null;
|
|
1245
|
+
const port = recorded?.port ?? config.codex_proxy_port;
|
|
1246
|
+
if (!port) {
|
|
1247
|
+
throw new CPACError("loopback proxy port unknown; run cpac inject first");
|
|
1248
|
+
}
|
|
1249
|
+
const lines = [
|
|
1250
|
+
KIMI_BLOCK_START,
|
|
1251
|
+
"[providers.cpac]",
|
|
1252
|
+
'type = "openai"',
|
|
1253
|
+
`base_url = "http://127.0.0.1:${port}/v1"`,
|
|
1254
|
+
'# Placeholder: the CPAC loopback proxy replaces it with the CPA key; run "cpac inject" to start the proxy.',
|
|
1255
|
+
'api_key = "cpac-loopback"',
|
|
1256
|
+
];
|
|
1257
|
+
for (const row of rows) {
|
|
1258
|
+
const slug = catalogModelId(row);
|
|
1259
|
+
if (!slug)
|
|
1260
|
+
continue;
|
|
1261
|
+
// ponytail: Kimi requires max_context_size; default 200000 when the catalog omits it.
|
|
1262
|
+
const context = typeof row.context_window === "number" && row.context_window > 0
|
|
1263
|
+
? Math.floor(row.context_window)
|
|
1264
|
+
: 200000;
|
|
1265
|
+
lines.push("", `[models."cpac/${slug}"]`, 'provider = "cpac"', `model = ${tomlString(slug)}`, `max_context_size = ${context}`);
|
|
1266
|
+
if (typeof row.display_name === "string" && row.display_name.trim()) {
|
|
1267
|
+
lines.push(`display_name = ${tomlString(row.display_name)}`);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
lines.push(KIMI_BLOCK_END);
|
|
1271
|
+
const target = kimiConfigPath();
|
|
1272
|
+
ensureCpacBackup(target);
|
|
1273
|
+
writeKimiBlock(target, lines.join("\n"));
|
|
1274
|
+
console.log(`Installed Kimi Code provider config: ${target}`);
|
|
1275
|
+
if (!recorded || !(await proxyIsHealthy(recorded))) {
|
|
1276
|
+
console.log("Loopback proxy is not running; run: cpac inject");
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
export async function uninstallKimiConfig() {
|
|
1280
|
+
const target = kimiConfigPath();
|
|
1281
|
+
if (!isKimiConfigInstalled())
|
|
1282
|
+
throw new CPACError("Kimi Code config is not installed");
|
|
1283
|
+
writeKimiBlock(target, null);
|
|
1284
|
+
console.log(`Removed Kimi Code provider config: ${target}`);
|
|
1285
|
+
}
|
|
1286
|
+
export async function runKimi(config, action) {
|
|
1287
|
+
if (action === "install") {
|
|
1288
|
+
await installKimiConfig(config);
|
|
1289
|
+
return 0;
|
|
1290
|
+
}
|
|
1291
|
+
if (action === "uninstall") {
|
|
1292
|
+
await uninstallKimiConfig();
|
|
1293
|
+
return 0;
|
|
1294
|
+
}
|
|
1295
|
+
if (action === "status") {
|
|
1296
|
+
const installed = isKimiConfigInstalled();
|
|
1297
|
+
console.log(`Kimi Code config: ${installed ? "installed" : "not installed"}`);
|
|
1298
|
+
return installed ? 0 : 1;
|
|
1299
|
+
}
|
|
1300
|
+
throw new CPACError(`unknown kimi action: ${action}; use install, uninstall, or status`);
|
|
1301
|
+
}
|
|
1302
|
+
function readPackageVersion() {
|
|
1303
|
+
try {
|
|
1304
|
+
const raw = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8");
|
|
1305
|
+
const version = JSON.parse(raw).version;
|
|
1306
|
+
return typeof version === "string" ? version : "0.0.0-dev";
|
|
1307
|
+
}
|
|
1308
|
+
catch {
|
|
1309
|
+
return "0.0.0-dev";
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const CPAC_VERSION = readPackageVersion();
|
|
1313
|
+
function binaryOnPath(name) {
|
|
1314
|
+
if (!/^[\w.-]+$/.test(name))
|
|
1315
|
+
return false;
|
|
1316
|
+
try {
|
|
1317
|
+
return spawnSync(`command -v ${name}`, { stdio: "ignore", shell: true }).status === 0;
|
|
1318
|
+
}
|
|
1319
|
+
catch {
|
|
1320
|
+
return false;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
const TARGETS = [
|
|
1324
|
+
{
|
|
1325
|
+
id: "codex",
|
|
1326
|
+
home: () => expandUserPath(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex")),
|
|
1327
|
+
detected: function () {
|
|
1328
|
+
return existsSync(this.home());
|
|
1329
|
+
},
|
|
1330
|
+
installed: (config) => {
|
|
1331
|
+
try {
|
|
1332
|
+
return hasProviderTable(readFileSync(config.codex_config, "utf8"));
|
|
1333
|
+
}
|
|
1334
|
+
catch {
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
install: async (config, dryRun) => {
|
|
1339
|
+
if (dryRun) {
|
|
1340
|
+
console.log("would inject CPA catalog and loopback proxy into Codex");
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
await inject(config);
|
|
1344
|
+
},
|
|
1345
|
+
uninstall: async (config, dryRun) => {
|
|
1346
|
+
if (dryRun) {
|
|
1347
|
+
console.log("would restore the original Codex config");
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
await restore(config);
|
|
1351
|
+
},
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
id: "claude",
|
|
1355
|
+
home: () => expandUserPath(process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude")),
|
|
1356
|
+
detected: () => binaryOnPath("claude") || existsSync(join(homedir(), ".claude")),
|
|
1357
|
+
installed: () => false,
|
|
1358
|
+
install: async (_config, dryRun) => {
|
|
1359
|
+
const message = "claude needs no install; launch it through CPA with: cpac claude";
|
|
1360
|
+
console.log(dryRun ? `would do nothing: ${message}` : message);
|
|
1361
|
+
},
|
|
1362
|
+
uninstall: async (_config, dryRun) => {
|
|
1363
|
+
const message = "claude keeps no persistent CPAC state; nothing to remove";
|
|
1364
|
+
console.log(dryRun ? `would do nothing: ${message}` : message);
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
id: "pi",
|
|
1369
|
+
home: () => piExtensionsDir(),
|
|
1370
|
+
detected: () => binaryOnPath("pi") || existsSync(piExtensionsDir()),
|
|
1371
|
+
installed: () => isPiExtensionInstalled(),
|
|
1372
|
+
install: async (config, dryRun) => {
|
|
1373
|
+
if (dryRun) {
|
|
1374
|
+
console.log(`would write Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
await installPiExtension(config);
|
|
1378
|
+
},
|
|
1379
|
+
uninstall: async (_config, dryRun) => {
|
|
1380
|
+
if (dryRun) {
|
|
1381
|
+
console.log(`would remove Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
await uninstallPiExtension();
|
|
1385
|
+
},
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
id: "kimi",
|
|
1389
|
+
home: () => dirname(kimiConfigPath()),
|
|
1390
|
+
detected: () => binaryOnPath("kimi") || existsSync(dirname(kimiConfigPath())),
|
|
1391
|
+
installed: () => isKimiConfigInstalled(),
|
|
1392
|
+
install: async (config, dryRun) => {
|
|
1393
|
+
if (dryRun) {
|
|
1394
|
+
console.log(`would write CPA provider block: ${kimiConfigPath()}`);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
await installKimiConfig(config);
|
|
1398
|
+
},
|
|
1399
|
+
uninstall: async (_config, dryRun) => {
|
|
1400
|
+
if (dryRun) {
|
|
1401
|
+
console.log(`would remove CPA provider block: ${kimiConfigPath()}`);
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
await uninstallKimiConfig();
|
|
1405
|
+
},
|
|
1406
|
+
},
|
|
1407
|
+
];
|
|
1408
|
+
export async function detectTargets(config) {
|
|
1409
|
+
return TARGETS.map((target) => ({
|
|
1410
|
+
id: target.id,
|
|
1411
|
+
detected: target.detected(),
|
|
1412
|
+
installed: target.installed(config),
|
|
1413
|
+
path: target.home(),
|
|
1414
|
+
}));
|
|
1415
|
+
}
|
|
1416
|
+
export async function runDetect(config, asJson) {
|
|
1417
|
+
const targets = await detectTargets(config);
|
|
1418
|
+
if (asJson) {
|
|
1419
|
+
console.log(JSON.stringify({ version: CPAC_VERSION, targets }, null, 2));
|
|
1420
|
+
return 0;
|
|
1421
|
+
}
|
|
1422
|
+
for (const target of targets) {
|
|
1423
|
+
console.log(`${target.id.padEnd(8)} ${(target.detected ? "detected" : "missing").padEnd(9)} ${(target.installed ? "installed" : "not installed").padEnd(14)} ${target.path}`);
|
|
1424
|
+
}
|
|
1425
|
+
return 0;
|
|
1426
|
+
}
|
|
1427
|
+
function selectTargets(requested, all, eligible) {
|
|
1428
|
+
const unknown = requested.filter((id) => !TARGETS.some((target) => target.id === id));
|
|
1429
|
+
if (unknown.length > 0) {
|
|
1430
|
+
throw new CPACError(`unknown target(s): ${unknown.join(", ")}; use ${TARGETS.map((target) => target.id).join(",")}`);
|
|
1431
|
+
}
|
|
1432
|
+
const ids = requested.length > 0 ? requested : all ? TARGETS.map((target) => target.id) : eligible;
|
|
1433
|
+
return TARGETS.filter((target) => ids.includes(target.id));
|
|
1434
|
+
}
|
|
1435
|
+
export async function runInstall(config, requested, options) {
|
|
1436
|
+
const eligible = TARGETS.filter((target) => target.detected()).map((target) => target.id);
|
|
1437
|
+
const selected = selectTargets(requested, options.all, eligible);
|
|
1438
|
+
if (selected.length === 0) {
|
|
1439
|
+
throw new CPACError(`no supported targets detected; use --target ${TARGETS.map((target) => target.id).join(",")} or --all`);
|
|
1440
|
+
}
|
|
1441
|
+
for (const target of selected) {
|
|
1442
|
+
if (target.installed(config) && !options.force) {
|
|
1443
|
+
console.log(`${target.id}: already installed; use --force to reinstall`);
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
await target.install(config, options.dryRun);
|
|
1447
|
+
}
|
|
1448
|
+
return 0;
|
|
1449
|
+
}
|
|
1450
|
+
export async function runUninstall(config, requested, options) {
|
|
1451
|
+
const eligible = TARGETS.filter((target) => target.installed(config)).map((target) => target.id);
|
|
1452
|
+
const selected = selectTargets(requested, options.all, eligible);
|
|
1453
|
+
if (selected.length === 0) {
|
|
1454
|
+
throw new CPACError("no installed CPAC integrations found; use --target <id> or --all");
|
|
1455
|
+
}
|
|
1456
|
+
for (const target of selected) {
|
|
1457
|
+
if (!target.installed(config)) {
|
|
1458
|
+
console.log(`${target.id}: not installed`);
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
await target.uninstall(config, options.dryRun);
|
|
1462
|
+
}
|
|
1463
|
+
return 0;
|
|
1464
|
+
}
|
|
1465
|
+
function compareVersions(a, b) {
|
|
1466
|
+
const left = a.split(".").map(Number);
|
|
1467
|
+
const right = b.split(".").map(Number);
|
|
1468
|
+
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
1469
|
+
const l = left[index] ?? 0;
|
|
1470
|
+
const r = right[index] ?? 0;
|
|
1471
|
+
if (l !== r)
|
|
1472
|
+
return l > r ? 1 : -1;
|
|
1473
|
+
}
|
|
1474
|
+
return 0;
|
|
1475
|
+
}
|
|
1476
|
+
export async function runUpgrade(checkOnly) {
|
|
1477
|
+
console.log(`Current version: ${CPAC_VERSION}`);
|
|
1478
|
+
let latest;
|
|
1479
|
+
try {
|
|
1480
|
+
const response = await fetch("https://registry.npmjs.org/@yhong91/cpac/latest", {
|
|
1481
|
+
headers: { Accept: "application/json" },
|
|
1482
|
+
signal: AbortSignal.timeout(10_000),
|
|
1483
|
+
});
|
|
1484
|
+
if (response.ok) {
|
|
1485
|
+
const data = (await response.json());
|
|
1486
|
+
if (typeof data.version === "string")
|
|
1487
|
+
latest = data.version;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
catch {
|
|
1491
|
+
latest = undefined;
|
|
1492
|
+
}
|
|
1493
|
+
if (!latest) {
|
|
1494
|
+
console.error("Could not fetch latest version from npm. Check your network connection.");
|
|
1495
|
+
return 1;
|
|
1496
|
+
}
|
|
1497
|
+
console.log(`Latest version: ${latest}`);
|
|
1498
|
+
if (compareVersions(CPAC_VERSION, latest) >= 0) {
|
|
1499
|
+
console.log("Already up to date.");
|
|
1500
|
+
return 0;
|
|
1501
|
+
}
|
|
1502
|
+
if (checkOnly) {
|
|
1503
|
+
console.log(`Update available. Run: npm install -g @yhong91/cpac@latest`);
|
|
1504
|
+
return 0;
|
|
1505
|
+
}
|
|
1506
|
+
console.log(`Update available. Run: npm install -g @yhong91/cpac@latest`);
|
|
1507
|
+
return 0;
|
|
1508
|
+
}
|
|
1198
1509
|
function usage() {
|
|
1199
1510
|
return [
|
|
1200
1511
|
"Usage: cpac",
|
|
@@ -1202,13 +1513,73 @@ function usage() {
|
|
|
1202
1513
|
" cpac proxy [--config PATH]",
|
|
1203
1514
|
" cpac claude [--config PATH] [--] [claude args...]",
|
|
1204
1515
|
" cpac pi <install|uninstall|status> [--config PATH]",
|
|
1516
|
+
" cpac kimi <install|uninstall|status> [--config PATH]",
|
|
1517
|
+
" cpac detect [--json] [--home PATH]",
|
|
1518
|
+
" cpac install [--target codex,claude,pi,kimi] [--all] [--dry-run] [--force] [--home PATH]",
|
|
1519
|
+
" cpac uninstall [--target codex,claude,pi,kimi] [--all] [--dry-run] [--home PATH]",
|
|
1520
|
+
" cpac upgrade [--check]",
|
|
1521
|
+
" cpac version",
|
|
1205
1522
|
].join("\n");
|
|
1206
1523
|
}
|
|
1207
1524
|
function parseArgs(args) {
|
|
1208
1525
|
if (args.length === 0) {
|
|
1209
1526
|
return { command: "guide", configPath: defaultConfigPath() };
|
|
1210
1527
|
}
|
|
1211
|
-
if (args[0] === "
|
|
1528
|
+
if (args[0] === "detect" ||
|
|
1529
|
+
args[0] === "install" ||
|
|
1530
|
+
args[0] === "uninstall" ||
|
|
1531
|
+
args[0] === "upgrade" ||
|
|
1532
|
+
args[0] === "version") {
|
|
1533
|
+
const parsed = {
|
|
1534
|
+
command: args[0],
|
|
1535
|
+
configPath: defaultConfigPath(),
|
|
1536
|
+
json: false,
|
|
1537
|
+
targets: [],
|
|
1538
|
+
all: false,
|
|
1539
|
+
dryRun: false,
|
|
1540
|
+
force: false,
|
|
1541
|
+
check: false,
|
|
1542
|
+
};
|
|
1543
|
+
for (let index = 1; index < args.length; index++) {
|
|
1544
|
+
const arg = args[index];
|
|
1545
|
+
if (arg === "--json")
|
|
1546
|
+
parsed.json = true;
|
|
1547
|
+
else if (arg === "--all")
|
|
1548
|
+
parsed.all = true;
|
|
1549
|
+
else if (arg === "--dry-run")
|
|
1550
|
+
parsed.dryRun = true;
|
|
1551
|
+
else if (arg === "--force")
|
|
1552
|
+
parsed.force = true;
|
|
1553
|
+
else if (arg === "--check")
|
|
1554
|
+
parsed.check = true;
|
|
1555
|
+
else if (arg === "--target" || arg === "--targets") {
|
|
1556
|
+
const value = args[++index];
|
|
1557
|
+
if (!value)
|
|
1558
|
+
throw new CPACError(`${arg} requires a comma-separated list`);
|
|
1559
|
+
parsed.targets.push(...value
|
|
1560
|
+
.split(",")
|
|
1561
|
+
.map((entry) => entry.trim())
|
|
1562
|
+
.filter(Boolean));
|
|
1563
|
+
}
|
|
1564
|
+
else if (arg === "--home") {
|
|
1565
|
+
const value = args[++index];
|
|
1566
|
+
if (!value)
|
|
1567
|
+
throw new CPACError("--home requires a path");
|
|
1568
|
+
parsed.home = resolve(expandUserPath(value));
|
|
1569
|
+
}
|
|
1570
|
+
else if (arg === "--config") {
|
|
1571
|
+
const value = args[++index];
|
|
1572
|
+
if (!value)
|
|
1573
|
+
throw new CPACError("--config requires a path");
|
|
1574
|
+
parsed.configPath = resolve(expandUserPath(value));
|
|
1575
|
+
}
|
|
1576
|
+
else {
|
|
1577
|
+
throw new CPACError(`unknown option: ${arg}`);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
return parsed;
|
|
1581
|
+
}
|
|
1582
|
+
if (args[0] === "pi" || args[0] === "kimi") {
|
|
1212
1583
|
let configPath = defaultConfigPath();
|
|
1213
1584
|
let index = 1;
|
|
1214
1585
|
if (args[index] === "--config") {
|
|
@@ -1219,7 +1590,7 @@ function parseArgs(args) {
|
|
|
1219
1590
|
index += 1;
|
|
1220
1591
|
}
|
|
1221
1592
|
const action = args[index] || "status";
|
|
1222
|
-
return { command:
|
|
1593
|
+
return { command: args[0], configPath, action };
|
|
1223
1594
|
}
|
|
1224
1595
|
if (args[0] === "claude") {
|
|
1225
1596
|
let configPath = defaultConfigPath();
|
|
@@ -1274,15 +1645,38 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1274
1645
|
const parsed = parseArgs(args);
|
|
1275
1646
|
if (!parsed)
|
|
1276
1647
|
return 0;
|
|
1648
|
+
if ("home" in parsed && parsed.home)
|
|
1649
|
+
process.env.HOME = parsed.home;
|
|
1277
1650
|
const config = loadConfig(parsed.configPath, true);
|
|
1278
1651
|
if (parsed.command === "guide")
|
|
1279
1652
|
return await guide(config);
|
|
1653
|
+
if (parsed.command === "detect")
|
|
1654
|
+
return await runDetect(config, parsed.json);
|
|
1655
|
+
if (parsed.command === "install")
|
|
1656
|
+
return await runInstall(config, parsed.targets, {
|
|
1657
|
+
all: parsed.all,
|
|
1658
|
+
dryRun: parsed.dryRun,
|
|
1659
|
+
force: parsed.force,
|
|
1660
|
+
});
|
|
1661
|
+
if (parsed.command === "uninstall")
|
|
1662
|
+
return await runUninstall(config, parsed.targets, {
|
|
1663
|
+
all: parsed.all,
|
|
1664
|
+
dryRun: parsed.dryRun,
|
|
1665
|
+
});
|
|
1666
|
+
if (parsed.command === "upgrade")
|
|
1667
|
+
return await runUpgrade(parsed.check);
|
|
1668
|
+
if (parsed.command === "version") {
|
|
1669
|
+
console.log(CPAC_VERSION);
|
|
1670
|
+
return 0;
|
|
1671
|
+
}
|
|
1280
1672
|
if (parsed.command === "claude")
|
|
1281
1673
|
return await runClaude(config, parsed.args);
|
|
1282
1674
|
if (parsed.command === "proxy")
|
|
1283
1675
|
return await runProxy(config);
|
|
1284
1676
|
if (parsed.command === "pi")
|
|
1285
1677
|
return await runPi(config, parsed.action);
|
|
1678
|
+
if (parsed.command === "kimi")
|
|
1679
|
+
return await runKimi(config, parsed.action);
|
|
1286
1680
|
if (parsed.command === "inject")
|
|
1287
1681
|
await inject(config);
|
|
1288
1682
|
else if (parsed.command === "restore")
|
|
@@ -16,7 +16,7 @@ const MAX_TOKENS = {
|
|
|
16
16
|
"claude-opus-4-6-thinking": 128000, "claude-sonnet-4-6": 64000,
|
|
17
17
|
"gemini-3.6-flash": 65536, "gemini-3.6-flash-high": 65536,
|
|
18
18
|
"deepseek-v4-pro": 128000, "deepseek-v4-flash": 128000,
|
|
19
|
-
"glm-5.
|
|
19
|
+
"glm-5.3": 128000, "kimi-k2.7-code": 128000, "minimax-m3": 128000,
|
|
20
20
|
"mimo-v2.5": 131072, "mimo-v2.5-pro": 131072, "grok-4.5": 128000,
|
|
21
21
|
"doubao-seed-2.0-lite": 128000, "doubao-seed-2.1-turbo": 128000,
|
|
22
22
|
};
|