@yhong91/cpac 0.1.28 → 0.1.30

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
@@ -248,7 +248,7 @@ Codex App 的长驻 `app-server` 可能缓存旧目录。`inject` 会删除 `mod
248
248
 
249
249
  注入命令可选 `--max_context`(`cpac inject --max_context` 或 `cpac install --target codex --max_context`,默认不带):把 catalog 中 `max_context_window` 高于 `context_window` 的模型提升到上限,并把 `auto_compact_token_limit` 设为上限的 90%。Codex 把 `context_window` 当输入预算而非展示标签,上游默认保留保守运营值(如 GPT-5.6 家族 272k),带上该参数后可用到实测上限(约 921k,opencodex 实测);90% 压缩线确保在硬上限前触发 auto-compact。不带参数注入的仍是原本上下文的目录。该参数仅限 codex,与其他 target 组合使用会直接报错。
250
250
 
251
- 第一次注入时,原文件按原始 bytes 备份到 `state_dir/config.toml.backup`。重复注入不会覆盖首次备份,并会保留 CPAC 管理字段之外的用户编辑。`restore`:若 `config.toml` 相对首次注入未改,按原 mode 逐字节还原备份;若用户在注入期间改过文件,只撤掉 CPAC 写入的 catalog、url 和标记。原先没有 `config.toml` 且撤掉后为空则删除文件。关闭 CPAC loopback 代理。
251
+ 第一次注入时,原文件按原始 bytes 备份到 `state_dir/codex/config.toml`。重复注入不会覆盖首次备份,并会保留 CPAC 管理字段之外的用户编辑。`restore`:若 `config.toml` 相对首次注入未改,按原 mode 逐字节还原备份;若用户在注入期间改过文件,只撤掉 CPAC 写入的 catalog、url 和标记。原先没有 `config.toml` 且撤掉后为空则删除文件。关闭 CPAC loopback 代理。
252
252
 
253
253
  loopback 代理是 detached 用户进程,不安装系统服务。机器重启或进程意外退出后,`cpac status` 会报告 `loopback proxy stopped`;重新执行 `cpac inject` 即可恢复。
254
254
 
@@ -265,6 +265,11 @@ cpac proxy
265
265
  ```text
266
266
  Codex 配置:$CODEX_HOME/config.toml,未设置 CODEX_HOME 时为 ~/.codex/config.toml
267
267
  CPAC state:~/.cpac
268
+ 各 agent 配置备份:~/.cpac/<agent>/<原文件名>
269
+ Codex ~/.cpac/codex/config.toml
270
+ Pi ~/.cpac/pi/pi-cpac.ts
271
+ Kimi ~/.cpac/kimi/config.toml
272
+ Grok ~/.cpac/grok/config.toml
268
273
  ```
269
274
 
270
275
  为防止误删或覆盖,`state_dir` 不能是文件系统根目录、用户 home、系统临时目录,也不能包含 Codex 配置文件。
@@ -285,7 +290,7 @@ cpac opencode [--] [opencode args...]
285
290
  cpac pi install
286
291
  ```
287
292
 
288
- 它将生成 `cpac.ts` 写入 `~/.pi/agent/extensions/`,Pi 启动时自动加载并从 CPA 动态注册模型目录。卸载:
293
+ 它将生成 `pi-cpac.ts` 写入 `~/.pi/agent/extensions/`,Pi 启动时自动加载并从 CPA 动态注册模型目录。卸载:
289
294
 
290
295
  ```bash
291
296
  cpac restore --target pi
@@ -307,7 +312,7 @@ cpac kimi install
307
312
  cpac restore --target kimi
308
313
  ```
309
314
 
310
- Kimi Code 配置目录尊重 `KIMI_CODE_HOME` 环境变量。首次写入前若 `config.toml` 已存在,会在同目录保留一份原始快照 `config.toml.cpac-backup`(只存最早版本),万一文件损坏可手动复制回去。
315
+ Kimi Code 配置目录尊重 `KIMI_CODE_HOME` 环境变量。首次写入前若 `config.toml` 已存在,会在 `state_dir/kimi/config.toml` 保留一份原始快照(只存最早版本),万一文件损坏可手动复制回去。
311
316
 
312
317
  ## 环境变量
313
318
 
@@ -326,7 +331,7 @@ CPAC CLI 可临时覆盖 CPA 地址:
326
331
  CPA_BASE_URL='https://cpa.example.com' cpac claude
327
332
  ```
328
333
 
329
- `pi-cpa.ts` 插件不读取此变量,固定连接远程 `http://124.223.178.52:8317`,并且只从 Pi 进程环境读取 `CPA_API_KEY`;不读取用户 home 中的私有文件。
334
+ `pi-cpac.ts` 插件固定连接配置指定的 CPA 地址,并且只从 Pi 进程环境读取 `CPA_API_KEY`;不读取用户 home 中的私有文件。
330
335
 
331
336
  ## 可选 JSON 配置
332
337
 
package/dist/agents.js CHANGED
@@ -209,14 +209,14 @@ const TARGETS = [
209
209
  version: () => detectClientVersion("pi"),
210
210
  install: async (config, dryRun) => {
211
211
  if (dryRun) {
212
- console.log(`would write Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
212
+ console.log(`would write Pi extension: ${join(piExtensionsDir(), "pi-cpac.ts")}`);
213
213
  return;
214
214
  }
215
215
  await installPiExtension(config);
216
216
  },
217
217
  uninstall: async (_config, dryRun) => {
218
218
  if (dryRun) {
219
- console.log(`would remove Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
219
+ console.log(`would remove Pi extension: ${join(piExtensionsDir(), "pi-cpac.ts")}`);
220
220
  return;
221
221
  }
222
222
  await uninstallPiExtension();
package/dist/config.js CHANGED
@@ -452,7 +452,9 @@ export function originalBytes(stateDir, state, expectedPath) {
452
452
  if (!state.config_existed)
453
453
  return new Uint8Array();
454
454
  try {
455
- return readFileSync(join(stateDir, "config.toml.backup"));
455
+ const next = join(stateDir, "codex", "config.toml");
456
+ const prev = join(stateDir, "config.toml.backup");
457
+ return readFileSync(existsSync(next) ? next : prev);
456
458
  }
457
459
  catch (error) {
458
460
  throw new CPACError(`cannot read original config backup: ${error instanceof Error ? error.message : String(error)}`);
@@ -461,6 +463,15 @@ export function originalBytes(stateDir, state, expectedPath) {
461
463
  export function cleanupStateFiles(stateDir) {
462
464
  for (const name of STATE_FILES)
463
465
  rmSync(join(stateDir, name), { force: true });
466
+ rmSync(join(stateDir, "codex", "config.toml"), { force: true });
467
+ try {
468
+ rmdirSync(join(stateDir, "codex"));
469
+ }
470
+ catch (error) {
471
+ const code = error.code;
472
+ if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST")
473
+ throw error;
474
+ }
464
475
  try {
465
476
  rmdirSync(stateDir);
466
477
  }
@@ -1,4 +1,4 @@
1
- // CPAC Pi extension: register CPA models as Pi providers
1
+ // Pi CPAC extension: register CPA models as Pi providers
2
2
  // Installed by: cpac pi install
3
3
 
4
4
  const CPA = "__CPA_URL__";
@@ -71,7 +71,7 @@ function intField(value) {
71
71
  export default async function (pi) {
72
72
  const apiKey = (process.env.CPA_API_KEY || "").trim();
73
73
  if (!apiKey) {
74
- console.error("[cpac] CPA_API_KEY not set; skipping CPA providers");
74
+ console.error("[pi-cpac] CPA_API_KEY not set; skipping CPA providers");
75
75
  return;
76
76
  }
77
77
  let payload;
@@ -81,19 +81,19 @@ export default async function (pi) {
81
81
  signal: AbortSignal.timeout(10000),
82
82
  });
83
83
  if (!res.ok) {
84
- console.error("[cpac] CPA models request failed: HTTP " + res.status);
84
+ console.error("[pi-cpac] CPA models request failed: HTTP " + res.status);
85
85
  return;
86
86
  }
87
87
  payload = await res.json();
88
88
  } catch (error) {
89
- console.error("[cpac] CPA models request failed: " + (error && error.message || error));
89
+ console.error("[pi-cpac] CPA models request failed: " + (error && error.message || error));
90
90
  return;
91
91
  }
92
92
  const source = payload && Array.isArray(payload.models)
93
93
  ? payload.models
94
94
  : payload && Array.isArray(payload.data) ? payload.data : null;
95
95
  if (!source) {
96
- console.error("[cpac] CPA models response has no models list");
96
+ console.error("[pi-cpac] CPA models response has no models list");
97
97
  return;
98
98
  }
99
99
  const rows = [];
@@ -110,7 +110,7 @@ export default async function (pi) {
110
110
  });
111
111
  }
112
112
  if (rows.length === 0) {
113
- console.error("[cpac] CPA models response has no usable models");
113
+ console.error("[pi-cpac] CPA models response has no usable models");
114
114
  return;
115
115
  }
116
116
  const groups = new Map();
@@ -286,7 +286,8 @@ export async function inject(config, v2Off = false, maxContext = false) {
286
286
  catalog.bytes = liftContextWindows(catalog.bytes);
287
287
  const stateDirExisted = existsSync(config.state_dir);
288
288
  if (!state &&
289
- STATE_FILES.some((name) => existsSync(join(config.state_dir, name)))) {
289
+ (STATE_FILES.some((name) => existsSync(join(config.state_dir, name))) ||
290
+ existsSync(join(config.state_dir, "codex", "config.toml")))) {
290
291
  throw new CPACError("state_dir contains CPAC files without a valid state; refusing to overwrite them");
291
292
  }
292
293
  const fingerprint = proxyFingerprint(config, apiKey);
@@ -350,7 +351,7 @@ export async function inject(config, v2Off = false, maxContext = false) {
350
351
  let configWritten = false;
351
352
  try {
352
353
  if (existed)
353
- atomicWrite(join(config.state_dir, "config.toml.backup"), original);
354
+ atomicWrite(join(config.state_dir, "codex", "config.toml"), original);
354
355
  atomicWrite(catalogPath, catalog.bytes);
355
356
  atomicWrite(config.codex_config, injected, originalMode);
356
357
  configWritten = true;
@@ -93,7 +93,7 @@ export async function installGrokConfig(config) {
93
93
  lines.pop();
94
94
  lines.push(GROK_BLOCK_END);
95
95
  const target = grokConfigPath();
96
- ensureCpacBackup(target);
96
+ ensureCpacBackup(config.state_dir, "grok", target);
97
97
  writeGrokBlock(target, lines.join("\n"));
98
98
  console.log(`Installed Grok Build provider config: ${target}`);
99
99
  if (!recorded || !(await proxyIsHealthy(recorded))) {
@@ -159,7 +159,7 @@ export async function installKimiConfig(config) {
159
159
  }
160
160
  lines.push(KIMI_BLOCK_END);
161
161
  const target = kimiConfigPath();
162
- ensureCpacBackup(target);
162
+ ensureCpacBackup(config.state_dir, "kimi", target);
163
163
  writeKimiBlock(target, lines.join("\n"));
164
164
  console.log(`Installed Kimi Code provider config: ${target}`);
165
165
  if (!recorded || !(await proxyIsHealthy(recorded))) {
@@ -7,7 +7,8 @@ export function piExtensionsDir() {
7
7
  return join(expandUserPath(process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent")), "extensions");
8
8
  }
9
9
  export function isPiExtensionInstalled() {
10
- return existsSync(join(piExtensionsDir(), "cpac.ts"));
10
+ const dir = piExtensionsDir();
11
+ return existsSync(join(dir, "pi-cpac.ts")) || existsSync(join(dir, "cpac.ts"));
11
12
  }
12
13
  function piTemplatePath() {
13
14
  return join(dirname(fileURLToPath(import.meta.url)), "..", "pi-extension.template");
@@ -18,15 +19,32 @@ function piExtensionContent(cpaUrl) {
18
19
  export async function installPiExtension(config) {
19
20
  const dir = piExtensionsDir();
20
21
  mkdirSync(dir, { recursive: true, mode: 0o700 });
21
- const target = join(dir, "cpac.ts");
22
- ensureCpacBackup(target);
22
+ const target = join(dir, "pi-cpac.ts");
23
+ ensureCpacBackup(config.state_dir, "pi", target);
23
24
  atomicWrite(target, Buffer.from(piExtensionContent(config.cpa_url)), 0o644);
25
+ for (const legacy of ["cpac.ts", "pi-cpa.ts"]) {
26
+ const legacyPath = join(dir, legacy);
27
+ if (existsSync(legacyPath) && legacyPath !== target) {
28
+ try {
29
+ unlinkSync(legacyPath);
30
+ }
31
+ catch { }
32
+ }
33
+ }
24
34
  console.log(`Installed Pi extension: ${target}`);
25
35
  }
26
36
  export async function uninstallPiExtension() {
27
- const target = join(piExtensionsDir(), "cpac.ts");
28
- if (!existsSync(target))
37
+ const dir = piExtensionsDir();
38
+ const target = join(dir, "pi-cpac.ts");
39
+ const legacyTargets = [join(dir, "cpac.ts"), join(dir, "pi-cpa.ts")];
40
+ let removed = false;
41
+ for (const file of [target, ...legacyTargets]) {
42
+ if (existsSync(file)) {
43
+ unlinkSync(file);
44
+ console.log(`Removed Pi extension: ${file}`);
45
+ removed = true;
46
+ }
47
+ }
48
+ if (!removed)
29
49
  throw new CPACError("Pi extension is not installed");
30
- unlinkSync(target);
31
- console.log(`Removed Pi extension: ${target}`);
32
50
  }
package/dist/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { chmodSync, closeSync, copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { dirname, join, parse } from "node:path";
3
+ import { basename, dirname, join, parse } from "node:path";
4
4
  import { createInterface } from "node:readline/promises";
5
5
  import { Writable } from "node:stream";
6
6
  let atomicSequence = 0;
@@ -9,13 +9,28 @@ export class CPACError extends Error {
9
9
  export function objectValue(value) {
10
10
  return typeof value === "object" && value !== null && !Array.isArray(value);
11
11
  }
12
- export function ensureCpacBackup(target) {
13
- if (!existsSync(target))
14
- return;
15
- const backup = `${target}.cpac-backup`;
16
- if (existsSync(backup))
17
- return;
18
- copyFileSync(target, backup);
12
+ export function ensureCpacBackup(stateDir, agent, target) {
13
+ const dest = join(stateDir, agent, basename(target));
14
+ const sidecar = `${target}.cpac-backup`;
15
+ if (!existsSync(dest)) {
16
+ const source = existsSync(sidecar)
17
+ ? sidecar
18
+ : existsSync(target)
19
+ ? target
20
+ : null;
21
+ if (!source)
22
+ return;
23
+ mkdirSync(dirname(dest), { recursive: true, mode: 0o700 });
24
+ try {
25
+ chmodSync(stateDir, 0o700);
26
+ }
27
+ catch {
28
+ // Directory may already have the desired mode.
29
+ }
30
+ copyFileSync(source, dest);
31
+ }
32
+ if (existsSync(sidecar))
33
+ unlinkSync(sidecar);
19
34
  }
20
35
  export function expandUserPath(value) {
21
36
  if (value === "~")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,6 @@
30
30
  "clean": "node -e \"for (const d of ['dist','dist-test']) require('fs').rmSync(d,{recursive:true,force:true})\"",
31
31
  "build": "npm run clean --if-present && tsc -p tsconfig.build.json && cp src/pi-extension.template dist/ && node -e \"const fs=require('fs');const p='dist/cpac.js';const s=fs.readFileSync(p,'utf8');if(!s.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+s);try{fs.chmodSync(p,0o755)}catch{}\"",
32
32
  "check": "tsc -p tsconfig.json --noEmit",
33
- "check:pi": "tsc -p tsconfig.pi.json --noEmit",
34
33
  "test": "npm run clean && tsc -p tsconfig.test.json && cp src/pi-extension.template dist-test/src/ && node --test --test-reporter=spec dist-test/cpac.test.js",
35
34
  "pack:check": "npm pack --dry-run"
36
35
  },