@yhong91/cpac 0.1.27 → 0.1.29

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  CPAC 将 Codex 和 Claude Code 接入远端 CLIProxyAPI(CPA):
4
4
 
5
- - **Codex**:注入模型目录和 loopback 网关,保留内置 `openai` provider,并可逐字节恢复原配置。
5
+ - **Codex**:注入模型目录和 loopback 网关,保留内置 `openai` provider。无用户改动时逐字节恢复原配置;注入期间改过文件则只撤掉 CPAC 字段。
6
6
  - **Claude Code**:通过临时环境变量启动,不修改 Claude Code 配置。
7
7
 
8
8
  实现基于 TypeScript + Node.js,运行时零依赖。
@@ -41,7 +41,7 @@ cpac install --all # 注入所有检测到的目标(codex/
41
41
  cpac install --target codex # 只注入 Codex(默认开启 multi-agent v2)
42
42
  cpac install --target codex --v2_models # 先多选 spawn_agent 模型再注入
43
43
  cpac install --target codex --max_context # 注入时把模型上下文提升到实测上限(opt-in,仅限 codex)
44
- cpac restore --target codex # 字节级恢复原配置
44
+ cpac restore --target codex # 撤掉注入;注入期间的用户改动会留下
45
45
 
46
46
  # 4b. 临时接管(退出无痕,不写任何配置文件)
47
47
  cpac claude # Claude Code 走 CPA
@@ -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` 按原 mode 逐字节恢复首次备份、关闭 CPAC loopback 代理;若原文件不存在,则删除注入创建的 `config.toml`。
251
+ 第一次注入时,原文件按原始 bytes 备份到 `state_dir/config.toml.backup`。重复注入不会覆盖首次备份,并会保留 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
 
@@ -285,7 +285,7 @@ cpac opencode [--] [opencode args...]
285
285
  cpac pi install
286
286
  ```
287
287
 
288
- 它将生成 `cpac.ts` 写入 `~/.pi/agent/extensions/`,Pi 启动时自动加载并从 CPA 动态注册模型目录。卸载:
288
+ 它将生成 `pi-cpac.ts` 写入 `~/.pi/agent/extensions/`,Pi 启动时自动加载并从 CPA 动态注册模型目录。卸载:
289
289
 
290
290
  ```bash
291
291
  cpac restore --target pi
@@ -326,7 +326,7 @@ CPAC CLI 可临时覆盖 CPA 地址:
326
326
  CPA_BASE_URL='https://cpa.example.com' cpac claude
327
327
  ```
328
328
 
329
- `pi-cpa.ts` 插件不读取此变量,固定连接远程 `http://124.223.178.52:8317`,并且只从 Pi 进程环境读取 `CPA_API_KEY`;不读取用户 home 中的私有文件。
329
+ `pi-cpac.ts` 插件固定连接配置指定的 CPA 地址,并且只从 Pi 进程环境读取 `CPA_API_KEY`;不读取用户 home 中的私有文件。
330
330
 
331
331
  ## 可选 JSON 配置
332
332
 
package/dist/agents.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { fetchCatalog, pickSpawnModels, proxyFingerprint, readState, saveSpawnModels, stateBytes, stateProxy, } from "./config.js";
6
+ import { fetchCatalog, injectedOwnership, pickSpawnModels, proxyFingerprint, readState, saveSpawnModels, stateBytes, stateProxy, } from "./config.js";
7
7
  import { proxyIsHealthy, startProxyProcess, stopProxyProcess, } from "./proxy.js";
8
8
  import { MANAGED_MARKER, inject, restore } from "./targets/codex.js";
9
9
  import { grokConfigPath, grokHome, installGrokConfig, isGrokConfigInstalled, uninstallGrokConfig, } from "./targets/grok.js";
@@ -76,13 +76,16 @@ export async function runTargetLauncher(config, targetId, args, executable = tar
76
76
  if (["kimi", "grok"].includes(targetId)) {
77
77
  const state = readState(config.state_dir);
78
78
  const proxy = state ? stateProxy(state) : null;
79
- if (!proxy || !(await proxyIsHealthy(proxy))) {
79
+ const apiKey = await resolveApiKey(config.api_key_env);
80
+ const fingerprint = proxyFingerprint(config, apiKey);
81
+ const reuse = proxy !== null &&
82
+ state?.proxy_fingerprint === fingerprint &&
83
+ (await proxyIsHealthy(proxy));
84
+ if (!reuse) {
80
85
  if (proxy)
81
86
  await stopProxyProcess(proxy);
82
- const apiKey = await resolveApiKey(config.api_key_env);
83
87
  const started = await startProxyProcess(config, apiKey);
84
88
  try {
85
- const fingerprint = proxyFingerprint(config, apiKey);
86
89
  const catalog = await fetchCatalog(config.cpa_url, apiKey);
87
90
  const catalogPath = join(config.state_dir, "codex-models.json");
88
91
  mkdirSync(config.state_dir, { recursive: true, mode: 0o700 });
@@ -90,7 +93,7 @@ export async function runTargetLauncher(config, targetId, args, executable = tar
90
93
  const originalMode = existsSync(config.codex_config)
91
94
  ? statSync(config.codex_config).mode & 0o7777
92
95
  : 0o600;
93
- atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, state ? state.config_existed : false, state ? state.config_mode : originalMode, started, fingerprint));
96
+ atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, state ? state.config_existed : false, state ? state.config_mode : originalMode, started, fingerprint, injectedOwnership(state)));
94
97
  }
95
98
  catch (error) {
96
99
  await stopProxyProcess(started);
@@ -206,14 +209,14 @@ const TARGETS = [
206
209
  version: () => detectClientVersion("pi"),
207
210
  install: async (config, dryRun) => {
208
211
  if (dryRun) {
209
- console.log(`would write Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
212
+ console.log(`would write Pi extension: ${join(piExtensionsDir(), "pi-cpac.ts")}`);
210
213
  return;
211
214
  }
212
215
  await installPiExtension(config);
213
216
  },
214
217
  uninstall: async (_config, dryRun) => {
215
218
  if (dryRun) {
216
- console.log(`would remove Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
219
+ console.log(`would remove Pi extension: ${join(piExtensionsDir(), "pi-cpac.ts")}`);
217
220
  return;
218
221
  }
219
222
  await uninstallPiExtension();
package/dist/config.js CHANGED
@@ -31,6 +31,9 @@ const STATE_KEYS = new Set([
31
31
  "proxy_fingerprint",
32
32
  "proxy_pid",
33
33
  "proxy_port",
34
+ "injected_config_hash",
35
+ "injected_openai_base_url",
36
+ "injected_catalog_path",
34
37
  ]);
35
38
  export const STATE_FILES = [
36
39
  "state.json",
@@ -388,6 +391,21 @@ export function readState(stateDir) {
388
391
  !/^[a-f0-9]{64}$/.test(value.proxy_fingerprint))) {
389
392
  throw new CPACError("invalid loopback proxy fingerprint");
390
393
  }
394
+ if (value.injected_config_hash !== undefined &&
395
+ (typeof value.injected_config_hash !== "string" ||
396
+ !/^[a-f0-9]{64}$/.test(value.injected_config_hash))) {
397
+ throw new CPACError("invalid injected_config_hash in state");
398
+ }
399
+ if (value.injected_openai_base_url !== undefined &&
400
+ (typeof value.injected_openai_base_url !== "string" ||
401
+ !value.injected_openai_base_url.trim())) {
402
+ throw new CPACError("invalid injected_openai_base_url in state");
403
+ }
404
+ if (value.injected_catalog_path !== undefined &&
405
+ (typeof value.injected_catalog_path !== "string" ||
406
+ !value.injected_catalog_path.trim())) {
407
+ throw new CPACError("invalid injected_catalog_path in state");
408
+ }
391
409
  return value;
392
410
  }
393
411
  export function stateProxy(state) {
@@ -395,7 +413,21 @@ export function stateProxy(state) {
395
413
  ? { id: state.proxy_id, pid: state.proxy_pid, port: state.proxy_port }
396
414
  : null;
397
415
  }
398
- export function stateBytes(config, existed, mode, proxy, proxyFingerprint) {
416
+ export function injectedOwnership(state) {
417
+ if (!state?.injected_config_hash ||
418
+ !state.injected_openai_base_url ||
419
+ !state.injected_catalog_path)
420
+ return undefined;
421
+ return {
422
+ hash: state.injected_config_hash,
423
+ openaiBaseUrl: state.injected_openai_base_url,
424
+ catalogPath: state.injected_catalog_path,
425
+ };
426
+ }
427
+ export function sha256Hex(bytes) {
428
+ return createHash("sha256").update(bytes).digest("hex");
429
+ }
430
+ export function stateBytes(config, existed, mode, proxy, proxyFingerprint, injected) {
399
431
  return Buffer.from(`${JSON.stringify({
400
432
  config_path: config.codex_config,
401
433
  config_existed: existed,
@@ -404,6 +436,13 @@ export function stateBytes(config, existed, mode, proxy, proxyFingerprint) {
404
436
  proxy_fingerprint: proxyFingerprint,
405
437
  proxy_pid: proxy.pid,
406
438
  proxy_port: proxy.port,
439
+ ...(injected
440
+ ? {
441
+ injected_config_hash: injected.hash,
442
+ injected_openai_base_url: injected.openaiBaseUrl,
443
+ injected_catalog_path: injected.catalogPath,
444
+ }
445
+ : {}),
407
446
  }, null, 2)}\n`);
408
447
  }
409
448
  export function originalBytes(stateDir, state, expectedPath) {
@@ -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();
package/dist/proxy.js CHANGED
@@ -5,7 +5,7 @@ import { createServer, request as httpRequest, } from "node:http";
5
5
  import { request as httpsRequest } from "node:https";
6
6
  import { join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
- import { apiBase, originalBytes, proxyFingerprint, readState, stateBytes, stateProxy, } from "./config.js";
8
+ import { apiBase, injectedOwnership, originalBytes, proxyFingerprint, readState, stateBytes, stateProxy, } from "./config.js";
9
9
  import { CPACError, atomicWrite, objectValue, resolveApiKey } from "./util.js";
10
10
  const HOP_BY_HOP_HEADERS = new Set([
11
11
  "connection",
@@ -322,7 +322,7 @@ export async function runProxy(config) {
322
322
  }
323
323
  const proxy = await createLoopbackProxy(config.cpa_url, apiKey, recorded.id, recorded.port);
324
324
  try {
325
- atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, state.config_existed, state.config_mode, { id: recorded.id, pid: process.pid, port: proxy.port }, proxyFingerprint(config, apiKey)));
325
+ atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, state.config_existed, state.config_mode, { id: recorded.id, pid: process.pid, port: proxy.port }, proxyFingerprint(config, apiKey), injectedOwnership(state)));
326
326
  }
327
327
  catch (error) {
328
328
  proxy.server.closeAllConnections?.();
@@ -1,6 +1,6 @@
1
1
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { PROVIDER, STATE_FILES, cleanupStateFiles, fetchCatalog, liftContextWindows, originalBytes, proxyFingerprint, readState, reorderCatalog, stateBytes, stateProxy, } from "../config.js";
3
+ import { PROVIDER, STATE_FILES, cleanupStateFiles, fetchCatalog, injectedOwnership, liftContextWindows, originalBytes, proxyFingerprint, readState, reorderCatalog, sha256Hex, stateBytes, stateProxy, } from "../config.js";
4
4
  import { proxyIsHealthy, startProxyProcess, stopProxyProcess, } from "../proxy.js";
5
5
  import { CPACError, atomicWrite, dominantEol, tomlString } from "../util.js";
6
6
  const MODEL_PROVIDER_KEY = /^\s*(?:model_provider|"model_provider"|'model_provider')\s*=/;
@@ -8,6 +8,37 @@ const MODEL_CATALOG_KEY = /^\s*(?:model_catalog_json|"model_catalog_json"|'model
8
8
  const OPENAI_BASE_URL_KEY = /^\s*(?:openai_base_url|"openai_base_url"|'openai_base_url')\s*=/;
9
9
  const ROOT_KEY = new RegExp(`(?:${MODEL_PROVIDER_KEY.source}|${MODEL_CATALOG_KEY.source}|${OPENAI_BASE_URL_KEY.source})`);
10
10
  export const MANAGED_MARKER = "# CPAC managed; run CPAC 'restore' to restore the original file.";
11
+ function tomlQuotedValue(line) {
12
+ const eq = line.indexOf("=");
13
+ if (eq === -1)
14
+ return undefined;
15
+ const rest = line.slice(eq + 1).trimStart();
16
+ if (rest.startsWith('"')) {
17
+ const match = /^(?:"(?:\\.|[^"\\])*")/.exec(rest);
18
+ if (!match)
19
+ return undefined;
20
+ try {
21
+ return JSON.parse(match[0]);
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ if (rest.startsWith("'")) {
28
+ const end = rest.indexOf("'", 1);
29
+ return end === -1 ? undefined : rest.slice(1, end);
30
+ }
31
+ return undefined;
32
+ }
33
+ function ownedRootLine(line, owned) {
34
+ if (!owned)
35
+ return false;
36
+ const value = tomlQuotedValue(line);
37
+ if (value === undefined)
38
+ return false;
39
+ return ((MODEL_CATALOG_KEY.test(line) && value === owned.catalogPath) ||
40
+ (OPENAI_BASE_URL_KEY.test(line) && value === owned.openaiBaseUrl));
41
+ }
11
42
  function providerPatterns(provider = PROVIDER) {
12
43
  const escaped = provider.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
44
  const modelProviders = `(?:model_providers|"model_providers"|'model_providers')`;
@@ -41,7 +72,7 @@ export function hasProviderTable(content, provider = PROVIDER) {
41
72
  }
42
73
  return false;
43
74
  }
44
- function stripManagedConfig(content) {
75
+ function stripManagedConfig(content, owned) {
45
76
  let text;
46
77
  try {
47
78
  text = new TextDecoder("utf-8", { fatal: true }).decode(content);
@@ -75,6 +106,8 @@ function stripManagedConfig(content) {
75
106
  managedRootKeys -= 1;
76
107
  continue;
77
108
  }
109
+ if (inRoot && ownedRootLine(line, owned))
110
+ continue;
78
111
  kept.push(line);
79
112
  }
80
113
  const output = kept.join("\n");
@@ -225,7 +258,7 @@ export async function inject(config, v2Off = false, maxContext = false) {
225
258
  const backup = originalBytes(config.state_dir, state, config.codex_config);
226
259
  try {
227
260
  original = existsSync(config.codex_config)
228
- ? stripManagedConfig(readFileSync(config.codex_config))
261
+ ? stripManagedConfig(readFileSync(config.codex_config), injectedOwnership(state))
229
262
  : backup;
230
263
  }
231
264
  catch (error) {
@@ -293,6 +326,11 @@ export async function inject(config, v2Off = false, maxContext = false) {
293
326
  await stopProxyProcess(proxy);
294
327
  throw error;
295
328
  }
329
+ const ownership = {
330
+ hash: state?.injected_config_hash ?? sha256Hex(injected),
331
+ openaiBaseUrl: `http://127.0.0.1:${proxy.port}/v1`,
332
+ catalogPath,
333
+ };
296
334
  mkdirSync(config.state_dir, { recursive: true, mode: 0o700 });
297
335
  if (!stateDirExisted)
298
336
  chmodSync(config.state_dir, 0o700);
@@ -300,7 +338,7 @@ export async function inject(config, v2Off = false, maxContext = false) {
300
338
  try {
301
339
  atomicWrite(catalogPath, catalog.bytes);
302
340
  atomicWrite(config.codex_config, injected, originalMode);
303
- atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint));
341
+ atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint, ownership));
304
342
  }
305
343
  catch (error) {
306
344
  if (startedProxy)
@@ -316,7 +354,7 @@ export async function inject(config, v2Off = false, maxContext = false) {
316
354
  atomicWrite(catalogPath, catalog.bytes);
317
355
  atomicWrite(config.codex_config, injected, originalMode);
318
356
  configWritten = true;
319
- atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint));
357
+ atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint, ownership));
320
358
  }
321
359
  catch (error) {
322
360
  if (configWritten) {
@@ -361,10 +399,25 @@ export async function restore(config) {
361
399
  if (!state)
362
400
  throw new CPACError("no active CPAC injection");
363
401
  const original = originalBytes(config.state_dir, state, config.codex_config);
364
- if (state.config_existed)
365
- atomicWrite(config.codex_config, original, state.config_mode);
366
- else
367
- rmSync(config.codex_config, { force: true });
402
+ const current = existsSync(config.codex_config)
403
+ ? readFileSync(config.codex_config)
404
+ : null;
405
+ const unchanged = !state.injected_config_hash ||
406
+ (current !== null && sha256Hex(current) === state.injected_config_hash);
407
+ if (!current || unchanged) {
408
+ if (state.config_existed)
409
+ atomicWrite(config.codex_config, original, state.config_mode);
410
+ else
411
+ rmSync(config.codex_config, { force: true });
412
+ }
413
+ else {
414
+ const stripped = stripManagedConfig(current, injectedOwnership(state));
415
+ const empty = new TextDecoder("utf-8").decode(stripped).trim() === "";
416
+ if (!state.config_existed && empty)
417
+ rmSync(config.codex_config, { force: true });
418
+ else
419
+ atomicWrite(config.codex_config, stripped, state.config_mode);
420
+ }
368
421
  const proxy = stateProxy(state);
369
422
  if (proxy)
370
423
  await stopProxyProcess(proxy);
@@ -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
+ const target = join(dir, "pi-cpac.ts");
22
23
  ensureCpacBackup(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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
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
  },