@yhong91/cpac 0.1.2 → 0.1.3
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 +18 -0
- package/dist/cpac.js +61 -2
- package/dist/pi-extension.template +83 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -175,12 +175,30 @@ CPAC state:~/.cpac
|
|
|
175
175
|
|
|
176
176
|
为防止误删或覆盖,`state_dir` 不能是文件系统根目录、用户 home、系统临时目录,也不能包含 Codex 配置文件。
|
|
177
177
|
|
|
178
|
+
### Pi
|
|
179
|
+
|
|
180
|
+
安装 CPA provider 扩展到 Pi:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
cpac pi install
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
它将生成 `cpac.ts` 写入 `~/.pi/agent/extensions/`,Pi 启动时自动加载并从 CPA 动态注册模型目录。卸载:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
cpac pi uninstall
|
|
190
|
+
cpac pi status
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Pi 扩展尊重 `PI_CODING_AGENT_DIR` 环境变量。
|
|
194
|
+
|
|
178
195
|
## 环境变量
|
|
179
196
|
|
|
180
197
|
| 变量 | 默认值 | 作用 |
|
|
181
198
|
| --- | --- | --- |
|
|
182
199
|
| `CPA_API_KEY` | 无 | CPA Bearer key;可由 `cpac` 首次引导写入 shell 启动文件 |
|
|
183
200
|
| `CPA_BASE_URL` | `https://cpa.vibetime.cc` | 覆盖内置 CPA 地址 |
|
|
201
|
+
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | 覆盖 Pi agent 目录(影响 `cpac pi install` 写入位置) |
|
|
184
202
|
| `CPAC_CONFIG` | `~/.config/cpac/config.json` | 指定可选 JSON 配置路径 |
|
|
185
203
|
| `CODEX_HOME` | `~/.codex` | Codex home 目录 |
|
|
186
204
|
|
package/dist/cpac.js
CHANGED
|
@@ -886,7 +886,8 @@ export async function status(config) {
|
|
|
886
886
|
}
|
|
887
887
|
}
|
|
888
888
|
console.log(`claude: ${keyConfigured ? "ready" : "not configured"}`);
|
|
889
|
-
|
|
889
|
+
const piInstalled = isPiExtensionInstalled();
|
|
890
|
+
console.log(`pi: ${keyConfigured ? (piInstalled ? "ready" : "extension not installed; run: cpac pi install") : "not configured"}`);
|
|
890
891
|
if (!state)
|
|
891
892
|
return 1;
|
|
892
893
|
const proxy = stateProxy(state);
|
|
@@ -952,7 +953,7 @@ export function saveApiKeyExport(profile, name, apiKey) {
|
|
|
952
953
|
atomicWrite(profile, Buffer.from(content), mode);
|
|
953
954
|
}
|
|
954
955
|
async function guide(config) {
|
|
955
|
-
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 claude [args...] Launch Claude Code through CPA\n cpac inject Inject CPA into Codex and start its loopback proxy\n cpac proxy Run the injected loopback proxy in the foreground\n cpac status Show agent support status (Codex, Claude, Pi)\n cpac restore Restore the original Codex config\n cpac --help Show command usage`);
|
|
956
|
+
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 claude [args...] Launch Claude Code through CPA\n cpac inject Inject CPA into Codex and start its loopback proxy\n cpac proxy Run the injected loopback proxy in the foreground\n cpac status Show agent support status (Codex, Claude, Pi)\n cpac restore Restore the original Codex config\n cpac pi install Install CPA provider extension for Pi\n cpac pi uninstall Remove the Pi CPA provider extension\n cpac pi status Show Pi extension install status\n cpac --help Show command usage`);
|
|
956
957
|
if (process.env[config.api_key_env]?.trim())
|
|
957
958
|
return 0;
|
|
958
959
|
const apiKey = await promptSecret(config.api_key_env);
|
|
@@ -1016,18 +1017,74 @@ export async function runProxy(config) {
|
|
|
1016
1017
|
await new Promise((resolveClose) => proxy.server.once("close", resolveClose));
|
|
1017
1018
|
return 0;
|
|
1018
1019
|
}
|
|
1020
|
+
function piExtensionsDir() {
|
|
1021
|
+
return join(expandUserPath(process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent")), "extensions");
|
|
1022
|
+
}
|
|
1023
|
+
export function isPiExtensionInstalled() {
|
|
1024
|
+
return existsSync(join(piExtensionsDir(), "cpac.ts"));
|
|
1025
|
+
}
|
|
1026
|
+
function piTemplatePath() {
|
|
1027
|
+
return join(dirname(fileURLToPath(import.meta.url)), "pi-extension.template");
|
|
1028
|
+
}
|
|
1029
|
+
function piExtensionContent(cpaUrl) {
|
|
1030
|
+
return readFileSync(piTemplatePath(), "utf8").replace("__CPA_URL__", cpaUrl);
|
|
1031
|
+
}
|
|
1032
|
+
export async function installPiExtension(config) {
|
|
1033
|
+
const dir = piExtensionsDir();
|
|
1034
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
1035
|
+
const target = join(dir, "cpac.ts");
|
|
1036
|
+
atomicWrite(target, Buffer.from(piExtensionContent(config.cpa_url)), 0o644);
|
|
1037
|
+
console.log(`Installed Pi extension: ${target}`);
|
|
1038
|
+
}
|
|
1039
|
+
export async function uninstallPiExtension() {
|
|
1040
|
+
const target = join(piExtensionsDir(), "cpac.ts");
|
|
1041
|
+
if (!existsSync(target))
|
|
1042
|
+
throw new CPACError("Pi extension is not installed");
|
|
1043
|
+
unlinkSync(target);
|
|
1044
|
+
console.log(`Removed Pi extension: ${target}`);
|
|
1045
|
+
}
|
|
1046
|
+
export async function runPi(config, action) {
|
|
1047
|
+
if (action === "install") {
|
|
1048
|
+
await installPiExtension(config);
|
|
1049
|
+
return 0;
|
|
1050
|
+
}
|
|
1051
|
+
if (action === "uninstall") {
|
|
1052
|
+
await uninstallPiExtension();
|
|
1053
|
+
return 0;
|
|
1054
|
+
}
|
|
1055
|
+
if (action === "status") {
|
|
1056
|
+
const installed = isPiExtensionInstalled();
|
|
1057
|
+
console.log(`Pi extension: ${installed ? "installed" : "not installed"}`);
|
|
1058
|
+
return installed ? 0 : 1;
|
|
1059
|
+
}
|
|
1060
|
+
throw new CPACError(`unknown pi action: ${action}; use install, uninstall, or status`);
|
|
1061
|
+
}
|
|
1019
1062
|
function usage() {
|
|
1020
1063
|
return [
|
|
1021
1064
|
"Usage: cpac",
|
|
1022
1065
|
" cpac <inject|status|restore> [--config PATH]",
|
|
1023
1066
|
" cpac proxy [--config PATH]",
|
|
1024
1067
|
" cpac claude [--config PATH] [--] [claude args...]",
|
|
1068
|
+
" cpac pi <install|uninstall|status> [--config PATH]",
|
|
1025
1069
|
].join("\n");
|
|
1026
1070
|
}
|
|
1027
1071
|
function parseArgs(args) {
|
|
1028
1072
|
if (args.length === 0) {
|
|
1029
1073
|
return { command: "guide", configPath: defaultConfigPath() };
|
|
1030
1074
|
}
|
|
1075
|
+
if (args[0] === "pi") {
|
|
1076
|
+
let configPath = defaultConfigPath();
|
|
1077
|
+
let index = 1;
|
|
1078
|
+
if (args[index] === "--config") {
|
|
1079
|
+
const value = args[++index];
|
|
1080
|
+
if (!value)
|
|
1081
|
+
throw new CPACError("--config requires a path");
|
|
1082
|
+
configPath = resolve(expandUserPath(value));
|
|
1083
|
+
index += 1;
|
|
1084
|
+
}
|
|
1085
|
+
const action = args[index] || "status";
|
|
1086
|
+
return { command: "pi", configPath, action };
|
|
1087
|
+
}
|
|
1031
1088
|
if (args[0] === "claude") {
|
|
1032
1089
|
let configPath = defaultConfigPath();
|
|
1033
1090
|
let index = 1;
|
|
@@ -1088,6 +1145,8 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
1088
1145
|
return await runClaude(config, parsed.args);
|
|
1089
1146
|
if (parsed.command === "proxy")
|
|
1090
1147
|
return await runProxy(config);
|
|
1148
|
+
if (parsed.command === "pi")
|
|
1149
|
+
return await runPi(config, parsed.action);
|
|
1091
1150
|
if (parsed.command === "inject")
|
|
1092
1151
|
await inject(config);
|
|
1093
1152
|
else if (parsed.command === "restore")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// CPAC Pi extension: register CPA models as Pi providers
|
|
2
|
+
// Installed by: cpac pi install
|
|
3
|
+
|
|
4
|
+
const CPA = "__CPA_URL__";
|
|
5
|
+
const BUILTIN = new Set(["openai", "github-copilot", "xai", "deepseek", "anthropic", "google"]);
|
|
6
|
+
const VENDOR_PREFIXES = [
|
|
7
|
+
["gpt-", "openai"], ["o1-", "openai"], ["o3-", "openai"], ["o4-", "openai"],
|
|
8
|
+
["claude-", "anthropic"], ["gemini-", "google"], ["grok-", "xai"],
|
|
9
|
+
["deepseek-", "deepseek"], ["glm-", "zhipu"], ["kimi-", "moonshot"],
|
|
10
|
+
["mimo-", "xiaomi"], ["doubao-", "volcengine"], ["ark-", "volcengine"],
|
|
11
|
+
["minimax-", "minimax"], ["step-", "stepfun"], ["qwen-", "alibaba"],
|
|
12
|
+
["hunyuan-", "tencent"],
|
|
13
|
+
];
|
|
14
|
+
const MAX_TOKENS = {
|
|
15
|
+
"gpt-5.6-sol": 128000, "gpt-5.6-terra": 128000, "gpt-5.6-luna": 128000,
|
|
16
|
+
"claude-opus-4-6-thinking": 128000, "claude-sonnet-4-6": 64000,
|
|
17
|
+
"gemini-3.6-flash": 65536, "gemini-3.6-flash-high": 65536,
|
|
18
|
+
"deepseek-v4-pro": 128000, "deepseek-v4-flash": 128000,
|
|
19
|
+
"glm-5.2": 128000, "kimi-k2.7-code": 128000, "minimax-m3": 128000,
|
|
20
|
+
"mimo-v2.5": 131072, "mimo-v2.5-pro": 131072, "grok-4.5": 128000,
|
|
21
|
+
"doubao-seed-2.0-lite": 128000, "doubao-seed-2.1-turbo": 128000,
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_MAX_TOKENS = 65536;
|
|
24
|
+
|
|
25
|
+
function vendorFor(slug) {
|
|
26
|
+
for (const [prefix, vendor] of VENDOR_PREFIXES) {
|
|
27
|
+
if (slug.startsWith(prefix)) return vendor;
|
|
28
|
+
}
|
|
29
|
+
return "misc";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function groupName(vendor) {
|
|
33
|
+
return BUILTIN.has(vendor) ? vendor + "-cpa" : vendor;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function intField(value) {
|
|
37
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
38
|
+
? Math.floor(value) : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default async function (pi) {
|
|
42
|
+
const apiKey = (process.env.CPA_API_KEY || "").trim();
|
|
43
|
+
if (!apiKey) return;
|
|
44
|
+
let payload;
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch(CPA + "/v1/models?client_version=1", {
|
|
47
|
+
headers: { Authorization: "Bearer " + apiKey },
|
|
48
|
+
signal: AbortSignal.timeout(10000),
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok) return;
|
|
51
|
+
payload = await res.json();
|
|
52
|
+
} catch { return; }
|
|
53
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.models)) return;
|
|
54
|
+
const rows = payload.models.filter(function (m) {
|
|
55
|
+
return m && typeof m === "object" && typeof m.slug === "string";
|
|
56
|
+
});
|
|
57
|
+
const groups = new Map();
|
|
58
|
+
for (const m of rows) {
|
|
59
|
+
const name = groupName(vendorFor(m.slug));
|
|
60
|
+
if (!groups.has(name)) groups.set(name, []);
|
|
61
|
+
groups.get(name).push(m);
|
|
62
|
+
}
|
|
63
|
+
for (const [provName, models] of groups) {
|
|
64
|
+
const vendor = provName.replace(/-cpa$/, "");
|
|
65
|
+
pi.registerProvider(provName, {
|
|
66
|
+
name: vendor + " (cpa)",
|
|
67
|
+
baseUrl: CPA + "/v1",
|
|
68
|
+
apiKey: apiKey,
|
|
69
|
+
api: "openai-responses",
|
|
70
|
+
models: models.map(function (m) {
|
|
71
|
+
return {
|
|
72
|
+
id: m.slug,
|
|
73
|
+
name: typeof m.display_name === "string" ? m.display_name : m.slug,
|
|
74
|
+
reasoning: Array.isArray(m.supported_reasoning_levels) && m.supported_reasoning_levels.length > 0,
|
|
75
|
+
input: ["text", "image"],
|
|
76
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
77
|
+
contextWindow: intField(m.context_window) || 200000,
|
|
78
|
+
maxTokens: MAX_TOKENS[m.slug] || DEFAULT_MAX_TOKENS,
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yhong91/cpac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/cpac.js",
|
|
11
|
+
"dist/pi-extension.template",
|
|
11
12
|
"cpac.example.json",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
@@ -28,10 +29,10 @@
|
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"clean": "node -e \"for (const d of ['dist','dist-test']) require('fs').rmSync(d,{recursive:true,force:true})\"",
|
|
31
|
-
"build": "npm run clean --if-present && tsc -p tsconfig.build.json && node -e \"const fs=require('fs');const p='dist/cpac.js';const s=fs.readFileSync(p,'utf8');if(!s.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+s);try{fs.chmodSync(p,0o755)}catch{}\"",
|
|
32
|
+
"build": "npm run clean --if-present && tsc -p tsconfig.build.json && cp src/pi-extension.template dist/ && node -e \"const fs=require('fs');const p='dist/cpac.js';const s=fs.readFileSync(p,'utf8');if(!s.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+s);try{fs.chmodSync(p,0o755)}catch{}\"",
|
|
32
33
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
33
34
|
"check:pi": "tsc -p tsconfig.pi.json --noEmit",
|
|
34
|
-
"test": "npm run clean && tsc -p tsconfig.test.json && node --test --test-reporter=spec dist-test/cpac.test.js",
|
|
35
|
+
"test": "npm run clean && tsc -p tsconfig.test.json && cp src/pi-extension.template dist-test/src/ && node --test --test-reporter=spec dist-test/cpac.test.js",
|
|
35
36
|
"pack:check": "npm pack --dry-run"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|