@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 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] === "pi") {
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: "pi", configPath, action };
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.2": 128000, "kimi-k2.7-code": 128000, "minimax-m3": 128000,
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {