@yhong91/cpac 0.1.30 → 0.1.32
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 +6 -5
- package/dist/agents.js +9 -38
- package/dist/config.js +127 -15
- package/dist/cpac.js +2 -2
- package/dist/pi-extension.template +4 -0
- package/dist/proxy.js +27 -7
- package/dist/targets/codex.js +37 -35
- package/dist/targets/grok.js +26 -15
- package/dist/targets/kimi.js +26 -15
- package/dist/targets/pi.js +8 -2
- package/dist/util.js +8 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -248,9 +248,9 @@ 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
|
-
|
|
251
|
+
`config.toml` 不存在时拒绝注入。当前是 native 时把原文件备份到 `state_dir/codex/config.toml`(覆盖更旧的 native 快照);已注入后再 inject 不覆盖备份。kimi/grok/pi 同样:没有旧配置不写空备份,native 覆盖旧快照,已接入则保留。`restore --target codex`:若 `config.toml` 相对注入未改,按原 mode 逐字节还原备份;若用户在注入期间改过文件,只撤掉 CPAC 写入的 catalog、url 和标记。没有其它需要代理的 agent 时才关闭代理。
|
|
252
252
|
|
|
253
|
-
loopback 代理是 detached 用户进程,不安装系统服务。机器重启或进程意外退出后,`cpac status` 会报告 `loopback proxy stopped`;重新执行 `cpac inject` 即可恢复。
|
|
253
|
+
loopback 代理是 detached 用户进程,不安装系统服务。机器重启或进程意外退出后,`cpac status` 会报告 `loopback proxy stopped`;重新执行 `cpac inject` 或 `cpac kimi` / `cpac grok` 即可恢复。
|
|
254
254
|
|
|
255
255
|
需要由现有进程管理器监督时,可使用前台入口:
|
|
256
256
|
|
|
@@ -258,18 +258,19 @@ loopback 代理是 detached 用户进程,不安装系统服务。机器重启
|
|
|
258
258
|
cpac proxy
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
它复用 `state.json` 里记录的端口和实例身份;没有 loopback 记录时会拒绝启动。
|
|
262
262
|
|
|
263
263
|
默认路径:
|
|
264
264
|
|
|
265
265
|
```text
|
|
266
266
|
Codex 配置:$CODEX_HOME/config.toml,未设置 CODEX_HOME 时为 ~/.codex/config.toml
|
|
267
|
-
CPAC state:~/.cpac
|
|
267
|
+
CPAC state:~/.cpac/state.json(公用 proxy 字段 + 已接入 agent 记录)
|
|
268
268
|
各 agent 配置备份:~/.cpac/<agent>/<原文件名>
|
|
269
269
|
Codex ~/.cpac/codex/config.toml
|
|
270
270
|
Pi ~/.cpac/pi/pi-cpac.ts
|
|
271
271
|
Kimi ~/.cpac/kimi/config.toml
|
|
272
272
|
Grok ~/.cpac/grok/config.toml
|
|
273
|
+
restore / `<agent> clear` 会删掉该 agent 的备份目录和 state.json 里的对应记录
|
|
273
274
|
```
|
|
274
275
|
|
|
275
276
|
为防止误删或覆盖,`state_dir` 不能是文件系统根目录、用户 home、系统临时目录,也不能包含 Codex 配置文件。
|
|
@@ -306,7 +307,7 @@ Pi 扩展尊重 `PI_CODING_AGENT_DIR` 环境变量。
|
|
|
306
307
|
cpac kimi install
|
|
307
308
|
```
|
|
308
309
|
|
|
309
|
-
它在 `~/.kimi-code/config.toml` 追加一个由 CPAC 管理的块,注册 `cpac` OpenAI 兼容 provider,并把 CPA 目录里的每个模型映射为一个 `cpac/<模型名>` 条目。带 `supported_reasoning_levels` 的模型会写入 `capabilities`、`support_efforts` 和 `default_effort`,否则 Kimi 只给 Claude 名套内置 thinking profile,其它模型会停在 thinking off 且无法改 effort。`base_url`
|
|
310
|
+
它在 `~/.kimi-code/config.toml` 追加一个由 CPAC 管理的块,注册 `cpac` OpenAI 兼容 provider,并把 CPA 目录里的每个模型映射为一个 `cpac/<模型名>` 条目。带 `supported_reasoning_levels` 的模型会写入 `capabilities`、`support_efforts` 和 `default_effort`,否则 Kimi 只给 Claude 名套内置 thinking profile,其它模型会停在 thinking off 且无法改 effort。`base_url` 指向共享 loopback 代理,因此密钥不落盘。install 会写入 `state.json` 并在需要时启动代理,不必先 `cpac inject`。卸载与状态:
|
|
310
311
|
|
|
311
312
|
```bash
|
|
312
313
|
cpac restore --target kimi
|
package/dist/agents.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readFileSync } 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 {
|
|
7
|
-
import { proxyIsHealthy, startProxyProcess, stopProxyProcess, } from "./proxy.js";
|
|
6
|
+
import { pickSpawnModels, saveSpawnModels } from "./config.js";
|
|
8
7
|
import { MANAGED_MARKER, inject, restore } from "./targets/codex.js";
|
|
9
8
|
import { grokConfigPath, grokHome, installGrokConfig, isGrokConfigInstalled, uninstallGrokConfig, } from "./targets/grok.js";
|
|
10
9
|
import { installKimiConfig, isKimiConfigInstalled, kimiConfigPath, uninstallKimiConfig, } from "./targets/kimi.js";
|
|
11
10
|
import { installPiExtension, isPiExtensionInstalled, piExtensionsDir, uninstallPiExtension, } from "./targets/pi.js";
|
|
12
|
-
import { CPACError, atomicWrite, checkboxPicker, expandUserPath, objectValue,
|
|
11
|
+
import { CPACError, atomicWrite, checkboxPicker, expandUserPath, objectValue, } from "./util.js";
|
|
13
12
|
export async function runCodexConfig(config, configPath, options = {}) {
|
|
14
13
|
if (options.reset) {
|
|
15
14
|
let raw = {};
|
|
@@ -73,34 +72,6 @@ export async function runTargetLauncher(config, targetId, args, executable = tar
|
|
|
73
72
|
const target = TARGETS.find((t) => t.id === targetId);
|
|
74
73
|
if (!target)
|
|
75
74
|
throw new CPACError(`unknown target: ${targetId}`);
|
|
76
|
-
if (["kimi", "grok"].includes(targetId)) {
|
|
77
|
-
const state = readState(config.state_dir);
|
|
78
|
-
const proxy = state ? stateProxy(state) : null;
|
|
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) {
|
|
85
|
-
if (proxy)
|
|
86
|
-
await stopProxyProcess(proxy);
|
|
87
|
-
const started = await startProxyProcess(config, apiKey);
|
|
88
|
-
try {
|
|
89
|
-
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
90
|
-
const catalogPath = join(config.state_dir, "codex-models.json");
|
|
91
|
-
mkdirSync(config.state_dir, { recursive: true, mode: 0o700 });
|
|
92
|
-
atomicWrite(catalogPath, catalog.bytes);
|
|
93
|
-
const originalMode = existsSync(config.codex_config)
|
|
94
|
-
? statSync(config.codex_config).mode & 0o7777
|
|
95
|
-
: 0o600;
|
|
96
|
-
atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, state ? state.config_existed : false, state ? state.config_mode : originalMode, started, fingerprint, injectedOwnership(state)));
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
await stopProxyProcess(started);
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
75
|
if (!options.skipSync) {
|
|
105
76
|
if (target.installed(config)) {
|
|
106
77
|
try {
|
|
@@ -214,12 +185,12 @@ const TARGETS = [
|
|
|
214
185
|
}
|
|
215
186
|
await installPiExtension(config);
|
|
216
187
|
},
|
|
217
|
-
uninstall: async (
|
|
188
|
+
uninstall: async (config, dryRun) => {
|
|
218
189
|
if (dryRun) {
|
|
219
190
|
console.log(`would remove Pi extension: ${join(piExtensionsDir(), "pi-cpac.ts")}`);
|
|
220
191
|
return;
|
|
221
192
|
}
|
|
222
|
-
await uninstallPiExtension();
|
|
193
|
+
await uninstallPiExtension(config);
|
|
223
194
|
},
|
|
224
195
|
},
|
|
225
196
|
{
|
|
@@ -235,12 +206,12 @@ const TARGETS = [
|
|
|
235
206
|
}
|
|
236
207
|
await installKimiConfig(config);
|
|
237
208
|
},
|
|
238
|
-
uninstall: async (
|
|
209
|
+
uninstall: async (config, dryRun) => {
|
|
239
210
|
if (dryRun) {
|
|
240
211
|
console.log(`would remove CPA provider block: ${kimiConfigPath()}`);
|
|
241
212
|
return;
|
|
242
213
|
}
|
|
243
|
-
await uninstallKimiConfig();
|
|
214
|
+
await uninstallKimiConfig(config);
|
|
244
215
|
},
|
|
245
216
|
},
|
|
246
217
|
{
|
|
@@ -256,12 +227,12 @@ const TARGETS = [
|
|
|
256
227
|
}
|
|
257
228
|
await installGrokConfig(config);
|
|
258
229
|
},
|
|
259
|
-
uninstall: async (
|
|
230
|
+
uninstall: async (config, dryRun) => {
|
|
260
231
|
if (dryRun) {
|
|
261
232
|
console.log(`would remove CPA provider block: ${grokConfigPath()}`);
|
|
262
233
|
return;
|
|
263
234
|
}
|
|
264
|
-
await uninstallGrokConfig();
|
|
235
|
+
await uninstallGrokConfig(config);
|
|
265
236
|
},
|
|
266
237
|
},
|
|
267
238
|
];
|
package/dist/config.js
CHANGED
|
@@ -20,13 +20,12 @@ const CONFIG_KEYS = new Set([
|
|
|
20
20
|
// spawn_agent overrides (codex-rs MAX_SPAWN_AGENT_MODEL_OVERRIDES).
|
|
21
21
|
const MAX_SPAWN_MODELS = 5;
|
|
22
22
|
export const CLAUDE_SLOT_KEYS = ["opus", "sonnet", "haiku"];
|
|
23
|
-
const
|
|
23
|
+
export const AGENT_IDS = ["codex", "pi", "kimi", "grok"];
|
|
24
|
+
const AGENT_ID_SET = new Set(AGENT_IDS);
|
|
25
|
+
const STATE_KEYS = new Set([
|
|
24
26
|
"config_path",
|
|
25
27
|
"config_existed",
|
|
26
28
|
"config_mode",
|
|
27
|
-
]);
|
|
28
|
-
const STATE_KEYS = new Set([
|
|
29
|
-
...REQUIRED_STATE_KEYS,
|
|
30
29
|
"proxy_id",
|
|
31
30
|
"proxy_fingerprint",
|
|
32
31
|
"proxy_pid",
|
|
@@ -34,6 +33,7 @@ const STATE_KEYS = new Set([
|
|
|
34
33
|
"injected_config_hash",
|
|
35
34
|
"injected_openai_base_url",
|
|
36
35
|
"injected_catalog_path",
|
|
36
|
+
"agents",
|
|
37
37
|
]);
|
|
38
38
|
export const STATE_FILES = [
|
|
39
39
|
"state.json",
|
|
@@ -359,19 +359,37 @@ export function readState(stateDir) {
|
|
|
359
359
|
throw new CPACError(`cannot read state: ${error instanceof Error ? error.message : String(error)}`);
|
|
360
360
|
}
|
|
361
361
|
if (!objectValue(value) ||
|
|
362
|
-
Object.keys(value).some((key) => !STATE_KEYS.has(key))
|
|
363
|
-
[...REQUIRED_STATE_KEYS].some((key) => !(key in value))) {
|
|
362
|
+
Object.keys(value).some((key) => !STATE_KEYS.has(key))) {
|
|
364
363
|
throw new CPACError("invalid state file");
|
|
365
364
|
}
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
const hasCodex = value.config_path !== undefined ||
|
|
366
|
+
value.config_existed !== undefined ||
|
|
367
|
+
value.config_mode !== undefined;
|
|
368
|
+
if (hasCodex) {
|
|
369
|
+
if (typeof value.config_path !== "string" || !isAbsolute(value.config_path))
|
|
370
|
+
throw new CPACError("invalid config path in state");
|
|
371
|
+
if (typeof value.config_existed !== "boolean")
|
|
372
|
+
throw new CPACError("invalid config_existed value in state");
|
|
373
|
+
if (!Number.isInteger(value.config_mode) ||
|
|
374
|
+
value.config_mode < 0 ||
|
|
375
|
+
value.config_mode > 0o7777) {
|
|
376
|
+
throw new CPACError("invalid config_mode value in state");
|
|
377
|
+
}
|
|
368
378
|
}
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
value.
|
|
373
|
-
|
|
374
|
-
|
|
379
|
+
if (value.agents !== undefined) {
|
|
380
|
+
if (!objectValue(value.agents))
|
|
381
|
+
throw new CPACError("invalid agents in state");
|
|
382
|
+
for (const [id, rec] of Object.entries(value.agents)) {
|
|
383
|
+
if (!AGENT_ID_SET.has(id))
|
|
384
|
+
throw new CPACError(`unknown agent in state: ${id}`);
|
|
385
|
+
if (!objectValue(rec) ||
|
|
386
|
+
typeof rec.path !== "string" ||
|
|
387
|
+
!rec.path ||
|
|
388
|
+
(rec.existed !== undefined && typeof rec.existed !== "boolean") ||
|
|
389
|
+
Object.keys(rec).some((key) => key !== "path" && key !== "existed")) {
|
|
390
|
+
throw new CPACError(`invalid agent record: ${id}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
375
393
|
}
|
|
376
394
|
const proxyKeys = ["proxy_id", "proxy_pid", "proxy_port"];
|
|
377
395
|
const proxyKeyCount = proxyKeys.filter((key) => key in value).length;
|
|
@@ -413,6 +431,90 @@ export function stateProxy(state) {
|
|
|
413
431
|
? { id: state.proxy_id, pid: state.proxy_pid, port: state.proxy_port }
|
|
414
432
|
: null;
|
|
415
433
|
}
|
|
434
|
+
export function needsProxy(state) {
|
|
435
|
+
if (state.config_path)
|
|
436
|
+
return true;
|
|
437
|
+
const agents = state.agents ?? {};
|
|
438
|
+
return Boolean(agents.codex || agents.kimi || agents.grok);
|
|
439
|
+
}
|
|
440
|
+
export function saveState(stateDir, state) {
|
|
441
|
+
atomicWrite(join(stateDir, "state.json"), Buffer.from(`${JSON.stringify(state, null, 2)}\n`));
|
|
442
|
+
}
|
|
443
|
+
export function recordAgent(stateDir, id, path, proxy, existed) {
|
|
444
|
+
const current = readState(stateDir) ?? {};
|
|
445
|
+
const prev = current.agents?.[id];
|
|
446
|
+
const record = { path };
|
|
447
|
+
if (!(prev?.existed ?? existed ?? true))
|
|
448
|
+
record.existed = false;
|
|
449
|
+
const next = {
|
|
450
|
+
...current,
|
|
451
|
+
agents: { ...current.agents, [id]: record },
|
|
452
|
+
};
|
|
453
|
+
if (proxy) {
|
|
454
|
+
next.proxy_id = proxy.id;
|
|
455
|
+
next.proxy_pid = proxy.pid;
|
|
456
|
+
next.proxy_port = proxy.port;
|
|
457
|
+
next.proxy_fingerprint = proxy.fingerprint;
|
|
458
|
+
}
|
|
459
|
+
saveState(stateDir, next);
|
|
460
|
+
return next;
|
|
461
|
+
}
|
|
462
|
+
export function removeCreatedTarget(stateDir, id, target) {
|
|
463
|
+
if (readState(stateDir)?.agents?.[id]?.existed === false)
|
|
464
|
+
rmSync(target, { force: true });
|
|
465
|
+
}
|
|
466
|
+
export function dropAgent(stateDir, id) {
|
|
467
|
+
rmSync(join(stateDir, id), { recursive: true, force: true });
|
|
468
|
+
const current = readState(stateDir);
|
|
469
|
+
if (!current) {
|
|
470
|
+
try {
|
|
471
|
+
rmdirSync(stateDir);
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
const code = error.code;
|
|
475
|
+
if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST")
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
return { state: null, stopProxy: null };
|
|
479
|
+
}
|
|
480
|
+
const proxy = stateProxy(current);
|
|
481
|
+
const agents = { ...current.agents };
|
|
482
|
+
delete agents[id];
|
|
483
|
+
const next = { ...current, agents };
|
|
484
|
+
if (id === "codex") {
|
|
485
|
+
delete next.config_path;
|
|
486
|
+
delete next.config_existed;
|
|
487
|
+
delete next.config_mode;
|
|
488
|
+
delete next.injected_config_hash;
|
|
489
|
+
delete next.injected_openai_base_url;
|
|
490
|
+
delete next.injected_catalog_path;
|
|
491
|
+
}
|
|
492
|
+
if (Object.keys(agents).length === 0)
|
|
493
|
+
delete next.agents;
|
|
494
|
+
const leftover = Boolean(next.config_path) ||
|
|
495
|
+
(next.agents && Object.keys(next.agents).length > 0);
|
|
496
|
+
const stopProxy = proxy && !needsProxy(next) ? proxy : null;
|
|
497
|
+
if (stopProxy) {
|
|
498
|
+
delete next.proxy_id;
|
|
499
|
+
delete next.proxy_pid;
|
|
500
|
+
delete next.proxy_port;
|
|
501
|
+
delete next.proxy_fingerprint;
|
|
502
|
+
}
|
|
503
|
+
if (!leftover) {
|
|
504
|
+
rmSync(join(stateDir, "state.json"), { force: true });
|
|
505
|
+
try {
|
|
506
|
+
rmdirSync(stateDir);
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
const code = error.code;
|
|
510
|
+
if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST")
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
return { state: null, stopProxy };
|
|
514
|
+
}
|
|
515
|
+
saveState(stateDir, next);
|
|
516
|
+
return { state: next, stopProxy };
|
|
517
|
+
}
|
|
416
518
|
export function injectedOwnership(state) {
|
|
417
519
|
if (!state?.injected_config_hash ||
|
|
418
520
|
!state.injected_openai_base_url ||
|
|
@@ -427,7 +529,7 @@ export function injectedOwnership(state) {
|
|
|
427
529
|
export function sha256Hex(bytes) {
|
|
428
530
|
return createHash("sha256").update(bytes).digest("hex");
|
|
429
531
|
}
|
|
430
|
-
export function stateBytes(config, existed, mode, proxy, proxyFingerprint, injected) {
|
|
532
|
+
export function stateBytes(config, existed, mode, proxy, proxyFingerprint, injected, existing) {
|
|
431
533
|
return Buffer.from(`${JSON.stringify({
|
|
432
534
|
config_path: config.codex_config,
|
|
433
535
|
config_existed: existed,
|
|
@@ -443,9 +545,19 @@ export function stateBytes(config, existed, mode, proxy, proxyFingerprint, injec
|
|
|
443
545
|
injected_catalog_path: injected.catalogPath,
|
|
444
546
|
}
|
|
445
547
|
: {}),
|
|
548
|
+
agents: {
|
|
549
|
+
...existing?.agents,
|
|
550
|
+
codex: {
|
|
551
|
+
path: config.codex_config,
|
|
552
|
+
...(existed ? {} : { existed: false }),
|
|
553
|
+
},
|
|
554
|
+
},
|
|
446
555
|
}, null, 2)}\n`);
|
|
447
556
|
}
|
|
448
557
|
export function originalBytes(stateDir, state, expectedPath) {
|
|
558
|
+
if (!state.config_path) {
|
|
559
|
+
throw new CPACError("no Codex injection in state");
|
|
560
|
+
}
|
|
449
561
|
if (state.config_path !== expectedPath) {
|
|
450
562
|
throw new CPACError(`active injection belongs to ${state.config_path}; restore it first`);
|
|
451
563
|
}
|
package/dist/cpac.js
CHANGED
|
@@ -27,7 +27,7 @@ export async function status(config) {
|
|
|
27
27
|
const apiKey = process.env[config.api_key_env]?.trim();
|
|
28
28
|
const keyConfigured = !!apiKey;
|
|
29
29
|
const state = readState(config.state_dir);
|
|
30
|
-
if (state) {
|
|
30
|
+
if (state?.config_path) {
|
|
31
31
|
originalBytes(config.state_dir, state, config.codex_config);
|
|
32
32
|
if (existsSync(config.codex_config)) {
|
|
33
33
|
const proxy = stateProxy(state);
|
|
@@ -65,7 +65,7 @@ export async function status(config) {
|
|
|
65
65
|
if (!state)
|
|
66
66
|
return 1;
|
|
67
67
|
const proxy = stateProxy(state);
|
|
68
|
-
if (
|
|
68
|
+
if (proxy && !(await proxyIsHealthy(proxy)))
|
|
69
69
|
return 2;
|
|
70
70
|
return 0;
|
|
71
71
|
}
|
|
@@ -137,6 +137,10 @@ export default async function (pi) {
|
|
|
137
137
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
138
138
|
contextWindow: intField(m.context_window) || 200000,
|
|
139
139
|
maxTokens: MAX_TOKENS[m.slug] || DEFAULT_MAX_TOKENS,
|
|
140
|
+
compat: {
|
|
141
|
+
sendSessionAffinityHeaders: true,
|
|
142
|
+
sessionAffinityFormat: "openai",
|
|
143
|
+
},
|
|
140
144
|
};
|
|
141
145
|
if (thinkingLevelMap) modelObj.thinkingLevelMap = thinkingLevelMap;
|
|
142
146
|
return modelObj;
|
package/dist/proxy.js
CHANGED
|
@@ -3,10 +3,9 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { createServer, request as httpRequest, } from "node:http";
|
|
5
5
|
import { request as httpsRequest } from "node:https";
|
|
6
|
-
import { join } from "node:path";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { apiBase,
|
|
9
|
-
import { CPACError,
|
|
7
|
+
import { apiBase, proxyFingerprint, readState, saveState, stateProxy, } from "./config.js";
|
|
8
|
+
import { CPACError, objectValue, resolveApiKey } from "./util.js";
|
|
10
9
|
const HOP_BY_HOP_HEADERS = new Set([
|
|
11
10
|
"connection",
|
|
12
11
|
"keep-alive",
|
|
@@ -307,22 +306,43 @@ export async function stopProxyProcess(proxy) {
|
|
|
307
306
|
await new Promise((resolveWait) => setTimeout(resolveWait, 200));
|
|
308
307
|
killCpacProxy(proxy.pid, "SIGKILL");
|
|
309
308
|
}
|
|
309
|
+
export async function ensureLoopbackProxy(config, apiKey) {
|
|
310
|
+
const fingerprint = proxyFingerprint(config, apiKey);
|
|
311
|
+
const recorded = readState(config.state_dir);
|
|
312
|
+
const existing = recorded ? stateProxy(recorded) : null;
|
|
313
|
+
if (existing &&
|
|
314
|
+
recorded?.proxy_fingerprint === fingerprint &&
|
|
315
|
+
(config.codex_proxy_port === 0 ||
|
|
316
|
+
config.codex_proxy_port === existing.port) &&
|
|
317
|
+
(await proxyIsHealthy(existing))) {
|
|
318
|
+
return { proxy: existing, fingerprint, started: false };
|
|
319
|
+
}
|
|
320
|
+
if (existing)
|
|
321
|
+
await stopProxyProcess(existing);
|
|
322
|
+
const proxy = await startProxyProcess(config, apiKey);
|
|
323
|
+
return { proxy, fingerprint, started: true };
|
|
324
|
+
}
|
|
310
325
|
export async function runProxy(config) {
|
|
311
326
|
const apiKey = await resolveApiKey(config.api_key_env);
|
|
312
327
|
const state = readState(config.state_dir);
|
|
313
328
|
if (!state)
|
|
314
|
-
throw new CPACError("no active CPAC
|
|
315
|
-
originalBytes(config.state_dir, state, config.codex_config);
|
|
329
|
+
throw new CPACError("no active CPAC state; run cpac inject or install an agent first");
|
|
316
330
|
const recorded = stateProxy(state);
|
|
317
331
|
if (!recorded) {
|
|
318
|
-
throw new CPACError("
|
|
332
|
+
throw new CPACError("state has no loopback proxy; run cpac inject or install kimi/grok first");
|
|
319
333
|
}
|
|
320
334
|
if (await proxyIsHealthy(recorded)) {
|
|
321
335
|
throw new CPACError(`loopback proxy is already running on 127.0.0.1:${recorded.port}`);
|
|
322
336
|
}
|
|
323
337
|
const proxy = await createLoopbackProxy(config.cpa_url, apiKey, recorded.id, recorded.port);
|
|
324
338
|
try {
|
|
325
|
-
|
|
339
|
+
saveState(config.state_dir, {
|
|
340
|
+
...state,
|
|
341
|
+
proxy_id: recorded.id,
|
|
342
|
+
proxy_pid: process.pid,
|
|
343
|
+
proxy_port: proxy.port,
|
|
344
|
+
proxy_fingerprint: proxyFingerprint(config, apiKey),
|
|
345
|
+
});
|
|
326
346
|
}
|
|
327
347
|
catch (error) {
|
|
328
348
|
proxy.server.closeAllConnections?.();
|
package/dist/targets/codex.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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, injectedOwnership, liftContextWindows, originalBytes,
|
|
4
|
-
import {
|
|
3
|
+
import { PROVIDER, STATE_FILES, cleanupStateFiles, dropAgent, fetchCatalog, injectedOwnership, liftContextWindows, originalBytes, readState, reorderCatalog, saveState, sha256Hex, stateBytes, } from "../config.js";
|
|
4
|
+
import { ensureLoopbackProxy, 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*=/;
|
|
7
7
|
const MODEL_CATALOG_KEY = /^\s*(?:model_catalog_json|"model_catalog_json"|'model_catalog_json')\s*=/;
|
|
@@ -251,10 +251,14 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
251
251
|
if (!apiKey)
|
|
252
252
|
throw new CPACError(`environment variable ${config.api_key_env} is not set`);
|
|
253
253
|
const state = readState(config.state_dir);
|
|
254
|
+
const prior = state;
|
|
255
|
+
if (!state?.config_path && !existsSync(config.codex_config)) {
|
|
256
|
+
throw new CPACError(`Codex config not found at ${config.codex_config}. Is Codex installed?`);
|
|
257
|
+
}
|
|
254
258
|
let existed;
|
|
255
259
|
let original;
|
|
256
260
|
let originalMode;
|
|
257
|
-
if (state) {
|
|
261
|
+
if (state?.config_path) {
|
|
258
262
|
const backup = originalBytes(config.state_dir, state, config.codex_config);
|
|
259
263
|
try {
|
|
260
264
|
original = existsSync(config.codex_config)
|
|
@@ -286,31 +290,20 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
286
290
|
catalog.bytes = liftContextWindows(catalog.bytes);
|
|
287
291
|
const stateDirExisted = existsSync(config.state_dir);
|
|
288
292
|
if (!state &&
|
|
289
|
-
|
|
290
|
-
existsSync(join(config.state_dir, "codex", "config.toml")))) {
|
|
293
|
+
STATE_FILES.some((name) => existsSync(join(config.state_dir, name)))) {
|
|
291
294
|
throw new CPACError("state_dir contains CPAC files without a valid state; refusing to overwrite them");
|
|
292
295
|
}
|
|
293
|
-
const fingerprint = proxyFingerprint(config, apiKey);
|
|
294
|
-
const previousProxy = state ? stateProxy(state) : null;
|
|
295
296
|
let proxy;
|
|
297
|
+
let fingerprint;
|
|
296
298
|
let startedProxy = false;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
proxy = previousProxy;
|
|
299
|
+
try {
|
|
300
|
+
const ensured = await ensureLoopbackProxy(config, apiKey);
|
|
301
|
+
proxy = ensured.proxy;
|
|
302
|
+
fingerprint = ensured.fingerprint;
|
|
303
|
+
startedProxy = ensured.started;
|
|
303
304
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
await stopProxyProcess(previousProxy);
|
|
307
|
-
try {
|
|
308
|
-
proxy = await startProxyProcess(config, apiKey);
|
|
309
|
-
startedProxy = true;
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
throw new CPACError(`${error instanceof Error ? error.message : String(error)}; choose another codex_proxy_port if the port is occupied`);
|
|
313
|
-
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
throw new CPACError(`${error instanceof Error ? error.message : String(error)}; choose another codex_proxy_port if the port is occupied`);
|
|
314
307
|
}
|
|
315
308
|
const catalogPath = join(config.state_dir, "codex-models.json");
|
|
316
309
|
let injected;
|
|
@@ -335,11 +328,11 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
335
328
|
mkdirSync(config.state_dir, { recursive: true, mode: 0o700 });
|
|
336
329
|
if (!stateDirExisted)
|
|
337
330
|
chmodSync(config.state_dir, 0o700);
|
|
338
|
-
if (state) {
|
|
331
|
+
if (state?.config_path) {
|
|
339
332
|
try {
|
|
340
333
|
atomicWrite(catalogPath, catalog.bytes);
|
|
341
334
|
atomicWrite(config.codex_config, injected, originalMode);
|
|
342
|
-
atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint, ownership));
|
|
335
|
+
atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint, ownership, prior));
|
|
343
336
|
}
|
|
344
337
|
catch (error) {
|
|
345
338
|
if (startedProxy)
|
|
@@ -350,12 +343,11 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
350
343
|
else {
|
|
351
344
|
let configWritten = false;
|
|
352
345
|
try {
|
|
353
|
-
|
|
354
|
-
atomicWrite(join(config.state_dir, "codex", "config.toml"), original);
|
|
346
|
+
atomicWrite(join(config.state_dir, "codex", "config.toml"), original);
|
|
355
347
|
atomicWrite(catalogPath, catalog.bytes);
|
|
356
348
|
atomicWrite(config.codex_config, injected, originalMode);
|
|
357
349
|
configWritten = true;
|
|
358
|
-
atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint, ownership));
|
|
350
|
+
atomicWrite(join(config.state_dir, "state.json"), stateBytes(config, existed, originalMode, proxy, fingerprint, ownership, prior));
|
|
359
351
|
}
|
|
360
352
|
catch (error) {
|
|
361
353
|
if (configWritten) {
|
|
@@ -371,7 +363,10 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
371
363
|
}
|
|
372
364
|
let cleanupError;
|
|
373
365
|
try {
|
|
374
|
-
|
|
366
|
+
if (prior)
|
|
367
|
+
saveState(config.state_dir, prior);
|
|
368
|
+
else
|
|
369
|
+
cleanupStateFiles(config.state_dir);
|
|
375
370
|
}
|
|
376
371
|
catch (caught) {
|
|
377
372
|
cleanupError = caught;
|
|
@@ -397,8 +392,8 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
397
392
|
}
|
|
398
393
|
export async function restore(config) {
|
|
399
394
|
const state = readState(config.state_dir);
|
|
400
|
-
if (!state)
|
|
401
|
-
throw new CPACError("no active CPAC injection");
|
|
395
|
+
if (!state?.config_path)
|
|
396
|
+
throw new CPACError("no active CPAC Codex injection");
|
|
402
397
|
const original = originalBytes(config.state_dir, state, config.codex_config);
|
|
403
398
|
const current = existsSync(config.codex_config)
|
|
404
399
|
? readFileSync(config.codex_config)
|
|
@@ -419,11 +414,18 @@ export async function restore(config) {
|
|
|
419
414
|
else
|
|
420
415
|
atomicWrite(config.codex_config, stripped, state.config_mode);
|
|
421
416
|
}
|
|
422
|
-
const
|
|
423
|
-
if (
|
|
424
|
-
await stopProxyProcess(
|
|
417
|
+
const { stopProxy } = dropAgent(config.state_dir, "codex");
|
|
418
|
+
if (stopProxy)
|
|
419
|
+
await stopProxyProcess(stopProxy);
|
|
425
420
|
try {
|
|
426
|
-
|
|
421
|
+
if (readState(config.state_dir)) {
|
|
422
|
+
rmSync(join(config.state_dir, "codex", "config.toml"), { force: true });
|
|
423
|
+
rmSync(join(config.state_dir, "config.toml.backup"), { force: true });
|
|
424
|
+
rmSync(join(config.state_dir, "codex-models.json"), { force: true });
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
cleanupStateFiles(config.state_dir);
|
|
428
|
+
}
|
|
427
429
|
}
|
|
428
430
|
catch (error) {
|
|
429
431
|
throw new CPACError(`config restored, but state cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
package/dist/targets/grok.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { catalogModelId, catalogModelRows, fetchCatalog,
|
|
5
|
-
import {
|
|
4
|
+
import { catalogModelId, catalogModelRows, dropAgent, fetchCatalog, recordAgent, removeCreatedTarget, } from "../config.js";
|
|
5
|
+
import { ensureLoopbackProxy, stopProxyProcess } from "../proxy.js";
|
|
6
6
|
import { CPACError, atomicWrite, ensureCpacBackup, expandUserPath, resolveApiKey, tomlString, } from "../util.js";
|
|
7
7
|
export function grokHome() {
|
|
8
8
|
const home = process.env.GROK_HOME?.trim() || join(homedir(), ".grok");
|
|
@@ -70,12 +70,8 @@ export async function installGrokConfig(config) {
|
|
|
70
70
|
const rows = catalogModelRows(document) ?? [];
|
|
71
71
|
if (rows.length === 0)
|
|
72
72
|
throw new CPACError("CPA catalog contains no models");
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const port = recorded?.port ?? config.codex_proxy_port;
|
|
76
|
-
if (!port) {
|
|
77
|
-
throw new CPACError("loopback proxy port unknown; run cpac inject first");
|
|
78
|
-
}
|
|
73
|
+
const { proxy, fingerprint, started } = await ensureLoopbackProxy(config, apiKey);
|
|
74
|
+
const port = proxy.port;
|
|
79
75
|
const lines = [GROK_BLOCK_START];
|
|
80
76
|
for (const row of rows) {
|
|
81
77
|
const slug = catalogModelId(row);
|
|
@@ -87,23 +83,38 @@ export async function installGrokConfig(config) {
|
|
|
87
83
|
const name = typeof row.display_name === "string" && row.display_name.trim()
|
|
88
84
|
? `${row.display_name.trim()} (CPAC)`
|
|
89
85
|
: `CPAC ${slug}`;
|
|
90
|
-
lines.push(`[model."cpac/${slug}"]`, `model = ${tomlString(slug)}`, `base_url = "http://127.0.0.1:${port}/v1"`, 'api_backend = "responses"',
|
|
86
|
+
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.", 'api_key = "cpac-loopback"', `name = ${tomlString(name)}`, `context_window = ${context}`, "");
|
|
91
87
|
}
|
|
92
88
|
if (lines[lines.length - 1] === "")
|
|
93
89
|
lines.pop();
|
|
94
90
|
lines.push(GROK_BLOCK_END);
|
|
95
91
|
const target = grokConfigPath();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
const hadFile = existsSync(target);
|
|
93
|
+
try {
|
|
94
|
+
ensureCpacBackup(config.state_dir, "grok", target, !isGrokConfigInstalled());
|
|
95
|
+
writeGrokBlock(target, lines.join("\n"));
|
|
96
|
+
recordAgent(config.state_dir, "grok", target, {
|
|
97
|
+
id: proxy.id,
|
|
98
|
+
pid: proxy.pid,
|
|
99
|
+
port: proxy.port,
|
|
100
|
+
fingerprint,
|
|
101
|
+
}, hadFile);
|
|
101
102
|
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (started)
|
|
105
|
+
await stopProxyProcess(proxy);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
console.log(`Installed Grok Build provider config: ${target}`);
|
|
102
109
|
}
|
|
103
|
-
export async function uninstallGrokConfig() {
|
|
110
|
+
export async function uninstallGrokConfig(config) {
|
|
104
111
|
const target = grokConfigPath();
|
|
105
112
|
if (!isGrokConfigInstalled())
|
|
106
113
|
throw new CPACError("Grok Build config is not installed");
|
|
107
114
|
writeGrokBlock(target, null);
|
|
115
|
+
removeCreatedTarget(config.state_dir, "grok", target);
|
|
116
|
+
const { stopProxy } = dropAgent(config.state_dir, "grok");
|
|
117
|
+
if (stopProxy)
|
|
118
|
+
await stopProxyProcess(stopProxy);
|
|
108
119
|
console.log(`Removed Grok Build provider config: ${target}`);
|
|
109
120
|
}
|
package/dist/targets/kimi.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { catalogModelId, catalogModelRows, fetchCatalog,
|
|
5
|
-
import {
|
|
4
|
+
import { catalogModelId, catalogModelRows, dropAgent, fetchCatalog, recordAgent, removeCreatedTarget, } from "../config.js";
|
|
5
|
+
import { ensureLoopbackProxy, stopProxyProcess } from "../proxy.js";
|
|
6
6
|
import { CPACError, atomicWrite, ensureCpacBackup, expandUserPath, objectValue, resolveApiKey, tomlString, tomlStringArray, } from "../util.js";
|
|
7
7
|
export function kimiConfigPath() {
|
|
8
8
|
const home = process.env.KIMI_CODE_HOME?.trim() || join(homedir(), ".kimi-code");
|
|
@@ -111,18 +111,14 @@ export async function installKimiConfig(config) {
|
|
|
111
111
|
const rows = catalogModelRows(document) ?? [];
|
|
112
112
|
if (rows.length === 0)
|
|
113
113
|
throw new CPACError("CPA catalog contains no models");
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const port = recorded?.port ?? config.codex_proxy_port;
|
|
117
|
-
if (!port) {
|
|
118
|
-
throw new CPACError("loopback proxy port unknown; run cpac inject first");
|
|
119
|
-
}
|
|
114
|
+
const { proxy, fingerprint, started } = await ensureLoopbackProxy(config, apiKey);
|
|
115
|
+
const port = proxy.port;
|
|
120
116
|
const lines = [
|
|
121
117
|
KIMI_BLOCK_START,
|
|
122
118
|
"[providers.cpac]",
|
|
123
119
|
'type = "openai"',
|
|
124
120
|
`base_url = "http://127.0.0.1:${port}/v1"`,
|
|
125
|
-
|
|
121
|
+
"# Placeholder: the CPAC loopback proxy replaces it with the CPA key.",
|
|
126
122
|
'api_key = "cpac-loopback"',
|
|
127
123
|
];
|
|
128
124
|
for (const row of rows) {
|
|
@@ -159,17 +155,32 @@ export async function installKimiConfig(config) {
|
|
|
159
155
|
}
|
|
160
156
|
lines.push(KIMI_BLOCK_END);
|
|
161
157
|
const target = kimiConfigPath();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
158
|
+
const hadFile = existsSync(target);
|
|
159
|
+
try {
|
|
160
|
+
ensureCpacBackup(config.state_dir, "kimi", target, !isKimiConfigInstalled());
|
|
161
|
+
writeKimiBlock(target, lines.join("\n"));
|
|
162
|
+
recordAgent(config.state_dir, "kimi", target, {
|
|
163
|
+
id: proxy.id,
|
|
164
|
+
pid: proxy.pid,
|
|
165
|
+
port: proxy.port,
|
|
166
|
+
fingerprint,
|
|
167
|
+
}, hadFile);
|
|
167
168
|
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (started)
|
|
171
|
+
await stopProxyProcess(proxy);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
console.log(`Installed Kimi Code provider config: ${target}`);
|
|
168
175
|
}
|
|
169
|
-
export async function uninstallKimiConfig() {
|
|
176
|
+
export async function uninstallKimiConfig(config) {
|
|
170
177
|
const target = kimiConfigPath();
|
|
171
178
|
if (!isKimiConfigInstalled())
|
|
172
179
|
throw new CPACError("Kimi Code config is not installed");
|
|
173
180
|
writeKimiBlock(target, null);
|
|
181
|
+
removeCreatedTarget(config.state_dir, "kimi", target);
|
|
182
|
+
const { stopProxy } = dropAgent(config.state_dir, "kimi");
|
|
183
|
+
if (stopProxy)
|
|
184
|
+
await stopProxyProcess(stopProxy);
|
|
174
185
|
console.log(`Removed Kimi Code provider config: ${target}`);
|
|
175
186
|
}
|
package/dist/targets/pi.js
CHANGED
|
@@ -2,6 +2,8 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dropAgent, recordAgent } from "../config.js";
|
|
6
|
+
import { stopProxyProcess } from "../proxy.js";
|
|
5
7
|
import { CPACError, atomicWrite, ensureCpacBackup, expandUserPath, } from "../util.js";
|
|
6
8
|
export function piExtensionsDir() {
|
|
7
9
|
return join(expandUserPath(process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent")), "extensions");
|
|
@@ -20,8 +22,9 @@ export async function installPiExtension(config) {
|
|
|
20
22
|
const dir = piExtensionsDir();
|
|
21
23
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
22
24
|
const target = join(dir, "pi-cpac.ts");
|
|
23
|
-
ensureCpacBackup(config.state_dir, "pi", target);
|
|
25
|
+
ensureCpacBackup(config.state_dir, "pi", target, !isPiExtensionInstalled());
|
|
24
26
|
atomicWrite(target, Buffer.from(piExtensionContent(config.cpa_url)), 0o644);
|
|
27
|
+
recordAgent(config.state_dir, "pi", target);
|
|
25
28
|
for (const legacy of ["cpac.ts", "pi-cpa.ts"]) {
|
|
26
29
|
const legacyPath = join(dir, legacy);
|
|
27
30
|
if (existsSync(legacyPath) && legacyPath !== target) {
|
|
@@ -33,7 +36,7 @@ export async function installPiExtension(config) {
|
|
|
33
36
|
}
|
|
34
37
|
console.log(`Installed Pi extension: ${target}`);
|
|
35
38
|
}
|
|
36
|
-
export async function uninstallPiExtension() {
|
|
39
|
+
export async function uninstallPiExtension(config) {
|
|
37
40
|
const dir = piExtensionsDir();
|
|
38
41
|
const target = join(dir, "pi-cpac.ts");
|
|
39
42
|
const legacyTargets = [join(dir, "cpac.ts"), join(dir, "pi-cpa.ts")];
|
|
@@ -47,4 +50,7 @@ export async function uninstallPiExtension() {
|
|
|
47
50
|
}
|
|
48
51
|
if (!removed)
|
|
49
52
|
throw new CPACError("Pi extension is not installed");
|
|
53
|
+
const { stopProxy } = dropAgent(config.state_dir, "pi");
|
|
54
|
+
if (stopProxy)
|
|
55
|
+
await stopProxyProcess(stopProxy);
|
|
50
56
|
}
|
package/dist/util.js
CHANGED
|
@@ -9,17 +9,16 @@ 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(stateDir, agent, target) {
|
|
12
|
+
export function ensureCpacBackup(stateDir, agent, target, native = false) {
|
|
13
13
|
const dest = join(stateDir, agent, basename(target));
|
|
14
14
|
const sidecar = `${target}.cpac-backup`;
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return;
|
|
15
|
+
if (existsSync(dest) && !native) {
|
|
16
|
+
if (existsSync(sidecar))
|
|
17
|
+
unlinkSync(sidecar);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const source = native && existsSync(target) ? target : existsSync(sidecar) ? sidecar : null;
|
|
21
|
+
if (source) {
|
|
23
22
|
mkdirSync(dirname(dest), { recursive: true, mode: 0o700 });
|
|
24
23
|
try {
|
|
25
24
|
chmodSync(stateDir, 0o700);
|