@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.
- package/README.md +3 -1
- package/dist/cpac.js +199 -99
- 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`
|
|
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" &&
|
|
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 (
|
|
1372
|
-
console.log("codex: native");
|
|
1373
|
-
}
|
|
1374
|
-
else {
|
|
1366
|
+
if (state) {
|
|
1375
1367
|
originalBytes(config.state_dir, state, config.codex_config);
|
|
1376
|
-
if (
|
|
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
|
|
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
|
|
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] =
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
|
1875
|
-
|
|
1876
|
-
|
|
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
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
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
|
-
|
|
1904
|
-
|
|
1905
|
-
return 0;
|
|
1904
|
+
catch {
|
|
1905
|
+
return false;
|
|
1906
1906
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
|
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 (
|
|
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" ||
|
|
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) {
|