@yhong91/cpac 0.1.22 → 0.1.24

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/cpac.js +199 -99
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -91,6 +91,8 @@ cpac status # 验证:各 target 显示 ready/injected
91
91
 
92
92
  5. **临时接管类 agent 无需安装**,直接告知用户用 `cpac claude` / `cpac opencode` 启动(步骤 3 的 claude_models 自动生效)。
93
93
 
94
+ 6. **已安装后升级**:npm 发布新版本后,等 publish 成功再执行 `cpac upgrade`。它会按需 `npm install -g`,再对已接入的 Codex / Pi / Kimi 做 `sync`。不要拆成 `npm install -g @yhong91/cpac` 再手动 `cpac sync`。只刷新配置、不升 CLI 时才用 `cpac sync`。
95
+
94
96
  失败处理:任何一步报错时先跑 `cpac status`;密钥未配置回到步骤 2;注入后异常用 `cpac uninstall --target <id>` 恢复原配置。
95
97
 
96
98
  ## 首次引导
@@ -173,7 +175,7 @@ cpac version
173
175
  - `--v2_models` 进入 arrow-key 复选框选择最多 5 个 spawn_agent 模型(勾选顺序即推荐顺序),选择结果存入配置文件的 `spawn_models`,注入时把这些模型排到 `codex-models.json` 最前——Codex 客户端只把 catalog 前 5 个可见模型广告为 spawn 覆盖项。
174
176
  - `uninstall` 默认作用于已安装目标,映射到 `cpac restore` / `cpac pi uninstall` / `cpac kimi uninstall`。
175
177
  - `--home PATH` 临时把 `HOME` 指向给定目录,便于在隔离环境中试验。
176
- - `upgrade` 检查 npm 上是否有新版本;默认会 `npm install -g` 装上最新包,再对**已接入**的 Codex / Pi / Kimi 执行 `sync`。`--check` 只报告版本,不安装也不 sync。没有已安装目标时跳过 sync,不报错。只刷新配置、不升 CLI 时用 `cpac sync`。
178
+ - `upgrade` 是发布后更新本机的一次性入口:查 npm 需要时 `npm install -g` 对**已接入**的 Codex / Pi / Kimi 执行 `sync`。已是最新也会 sync。不要先手动 `npm install -g` 再 `cpac sync`。`--check` 只报告版本,不安装也不 sync。没有已安装目标时跳过 sync,不报错。只刷新配置、不升 CLI 时用 `cpac sync`。
177
179
 
178
180
  ### Claude Code
179
181
 
package/dist/cpac.js CHANGED
@@ -161,8 +161,7 @@ export function loadConfig(path, useDefaultsIfMissing = false) {
161
161
  });
162
162
  }
