@yhong91/cpac 0.1.45 → 0.1.46

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
@@ -327,13 +327,13 @@ cpac install hermes
327
327
  cpac hermes
328
328
  ```
329
329
 
330
- 它在 `~/.hermes/config.yaml` 的 `providers.cpac` 写入 loopback `base_url`/`api`、占位 `api_key`、`api_mode: responses`,以及 CPA catalog 的 slug 列表(`discover_models: false`)。不改当前默认模型;切换用 `hermes model` 或 `/model`。密钥由 loopback 注入,不写进 `.env`。卸载:
330
+ 它在 default `~/.hermes/config.yaml` 以及每个 `~/.hermes/profiles/<name>/config.yaml` 的 `providers.cpac` 写入 loopback `base_url`/`api`、占位 `api_key`、`api_mode: responses`,以及 CPA catalog 的 slug 列表(`discover_models: false`)。不改当前默认模型;切换用 `hermes model` 或 `/model`。密钥由 loopback 注入,不写进 `.env`。新建 profile 后再跑一次 `cpac install hermes`(或 `cpac hermes`)即可写入。卸载:
331
331
 
332
332
  ```bash
333
333
  cpac restore hermes
334
334
  ```
335
335
 
336
- 配置目录尊重 `HERMES_HOME`(Windows 默认 `%LOCALAPPDATA%\hermes`)。首次写入前若 `config.yaml` 已存在,会在 `state_dir/hermes/config.yaml` 保留原始快照。
336
+ 配置目录尊重 `HERMES_HOME`(Windows 默认 `%LOCALAPPDATA%\hermes`)。首次写入前若 default `config.yaml` 已存在,会在 `state_dir/hermes/config.yaml` 保留原始快照。profile 文件不单独备份,restore 只剥 `providers.cpac`。
337
337
 
338
338
  ## 环境变量
339
339
 
package/dist/agents.js CHANGED
@@ -9,7 +9,7 @@ import { grokConfigPath, grokHome, installGrokConfig, isGrokConfigInstalled, uni
9
9
  import { installKimiConfig, isKimiConfigInstalled, kimiConfigPath, uninstallKimiConfig, } from "./targets/kimi.js";
10
10
  import { installPiConfig, isPiConfigInstalled, piAgentDir, piModelsPath, uninstallPiConfig, } from "./targets/pi.js";
11
11
  import { installZedConfig, isZedConfigInstalled, uninstallZedConfig, zedConfigPath, zedHome, } from "./targets/zed.js";
12
- import { hermesConfigPath, hermesHome, installHermesConfig, isHermesConfigInstalled, uninstallHermesConfig, } from "./targets/hermes.js";
12
+ import { hermesConfigPaths, hermesHome, installHermesConfig, isHermesConfigInstalled, uninstallHermesConfig, } from "./targets/hermes.js";
13
13
  import { CPACError, atomicWrite, checkboxPicker, compareVersions, expandUserPath, objectValue, tabWizard, } from "./util.js";
14
14
  const CODEX_CTX_STD = "Standard (default input budget, e.g. 200K~272K)";
15
15
  const CODEX_CTX_MAX = "Max Context Window (lift to full upper bound, e.g. 921K~1M, 90% auto-compact limit)";
@@ -310,14 +310,16 @@ const TARGETS = [
310
310
  version: () => detectClientVersion("hermes"),
311
311
  install: async (config, dryRun) => {
312
312
  if (dryRun) {
313
- console.log(`would write CPA provider: ${hermesConfigPath()}`);
313
+ for (const path of hermesConfigPaths())
314
+ console.log(`would write CPA provider: ${path}`);
314
315
  return;
315
316
  }
316
317
  await installHermesConfig(config);
317
318
  },
318
319
  uninstall: async (config, dryRun) => {
319
320
  if (dryRun) {
320
- console.log(`would remove CPA provider: ${hermesConfigPath()}`);
321
+ for (const path of hermesConfigPaths())
322
+ console.log(`would remove CPA provider: ${path}`);
321
323
  return;
322
324
  }
323
325
  await uninstallHermesConfig(config);
@@ -368,7 +370,10 @@ export async function runInstall(config, requested, options) {
368
370
  throw new CPACError("--max_context is only valid with codex");
369
371
  }
370
372
  for (const target of selected) {
371
- if (target.installed(config) && !options.force && target.id !== "pi") {
373
+ if (target.installed(config) &&
374
+ !options.force &&
375
+ target.id !== "pi" &&
376
+ target.id !== "hermes") {
372
377
  console.log(`${target.id}: already installed; use --force to reinstall`);
373
378
  continue;
374
379
  }
@@ -492,5 +497,12 @@ export async function runUpgrade(config, checkOnly) {
492
497
  return 0;
493
498
  }
494
499
  console.log(`Syncing ${installed.join(", ")}`);
500
+ if (!upToDate) {
501
+ const cli = fileURLToPath(new URL("./cpac.js", import.meta.url));
502
+ const result = spawnSync(process.execPath, [cli, "sync"], {
503
+ stdio: "inherit",
504
+ });
505
+ return result.status ?? 1;
506
+ }
495
507
  return runSync(config, [], { all: false, dryRun: false });
496
508
  }
@@ -1,7 +1,7 @@
1
- import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
- import { catalogModelId, catalogModelRows, dropAgent, fetchCatalog, recordWrittenConfig, restoreOwnedConfig, } from "../config.js";
4
+ import { catalogModelId, catalogModelRows, dropAgent, fetchCatalog, readState, recordWrittenConfig, restoreOwnedConfig, } from "../config.js";
5
5
  import { ensureLoopbackProxy, stopProxyProcess } from "../proxy.js";
6
6
  import { CPACError, atomicWrite, ensureCpacBackup, expandUserPath, resolveApiKey, } from "../util.js";
7
7
  export const HERMES_PROVIDER_ID = "cpac";
@@ -18,6 +18,23 @@ export function hermesHome() {
18
18
  export function hermesConfigPath() {
19
19
  return join(hermesHome(), "config.yaml");
20
20
  }
21
+ export function hermesConfigPaths() {
22
+ const home = hermesHome();
23
+ const paths = [hermesConfigPath()];
24
+ let entries;
25
+ try {
26
+ entries = readdirSync(join(home, "profiles"), { withFileTypes: true });
27
+ }
28
+ catch {
29
+ return paths;
30
+ }
31
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
32
+ if (!entry.isDirectory() || entry.name.startsWith("."))
33
+ continue;
34
+ paths.push(join(home, "profiles", entry.name, "config.yaml"));
35
+ }
36
+ return paths;
37
+ }
21
38
  function lineIndent(line) {
22
39
  const match = line.match(/^( *)/);
23
40
  return match ? match[1].length : 0;
@@ -121,14 +138,17 @@ function hasProvidersCpac(content) {
121
138
  const indent = childIndent(lines, range.idx, range.end, 0);
122
139
  return Boolean(findCpacChild(lines, range.idx, range.end, indent));
123
140
  }
124
- export function isHermesConfigInstalled() {
141
+ function pathHasCpac(path) {
125
142
  try {
126
- return hasProvidersCpac(readFileSync(hermesConfigPath(), "utf8"));
143
+ return hasProvidersCpac(readFileSync(path, "utf8"));
127
144
  }
128
145
  catch {
129
146
  return false;
130
147
  }
131
148
  }
149
+ export function isHermesConfigInstalled() {
150
+ return hermesConfigPaths().some(pathHasCpac);
151
+ }
132
152
  function stripProvidersCpac(content) {
133
153
  const eol = content.includes("\r\n") ? "\r\n" : "\n";
134
154
  const lines = content.split(/\r?\n/);
@@ -194,6 +214,20 @@ function writeHermesProvider(path, port, rows) {
194
214
  const mode = existsSync(path) ? statSync(path).mode & 0o7777 : 0o600;
195
215
  atomicWrite(path, Buffer.from(content), mode);
196
216
  }
217
+ function stripHermesFile(path) {
218
+ if (!existsSync(path))
219
+ return;
220
+ const current = readFileSync(path, "utf8");
221
+ if (!hasProvidersCpac(current))
222
+ return;
223
+ const stripped = stripProvidersCpac(current);
224
+ if (stripped.trim() === "") {
225
+ rmSync(path, { force: true });
226
+ return;
227
+ }
228
+ const mode = statSync(path).mode & 0o7777;
229
+ atomicWrite(path, Buffer.from(stripped.endsWith("\n") ? stripped : `${stripped}\n`), mode);
230
+ }
197
231
  export async function installHermesConfig(config) {
198
232
  const apiKey = await resolveApiKey(config.api_key_env);
199
233
  const catalog = await fetchCatalog(config.cpa_url, apiKey);
@@ -208,12 +242,14 @@ export async function installHermesConfig(config) {
208
242
  if (rows.length === 0)
209
243
  throw new CPACError("CPA catalog contains no models");
210
244
  const { proxy, fingerprint, started } = await ensureLoopbackProxy(config, apiKey);
211
- const target = hermesConfigPath();
212
- const hadFile = existsSync(target);
245
+ const targets = hermesConfigPaths();
246
+ const primary = targets[0];
247
+ const hadFile = existsSync(primary);
213
248
  try {
214
- ensureCpacBackup(config.state_dir, "hermes", target, !isHermesConfigInstalled());
215
- writeHermesProvider(target, proxy.port, rows);
216
- recordWrittenConfig(config.state_dir, "hermes", target, {
249
+ ensureCpacBackup(config.state_dir, "hermes", primary, !pathHasCpac(primary));
250
+ for (const target of targets)
251
+ writeHermesProvider(target, proxy.port, rows);
252
+ recordWrittenConfig(config.state_dir, "hermes", primary, {
217
253
  id: proxy.id,
218
254
  pid: proxy.pid,
219
255
  port: proxy.port,
@@ -225,15 +261,20 @@ export async function installHermesConfig(config) {
225
261
  await stopProxyProcess(proxy);
226
262
  throw error;
227
263
  }
228
- console.log(`Installed Hermes provider "${HERMES_PROVIDER_ID}": ${target}`);
264
+ console.log(`Installed Hermes provider "${HERMES_PROVIDER_ID}": ${targets.join(", ")}`);
229
265
  }
230
266
  export async function uninstallHermesConfig(config) {
231
- const target = hermesConfigPath();
267
+ const targets = hermesConfigPaths();
268
+ const primary = targets[0];
232
269
  if (!isHermesConfigInstalled())
233
270
  throw new CPACError("Hermes CPAC provider is not installed");
234
- restoreOwnedConfig(config.state_dir, "hermes", target, (current) => Buffer.from(stripProvidersCpac(new TextDecoder("utf-8").decode(current))));
271
+ if (readState(config.state_dir)?.agents?.hermes || pathHasCpac(primary)) {
272
+ restoreOwnedConfig(config.state_dir, "hermes", primary, (current) => Buffer.from(stripProvidersCpac(new TextDecoder("utf-8").decode(current))));
273
+ }
274
+ for (const target of targets.slice(1))
275
+ stripHermesFile(target);
235
276
  const { stopProxy } = dropAgent(config.state_dir, "hermes");
236
277
  if (stopProxy)
237
278
  await stopProxyProcess(stopProxy);
238
- console.log(`Removed Hermes provider "${HERMES_PROVIDER_ID}": ${target}`);
279
+ console.log(`Removed Hermes provider "${HERMES_PROVIDER_ID}": ${targets.join(", ")}`);
239
280
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {