@yhong91/cpac 0.2.0 → 0.2.2
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 +12 -1
- package/dist/cpac.js +37 -2
- package/dist/ping.js +67 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -363,11 +363,22 @@ cpac restore cline
|
|
|
363
363
|
|
|
364
364
|
配置目录尊重 `CLINE_DATA_DIR`(否则 `~/.cline/data`,CLI 与 Desktop 共享)。首次写入前若 `providers.json` 已存在,会在 `state_dir/cline/providers.json` 保留原始快照。
|
|
365
365
|
|
|
366
|
+
### 手动打开 5h 窗口
|
|
367
|
+
|
|
368
|
+
本机手动发一条最小请求,用来激活当前 5h 额度。不定时,也不查配额。定时仍在 VPS 的 `scripts/quota-heartbeat.py`。
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
cpac ping codex # 目录里匹配 gpt-*-luna 的一条
|
|
372
|
+
cpac ping agy # gemini-3.8-flash 和 claude-sonnet-4-6,各一条
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
用 `CPA_API_KEY`。日志只打模型、HTTP 状态和不含 key 的 `x-cpa-*` trace。CPA 按账号轮询,一次只会落在一个账号上。
|
|
376
|
+
|
|
366
377
|
## 环境变量
|
|
367
378
|
|
|
368
379
|
| 变量 | 默认值 | 作用 |
|
|
369
380
|
| --- | --- | --- |
|
|
370
|
-
| `CPA_API_KEY` | 无 | CPA Bearer key;可由 `cpac` 首次引导写入 shell
|
|
381
|
+
| `CPA_API_KEY` | 无 | CPA Bearer key;可由 `cpac` 首次引导写入 shell 启动文件;`cpac ping` 也用它 |
|
|
371
382
|
| `CPA_BASE_URL` | `http://124.223.178.52:8317` | 覆盖内置 CPA 地址 |
|
|
372
383
|
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | 覆盖 Pi agent 目录(影响 `models.json` 写入位置) |
|
|
373
384
|
| `KIMI_CODE_HOME` | `~/.kimi-code` | 覆盖 Kimi Code 目录(影响 `cpac install kimi` 写入位置) |
|
package/dist/cpac.js
CHANGED
|
@@ -13,6 +13,7 @@ import { isZedConfigInstalled } from "./targets/zed.js";
|
|
|
13
13
|
import { isHermesConfigInstalled } from "./targets/hermes.js";
|
|
14
14
|
import { CLINE_PROVIDER_ID, isClineConfigInstalled } from "./targets/cline.js";
|
|
15
15
|
import { defaultConfigPath, loadConfig, originalBytes, pickSpawnModels, readState, saveSpawnModels, stateProxy, catalogMaxContextLive, catalogModelId, catalogModelRows, catalogSlugs, fetchCatalog, } from "./config.js";
|
|
16
|
+
import { runPing } from "./ping.js";
|
|
16
17
|
import { proxyIsHealthy, runProxyChild } from "./proxy.js";
|
|
17
18
|
import { CPACError, expandUserPath, promptSecret, saveApiKeyExport, shellProfile, } from "./util.js";
|
|
18
19
|
export { PROVIDER, loadConfig, saveSpawnModels, saveCodexSetup, liftContextWindows, reorderCatalog, sanitizeCatalogModalities, stampMultiAgentVersion, catalogMaxContextLive, readState, stateProxy, } from "./config.js";
|
|
@@ -102,7 +103,13 @@ async function listModels(config, json) {
|
|
|
102
103
|
console.log(slug);
|
|
103
104
|
return 0;
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
+
let document;
|
|
107
|
+
try {
|
|
108
|
+
document = JSON.parse(new TextDecoder().decode(catalog.bytes));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
throw new CPACError("CPA catalog is not valid JSON");
|
|
112
|
+
}
|
|
106
113
|
const models = (catalogModelRows(document) ?? []).flatMap((row) => {
|
|
107
114
|
const slug = catalogModelId(row);
|
|
108
115
|
if (!slug)
|
|
@@ -127,7 +134,7 @@ async function guide(config) {
|
|
|
127
134
|
cpac detect Show supported targets and install status
|
|
128
135
|
cpac install [codex|pi|kimi|grok|zed|hermes|cline...] Install CPA into detected targets, or named agents
|
|
129
136
|
cpac sync Refresh installed Codex/Pi/Kimi/Grok/Zed/Hermes/Cline injections from CPA
|
|
130
|
-
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 models List remote CPA catalog models\n cpac --help Show command usage`);
|
|
137
|
+
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 models List remote CPA catalog models\n cpac ping codex|agy Send one request to open that 5h window\n cpac --help Show command usage`);
|
|
131
138
|
if (process.env[config.api_key_env]?.trim())
|
|
132
139
|
return 0;
|
|
133
140
|
const apiKey = await promptSecret(config.api_key_env);
|
|
@@ -164,6 +171,7 @@ function usage() {
|
|
|
164
171
|
cmd("cpac restore <agent...>", "detach injected agents"),
|
|
165
172
|
cmd("cpac uninstall", "detach all agents, then remove the cpac package"),
|
|
166
173
|
cmd("cpac upgrade", "update cpac from npm and sync installed agents"),
|
|
174
|
+
cmd("cpac ping <codex|agy>", "send one request to open that 5h window"),
|
|
167
175
|
"",
|
|
168
176
|
"Positionals:",
|
|
169
177
|
helpRow("agent", "codex, kimi, grok, pi, zed, hermes, cline, or claude (claude is launch-only)", "", 18),
|
|
@@ -227,6 +235,31 @@ function parseArgs(args) {
|
|
|
227
235
|
if (args.length === 0) {
|
|
228
236
|
return { command: "guide", configPath: defaultConfigPath() };
|
|
229
237
|
}
|
|
238
|
+
if (args[0] === "ping") {
|
|
239
|
+
let configPath = defaultConfigPath();
|
|
240
|
+
let target = "";
|
|
241
|
+
for (let index = 1; index < args.length; index++) {
|
|
242
|
+
const arg = args[index];
|
|
243
|
+
if (arg === "-h" || arg === "--help") {
|
|
244
|
+
console.log(usage());
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
if (arg === "--config") {
|
|
248
|
+
const value = args[++index];
|
|
249
|
+
if (!value)
|
|
250
|
+
throw new CPACError("--config requires a path");
|
|
251
|
+
configPath = resolve(expandUserPath(value));
|
|
252
|
+
}
|
|
253
|
+
else if (!arg.startsWith("-") && !target)
|
|
254
|
+
target = arg;
|
|
255
|
+
else
|
|
256
|
+
throw new CPACError(`unknown option: ${arg}`);
|
|
257
|
+
}
|
|
258
|
+
if (target !== "codex" && target !== "agy") {
|
|
259
|
+
throw new CPACError("cpac ping requires codex or agy");
|
|
260
|
+
}
|
|
261
|
+
return { command: "ping", configPath, target };
|
|
262
|
+
}
|
|
230
263
|
if (args[0] === "detect" ||
|
|
231
264
|
args[0] === "install" ||
|
|
232
265
|
args[0] === "sync" ||
|
|
@@ -663,6 +696,8 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
663
696
|
}
|
|
664
697
|
if (parsed.command === "upgrade")
|
|
665
698
|
return await runUpgrade(config, parsed.check);
|
|
699
|
+
if (parsed.command === "ping")
|
|
700
|
+
return await runPing(config, parsed.target);
|
|
666
701
|
if (parsed.command === "claude")
|
|
667
702
|
return await runClaude(config, parsed.args);
|
|
668
703
|
if (parsed.command === "claude-config") {
|
package/dist/ping.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { apiBase, catalogSlugs, fetchCatalog } from "./config.js";
|
|
2
|
+
import { CPACError } from "./util.js";
|
|
3
|
+
const LUNA = /^gpt-.+-luna$/;
|
|
4
|
+
export const PING_MODELS = {
|
|
5
|
+
agy: ["gemini-3.8-flash", "claude-sonnet-4-6"],
|
|
6
|
+
};
|
|
7
|
+
export function matchLuna(slugs) {
|
|
8
|
+
const model = slugs.filter((slug) => LUNA.test(slug)).sort().at(-1);
|
|
9
|
+
if (!model)
|
|
10
|
+
throw new CPACError("no catalog model matches gpt-*-luna");
|
|
11
|
+
return model;
|
|
12
|
+
}
|
|
13
|
+
const EMAIL_RE = /[A-Za-z0-9._%+\-]+@/g;
|
|
14
|
+
function redact(value) {
|
|
15
|
+
return String(value).replace(EMAIL_RE, "*@");
|
|
16
|
+
}
|
|
17
|
+
export async function runPing(config, target, deps = {}) {
|
|
18
|
+
const env = deps.env ?? process.env;
|
|
19
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
20
|
+
const fetchImpl = deps.fetch ?? fetch;
|
|
21
|
+
const apiKey = env[config.api_key_env]?.trim();
|
|
22
|
+
if (!apiKey)
|
|
23
|
+
throw new CPACError(`environment variable ${config.api_key_env} is not set`);
|
|
24
|
+
const url = `${apiBase(config.cpa_url)}/chat/completions`;
|
|
25
|
+
const models = target === "codex"
|
|
26
|
+
? [matchLuna(catalogSlugs((await fetchCatalog(config.cpa_url, apiKey)).bytes))]
|
|
27
|
+
: PING_MODELS[target];
|
|
28
|
+
let failed = 0;
|
|
29
|
+
for (const model of models) {
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await fetchImpl(url, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: "application/json",
|
|
36
|
+
Authorization: `Bearer ${apiKey}`,
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
model,
|
|
41
|
+
messages: [{ role: "user", content: "hi" }],
|
|
42
|
+
max_tokens: 16,
|
|
43
|
+
stream: false,
|
|
44
|
+
}),
|
|
45
|
+
redirect: "error",
|
|
46
|
+
signal: AbortSignal.timeout(90_000),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
log(`${model} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
failed += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const text = await response.text();
|
|
55
|
+
const trace = [];
|
|
56
|
+
response.headers.forEach((value, key) => {
|
|
57
|
+
const name = key.toLowerCase();
|
|
58
|
+
if (name.startsWith("x-cpa-") && !name.includes("key"))
|
|
59
|
+
trace.push(`${name}=${redact(value)}`);
|
|
60
|
+
});
|
|
61
|
+
const detail = response.status === 200 ? "" : ` ${redact(text).replaceAll(apiKey, "***").slice(0, 180)}`;
|
|
62
|
+
log(`${model} HTTP ${response.status}${trace.length ? ` ${trace.join(" ")}` : ""}${detail}`);
|
|
63
|
+
if (response.status !== 200)
|
|
64
|
+
failed += 1;
|
|
65
|
+
}
|
|
66
|
+
return failed ? 1 : 0;
|
|
67
|
+
}
|