@yhong91/cpac 0.1.32 → 0.1.34
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 +37 -46
- package/cpac.example.json +3 -1
- package/dist/agents.js +67 -26
- package/dist/config.js +93 -11
- package/dist/cpac.js +120 -69
- package/dist/targets/claude.js +39 -18
- package/dist/targets/codex.js +31 -4
- package/dist/targets/pi.js +119 -35
- package/dist/util.js +173 -0
- package/{dist/pi-extension.template → extensions/pi-cpac.ts} +91 -36
- package/package.json +12 -3
package/dist/cpac.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { resolve } from "node:path";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { CPAC_VERSION, runCodexConfig, runDetect, runInstall, runRestore, runSync, runTargetLauncher, runUninstall, runUpgrade, } from "./agents.js";
|
|
7
|
-
import { runClaude, runClaudeConfig
|
|
8
|
-
import {
|
|
7
|
+
import { runClaude, runClaudeConfig } from "./targets/claude.js";
|
|
8
|
+
import { multiAgentV2Enabled } from "./targets/codex.js";
|
|
9
9
|
import { isGrokConfigInstalled } from "./targets/grok.js";
|
|
10
10
|
import { isKimiConfigInstalled } from "./targets/kimi.js";
|
|
11
11
|
import { runOpencode } from "./targets/opencode.js";
|
|
12
12
|
import { isPiExtensionInstalled } from "./targets/pi.js";
|
|
13
|
-
import { defaultConfigPath, loadConfig, originalBytes, pickSpawnModels, readState, saveSpawnModels, stateProxy, } from "./config.js";
|
|
14
|
-
import { proxyIsHealthy,
|
|
13
|
+
import { defaultConfigPath, loadConfig, originalBytes, pickSpawnModels, readState, saveSpawnModels, stateProxy, catalogMaxContextLive, } from "./config.js";
|
|
14
|
+
import { proxyIsHealthy, runProxyChild } from "./proxy.js";
|
|
15
15
|
import { CPACError, expandUserPath, promptSecret, saveApiKeyExport, shellProfile, } from "./util.js";
|
|
16
|
-
export { PROVIDER, loadConfig, saveSpawnModels, liftContextWindows, reorderCatalog, readState, stateProxy, } from "./config.js";
|
|
16
|
+
export { PROVIDER, loadConfig, saveSpawnModels, saveCodexSetup, liftContextWindows, reorderCatalog, sanitizeCatalogModalities, stampMultiAgentVersion, catalogMaxContextLive, readState, stateProxy, } from "./config.js";
|
|
17
17
|
export { saveApiKeyExport } from "./util.js";
|
|
18
18
|
export { stopProxyProcess } from "./proxy.js";
|
|
19
|
-
export { buildCodexConfig, hasProviderTable, inject, multiAgentV2Enabled, restore, withMultiAgentV2, } from "./targets/codex.js";
|
|
19
|
+
export { buildCodexConfig, hasProviderTable, inject, multiAgentV2Enabled, restore, stripAgentsMaxThreads, withMultiAgentV2, } from "./targets/codex.js";
|
|
20
20
|
export { claudeTierSlots, createClaudeProxy, runClaude, } from "./targets/claude.js";
|
|
21
21
|
export { detectClientVersion, detectTargets, runInstall, runRestore, runSync, runTargetLauncher, } from "./agents.js";
|
|
22
22
|
export { installGrokConfig, isGrokConfigInstalled, uninstallGrokConfig, } from "./targets/grok.js";
|
|
@@ -42,11 +42,23 @@ export async function status(config) {
|
|
|
42
42
|
console.log(`codex: injected proxy=http://127.0.0.1:${proxy.port}/v1 sha256=${digest}`);
|
|
43
43
|
}
|
|
44
44
|
try {
|
|
45
|
-
|
|
45
|
+
const liveV2 = multiAgentV2Enabled(readFileSync(config.codex_config, "utf8"));
|
|
46
|
+
console.log(`codex v2: expected=${config.v2_off ? "off" : "on"} live=${liveV2 ? "on" : "off"}`);
|
|
46
47
|
}
|
|
47
48
|
catch {
|
|
48
49
|
// Unreadable config: the injected line above already reports it.
|
|
49
50
|
}
|
|
51
|
+
const catalogPath = join(config.state_dir, "codex-models.json");
|
|
52
|
+
let liveMax = false;
|
|
53
|
+
if (existsSync(catalogPath)) {
|
|
54
|
+
try {
|
|
55
|
+
liveMax = catalogMaxContextLive(readFileSync(catalogPath));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Unreadable catalog: report live=off.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log(`codex max_context: expected=${config.max_context ? "on" : "off"} live=${liveMax ? "on" : "off"}`);
|
|
50
62
|
}
|
|
51
63
|
else {
|
|
52
64
|
console.log(`codex: injected (config missing: ${config.codex_config})`);
|
|
@@ -57,11 +69,11 @@ export async function status(config) {
|
|
|
57
69
|
}
|
|
58
70
|
console.log(`claude: ${keyConfigured ? "ready" : "not configured"}`);
|
|
59
71
|
const piInstalled = isPiExtensionInstalled();
|
|
60
|
-
console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac install
|
|
72
|
+
console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac install pi") : "not configured"}`);
|
|
61
73
|
const kimiInstalled = isKimiConfigInstalled();
|
|
62
|
-
console.log(`kimi: ${keyConfigured ? (kimiInstalled ? "ready" : "config not installed; run: cpac install
|
|
74
|
+
console.log(`kimi: ${keyConfigured ? (kimiInstalled ? "ready" : "config not installed; run: cpac install kimi") : "not configured"}`);
|
|
63
75
|
const grokInstalled = isGrokConfigInstalled();
|
|
64
|
-
console.log(`grok: ${keyConfigured ? (grokInstalled ? "ready" : "config not installed; run: cpac install
|
|
76
|
+
console.log(`grok: ${keyConfigured ? (grokInstalled ? "ready" : "config not installed; run: cpac install grok") : "not configured"}`);
|
|
65
77
|
if (!state)
|
|
66
78
|
return 1;
|
|
67
79
|
const proxy = stateProxy(state);
|
|
@@ -70,9 +82,9 @@ export async function status(config) {
|
|
|
70
82
|
return 0;
|
|
71
83
|
}
|
|
72
84
|
async function guide(config) {
|
|
73
|
-
console.log(`CPA Companion\n\nCPA: ${config.cpa_url}\nCPA_API_KEY: ${process.env[config.api_key_env]?.trim() ? "configured" : "not configured"}\n\nCommands:\n cpac <agent> [args...] Launch an agent (codex, kimi, grok, pi, claude, opencode) through CPA\n cpac
|
|
85
|
+
console.log(`CPA Companion\n\nCPA: ${config.cpa_url}\nCPA_API_KEY: ${process.env[config.api_key_env]?.trim() ? "configured" : "not configured"}\n\nCommands:\n cpac <agent> [args...] Launch an agent (codex, kimi, grok, pi, claude, opencode) through CPA\n cpac setup <codex|claude> Configure Codex or Claude, then launch\n cpac status Show agent support status (Codex, Claude, Pi, Kimi, Grok)\n cpac restore <id> | --all Detach injected agents
|
|
74
86
|
cpac detect Show supported targets and install status
|
|
75
|
-
cpac install
|
|
87
|
+
cpac install [ids...] Install CPA into detected targets, or named agents
|
|
76
88
|
cpac sync Refresh installed Codex/Pi/Kimi/Grok injections from CPA
|
|
77
89
|
cpac uninstall Detach all agents, then remove the cpac package (confirms first)\n cpac upgrade Update cpac from npm and sync installed agents\n cpac --help Show command usage`);
|
|
78
90
|
if (process.env[config.api_key_env]?.trim())
|
|
@@ -90,21 +102,33 @@ function usage() {
|
|
|
90
102
|
return [
|
|
91
103
|
"Usage: cpac",
|
|
92
104
|
" cpac <codex|kimi|grok|pi|claude|opencode> [args...]",
|
|
93
|
-
" cpac <codex|claude>
|
|
105
|
+
" cpac setup <codex|claude> [options]",
|
|
94
106
|
" cpac <codex|claude|kimi|grok|pi> clear",
|
|
95
|
-
" cpac
|
|
96
|
-
" cpac restore --target <id> | --all [--dry-run] [--config PATH]",
|
|
107
|
+
" cpac restore <id...> | --all [--dry-run] [--config PATH]",
|
|
97
108
|
" cpac status [--config PATH]",
|
|
98
|
-
" cpac proxy [--config PATH]",
|
|
99
109
|
" cpac detect [--json] [--home PATH]",
|
|
100
|
-
" cpac install [
|
|
101
|
-
" cpac sync [
|
|
110
|
+
" cpac install [codex|pi|kimi|grok...] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
|
|
111
|
+
" cpac sync [codex|pi|kimi|grok...] [--all] [--dry-run] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
|
|
102
112
|
" cpac uninstall [--dry-run]",
|
|
103
113
|
" cpac upgrade [--check]",
|
|
104
114
|
" cpac -v | --version",
|
|
105
115
|
"",
|
|
106
116
|
"launch an agent directly with `cpac <agent> [args...]` (all args forwarded).",
|
|
107
|
-
"configure then launch with `cpac <codex|claude
|
|
117
|
+
"configure then launch with `cpac setup <codex|claude>`; restore with `cpac <agent> clear`.",
|
|
118
|
+
].join("\n");
|
|
119
|
+
}
|
|
120
|
+
function setupUsage() {
|
|
121
|
+
return [
|
|
122
|
+
"Usage: cpac setup claude [--opus [M]] [--sonnet [M]] [--haiku [M]] [--reset] [--config PATH]",
|
|
123
|
+
" cpac setup codex [--v2_off] [--v2_models] [--max_context] [--config PATH]",
|
|
124
|
+
"",
|
|
125
|
+
"Claude slots (saved to config, applied on every `cpac claude`):",
|
|
126
|
+
" --opus heavy reasoning / planning fallback (ANTHROPIC_DEFAULT_OPUS_MODEL)",
|
|
127
|
+
" --sonnet main conversation default (ANTHROPIC_DEFAULT_SONNET_MODEL)",
|
|
128
|
+
" --haiku high-frequency small tasks (ANTHROPIC_DEFAULT_HAIKU_MODEL)",
|
|
129
|
+
"",
|
|
130
|
+
"A slot without a value opens an arrow-key picker; --reset clears overrides.",
|
|
131
|
+
"No flags on a TTY opens the wizard, then launches.",
|
|
108
132
|
].join("\n");
|
|
109
133
|
}
|
|
110
134
|
function parseArgs(args) {
|
|
@@ -172,6 +196,12 @@ function parseArgs(args) {
|
|
|
172
196
|
else if (arg === "--max_context" || arg === "--max-context") {
|
|
173
197
|
parsed.maxContext = true;
|
|
174
198
|
}
|
|
199
|
+
else if (!arg.startsWith("-")) {
|
|
200
|
+
parsed.targets.push(...arg
|
|
201
|
+
.split(",")
|
|
202
|
+
.map((entry) => entry.trim())
|
|
203
|
+
.filter(Boolean));
|
|
204
|
+
}
|
|
175
205
|
else {
|
|
176
206
|
throw new CPACError(`unknown option: ${arg}`);
|
|
177
207
|
}
|
|
@@ -223,6 +253,17 @@ function parseArgs(args) {
|
|
|
223
253
|
interactive: false,
|
|
224
254
|
};
|
|
225
255
|
}
|
|
256
|
+
if (targetId === "codex") {
|
|
257
|
+
return {
|
|
258
|
+
command: "codex-config",
|
|
259
|
+
configPath,
|
|
260
|
+
v2Off: false,
|
|
261
|
+
v2Models: false,
|
|
262
|
+
maxContext: false,
|
|
263
|
+
reset: true,
|
|
264
|
+
interactive: false,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
226
267
|
return {
|
|
227
268
|
command: "restore",
|
|
228
269
|
configPath,
|
|
@@ -274,7 +315,7 @@ function parseArgs(args) {
|
|
|
274
315
|
args: launchArgs,
|
|
275
316
|
};
|
|
276
317
|
}
|
|
277
|
-
throw new CPACError(`${targetId} does not require setup (all CPA models are auto-synced). Run 'cpac ${targetId}' or 'cpac install
|
|
318
|
+
throw new CPACError(`${targetId} does not require setup (all CPA models are auto-synced). Run 'cpac ${targetId}' or 'cpac install ${targetId}'.`);
|
|
278
319
|
}
|
|
279
320
|
const passArgs = rest;
|
|
280
321
|
if (targetId === "codex") {
|
|
@@ -300,31 +341,53 @@ function parseArgs(args) {
|
|
|
300
341
|
args: passArgs,
|
|
301
342
|
};
|
|
302
343
|
}
|
|
303
|
-
if (args[0] === "
|
|
344
|
+
if (args[0] === "setup") {
|
|
304
345
|
if (args.includes("-h") || args.includes("--help")) {
|
|
305
|
-
console.log(
|
|
306
|
-
"Usage: cpac claude --config [--opus [M]] [--sonnet [M]] [--haiku [M]] [--reset] [--config PATH]",
|
|
307
|
-
"",
|
|
308
|
-
"Sets the model Claude Code uses per task tier. Saved to the config file",
|
|
309
|
-
"and applied on every `cpac claude` launch:",
|
|
310
|
-
" --opus heavy reasoning / planning fallback tier (ANTHROPIC_DEFAULT_OPUS_MODEL)",
|
|
311
|
-
" --sonnet main conversation default tier (ANTHROPIC_DEFAULT_SONNET_MODEL)",
|
|
312
|
-
" --haiku high-frequency small tasks: titles, summaries, background",
|
|
313
|
-
" (ANTHROPIC_DEFAULT_HAIKU_MODEL + ANTHROPIC_SMALL_FAST_MODEL)",
|
|
314
|
-
"",
|
|
315
|
-
"Unset slots auto-select from the catalog: same-family claude model first,",
|
|
316
|
-
"then gemini → grok → luna → first model (opus stays unset without a match).",
|
|
317
|
-
"",
|
|
318
|
-
"A slot without a value opens an arrow-key picker over the CPA catalog;",
|
|
319
|
-
"--reset clears all overrides back to catalog auto-selection.",
|
|
320
|
-
].join("\n"));
|
|
346
|
+
console.log(setupUsage());
|
|
321
347
|
return null;
|
|
322
348
|
}
|
|
349
|
+
const agent = args[1];
|
|
350
|
+
if (agent !== "claude" && agent !== "codex") {
|
|
351
|
+
throw new CPACError("setup requires claude or codex");
|
|
352
|
+
}
|
|
323
353
|
let configPath = defaultConfigPath();
|
|
354
|
+
if (agent === "codex") {
|
|
355
|
+
let v2Off = false;
|
|
356
|
+
let v2Models = false;
|
|
357
|
+
let maxContext = false;
|
|
358
|
+
for (let index = 2; index < args.length; index++) {
|
|
359
|
+
const arg = args[index];
|
|
360
|
+
if (arg === "--config") {
|
|
361
|
+
const value = args[++index];
|
|
362
|
+
if (!value)
|
|
363
|
+
throw new CPACError("--config requires a path");
|
|
364
|
+
configPath = resolve(expandUserPath(value));
|
|
365
|
+
}
|
|
366
|
+
else if (arg === "--v2_off" || arg === "--v2-off")
|
|
367
|
+
v2Off = true;
|
|
368
|
+
else if (arg === "--v2_models" || arg === "--v2-models")
|
|
369
|
+
v2Models = true;
|
|
370
|
+
else if (arg === "--max_context" || arg === "--max-context")
|
|
371
|
+
maxContext = true;
|
|
372
|
+
else
|
|
373
|
+
throw new CPACError(`unknown option: ${arg}`);
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
command: "codex-config",
|
|
377
|
+
configPath,
|
|
378
|
+
v2Off,
|
|
379
|
+
v2Models,
|
|
380
|
+
maxContext,
|
|
381
|
+
reset: false,
|
|
382
|
+
interactive: true,
|
|
383
|
+
launch: true,
|
|
384
|
+
args: [],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
324
387
|
const models = {};
|
|
325
388
|
const pick = [];
|
|
326
389
|
let reset = false;
|
|
327
|
-
for (let index =
|
|
390
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
328
391
|
const arg = args[index];
|
|
329
392
|
if (arg === "--config") {
|
|
330
393
|
const value = args[++index];
|
|
@@ -352,15 +415,20 @@ function parseArgs(args) {
|
|
|
352
415
|
throw new CPACError(`unknown option: ${arg}`);
|
|
353
416
|
}
|
|
354
417
|
}
|
|
418
|
+
const interactive = !reset && Object.keys(models).length === 0 && pick.length === 0;
|
|
355
419
|
return {
|
|
356
420
|
command: "claude-config",
|
|
357
421
|
configPath,
|
|
358
422
|
models,
|
|
359
423
|
pick,
|
|
360
424
|
reset,
|
|
361
|
-
interactive
|
|
425
|
+
interactive,
|
|
426
|
+
launch: interactive && !reset,
|
|
362
427
|
};
|
|
363
428
|
}
|
|
429
|
+
if (args[0] === "claude-models") {
|
|
430
|
+
throw new CPACError("claude-models was removed; use: cpac setup claude");
|
|
431
|
+
}
|
|
364
432
|
if (args.includes("-h") || args.includes("--help")) {
|
|
365
433
|
console.log(usage());
|
|
366
434
|
return null;
|
|
@@ -393,20 +461,19 @@ function parseArgs(args) {
|
|
|
393
461
|
else
|
|
394
462
|
positional.push(args[index]);
|
|
395
463
|
}
|
|
396
|
-
if (positional.length
|
|
397
|
-
|
|
398
|
-
|
|
464
|
+
if (positional.length === 1 && positional[0] === "inject") {
|
|
465
|
+
throw new CPACError("inject was removed; use: cpac install codex");
|
|
466
|
+
}
|
|
467
|
+
if (positional.length === 1 && positional[0] === "proxy") {
|
|
468
|
+
throw new CPACError("proxy was removed; restart with: cpac install codex (or kimi / grok)");
|
|
399
469
|
}
|
|
400
|
-
if (positional[0] !== "
|
|
401
|
-
throw new CPACError(
|
|
470
|
+
if (positional.length !== 1 || positional[0] !== "status") {
|
|
471
|
+
throw new CPACError(usage());
|
|
402
472
|
}
|
|
403
|
-
if (
|
|
404
|
-
|
|
473
|
+
if (v2Off || v2Models || maxContext) {
|
|
474
|
+
throw new CPACError("--v2_off / --v2_models / --max_context is only valid with install or setup");
|
|
405
475
|
}
|
|
406
|
-
return {
|
|
407
|
-
command: positional[0],
|
|
408
|
-
configPath,
|
|
409
|
-
};
|
|
476
|
+
return { command: "status", configPath };
|
|
410
477
|
}
|
|
411
478
|
export async function main(args = process.argv.slice(2)) {
|
|
412
479
|
try {
|
|
@@ -468,7 +535,7 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
468
535
|
});
|
|
469
536
|
if (parsed.command === "uninstall") {
|
|
470
537
|
if (parsed.targets.length || parsed.all) {
|
|
471
|
-
throw new CPACError("uninstall removes the cpac package; detach agents with: cpac restore
|
|
538
|
+
throw new CPACError("uninstall removes the cpac package; detach agents with: cpac restore <id> | --all");
|
|
472
539
|
}
|
|
473
540
|
return await runUninstall(config, { dryRun: parsed.dryRun });
|
|
474
541
|
}
|
|
@@ -520,23 +587,7 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
520
587
|
parsed.command === "pi") {
|
|
521
588
|
return await runTargetLauncher(config, parsed.command, parsed.args);
|
|
522
589
|
}
|
|
523
|
-
|
|
524
|
-
return await runClaudeModels(parsed);
|
|
525
|
-
if (parsed.command === "proxy")
|
|
526
|
-
return await runProxy(config);
|
|
527
|
-
if (parsed.command === "inject") {
|
|
528
|
-
let injectConfig = config;
|
|
529
|
-
if (parsed.v2Models) {
|
|
530
|
-
const picked = await pickSpawnModels(config);
|
|
531
|
-
saveSpawnModels(parsed.configPath, picked);
|
|
532
|
-
console.log(`spawn_models saved to ${parsed.configPath}: ${picked.join(", ")}`);
|
|
533
|
-
injectConfig = { ...config, spawn_models: picked };
|
|
534
|
-
}
|
|
535
|
-
await inject(injectConfig, parsed.v2Off, parsed.maxContext);
|
|
536
|
-
}
|
|
537
|
-
else
|
|
538
|
-
return await status(config);
|
|
539
|
-
return 0;
|
|
590
|
+
return await status(config);
|
|
540
591
|
}
|
|
541
592
|
catch (error) {
|
|
542
593
|
console.error(`cpac: ${error instanceof Error ? error.message : String(error)}`);
|
package/dist/targets/claude.js
CHANGED
|
@@ -2,9 +2,9 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { createServer, request as httpRequest, } from "node:http";
|
|
4
4
|
import { request as httpsRequest } from "node:https";
|
|
5
|
-
import { apiBase, catalogModelId, catalogModelRows, fetchCatalog, loadConfig, } from "../config.js";
|
|
5
|
+
import { apiBase, catalogModelId, catalogModelRows, catalogSlugs, fetchCatalog, loadConfig, } from "../config.js";
|
|
6
6
|
import { proxyHeaders, responseHeaders, upstreamUrl } from "../proxy.js";
|
|
7
|
-
import { CPACError, atomicWrite,
|
|
7
|
+
import { CPACError, atomicWrite, objectValue, resolveApiKey, tabWizard, } from "../util.js";
|
|
8
8
|
const CLAUDE_ALIAS_PREFIX = "claude-cpac--";
|
|
9
9
|
// Claude Code accepts CLAUDE_CODE_AUTO_COMPACT_WINDOW in 100K–1M (binary-verified).
|
|
10
10
|
// A single global window cannot be per-model; 350K is opencodex's user-approved
|
|
@@ -235,25 +235,34 @@ export async function runClaudeConfig(config, configPath, options = {}) {
|
|
|
235
235
|
if (shouldPick) {
|
|
236
236
|
const apiKey = await resolveApiKey(config.api_key_env);
|
|
237
237
|
const catalog = await fetchCatalog(config.cpa_url, apiKey);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
document = JSON.parse(new TextDecoder().decode(catalog.bytes));
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
throw new CPACError("CPA catalog response is not valid JSON");
|
|
244
|
-
}
|
|
245
|
-
const slugs = (document.models ?? [])
|
|
246
|
-
.map((model) => (typeof model?.slug === "string" ? model.slug : ""))
|
|
247
|
-
.filter(Boolean);
|
|
238
|
+
const slugs = catalogSlugs(catalog.bytes);
|
|
239
|
+
const auto = "(auto-select from catalog)";
|
|
248
240
|
const slotsToPick = options.pick && options.pick.length > 0
|
|
249
241
|
? options.pick
|
|
250
242
|
: ["sonnet", "haiku", "opus"];
|
|
243
|
+
const current = { ...config.claude_models, ...models };
|
|
244
|
+
const picked = await tabWizard(slotsToPick.map((slot) => {
|
|
245
|
+
const items = slot === "sonnet" ? slugs : [auto, ...slugs];
|
|
246
|
+
const preset = current[slot];
|
|
247
|
+
const selected = preset && slugs.includes(preset)
|
|
248
|
+
? [preset]
|
|
249
|
+
: slot === "sonnet"
|
|
250
|
+
? undefined
|
|
251
|
+
: [auto];
|
|
252
|
+
return {
|
|
253
|
+
id: slot,
|
|
254
|
+
label: slot.toUpperCase(),
|
|
255
|
+
items,
|
|
256
|
+
max: 1,
|
|
257
|
+
selected,
|
|
258
|
+
};
|
|
259
|
+
}));
|
|
251
260
|
for (const slot of slotsToPick) {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
261
|
+
const choice = picked[slot]?.[0];
|
|
262
|
+
if (choice && choice !== auto)
|
|
263
|
+
models[slot] = choice;
|
|
264
|
+
else
|
|
265
|
+
delete models[slot];
|
|
257
266
|
}
|
|
258
267
|
}
|
|
259
268
|
if (!Object.keys(models).length && !shouldPick) {
|
|
@@ -280,7 +289,19 @@ export async function runClaudeConfig(config, configPath, options = {}) {
|
|
|
280
289
|
? { ...raw.claude_models }
|
|
281
290
|
: {};
|
|
282
291
|
Object.assign(existing, models);
|
|
283
|
-
|
|
292
|
+
if (shouldPick) {
|
|
293
|
+
const slots = options.pick && options.pick.length > 0
|
|
294
|
+
? options.pick
|
|
295
|
+
: ["sonnet", "haiku", "opus"];
|
|
296
|
+
for (const slot of slots) {
|
|
297
|
+
if (!models[slot])
|
|
298
|
+
delete existing[slot];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (Object.keys(existing).length === 0)
|
|
302
|
+
delete raw.claude_models;
|
|
303
|
+
else
|
|
304
|
+
raw.claude_models = existing;
|
|
284
305
|
atomicWrite(configPath, Buffer.from(`${JSON.stringify(raw, null, 2)}\n`));
|
|
285
306
|
loadConfig(configPath);
|
|
286
307
|
console.log(`Claude Code tier models updated in ${configPath}:\n${JSON.stringify(raw.claude_models, null, 2)}`);
|
package/dist/targets/codex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { PROVIDER, STATE_FILES, cleanupStateFiles, dropAgent, fetchCatalog, injectedOwnership, liftContextWindows, originalBytes, readState, reorderCatalog, saveState, sha256Hex, stateBytes, } from "../config.js";
|
|
3
|
+
import { PROVIDER, STATE_FILES, cleanupStateFiles, dropAgent, fetchCatalog, injectedOwnership, liftContextWindows, originalBytes, readState, reorderCatalog, stampMultiAgentVersion, saveState, sha256Hex, stateBytes, } from "../config.js";
|
|
4
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*=/;
|
|
@@ -239,6 +239,27 @@ function hasAgentsMaxThreads(content) {
|
|
|
239
239
|
}
|
|
240
240
|
return false;
|
|
241
241
|
}
|
|
242
|
+
export function stripAgentsMaxThreads(content) {
|
|
243
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
244
|
+
const kept = [];
|
|
245
|
+
let table = "root";
|
|
246
|
+
for (const line of lines) {
|
|
247
|
+
if (/^\s*\[/.test(line)) {
|
|
248
|
+
table = /^\s*\[\s*(?:agents|"agents"|'agents')\s*\]\s*(?:#.*)?$/.test(line)
|
|
249
|
+
? "agents"
|
|
250
|
+
: "other";
|
|
251
|
+
kept.push(line);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (table === "agents" && /^\s*max_threads\s*=/.test(line))
|
|
255
|
+
continue;
|
|
256
|
+
if (table === "root" &&
|
|
257
|
+
/^\s*(?:agents|"agents"|'agents')\s*\.\s*max_threads\s*=/.test(line))
|
|
258
|
+
continue;
|
|
259
|
+
kept.push(line);
|
|
260
|
+
}
|
|
261
|
+
return kept.join("\n");
|
|
262
|
+
}
|
|
242
263
|
export function withMultiAgentV2(content, enabled, eol) {
|
|
243
264
|
let body = stripMultiAgentV2(content);
|
|
244
265
|
if (body && !body.endsWith("\n"))
|
|
@@ -247,6 +268,8 @@ export function withMultiAgentV2(content, enabled, eol) {
|
|
|
247
268
|
return eol === "\n" ? output : output.replace(/\n/g, "\r\n");
|
|
248
269
|
}
|
|
249
270
|
export async function inject(config, v2Off = false, maxContext = false) {
|
|
271
|
+
v2Off = v2Off || !!config.v2_off;
|
|
272
|
+
maxContext = maxContext || !!config.max_context;
|
|
250
273
|
const apiKey = process.env[config.api_key_env]?.trim();
|
|
251
274
|
if (!apiKey)
|
|
252
275
|
throw new CPACError(`environment variable ${config.api_key_env} is not set`);
|
|
@@ -288,6 +311,7 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
288
311
|
catalog.bytes = reorderCatalog(catalog.bytes, config.spawn_models);
|
|
289
312
|
if (maxContext)
|
|
290
313
|
catalog.bytes = liftContextWindows(catalog.bytes);
|
|
314
|
+
catalog.bytes = stampMultiAgentVersion(catalog.bytes, !v2Off);
|
|
291
315
|
const stateDirExisted = existsSync(config.state_dir);
|
|
292
316
|
if (!state &&
|
|
293
317
|
STATE_FILES.some((name) => existsSync(join(config.state_dir, name)))) {
|
|
@@ -309,11 +333,14 @@ export async function inject(config, v2Off = false, maxContext = false) {
|
|
|
309
333
|
let injected;
|
|
310
334
|
try {
|
|
311
335
|
injected = buildCodexConfig(original, proxy.port, catalogPath);
|
|
312
|
-
|
|
313
|
-
|
|
336
|
+
let injectedText = new TextDecoder("utf-8").decode(injected);
|
|
337
|
+
const eol = dominantEol(injectedText);
|
|
314
338
|
if (!v2Off && hasAgentsMaxThreads(injectedText)) {
|
|
315
|
-
|
|
339
|
+
const stripped = stripAgentsMaxThreads(injectedText);
|
|
340
|
+
injectedText = eol === "\n" ? stripped : stripped.replace(/\n/g, "\r\n");
|
|
341
|
+
console.warn("stripped [agents] max_threads (codex refuses it with multi_agent_v2)");
|
|
316
342
|
}
|
|
343
|
+
injected = Buffer.from(withMultiAgentV2(injectedText, !v2Off, eol));
|
|
317
344
|
}
|
|
318
345
|
catch (error) {
|
|
319
346
|
if (startedProxy)
|
package/dist/targets/pi.js
CHANGED
|
@@ -1,51 +1,135 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import {
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { join } from "node:path";
|
|
5
4
|
import { dropAgent, recordAgent } from "../config.js";
|
|
6
5
|
import { stopProxyProcess } from "../proxy.js";
|
|
7
|
-
import { CPACError, atomicWrite,
|
|
8
|
-
export
|
|
9
|
-
|
|
6
|
+
import { CPACError, atomicWrite, expandUserPath, objectValue, } from "../util.js";
|
|
7
|
+
export const PI_PACKAGE = "npm:@yhong91/cpac";
|
|
8
|
+
const PI_PACKAGE_NAME = "@yhong91/cpac";
|
|
9
|
+
const LEGACY_EXTENSIONS = ["pi-cpac.ts", "cpac.ts", "pi-cpa.ts"];
|
|
10
|
+
export function piAgentDir() {
|
|
11
|
+
return expandUserPath(process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent"));
|
|
10
12
|
}
|
|
11
|
-
export function
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
export function piSettingsPath() {
|
|
14
|
+
return join(piAgentDir(), "settings.json");
|
|
15
|
+
}
|
|
16
|
+
function leftoverExtensions() {
|
|
17
|
+
const dir = join(piAgentDir(), "extensions");
|
|
18
|
+
return LEGACY_EXTENSIONS.map((name) => join(dir, name)).filter(existsSync);
|
|
19
|
+
}
|
|
20
|
+
function specName(source) {
|
|
21
|
+
const rest = source.startsWith("npm:") ? source.slice(4) : source;
|
|
22
|
+
if (rest.startsWith("@")) {
|
|
23
|
+
const cut = rest.lastIndexOf("@");
|
|
24
|
+
return cut > 0 ? rest.slice(0, cut) : rest;
|
|
25
|
+
}
|
|
26
|
+
const cut = rest.lastIndexOf("@");
|
|
27
|
+
return cut === -1 ? rest : rest.slice(0, cut);
|
|
28
|
+
}
|
|
29
|
+
function packageSource(entry) {
|
|
30
|
+
if (typeof entry === "string")
|
|
31
|
+
return entry.replace(/^-/, "");
|
|
32
|
+
if (objectValue(entry) && typeof entry.source === "string")
|
|
33
|
+
return entry.source;
|
|
34
|
+
}
|
|
35
|
+
function isCpacPackage(entry) {
|
|
36
|
+
const source = packageSource(entry);
|
|
37
|
+
return source !== undefined && specName(source) === PI_PACKAGE_NAME;
|
|
38
|
+
}
|
|
39
|
+
function isEnabledCpacPackage(entry) {
|
|
40
|
+
if (!isCpacPackage(entry))
|
|
41
|
+
return false;
|
|
42
|
+
if (typeof entry === "string")
|
|
43
|
+
return !entry.startsWith("-");
|
|
44
|
+
return !objectValue(entry) || entry.enabled !== false;
|
|
45
|
+
}
|
|
46
|
+
function readSettings(path) {
|
|
47
|
+
let raw;
|
|
48
|
+
try {
|
|
49
|
+
raw = JSON.parse(readFileSync(path, "utf8"));
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error.code === "ENOENT")
|
|
53
|
+
return {};
|
|
54
|
+
throw new CPACError(`cannot parse Pi settings: ${path}`);
|
|
55
|
+
}
|
|
56
|
+
if (!objectValue(raw)) {
|
|
57
|
+
throw new CPACError(`Pi settings must be a JSON object: ${path}`);
|
|
58
|
+
}
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
function settingsPackages(settings, path) {
|
|
62
|
+
if (!Object.hasOwn(settings, "packages"))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!Array.isArray(settings.packages)) {
|
|
65
|
+
throw new CPACError(`Pi settings packages must be an array: ${path}`);
|
|
66
|
+
}
|
|
67
|
+
return settings.packages;
|
|
68
|
+
}
|
|
69
|
+
function writeSettings(path, value) {
|
|
70
|
+
atomicWrite(path, Buffer.from(`${JSON.stringify(value, null, 2)}\n`));
|
|
14
71
|
}
|
|
15
|
-
function
|
|
16
|
-
|
|
72
|
+
function removeLeftovers() {
|
|
73
|
+
const removed = [];
|
|
74
|
+
for (const file of leftoverExtensions()) {
|
|
75
|
+
unlinkSync(file);
|
|
76
|
+
removed.push(file);
|
|
77
|
+
}
|
|
78
|
+
return removed;
|
|
17
79
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
80
|
+
export function isPiExtensionInstalled() {
|
|
81
|
+
if (leftoverExtensions().length > 0)
|
|
82
|
+
return true;
|
|
83
|
+
const path = piSettingsPath();
|
|
84
|
+
if (!existsSync(path))
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
const packages = settingsPackages(readSettings(path), path);
|
|
88
|
+
return packages?.some(isEnabledCpacPackage) ?? false;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
20
93
|
}
|
|
21
94
|
export async function installPiExtension(config) {
|
|
22
|
-
const
|
|
23
|
-
mkdirSync(
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
95
|
+
const path = piSettingsPath();
|
|
96
|
+
mkdirSync(piAgentDir(), { recursive: true, mode: 0o700 });
|
|
97
|
+
const settings = existsSync(path) ? readSettings(path) : {};
|
|
98
|
+
const packages = settingsPackages(settings, path) ?? [];
|
|
99
|
+
if (!packages.some(isEnabledCpacPackage)) {
|
|
100
|
+
settings.packages = [
|
|
101
|
+
...packages.filter((entry) => !isCpacPackage(entry)),
|
|
102
|
+
PI_PACKAGE,
|
|
103
|
+
];
|
|
104
|
+
writeSettings(path, settings);
|
|
105
|
+
}
|
|
106
|
+
recordAgent(config.state_dir, "pi", path);
|
|
107
|
+
for (const file of removeLeftovers()) {
|
|
108
|
+
console.log(`Removed leftover Pi extension: ${file}`);
|
|
36
109
|
}
|
|
37
|
-
console.log(`Installed Pi
|
|
110
|
+
console.log(`Installed Pi package ${PI_PACKAGE} in ${path}`);
|
|
38
111
|
}
|
|
39
112
|
export async function uninstallPiExtension(config) {
|
|
40
|
-
const
|
|
41
|
-
const target = join(dir, "pi-cpac.ts");
|
|
42
|
-
const legacyTargets = [join(dir, "cpac.ts"), join(dir, "pi-cpa.ts")];
|
|
113
|
+
const path = piSettingsPath();
|
|
43
114
|
let removed = false;
|
|
44
|
-
for (const file of
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
115
|
+
for (const file of removeLeftovers()) {
|
|
116
|
+
console.log(`Removed leftover Pi extension: ${file}`);
|
|
117
|
+
removed = true;
|
|
118
|
+
}
|
|
119
|
+
if (existsSync(path)) {
|
|
120
|
+
try {
|
|
121
|
+
const settings = readSettings(path);
|
|
122
|
+
const packages = settingsPackages(settings, path);
|
|
123
|
+
if (packages?.some(isCpacPackage)) {
|
|
124
|
+
settings.packages = packages.filter((entry) => !isCpacPackage(entry));
|
|
125
|
+
writeSettings(path, settings);
|
|
126
|
+
console.log(`Removed Pi package ${PI_PACKAGE} from ${path}`);
|
|
127
|
+
removed = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (!removed)
|
|
132
|
+
throw error;
|
|
49
133
|
}
|
|
50
134
|
}
|
|
51
135
|
if (!removed)
|