163
163
  const codexHome = expandUserPath(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
164
- const codexConfig = resolve(expandUserPath(value.codex_config ||
165
- join(codexHome, "config.toml")));
164
+ const codexConfig = resolve(expandUserPath(value.codex_config || join(codexHome, "config.toml")));
166
165
  const stateDir = resolve(expandUserPath(value.state_dir || join(homedir(), ".cpac")));
167
166
  const stateToConfig = relative(stateDir, codexConfig);
168
167
  if ([parse(stateDir).root, resolve(homedir()), resolve(tmpdir())].includes(stateDir) ||
@@ -248,9 +247,7 @@ export async function fetchCatalog(cpaUrl, apiKey) {
248
247
  if (!Array.isArray(models) || models.length === 0) {
249
248
  throw new CPACError("CPA rich catalog must contain a non-empty models array");
250
249
  }
251
- if (models.some((model) => !objectValue(model) ||
252
- typeof model.slug !== "string" ||
253
- !model.slug.trim())) {
250
+ if (models.some((model) => !objectValue(model) || typeof model.slug !== "string" || !model.slug.trim())) {
254
251
  throw new CPACError("CPA catalog contains a model without a slug");
255
252
  }
256
253
  return {
@@ -267,8 +264,7 @@ export async function fetchCatalog(cpaUrl, apiKey) {
267
264
  export function liftContextWindows(bytes) {
268
265
  const document = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
269
266
  for (const model of document.models) {
270
- const maxWindow = typeof model.max_context_window === "number" &&
271
- model.max_context_window > 0
267
+ const maxWindow = typeof model.max_context_window === "number" && model.max_context_window > 0
272
268
  ? Math.floor(model.max_context_window)
273
269
  : 0;
274
270
  const window = typeof model.context_window === "number"
@@ -604,7 +600,8 @@ function hasAgentsMaxThreads(content) {
604
600
  }
605
601
  if (table === "agents" && /^\s*max_threads\s*=/.test(line))
606
602
  return true;
607
- if (table === "root" && /^\s*(?:agents|"agents"|'agents')\s*\.\s*max_threads\s*=/.test(line))
603
+ if (table === "root" &&
604
+ /^\s*(?:agents|"agents"|'agents')\s*\.\s*max_threads\s*=/.test(line))
608
605
  return true;
609
606
  }
610
607
  return false;
@@ -672,7 +669,7 @@ function localBrowserOrigin(value) {
672
669
  return true;
673
670
  try {
674
671
  const hostname = new URL(value).hostname.toLowerCase();
675
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
672
+ return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
676
673
  }
677
674
  catch {
678
675
  return false;
@@ -804,7 +801,7 @@ function shouldMarkOneMillion(id, window, compactWindow) {
804
801
  return false;
805
802
  if (window >= ONE_MILLION)
806
803
  return true;
807
- return compactWindow !== undefined && window > 200_000 && window >= compactWindow;
804
+ return (compactWindow !== undefined && window > 200_000 && window >= compactWindow);
808
805
  }
809
806
  // Auto tier defaults. Within the claude-family pool (whole catalog when no
810
807
  // claude model exists), opus matches its family name or stays unset; sonnet
@@ -1226,9 +1223,7 @@ export async function inject(config, v2Off = false, maxContext = false) {
1226
1223
  existed = existsSync(config.codex_config);
1227
1224
  try {
1228
1225
  original = existed ? readFileSync(config.codex_config) : new Uint8Array();
1229
- originalMode = existed
1230
- ? statSync(config.codex_config).mode & 0o7777
1231
- : 0o600;
1226
+ originalMode = existed ? statSync(config.codex_config).mode & 0o7777 : 0o600;
1232
1227
  }
1233
1228
  catch (error) {
1234
1229
  throw new CPACError(`cannot read Codex config: ${error instanceof Error ? error.message : String(error)}`);
@@ -1368,15 +1363,9 @@ export async function status(config) {
1368
1363
  const apiKey = process.env[config.api_key_env]?.trim();
1369
1364
  const keyConfigured = !!apiKey;
1370
1365
  const state = readState(config.state_dir);
1371
- if (!state) {
1372
- console.log("codex: native");
1373
- }
1374
- else {
1366
+ if (state) {
1375
1367
  originalBytes(config.state_dir, state, config.codex_config);
1376
- if (!existsSync(config.codex_config)) {
1377
- console.log(`codex: injected (config missing: ${config.codex_config})`);
1378
- }
1379
- else {
1368
+ if (existsSync(config.codex_config)) {
1380
1369
  const proxy = stateProxy(state);
1381
1370
  if (!proxy || !(await proxyIsHealthy(proxy))) {
1382
1371
  console.log(`codex: injected (loopback proxy stopped: ${config.codex_config})`);
@@ -1395,12 +1384,20 @@ export async function status(config) {
1395
1384
  // Unreadable config: the injected line above already reports it.
1396
1385
  }
1397
1386
  }
1387
+ else {
1388
+ console.log(`codex: injected (config missing: ${config.codex_config})`);
1389
+ }
1390
+ }
1391
+ else {
1392
+ console.log("codex: native");
1398
1393
  }
1399
1394
  console.log(`claude: ${keyConfigured ? "ready" : "not configured"}`);
1400
1395
  const piInstalled = isPiExtensionInstalled();
1401
- console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac pi install") : "not configured"}`);
1396
+ console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac install --target pi") : "not configured"}`);
1402
1397
  const kimiInstalled = isKimiConfigInstalled();
1403
- console.log(`kimi: ${keyConfigured ? (kimiInstalled ? "ready" : "config not installed; run: cpac kimi install") : "not configured"}`);
1398
+ console.log(`kimi: ${keyConfigured ? (kimiInstalled ? "ready" : "config not installed; run: cpac install --target kimi") : "not configured"}`);
1399
+ const grokInstalled = isGrokConfigInstalled();
1400
+ console.log(`grok: ${keyConfigured ? (grokInstalled ? "ready" : "config not installed; run: cpac install --target grok") : "not configured"}`);
1404
1401
  if (!state)
1405
1402
  return 1;
1406
1403
  const proxy = stateProxy(state);
@@ -1493,14 +1490,15 @@ export async function runOpencode(config, args, executable = "opencode") {
1493
1490
  const window = typeof row.context_window === "number" && row.context_window > 0
1494
1491
  ? Math.floor(row.context_window)
1495
1492
  : 0;
1496
- models[id] = window > 0
1497
- ? {
1498
- limit: {
1499
- context: window,
1500
- output: Math.min(OPENCODE_OUTPUT_BUDGET, window),
1501
- },
1502
- }
1503
- : {};
1493
+ models[id] =
1494
+ window > 0
1495
+ ? {
1496
+ limit: {
1497
+ context: window,
1498
+ output: Math.min(OPENCODE_OUTPUT_BUDGET, window),
1499
+ },
1500
+ }
1501
+ : {};
1504
1502
  }
1505
1503
  const content = JSON.stringify({
1506
1504
  provider: {
@@ -1618,7 +1616,8 @@ export async function runClaude(config, args, executable = "claude") {
1618
1616
  CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
1619
1617
  CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: "1",
1620
1618
  };
1621
- if (compactWindow !== undefined && !(Number(process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW) > 0)) {
1619
+ if (compactWindow !== undefined &&
1620
+ !(Number(process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW) > 0)) {
1622
1621
  env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = String(compactWindow);
1623
1622
  }
1624
1623
  const setSlot = (name, value) => {
@@ -1713,22 +1712,6 @@ export async function uninstallPiExtension() {
1713
1712
  unlinkSync(target);
1714
1713
  console.log(`Removed Pi extension: ${target}`);
1715
1714
  }
1716
- export async function runPi(config, action) {
1717
- if (action === "install") {
1718
- await installPiExtension(config);
1719
- return 0;
1720
- }
1721
- if (action === "uninstall") {
1722
- await uninstallPiExtension();
1723
- return 0;
1724
- }
1725
- if (action === "status") {
1726
- const installed = isPiExtensionInstalled();
1727
- console.log(`Pi extension: ${installed ? "installed" : "not installed"}`);
1728
- return installed ? 0 : 1;
1729
- }
1730
- throw new CPACError(`unknown pi action: ${action}; use install, uninstall, or status`);
1731
- }
1732
1715
  // Pre-write snapshot kept at <target>.cpac-backup (first version only) so a
1733
1716
  // damaged config can be restored by hand even if uninstall cannot parse it.
1734
1717
  function ensureCpacBackup(target) {
@@ -1748,7 +1731,8 @@ const KIMI_BLOCK_END = "# <<< CPAC Kimi <<<";
1748
1731
  const kimiBlockRegex = new RegExp(`${KIMI_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${KIMI_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
1749
1732
  export function isKimiConfigInstalled() {
1750
1733
  try {
1751
- return kimiBlockRegex.test(readFileSync(kimiConfigPath(), "utf8"));
1734
+ const content = readFileSync(kimiConfigPath(), "utf8");
1735
+ return (kimiBlockRegex.test(content) || /^\s*\[providers\.cpac\]\s*$/m.test(content));
1752
1736
  }
1753
1737
  catch {
1754
1738
  return false;
@@ -1806,16 +1790,18 @@ const KIMI_REASONING_EFFORTS = new Set([
1806
1790
  "ultra",
1807
1791
  ]);
1808
1792
  function kimiSupportEfforts(row) {
1809
- const levels = row.supported_reasoning_levels;
1793
+ const levels = row.supported_reasoning_levels ??
1794
+ row.reasoning_effort_levels ??
1795
+ row.reasoning_levels;
1810
1796
  if (!Array.isArray(levels))
1811
1797
  return [];
1812
1798
  const seen = new Set();
1813
1799
  const efforts = [];
1814
1800
  for (const level of levels) {
1815
1801
  const effort = typeof level === "string"
1816
- ? level
1802
+ ? level.toLowerCase()
1817
1803
  : objectValue(level) && typeof level.effort === "string"
1818
- ? level.effort
1804
+ ? level.effort.toLowerCase()
1819
1805
  : undefined;
1820
1806
  if (!effort || !KIMI_REASONING_EFFORTS.has(effort) || seen.has(effort))
1821
1807
  continue;
@@ -1871,9 +1857,13 @@ export async function installKimiConfig(config) {
1871
1857
  }
1872
1858
  if (efforts.length > 0) {
1873
1859
  lines.push(`support_efforts = ${tomlStringArray(efforts)}`);
1874
- const defaultEffort = typeof row.default_reasoning_level === "string" &&
1875
- efforts.includes(row.default_reasoning_level)
1876
- ? row.default_reasoning_level
1860
+ const rawDefaultEffort = (typeof row.default_reasoning_level === "string" &&
1861
+ row.default_reasoning_level) ||
1862
+ (typeof row.default_reasoning_effort === "string" &&
1863
+ row.default_reasoning_effort) ||
1864
+ (typeof row.default_effort === "string" && row.default_effort);
1865
+ const defaultEffort = rawDefaultEffort && efforts.includes(rawDefaultEffort.toLowerCase())
1866
+ ? rawDefaultEffort.toLowerCase()
1877
1867
  : undefined;
1878
1868
  if (defaultEffort)
1879
1869
  lines.push(`default_effort = ${tomlString(defaultEffort)}`);
@@ -1895,21 +1885,102 @@ export async function uninstallKimiConfig() {
1895
1885
  writeKimiBlock(target, null);
1896
1886
  console.log(`Removed Kimi Code provider config: ${target}`);
1897
1887
  }
1898
- export async function runKimi(config, action) {
1899
- if (action === "install") {
1900
- await installKimiConfig(config);
1901
- return 0;
1888
+ function grokHome() {
1889
+ const home = process.env.GROK_HOME?.trim() || join(homedir(), ".grok");
1890
+ return expandUserPath(home);
1891
+ }
1892
+ function grokConfigPath() {
1893
+ return join(grokHome(), "config.toml");
1894
+ }
1895
+ const GROK_BLOCK_START = "# >>> CPAC Grok >>>";
1896
+ const GROK_BLOCK_END = "# <<< CPAC Grok <<<";
1897
+ const grokBlockRegex = new RegExp(`${GROK_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${GROK_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
1898
+ export function isGrokConfigInstalled() {
1899
+ try {
1900
+ const content = readFileSync(grokConfigPath(), "utf8");
1901
+ return (grokBlockRegex.test(content) ||
1902
+ /^\s*\[model\.(?:"cpac\/|'cpac\/|cpac-)/m.test(content));
1902
1903
  }
1903
- if (action === "uninstall") {
1904
- await uninstallKimiConfig();
1905
- return 0;
1904
+ catch {
1905
+ return false;
1906
1906
  }
1907
- if (action === "status") {
1908
- const installed = isKimiConfigInstalled();
1909
- console.log(`Kimi Code config: ${installed ? "installed" : "not installed"}`);
1910
- return installed ? 0 : 1;
1907
+ }
1908
+ function isCpacGrokTableHeader(line) {
1909
+ const header = line.match(/^\[([^\]]+)\]\s*$/);
1910
+ if (!header)
1911
+ return false;
1912
+ const name = header[1];
1913
+ return /^model\.(?:"cpac\/|'cpac\/|cpac-)/.test(name);
1914
+ }
1915
+ function stripOrphanCpacGrokTables(content) {
1916
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
1917
+ const out = [];
1918
+ let skipping = false;
1919
+ for (const line of content.split(/\r?\n/)) {
1920
+ if (/^\[[^\]]+\]\s*$/.test(line))
1921
+ skipping = isCpacGrokTableHeader(line);
1922
+ if (!skipping)
1923
+ out.push(line);
1911
1924
  }
1912
- throw new CPACError(`unknown kimi action: ${action}; use install, uninstall, or status`);
1925
+ return out.join(eol).replace(/(?:\r?\n){3,}/g, `${eol}${eol}`);
1926
+ }
1927
+ function writeGrokBlock(path, block) {
1928
+ let content = existsSync(path) ? readFileSync(path, "utf8") : "";
1929
+ content = content.replace(grokBlockRegex, "");
1930
+ const orphan = content.indexOf(GROK_BLOCK_START);
1931
+ if (orphan !== -1)
1932
+ content = content.slice(0, orphan);
1933
+ content = stripOrphanCpacGrokTables(content);
1934
+ if (block) {
1935
+ content = `${content}${content && !content.endsWith("\n") ? "\n" : ""}${block}\n`;
1936
+ }
1937
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
1938
+ const mode = existsSync(path) ? statSync(path).mode & 0o7777 : 0o600;
1939
+ atomicWrite(path, Buffer.from(content), mode);
1940
+ }
1941
+ export async function installGrokConfig(config) {
1942
+ const apiKey = await resolveApiKey(config.api_key_env);
1943
+ const catalog = await fetchCatalog(config.cpa_url, apiKey);
1944
+ const document = JSON.parse(new TextDecoder("utf-8").decode(catalog.bytes));
1945
+ const rows = catalogModelRows(document) ?? [];
1946
+ if (rows.length === 0)
1947
+ throw new CPACError("CPA catalog contains no models");
1948
+ const state = readState(config.state_dir);
1949
+ const recorded = state ? stateProxy(state) : null;
1950
+ const port = recorded?.port ?? config.codex_proxy_port;
1951
+ if (!port) {
1952
+ throw new CPACError("loopback proxy port unknown; run cpac inject first");
1953
+ }
1954
+ const lines = [GROK_BLOCK_START];
1955
+ for (const row of rows) {
1956
+ const slug = catalogModelId(row);
1957
+ if (!slug)
1958
+ continue;
1959
+ const context = typeof row.context_window === "number" && row.context_window > 0
1960
+ ? Math.floor(row.context_window)
1961
+ : 200000;
1962
+ const name = typeof row.display_name === "string" && row.display_name.trim()
1963
+ ? `${row.display_name.trim()} (CPAC)`
1964
+ : `CPAC ${slug}`;
1965
+ 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; run "cpac inject" to start the proxy.', 'api_key = "cpac-loopback"', `name = ${tomlString(name)}`, `context_window = ${context}`, "");
1966
+ }
1967
+ if (lines[lines.length - 1] === "")
1968
+ lines.pop();
1969
+ lines.push(GROK_BLOCK_END);
1970
+ const target = grokConfigPath();
1971
+ ensureCpacBackup(target);
1972
+ writeGrokBlock(target, lines.join("\n"));
1973
+ console.log(`Installed Grok Build provider config: ${target}`);
1974
+ if (!recorded || !(await proxyIsHealthy(recorded))) {
1975
+ console.log("Loopback proxy is not running; run: cpac inject");
1976
+ }
1977
+ }
1978
+ export async function uninstallGrokConfig() {
1979
+ const target = grokConfigPath();
1980
+ if (!isGrokConfigInstalled())
1981
+ throw new CPACError("Grok Build config is not installed");
1982
+ writeGrokBlock(target, null);
1983
+ console.log(`Removed Grok Build provider config: ${target}`);
1913
1984
  }
1914
1985
  function readPackageVersion() {
1915
1986
  try {
@@ -1922,11 +1993,31 @@ function readPackageVersion() {
1922
1993
  }
1923
1994
  }
1924
1995
  const CPAC_VERSION = readPackageVersion();
1996
+ export function detectClientVersion(binary) {
1997
+ if (!/^[\w.-]+$/.test(binary))
1998
+ return undefined;
1999
+ try {
2000
+ const res = spawnSync(binary, ["--version"], {
2001
+ encoding: "utf8",
2002
+ timeout: 2000,
2003
+ stdio: ["ignore", "pipe", "ignore"],
2004
+ });
2005
+ if (res.status === 0 && typeof res.stdout === "string") {
2006
+ const match = res.stdout.match(/v?(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
2007
+ return match ? match[1] : res.stdout.trim().split(/\s+/)[0] || undefined;
2008
+ }
2009
+ }
2010
+ catch {
2011
+ // ignored
2012
+ }
2013
+ return undefined;
2014
+ }
1925
2015
  function binaryOnPath(name) {
1926
2016
  if (!/^[\w.-]+$/.test(name))
1927
2017
  return false;
1928
2018
  try {
1929
- return spawnSync(`command -v ${name}`, { stdio: "ignore", shell: true }).status === 0;
2019
+ return (spawnSync(`command -v ${name}`, { stdio: "ignore", shell: true }).status ===
2020
+ 0);
1930
2021
  }
1931
2022
  catch {
1932
2023
  return false;
@@ -1947,6 +2038,7 @@ const TARGETS = [
1947
2038
  return false;
1948
2039
  }
1949
2040
  },
2041
+ version: () => detectClientVersion("codex"),
1950
2042
  install: async (config, dryRun, v2Off, maxContext) => {
1951
2043
  if (dryRun) {
1952
2044
  console.log("would inject CPA catalog and loopback proxy into Codex");
@@ -1967,6 +2059,7 @@ const TARGETS = [
1967
2059
  home: () => piExtensionsDir(),
1968
2060
  detected: () => binaryOnPath("pi") || existsSync(piExtensionsDir()),
1969
2061
  installed: () => isPiExtensionInstalled(),
2062
+ version: () => detectClientVersion("pi"),
1970
2063
  install: async (config, dryRun) => {
1971
2064
  if (dryRun) {
1972
2065
  console.log(`would write Pi extension: ${join(piExtensionsDir(), "cpac.ts")}`);
@@ -1987,6 +2080,7 @@ const TARGETS = [
1987
2080
  home: () => dirname(kimiConfigPath()),
1988
2081
  detected: () => binaryOnPath("kimi") || existsSync(dirname(kimiConfigPath())),
1989
2082
  installed: () => isKimiConfigInstalled(),
2083
+ version: () => detectClientVersion("kimi"),
1990
2084
  install: async (config, dryRun) => {
1991
2085
  if (dryRun) {
1992
2086
  console.log(`would write CPA provider block: ${kimiConfigPath()}`);
@@ -2002,6 +2096,27 @@ const TARGETS = [
2002
2096
  await uninstallKimiConfig();
2003
2097
  },
2004
2098
  },
2099
+ {
2100
+ id: "grok",
2101
+ home: () => grokHome(),
2102
+ detected: () => binaryOnPath("grok") || existsSync(grokHome()),
2103
+ installed: () => isGrokConfigInstalled(),
2104
+ version: () => detectClientVersion("grok"),
2105
+ install: async (config, dryRun) => {
2106
+ if (dryRun) {
2107
+ console.log(`would write CPA provider block: ${grokConfigPath()}`);
2108
+ return;
2109
+ }
2110
+ await installGrokConfig(config);
2111
+ },
2112
+ uninstall: async (_config, dryRun) => {
2113
+ if (dryRun) {
2114
+ console.log(`would remove CPA provider block: ${grokConfigPath()}`);
2115
+ return;
2116
+ }
2117
+ await uninstallGrokConfig();
2118
+ },
2119
+ },
2005
2120
  ];
2006
2121
  export async function detectTargets(config) {
2007
2122
  return TARGETS.map((target) => ({
@@ -2009,6 +2124,7 @@ export async function detectTargets(config) {
2009
2124
  detected: target.detected(),
2010
2125
  installed: target.installed(config),
2011
2126
  path: target.home(),
2127
+ version: target.version?.(),
2012
2128
  }));
2013
2129
  }
2014
2130
  export async function runDetect(config, asJson) {
@@ -2027,7 +2143,11 @@ function selectTargets(requested, all, eligible) {
2027
2143
  if (unknown.length > 0) {
2028
2144
  throw new CPACError(`unknown target(s): ${unknown.join(", ")}; use ${TARGETS.map((target) => target.id).join(",")}`);
2029
2145
  }
2030
- const ids = requested.length > 0 ? requested : all ? TARGETS.map((target) => target.id) : eligible;
2146
+ const ids = requested.length > 0
2147
+ ? requested
2148
+ : all
2149
+ ? TARGETS.map((target) => target.id)
2150
+ : eligible;
2031
2151
  return TARGETS.filter((target) => ids.includes(target.id));
2032
2152
  }
2033
2153
  export async function runInstall(config, requested, options) {
@@ -2128,7 +2248,10 @@ export async function runUpgrade(config, checkOnly) {
2128
2248
  console.log("Update available. Run: cpac upgrade");
2129
2249
  return 0;
2130
2250
  }
2131
- if (!upToDate) {
2251
+ if (upToDate) {
2252
+ console.log("Already up to date.");
2253
+ }
2254
+ else {
2132
2255
  console.log(`Installing @yhong91/cpac@${latest}`);
2133
2256
  const result = spawnSync("npm", ["install", "-g", `@yhong91/cpac@${latest}`], { stdio: "inherit" });
2134
2257
  if (result.status !== 0) {
@@ -2136,9 +2259,6 @@ export async function runUpgrade(config, checkOnly) {
2136
2259
  return result.status ?? 1;
2137
2260
  }
2138
2261
  }
2139
- else {
2140
- console.log("Already up to date.");
2141
- }
2142
2262
  const installed = TARGETS.filter((target) => target.installed(config)).map((target) => target.id);
2143
2263
  if (installed.length === 0) {
2144
2264
  console.log("No installed agents to sync.");
@@ -2156,12 +2276,10 @@ function usage() {
2156
2276
  " cpac claude [--config PATH] [--] [claude args...]",
2157
2277
  " cpac opencode [--config PATH] [--] [opencode args...]",
2158
2278
  " cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--reset] [--config PATH]",
2159
- " cpac pi <install|uninstall|status> [--config PATH]",
2160
- " cpac kimi <install|uninstall|status> [--config PATH]",
2161
2279
  " cpac detect [--json] [--home PATH]",
2162
- " cpac install [--target codex,pi,kimi] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
2163
- " cpac sync [--target codex,pi,kimi] [--all] [--dry-run] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
2164
- " cpac uninstall [--target codex,pi,kimi] [--all] [--dry-run] [--home PATH]",
2280
+ " cpac install [--target codex,pi,kimi,grok] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
2281
+ " cpac sync [--target codex,pi,kimi,grok] [--all] [--dry-run] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
2282
+ " cpac uninstall [--target codex,pi,kimi,grok] [--all] [--dry-run] [--home PATH]",
2165
2283
  " cpac upgrade [--check]",
2166
2284
  " cpac version",
2167
2285
  "",
@@ -2240,19 +2358,6 @@ function parseArgs(args) {
2240
2358
  }
2241
2359
  return parsed;
2242
2360
  }
2243
- if (args[0] === "pi" || args[0] === "kimi") {
2244
- let configPath = defaultConfigPath();
2245
- let index = 1;
2246
- if (args[index] === "--config") {
2247
- const value = args[++index];
2248
- if (!value)
2249
- throw new CPACError("--config requires a path");
2250
- configPath = resolve(expandUserPath(value));
2251
- index += 1;
2252
- }
2253
- const action = args[index] || "status";
2254
- return { command: args[0], configPath, action };
2255
- }
2256
2361
  if (args[0] === "claude-models") {
2257
2362
  if (args.includes("-h") || args.includes("--help")) {
2258
2363
  console.log([
@@ -2288,9 +2393,7 @@ function parseArgs(args) {
2288
2393
  else if (arg === "--reset") {
2289
2394
  reset = true;
2290
2395
  }
2291
- else if (arg === "--opus" ||
2292
- arg === "--sonnet" ||
2293
- arg === "--haiku") {
2396
+ else if (arg === "--opus" || arg === "--sonnet" || arg === "--haiku") {
2294
2397
  const slot = arg.slice(2);
2295
2398
  const value = args[index + 1];
2296
2399
  if (value !== undefined && !value.startsWith("-")) {
@@ -2359,7 +2462,8 @@ function parseArgs(args) {
2359
2462
  else if (args[index] === "--v2_models" || args[index] === "--v2-models") {
2360
2463
  v2Models = true;
2361
2464
  }
2362
- else if (args[index] === "--max_context" || args[index] === "--max-context") {
2465
+ else if (args[index] === "--max_context" ||
2466
+ args[index] === "--max-context") {
2363
2467
  maxContext = true;
2364
2468
  }
2365
2469
  else if (args[index].startsWith("-")) {
@@ -2451,10 +2555,6 @@ export async function main(args = process.argv.slice(2)) {
2451
2555
  return await runClaudeModels(parsed);
2452
2556
  if (parsed.command === "proxy")
2453
2557
  return await runProxy(config);
2454
- if (parsed.command === "pi")
2455
- return await runPi(config, parsed.action);
2456
- if (parsed.command === "kimi")
2457
- return await runKimi(config, parsed.action);
2458
2558
  if (parsed.command === "inject") {
2459
2559
  let injectConfig = config;
2460
2560
  if (parsed.v2Models) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {