trinity-config 1.2.4 → 1.3.0
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 +22 -8
- package/dist/chunk-5WZW5Y4W.js +45 -0
- package/dist/index.js +566 -41
- package/dist/{merge-openclaw-config-KMG3FYK6.js → merge-openclaw-config-F77TZKPG.js} +3 -12
- package/dist/{merge-opencode-config-XGIZYPOW.js → merge-opencode-config-JTWAGCET.js} +3 -6
- package/dist/{merge-zcode-config-PTPY6RXN.js → merge-zcode-config-LGTBU4UC.js} +2 -6
- package/package.json +4 -2
- package/dist/chunk-QS4QYSMM.js +0 -26
package/README.md
CHANGED
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
# trinity-config
|
|
2
2
|
|
|
3
|
-
一键接入 Trinity:Claude Code / Codex / OpenCode / OpenClaw / ZCode
|
|
3
|
+
一键接入 Trinity:Claude Code / Codex / OpenCode / OpenClaw / ZCode / **CC Switch**。
|
|
4
4
|
|
|
5
5
|
协议按 `GET /v1/models/{code}` 的 `capabilities.text.interfaces` **自动择优**(Anthropic → Responses → Completions),再与工具能力求交。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
写入客户端的模型列表来自该 API Key 的 `GET /v1/models?modality=text`(Call Allowlist 内生文/多模态;不含生图/生视频,也不含无权限模型)。
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx -y trinity-config --type zcode -k xh-你的key --model gpt-5.4
|
|
11
|
+
|
|
12
|
+
# CC Switch:按 App 槽位生成 Deep Link(非 Universal)
|
|
13
|
+
npx -y trinity-config --type ccswitch --app claude -k xh-你的key
|
|
14
|
+
npx -y trinity-config --type ccswitch --app codex -k xh-你的key --model gpt-5.4
|
|
15
|
+
npx -y trinity-config --type ccswitch --app claude,codex -k xh-你的key --print-only
|
|
11
16
|
```
|
|
12
17
|
|
|
13
|
-
| type |
|
|
18
|
+
| type | 配置方式 |
|
|
14
19
|
| --- | --- |
|
|
15
|
-
| claude | `~/.claude/settings.json
|
|
16
|
-
| codex | `~/.codex/config.toml` |
|
|
17
|
-
| opencode | `~/.config/opencode/opencode.json
|
|
18
|
-
| openclaw | `~/.openclaw/openclaw.json
|
|
19
|
-
| zcode | `~/.zcode/v2/config.json
|
|
20
|
+
| claude | `~/.claude/settings.json`(直连) |
|
|
21
|
+
| codex | `~/.codex/config.toml` + `auth.json`(直连) |
|
|
22
|
+
| opencode | `~/.config/opencode/opencode.json`(直连) |
|
|
23
|
+
| openclaw | `~/.openclaw/openclaw.json`(直连) |
|
|
24
|
+
| zcode | `~/.zcode/v2/config.json`(直连) |
|
|
25
|
+
| ccswitch | `ccswitch://v1/import` Deep Link;落盘 `~/.cc-switch/trinity-imports/` |
|
|
26
|
+
|
|
27
|
+
### CC Switch 注意
|
|
28
|
+
|
|
29
|
+
- **必填** `--app`:`claude` / `codex` / `opencode` / `openclaw`(不支持 `gemini` / Universal)
|
|
30
|
+
- 与直连 `--type claude` 等**不要混用**同一工具,以免互相覆盖
|
|
31
|
+
- Claude:Anthropic Messages,Endpoint **不带** `/v1`
|
|
32
|
+
- Codex:`wire_api=responses`,Endpoint **带** `/v1`,关闭 Needs Local Routing
|
|
33
|
+
- `--print-only`:只生成链接,不唤起应用
|
|
20
34
|
|
|
21
35
|
Anthropic Base **不带** `/v1`;Responses / Completions **带** `/v1`。
|
|
22
36
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/text-models.ts
|
|
2
|
+
function isLikelyNonTextModel(id) {
|
|
3
|
+
const lower = id.toLowerCase();
|
|
4
|
+
return lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling") || lower.includes("og-image") || lower.includes("midjourney") || lower.includes("flux");
|
|
5
|
+
}
|
|
6
|
+
function filterTextModelIds(primaryModel, modelIds, limit = 80) {
|
|
7
|
+
const ids = [];
|
|
8
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9
|
+
const push = (raw, enforceTextHeuristic) => {
|
|
10
|
+
const trimmed = raw?.trim();
|
|
11
|
+
if (!trimmed || seen.has(trimmed)) return;
|
|
12
|
+
if (enforceTextHeuristic && isLikelyNonTextModel(trimmed)) return;
|
|
13
|
+
seen.add(trimmed);
|
|
14
|
+
ids.push(trimmed);
|
|
15
|
+
};
|
|
16
|
+
if (modelIds && modelIds.length > 0) {
|
|
17
|
+
const allow = new Set(
|
|
18
|
+
modelIds.map((id) => id?.trim()).filter((id) => Boolean(id))
|
|
19
|
+
);
|
|
20
|
+
if (allow.has(primaryModel.trim())) {
|
|
21
|
+
push(primaryModel, false);
|
|
22
|
+
}
|
|
23
|
+
for (const id of modelIds) {
|
|
24
|
+
if (ids.length >= limit) break;
|
|
25
|
+
push(id, true);
|
|
26
|
+
}
|
|
27
|
+
return ids;
|
|
28
|
+
}
|
|
29
|
+
push(primaryModel, true);
|
|
30
|
+
return ids;
|
|
31
|
+
}
|
|
32
|
+
function pickAllowedDefaultModel(preferred, allowlist, candidates) {
|
|
33
|
+
const allow = new Set(allowlist.map((id) => id.trim()).filter(Boolean));
|
|
34
|
+
if (allow.has(preferred.trim())) return preferred.trim();
|
|
35
|
+
for (const c of candidates) {
|
|
36
|
+
const t = c?.trim();
|
|
37
|
+
if (t && allow.has(t)) return t;
|
|
38
|
+
}
|
|
39
|
+
return allowlist.map((id) => id.trim()).find(Boolean);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
filterTextModelIds,
|
|
44
|
+
pickAllowedDefaultModel
|
|
45
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
pickAllowedDefaultModel
|
|
4
|
+
} from "./chunk-5WZW5Y4W.js";
|
|
5
|
+
|
|
2
6
|
// src/index.ts
|
|
3
7
|
import * as p from "@clack/prompts";
|
|
4
8
|
import pc from "picocolors";
|
|
5
9
|
|
|
6
10
|
// src/constants.ts
|
|
7
11
|
var PACKAGE_NAME = "trinity-config";
|
|
8
|
-
var PACKAGE_VERSION = "1.
|
|
12
|
+
var PACKAGE_VERSION = "1.3.0";
|
|
9
13
|
var TRINITY_BASE_URL = "https://api.trinitydesk.ai";
|
|
10
14
|
var DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
11
15
|
var DEFAULT_CLAUDE_FAST_MODEL = "claude-sonnet-4-6";
|
|
@@ -16,7 +20,23 @@ var DEFAULT_ZCODE_MODEL = "gpt-5.4";
|
|
|
16
20
|
var VERIFY_TIMEOUT_MS = 15e3;
|
|
17
21
|
|
|
18
22
|
// src/model.ts
|
|
19
|
-
|
|
23
|
+
var CC_SWITCH_APPS = [
|
|
24
|
+
"claude",
|
|
25
|
+
"codex",
|
|
26
|
+
"gemini",
|
|
27
|
+
"opencode",
|
|
28
|
+
"openclaw"
|
|
29
|
+
];
|
|
30
|
+
var CC_SWITCH_SUPPORTED_APPS = [
|
|
31
|
+
"claude",
|
|
32
|
+
"codex",
|
|
33
|
+
"opencode",
|
|
34
|
+
"openclaw"
|
|
35
|
+
];
|
|
36
|
+
function defaultModelForType(type, app) {
|
|
37
|
+
if (type === "ccswitch") {
|
|
38
|
+
return defaultModelForCcSwitchApp(app ?? "claude");
|
|
39
|
+
}
|
|
20
40
|
switch (type) {
|
|
21
41
|
case "claude":
|
|
22
42
|
return DEFAULT_CLAUDE_MODEL;
|
|
@@ -30,17 +50,77 @@ function defaultModelForType(type) {
|
|
|
30
50
|
return DEFAULT_ZCODE_MODEL;
|
|
31
51
|
}
|
|
32
52
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
53
|
+
function defaultModelForCcSwitchApp(app) {
|
|
54
|
+
switch (app) {
|
|
55
|
+
case "claude":
|
|
56
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
57
|
+
case "codex":
|
|
58
|
+
case "opencode":
|
|
59
|
+
case "openclaw":
|
|
60
|
+
return DEFAULT_CODEX_MODEL;
|
|
61
|
+
case "gemini":
|
|
62
|
+
return DEFAULT_CODEX_MODEL;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function resolveModel(type, model, app) {
|
|
66
|
+
return model?.trim() || defaultModelForType(type, app);
|
|
35
67
|
}
|
|
36
68
|
function parseToolType(raw) {
|
|
37
69
|
const value = raw.trim().toLowerCase();
|
|
38
|
-
if (value === "claude" || value === "codex" || value === "opencode" || value === "openclaw" || value === "zcode") {
|
|
70
|
+
if (value === "claude" || value === "codex" || value === "opencode" || value === "openclaw" || value === "zcode" || value === "ccswitch") {
|
|
39
71
|
return value;
|
|
40
72
|
}
|
|
41
|
-
throw new Error(
|
|
73
|
+
throw new Error(
|
|
74
|
+
"--type \u987B\u4E3A claude\u3001codex\u3001opencode\u3001openclaw\u3001zcode \u6216 ccswitch"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function parseCcSwitchApps(raw) {
|
|
78
|
+
const parts = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
79
|
+
if (parts.length === 0) {
|
|
80
|
+
throw new Error("--app \u4E0D\u80FD\u4E3A\u7A7A\uFF08\u4F8B: --app claude \u6216 --app claude,codex\uFF09");
|
|
81
|
+
}
|
|
82
|
+
const out = [];
|
|
83
|
+
const seen = /* @__PURE__ */ new Set();
|
|
84
|
+
for (const p2 of parts) {
|
|
85
|
+
if (!CC_SWITCH_APPS.includes(p2)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`--app \u65E0\u6548: ${p2}\uFF08\u652F\u6301: ${CC_SWITCH_SUPPORTED_APPS.join(" / ")}\uFF09`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
assertCcSwitchAppSupported(p2);
|
|
91
|
+
if (seen.has(p2)) continue;
|
|
92
|
+
seen.add(p2);
|
|
93
|
+
out.push(p2);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
42
96
|
}
|
|
43
|
-
function
|
|
97
|
+
function protocolToolForCcSwitchApp(app) {
|
|
98
|
+
switch (app) {
|
|
99
|
+
case "claude":
|
|
100
|
+
return "claude";
|
|
101
|
+
case "codex":
|
|
102
|
+
return "codex";
|
|
103
|
+
case "opencode":
|
|
104
|
+
return "opencode";
|
|
105
|
+
case "openclaw":
|
|
106
|
+
return "openclaw";
|
|
107
|
+
case "gemini":
|
|
108
|
+
throw new Error(
|
|
109
|
+
"CC Switch --app gemini\uFF1ATrinity \u5BF9\u5916\u672A\u63D0\u4F9B Gemini \u539F\u751F\u534F\u8BAE"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function assertCcSwitchAppSupported(app) {
|
|
114
|
+
if (app === "gemini") {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"CC Switch --app gemini\uFF1ATrinity \u5BF9\u5916\u672A\u63D0\u4F9B Gemini \u539F\u751F\u534F\u8BAE\uFF0C\u8BF7\u6539\u7528 --app claude / codex / opencode / openclaw"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function toolLabel(type, app) {
|
|
121
|
+
if (type === "ccswitch") {
|
|
122
|
+
return app ? `CC Switch \xB7 ${app}` : "CC Switch";
|
|
123
|
+
}
|
|
44
124
|
switch (type) {
|
|
45
125
|
case "claude":
|
|
46
126
|
return "Claude Code";
|
|
@@ -66,6 +146,8 @@ function launchCommand(type) {
|
|
|
66
146
|
return "openclaw dashboard";
|
|
67
147
|
case "zcode":
|
|
68
148
|
return "ZCode \u684C\u9762\u5E94\u7528";
|
|
149
|
+
case "ccswitch":
|
|
150
|
+
return "CC Switch \u684C\u9762\u5E94\u7528";
|
|
69
151
|
}
|
|
70
152
|
}
|
|
71
153
|
|
|
@@ -75,27 +157,33 @@ function printHelp() {
|
|
|
75
157
|
trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF / Agent / \u684C\u9762\u7F16\u7A0B\u52A9\u624B
|
|
76
158
|
|
|
77
159
|
\u7528\u6CD5:
|
|
78
|
-
npx -y trinity-config --type <claude|codex|opencode|openclaw|zcode> [\u9009\u9879]
|
|
160
|
+
npx -y trinity-config --type <claude|codex|opencode|openclaw|zcode|ccswitch> [\u9009\u9879]
|
|
79
161
|
|
|
80
162
|
\u9009\u9879:
|
|
81
163
|
-t, --type <\u2026> \u8981\u914D\u7F6E\u7684\u5DE5\u5177\uFF08\u975E\u4EA4\u4E92\u987B\u6307\u5B9A\uFF09
|
|
82
164
|
-k, --api-key <key> Trinity API Key\uFF08xh- \u524D\u7F00\uFF09
|
|
83
165
|
-M, --model <code> \u9ED8\u8BA4\u6A21\u578B\uFF1B\u534F\u8BAE\u6309\u6A21\u578B\u8BE6\u60C5\u81EA\u52A8\u62E9\u4F18\uFF08\u975E completions \u4F18\u5148\uFF09
|
|
166
|
+
--app <apps> \u4EC5 --type ccswitch\uFF1A\u76EE\u6807\u69FD\u4F4D\uFF0C\u9017\u53F7\u5206\u9694
|
|
167
|
+
claude|codex|opencode|openclaw\uFF08\u4E0D\u652F\u6301 gemini / Universal\uFF09
|
|
168
|
+
--print-only \u4EC5 --type ccswitch\uFF1A\u53EA\u751F\u6210 Deep Link\uFF0C\u4E0D\u5524\u8D77\u5E94\u7528
|
|
84
169
|
--base-url <url> API \u6839\u5730\u5740\uFF08\u9ED8\u8BA4 https://api.trinitydesk.ai\uFF09
|
|
85
170
|
--skip-verify \u8DF3\u8FC7\u8FDE\u901A\u6027 / \u8BE6\u60C5\u62C9\u53D6
|
|
86
|
-
-v, --verbose \u663E\u793A zcf \u8BE6\u7EC6\u8F93\u51FA\uFF08\u4EC5 Claude\uFF09
|
|
171
|
+
-v, --verbose \u663E\u793A zcf \u8BE6\u7EC6\u8F93\u51FA\uFF08\u4EC5 Claude \u76F4\u8FDE\uFF09
|
|
87
172
|
-h, --help \u663E\u793A\u5E2E\u52A9
|
|
88
173
|
-V, --version \u663E\u793A\u7248\u672C
|
|
89
174
|
|
|
90
175
|
\u793A\u4F8B:
|
|
91
176
|
npx -y trinity-config --type zcode -k xh-your-key --model gpt-5.4
|
|
92
|
-
npx -y trinity-config --type
|
|
177
|
+
npx -y trinity-config --type ccswitch --app claude -k xh-your-key
|
|
178
|
+
npx -y trinity-config --type ccswitch --app codex -k xh-your-key --model gpt-5.4
|
|
179
|
+
npx -y trinity-config --type ccswitch --app claude,codex -k xh-your-key
|
|
93
180
|
`);
|
|
94
181
|
}
|
|
95
182
|
function parseArgs(argv) {
|
|
96
183
|
const opts = {
|
|
97
184
|
skipVerify: false,
|
|
98
185
|
verbose: false,
|
|
186
|
+
printOnly: false,
|
|
99
187
|
help: false,
|
|
100
188
|
version: false
|
|
101
189
|
};
|
|
@@ -119,6 +207,9 @@ function parseArgs(argv) {
|
|
|
119
207
|
case "--skip-verify":
|
|
120
208
|
opts.skipVerify = true;
|
|
121
209
|
break;
|
|
210
|
+
case "--print-only":
|
|
211
|
+
opts.printOnly = true;
|
|
212
|
+
break;
|
|
122
213
|
case "-k":
|
|
123
214
|
case "--api-key":
|
|
124
215
|
opts.apiKey = args.shift();
|
|
@@ -131,6 +222,9 @@ function parseArgs(argv) {
|
|
|
131
222
|
case "--model":
|
|
132
223
|
opts.model = args.shift();
|
|
133
224
|
break;
|
|
225
|
+
case "--app":
|
|
226
|
+
opts.apps = parseCcSwitchApps(String(args.shift() ?? ""));
|
|
227
|
+
break;
|
|
134
228
|
case "--base-url":
|
|
135
229
|
opts.baseUrl = args.shift();
|
|
136
230
|
break;
|
|
@@ -155,6 +249,17 @@ function parseArgs(argv) {
|
|
|
155
249
|
if (opts.help) {
|
|
156
250
|
printHelp();
|
|
157
251
|
}
|
|
252
|
+
if (opts.type === "ccswitch" && (!opts.apps || opts.apps.length === 0) && opts.apiKey) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"\u975E\u4EA4\u4E92\u914D\u7F6E CC Switch \u987B\u6307\u5B9A --app\uFF08\u4F8B: --app claude \u6216 --app claude,codex\uFF09"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (opts.apps && opts.type && opts.type !== "ccswitch") {
|
|
258
|
+
throw new Error("--app \u4EC5\u7528\u4E8E --type ccswitch");
|
|
259
|
+
}
|
|
260
|
+
if (opts.printOnly && opts.type && opts.type !== "ccswitch") {
|
|
261
|
+
throw new Error("--print-only \u4EC5\u7528\u4E8E --type ccswitch");
|
|
262
|
+
}
|
|
158
263
|
return opts;
|
|
159
264
|
}
|
|
160
265
|
function normalizeApiKey(raw) {
|
|
@@ -167,22 +272,262 @@ function normalizeApiKey(raw) {
|
|
|
167
272
|
function requireType(type, hasApiKeyFlag) {
|
|
168
273
|
if (type) return type;
|
|
169
274
|
if (!hasApiKeyFlag) return void 0;
|
|
170
|
-
throw new Error(
|
|
275
|
+
throw new Error(
|
|
276
|
+
"\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude\u3001codex\u3001opencode\u3001openclaw\u3001zcode \u6216 ccswitch"
|
|
277
|
+
);
|
|
171
278
|
}
|
|
172
279
|
|
|
173
|
-
// src/
|
|
280
|
+
// src/ccswitch-deeplink.ts
|
|
174
281
|
import fs from "fs";
|
|
175
282
|
import os from "os";
|
|
176
283
|
import path from "path";
|
|
284
|
+
import { execa } from "execa";
|
|
285
|
+
var TRINITY_CCSWITCH_PROVIDER_NAME = "Trinity";
|
|
286
|
+
function encodeConfigBase64(raw) {
|
|
287
|
+
return Buffer.from(raw, "utf8").toString("base64");
|
|
288
|
+
}
|
|
289
|
+
function buildClaudeConfigJson(apiKey, endpoint, model) {
|
|
290
|
+
return `${JSON.stringify(
|
|
291
|
+
{
|
|
292
|
+
env: {
|
|
293
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
294
|
+
ANTHROPIC_API_KEY: apiKey,
|
|
295
|
+
ANTHROPIC_BASE_URL: endpoint.replace(/\/v1\/?$/, "").replace(/\/+$/, ""),
|
|
296
|
+
ANTHROPIC_MODEL: model,
|
|
297
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
|
298
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: model
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
null,
|
|
302
|
+
2
|
|
303
|
+
)}
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
function buildCodexConfigToml(apiKey, endpointWithV1, model) {
|
|
307
|
+
const base = endpointWithV1.replace(/\/+$/, "");
|
|
308
|
+
return `model_provider = "trinity"
|
|
309
|
+
model = ${JSON.stringify(model)}
|
|
310
|
+
disable_response_storage = true
|
|
311
|
+
|
|
312
|
+
[model_providers.trinity]
|
|
313
|
+
name = "Trinity"
|
|
314
|
+
base_url = ${JSON.stringify(base)}
|
|
315
|
+
wire_api = "responses"
|
|
316
|
+
requires_openai_auth = true
|
|
317
|
+
temp_env_key = "TRINITY_API_KEY"
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
function buildOpenCodeConfigJson(apiKey, endpoint, model, protocol) {
|
|
321
|
+
const npm = protocol === "anthropic_messages" ? "@ai-sdk/anthropic" : protocol === "responses" ? "@ai-sdk/openai" : "@ai-sdk/openai-compatible";
|
|
322
|
+
return `${JSON.stringify(
|
|
323
|
+
{
|
|
324
|
+
$schema: "https://opencode.ai/config.json",
|
|
325
|
+
model: `trinity/${model}`,
|
|
326
|
+
provider: {
|
|
327
|
+
trinity: {
|
|
328
|
+
npm,
|
|
329
|
+
name: "Trinity",
|
|
330
|
+
options: {
|
|
331
|
+
baseURL: endpoint.replace(/\/+$/, ""),
|
|
332
|
+
apiKey
|
|
333
|
+
},
|
|
334
|
+
models: {
|
|
335
|
+
[model]: { name: model }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
null,
|
|
341
|
+
2
|
|
342
|
+
)}
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
345
|
+
function buildOpenClawConfigJson(apiKey, endpoint, model, protocol) {
|
|
346
|
+
const api = protocol === "anthropic_messages" ? "anthropic-messages" : protocol === "responses" ? "openai-responses" : "openai-completions";
|
|
347
|
+
return `${JSON.stringify(
|
|
348
|
+
{
|
|
349
|
+
models: {
|
|
350
|
+
mode: "merge",
|
|
351
|
+
providers: {
|
|
352
|
+
trinity: {
|
|
353
|
+
baseUrl: endpoint.replace(/\/+$/, ""),
|
|
354
|
+
apiKey,
|
|
355
|
+
api,
|
|
356
|
+
models: [
|
|
357
|
+
{
|
|
358
|
+
id: model,
|
|
359
|
+
name: model,
|
|
360
|
+
reasoning: false,
|
|
361
|
+
input: ["text"],
|
|
362
|
+
contextWindow: 128e3,
|
|
363
|
+
maxTokens: 8192
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
agents: {
|
|
370
|
+
defaults: {
|
|
371
|
+
model: { primary: `trinity/${model}` },
|
|
372
|
+
models: { "trinity/*": {} }
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
null,
|
|
377
|
+
2
|
|
378
|
+
)}
|
|
379
|
+
`;
|
|
380
|
+
}
|
|
381
|
+
function notesForCcSwitchApp(app, protocol) {
|
|
382
|
+
const common = [
|
|
383
|
+
"App-specific Provider\uFF08\u975E Universal\uFF09",
|
|
384
|
+
"\u5BFC\u5165\u540E\u8BF7\u5728 CC Switch \u4E2D Activate Trinity",
|
|
385
|
+
"\u52FF\u518D\u5BF9\u540C\u4E00\u5DE5\u5177\u8DD1 trinity-config --type \u76F4\u8FDE\uFF0C\u4EE5\u514D\u4E92\u76F8\u8986\u76D6",
|
|
386
|
+
"\u6A21\u578B\u5217\u8868\u4EE5\u8BE5 API Key Call Allowlist \u4E3A\u51C6"
|
|
387
|
+
];
|
|
388
|
+
switch (app) {
|
|
389
|
+
case "claude":
|
|
390
|
+
return [
|
|
391
|
+
...common,
|
|
392
|
+
"API Format = Anthropic Messages\uFF08\u9ED8\u8BA4\uFF09",
|
|
393
|
+
"Endpoint \u4E0D\u5E26 /v1",
|
|
394
|
+
protocol !== "anthropic_messages" ? `\u8B66\u544A: \u5F53\u524D\u62E9\u4F18\u534F\u8BAE\u4E3A ${protocol}\uFF0CClaude \u69FD\u4ECD\u6309 Messages \u5199\u5165\uFF1B\u82E5\u6A21\u578B\u65E0 Messages \u8BF7\u6362\u6A21\u578B` : ""
|
|
395
|
+
].filter(Boolean).join("\uFF1B");
|
|
396
|
+
case "codex":
|
|
397
|
+
return [
|
|
398
|
+
...common,
|
|
399
|
+
"wire_api=responses\uFF1B\u8BF7\u5173\u95ED Needs Local Routing\uFF08\u76F4\u8FDE Trinity Responses\uFF09",
|
|
400
|
+
"Endpoint \u586B\u5E26 /v1 \u7684\u524D\u7F00\uFF0C\u907F\u514D\u5199\u6210 \u2026/v1/v1"
|
|
401
|
+
].join("\uFF1B");
|
|
402
|
+
case "opencode":
|
|
403
|
+
return [...common, `\u534F\u8BAE: ${protocol}`].join("\uFF1B");
|
|
404
|
+
case "openclaw":
|
|
405
|
+
return [...common, `\u534F\u8BAE: ${protocol}`, "\u6539\u5B8C\u8BF7\u91CD\u542F OpenClaw Gateway"].join("\uFF1B");
|
|
406
|
+
case "gemini":
|
|
407
|
+
return "\u4E0D\u652F\u6301";
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function buildCcSwitchDeepLink(options) {
|
|
411
|
+
const root = (options.rootBaseUrl ?? TRINITY_BASE_URL).replace(/\/+$/, "");
|
|
412
|
+
const withoutV1 = root.replace(/\/v1\/?$/, "");
|
|
413
|
+
const withV1 = `${withoutV1}/v1`;
|
|
414
|
+
let endpoint;
|
|
415
|
+
let config;
|
|
416
|
+
let configFormat;
|
|
417
|
+
switch (options.app) {
|
|
418
|
+
case "claude": {
|
|
419
|
+
endpoint = withoutV1;
|
|
420
|
+
configFormat = "json";
|
|
421
|
+
config = buildClaudeConfigJson(options.apiKey, endpoint, options.model);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case "codex": {
|
|
425
|
+
endpoint = withV1;
|
|
426
|
+
configFormat = "toml";
|
|
427
|
+
config = buildCodexConfigToml(options.apiKey, endpoint, options.model);
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case "opencode": {
|
|
431
|
+
endpoint = options.protocol === "anthropic_messages" ? withoutV1 : withV1;
|
|
432
|
+
configFormat = "json";
|
|
433
|
+
config = buildOpenCodeConfigJson(
|
|
434
|
+
options.apiKey,
|
|
435
|
+
endpoint,
|
|
436
|
+
options.model,
|
|
437
|
+
options.protocol
|
|
438
|
+
);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case "openclaw": {
|
|
442
|
+
endpoint = options.protocol === "anthropic_messages" ? withoutV1 : withV1;
|
|
443
|
+
configFormat = "json";
|
|
444
|
+
config = buildOpenClawConfigJson(
|
|
445
|
+
options.apiKey,
|
|
446
|
+
endpoint,
|
|
447
|
+
options.model,
|
|
448
|
+
options.protocol
|
|
449
|
+
);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case "gemini":
|
|
453
|
+
throw new Error("gemini \u4E0D\u53D7\u652F\u6301");
|
|
454
|
+
}
|
|
455
|
+
const notes = notesForCcSwitchApp(options.app, options.protocol);
|
|
456
|
+
const params = new URLSearchParams();
|
|
457
|
+
params.set("resource", "provider");
|
|
458
|
+
params.set("app", options.app);
|
|
459
|
+
params.set("name", TRINITY_CCSWITCH_PROVIDER_NAME);
|
|
460
|
+
params.set("endpoint", endpoint);
|
|
461
|
+
params.set("apiKey", options.apiKey);
|
|
462
|
+
params.set("homepage", "https://trinitydesk.ai");
|
|
463
|
+
params.set("model", options.model);
|
|
464
|
+
if (options.app === "claude") {
|
|
465
|
+
params.set("sonnetModel", options.model);
|
|
466
|
+
params.set("haikuModel", options.model);
|
|
467
|
+
}
|
|
468
|
+
params.set("notes", notes);
|
|
469
|
+
params.set("config", encodeConfigBase64(config));
|
|
470
|
+
params.set("configFormat", configFormat);
|
|
471
|
+
if (options.enabled !== false) {
|
|
472
|
+
params.set("enabled", "true");
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
url: `ccswitch://v1/import?${params.toString()}`,
|
|
476
|
+
endpoint,
|
|
477
|
+
notes,
|
|
478
|
+
configFormat
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function ccSwitchImportDir() {
|
|
482
|
+
return path.join(os.homedir(), ".cc-switch", "trinity-imports");
|
|
483
|
+
}
|
|
484
|
+
function persistCcSwitchDeepLink(app, url) {
|
|
485
|
+
const dir = ccSwitchImportDir();
|
|
486
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
487
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
488
|
+
const base = path.join(dir, `trinity-${app}-${stamp}`);
|
|
489
|
+
const urlFile = `${base}.url`;
|
|
490
|
+
const txtFile = `${base}.txt`;
|
|
491
|
+
fs.writeFileSync(
|
|
492
|
+
urlFile,
|
|
493
|
+
`[InternetShortcut]\r
|
|
494
|
+
URL=${url}\r
|
|
495
|
+
`,
|
|
496
|
+
"utf8"
|
|
497
|
+
);
|
|
498
|
+
fs.writeFileSync(txtFile, `${url}
|
|
499
|
+
`, "utf8");
|
|
500
|
+
return { urlFile, txtFile };
|
|
501
|
+
}
|
|
502
|
+
async function openCcSwitchDeepLink(url) {
|
|
503
|
+
const platform = process.platform;
|
|
504
|
+
if (platform === "win32") {
|
|
505
|
+
await execa("cmd", ["/c", "start", "", url], {
|
|
506
|
+
windowsHide: true,
|
|
507
|
+
reject: false
|
|
508
|
+
});
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (platform === "darwin") {
|
|
512
|
+
await execa("open", [url], { reject: false });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
await execa("xdg-open", [url], { reject: false });
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/merge-claude-env.ts
|
|
519
|
+
import fs2 from "fs";
|
|
520
|
+
import os2 from "os";
|
|
521
|
+
import path2 from "path";
|
|
177
522
|
function claudeSettingsPath() {
|
|
178
|
-
return
|
|
523
|
+
return path2.join(os2.homedir(), ".claude", "settings.json");
|
|
179
524
|
}
|
|
180
525
|
function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
|
|
181
526
|
const filePath = claudeSettingsPath();
|
|
182
|
-
if (!
|
|
527
|
+
if (!fs2.existsSync(filePath)) {
|
|
183
528
|
return;
|
|
184
529
|
}
|
|
185
|
-
const raw =
|
|
530
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
186
531
|
let settings;
|
|
187
532
|
try {
|
|
188
533
|
settings = JSON.parse(raw);
|
|
@@ -195,8 +540,8 @@ function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
|
|
|
195
540
|
ANTHROPIC_MODEL: claudeModel,
|
|
196
541
|
ANTHROPIC_SMALL_FAST_MODEL: fastModel
|
|
197
542
|
};
|
|
198
|
-
|
|
199
|
-
|
|
543
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
544
|
+
fs2.writeFileSync(filePath, `${JSON.stringify(settings, null, 2)}
|
|
200
545
|
`, "utf8");
|
|
201
546
|
}
|
|
202
547
|
|
|
@@ -362,7 +707,7 @@ function protocolLabel(protocol) {
|
|
|
362
707
|
}
|
|
363
708
|
|
|
364
709
|
// src/zcf.ts
|
|
365
|
-
import { execa } from "execa";
|
|
710
|
+
import { execa as execa2 } from "execa";
|
|
366
711
|
function sharedZcfArgs(verbose) {
|
|
367
712
|
return [
|
|
368
713
|
"zcf",
|
|
@@ -401,7 +746,7 @@ async function runZcfInit(options) {
|
|
|
401
746
|
args.push("--api-haiku-model", options.fastModel);
|
|
402
747
|
args.push("--api-sonnet-model", options.primaryModel);
|
|
403
748
|
}
|
|
404
|
-
await
|
|
749
|
+
await execa2("npx", ["-y", ...args], {
|
|
405
750
|
stdio: options.verbose ? "inherit" : "pipe",
|
|
406
751
|
reject: true,
|
|
407
752
|
timeout: 18e4
|
|
@@ -422,7 +767,7 @@ async function configureCodex(apiKey, codexBaseUrl, codexModel, _verbose) {
|
|
|
422
767
|
return configureCodexDirect(apiKey, codexBaseUrl, codexModel);
|
|
423
768
|
}
|
|
424
769
|
async function configureOpenCode(apiKey, openCodeBaseUrl, model, modelIds, protocol) {
|
|
425
|
-
const { configureOpenCodeDirect } = await import("./merge-opencode-config-
|
|
770
|
+
const { configureOpenCodeDirect } = await import("./merge-opencode-config-JTWAGCET.js");
|
|
426
771
|
return configureOpenCodeDirect({
|
|
427
772
|
apiKey,
|
|
428
773
|
baseUrl: openCodeBaseUrl,
|
|
@@ -432,7 +777,7 @@ async function configureOpenCode(apiKey, openCodeBaseUrl, model, modelIds, proto
|
|
|
432
777
|
});
|
|
433
778
|
}
|
|
434
779
|
async function configureOpenClaw(apiKey, openClawBaseUrl, model, modelIds, protocol) {
|
|
435
|
-
const { configureOpenClawDirect } = await import("./merge-openclaw-config-
|
|
780
|
+
const { configureOpenClawDirect } = await import("./merge-openclaw-config-F77TZKPG.js");
|
|
436
781
|
return configureOpenClawDirect({
|
|
437
782
|
apiKey,
|
|
438
783
|
baseUrl: openClawBaseUrl,
|
|
@@ -442,7 +787,7 @@ async function configureOpenClaw(apiKey, openClawBaseUrl, model, modelIds, proto
|
|
|
442
787
|
});
|
|
443
788
|
}
|
|
444
789
|
async function configureZCode(apiKey, zcodeBaseUrl, model, modelIds, protocol) {
|
|
445
|
-
const { configureZCodeDirect } = await import("./merge-zcode-config-
|
|
790
|
+
const { configureZCodeDirect } = await import("./merge-zcode-config-LGTBU4UC.js");
|
|
446
791
|
return configureZCodeDirect({
|
|
447
792
|
apiKey,
|
|
448
793
|
baseUrl: zcodeBaseUrl,
|
|
@@ -532,7 +877,7 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
|
|
|
532
877
|
return {
|
|
533
878
|
ok: true,
|
|
534
879
|
statusCode,
|
|
535
|
-
message: `\u8FDE\u901A\u6210\u529F\uFF08
|
|
880
|
+
message: `\u8FDE\u901A\u6210\u529F\uFF08\u8BE5 Key \u53EF\u7528\u751F\u6587\u6A21\u578B ${modelIds.length} \u4E2A\uFF09`,
|
|
536
881
|
modelIds
|
|
537
882
|
};
|
|
538
883
|
} catch (error) {
|
|
@@ -577,11 +922,12 @@ async function promptToolType(initial) {
|
|
|
577
922
|
const value = await p.select({
|
|
578
923
|
message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
|
|
579
924
|
options: [
|
|
580
|
-
{ value: "claude", label: "Claude Code" },
|
|
581
|
-
{ value: "codex", label: "Codex CLI" },
|
|
582
|
-
{ value: "opencode", label: "OpenCode" },
|
|
583
|
-
{ value: "openclaw", label: "OpenClaw" },
|
|
584
|
-
{ value: "zcode", label: "ZCode" }
|
|
925
|
+
{ value: "claude", label: "Claude Code\uFF08\u76F4\u8FDE\uFF09" },
|
|
926
|
+
{ value: "codex", label: "Codex CLI\uFF08\u76F4\u8FDE\uFF09" },
|
|
927
|
+
{ value: "opencode", label: "OpenCode\uFF08\u76F4\u8FDE\uFF09" },
|
|
928
|
+
{ value: "openclaw", label: "OpenClaw\uFF08\u76F4\u8FDE\uFF09" },
|
|
929
|
+
{ value: "zcode", label: "ZCode" },
|
|
930
|
+
{ value: "ccswitch", label: "CC Switch\uFF08\u6309 App \u69FD\u4F4D\u5BFC\u5165\uFF09" }
|
|
585
931
|
]
|
|
586
932
|
});
|
|
587
933
|
if (p.isCancel(value)) {
|
|
@@ -590,13 +936,31 @@ async function promptToolType(initial) {
|
|
|
590
936
|
}
|
|
591
937
|
return value;
|
|
592
938
|
}
|
|
593
|
-
async function
|
|
939
|
+
async function promptCcSwitchApps(initial) {
|
|
940
|
+
if (initial && initial.length > 0) {
|
|
941
|
+
return initial;
|
|
942
|
+
}
|
|
943
|
+
const value = await p.multiselect({
|
|
944
|
+
message: "\u8981\u5199\u5165 CC Switch \u7684\u54EA\u4E9B App \u69FD\u4F4D\uFF1F\uFF08\u975E Universal\uFF09",
|
|
945
|
+
options: CC_SWITCH_SUPPORTED_APPS.map((app) => ({
|
|
946
|
+
value: app,
|
|
947
|
+
label: app
|
|
948
|
+
})),
|
|
949
|
+
required: true
|
|
950
|
+
});
|
|
951
|
+
if (p.isCancel(value)) {
|
|
952
|
+
p.cancel("\u5DF2\u53D6\u6D88");
|
|
953
|
+
process.exit(0);
|
|
954
|
+
}
|
|
955
|
+
return value;
|
|
956
|
+
}
|
|
957
|
+
async function promptModel(type, app, initial) {
|
|
594
958
|
if (initial?.trim()) {
|
|
595
959
|
return initial.trim();
|
|
596
960
|
}
|
|
597
|
-
const fallback = defaultModelForType(type);
|
|
961
|
+
const fallback = defaultModelForType(type, app);
|
|
598
962
|
const value = await p.text({
|
|
599
|
-
message: `${toolLabel(type)} \u9ED8\u8BA4 model_code`,
|
|
963
|
+
message: `${toolLabel(type, app)} \u9ED8\u8BA4 model_code`,
|
|
600
964
|
placeholder: fallback,
|
|
601
965
|
initialValue: fallback,
|
|
602
966
|
validate: (input) => {
|
|
@@ -609,8 +973,8 @@ async function promptModel(type, initial) {
|
|
|
609
973
|
}
|
|
610
974
|
return String(value).trim();
|
|
611
975
|
}
|
|
612
|
-
function recommendedHint(
|
|
613
|
-
switch (
|
|
976
|
+
function recommendedHint(protocolTool, recommended) {
|
|
977
|
+
switch (protocolTool) {
|
|
614
978
|
case "claude":
|
|
615
979
|
return recommended.claude;
|
|
616
980
|
case "codex":
|
|
@@ -623,6 +987,13 @@ function recommendedHint(toolType, recommended) {
|
|
|
623
987
|
return recommended.zcode;
|
|
624
988
|
}
|
|
625
989
|
}
|
|
990
|
+
function protocolToolFor(type, app) {
|
|
991
|
+
if (type === "ccswitch") {
|
|
992
|
+
if (!app) throw new Error("ccswitch \u7F3A\u5C11 --app");
|
|
993
|
+
return protocolToolForCcSwitchApp(app);
|
|
994
|
+
}
|
|
995
|
+
return type;
|
|
996
|
+
}
|
|
626
997
|
async function run() {
|
|
627
998
|
let opts;
|
|
628
999
|
try {
|
|
@@ -641,38 +1012,192 @@ async function run() {
|
|
|
641
1012
|
}
|
|
642
1013
|
p.intro(pc.bgCyan(pc.black(` ${PACKAGE_NAME} v${PACKAGE_VERSION} `)));
|
|
643
1014
|
const toolType = await promptToolType(opts.type);
|
|
1015
|
+
const ccApps = toolType === "ccswitch" ? await promptCcSwitchApps(opts.apps) : void 0;
|
|
1016
|
+
if (ccApps) {
|
|
1017
|
+
for (const app of ccApps) {
|
|
1018
|
+
try {
|
|
1019
|
+
assertCcSwitchAppSupported(app);
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
1022
|
+
return 1;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const primaryApp = ccApps?.[0];
|
|
644
1027
|
const apiKey = await promptApiKey(opts.apiKey);
|
|
645
|
-
|
|
1028
|
+
let model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType, void 0, primaryApp) : await promptModel(toolType, primaryApp);
|
|
646
1029
|
const apiRoot = rootBaseUrl(opts.baseUrl);
|
|
647
|
-
p.log.info(
|
|
1030
|
+
p.log.info(
|
|
1031
|
+
`\u5DE5\u5177: ${toolLabel(toolType, primaryApp)}` + (ccApps && ccApps.length > 1 ? `\uFF08${ccApps.join(",")}\uFF09` : "") + ` \xB7 \u6A21\u578B: ${model}`
|
|
1032
|
+
);
|
|
1033
|
+
if (toolType === "ccswitch") {
|
|
1034
|
+
p.note(
|
|
1035
|
+
[
|
|
1036
|
+
"\u5C06\u751F\u6210 App-specific Provider Deep Link\uFF08\u975E Universal\uFF09",
|
|
1037
|
+
"\u5BFC\u5165\u540E\u8BF7\u5728\u5BF9\u5E94 App \u4E0B Activate\u300CTrinity\u300D",
|
|
1038
|
+
"\u8BF7\u52FF\u540C\u65F6\u5BF9\u672C\u5DE5\u5177\u518D\u8DD1 --type \u76F4\u8FDE\uFF0C\u4EE5\u514D\u914D\u7F6E\u4E92\u76F8\u8986\u76D6"
|
|
1039
|
+
].join("\n"),
|
|
1040
|
+
"CC Switch"
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
648
1043
|
const spinner2 = p.spinner();
|
|
649
1044
|
let verifiedModelIds;
|
|
650
1045
|
let verifyFailed = false;
|
|
1046
|
+
const protocolToolHint = protocolToolFor(toolType, primaryApp);
|
|
651
1047
|
if (!opts.skipVerify) {
|
|
652
1048
|
spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
|
|
653
1049
|
const result = await verifyTrinityApi(apiKey, apiRoot);
|
|
654
1050
|
if (result.ok) {
|
|
655
1051
|
spinner2.stop(pc.green(result.message));
|
|
656
|
-
verifiedModelIds = result.modelIds;
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1052
|
+
verifiedModelIds = result.modelIds ?? [];
|
|
1053
|
+
if (verifiedModelIds.length === 0) {
|
|
1054
|
+
p.outro(pc.red("\u8BE5 API Key \u6CA1\u6709\u53EF\u7528\u7684\u751F\u6587\u6A21\u578B\uFF08Call Allowlist \u4E3A\u7A7A\uFF09"));
|
|
1055
|
+
return 1;
|
|
1056
|
+
}
|
|
1057
|
+
const recommended = pickRecommendedModels(verifiedModelIds);
|
|
1058
|
+
const hint = recommendedHint(protocolToolHint, recommended);
|
|
1059
|
+
const allowed = pickAllowedDefaultModel(model, verifiedModelIds, [
|
|
1060
|
+
hint,
|
|
1061
|
+
recommended.claude,
|
|
1062
|
+
recommended.codex,
|
|
1063
|
+
recommended.opencode,
|
|
1064
|
+
recommended.openclaw,
|
|
1065
|
+
recommended.zcode,
|
|
1066
|
+
defaultModelForType(toolType, primaryApp)
|
|
1067
|
+
]);
|
|
1068
|
+
if (!allowed) {
|
|
1069
|
+
p.outro(pc.red("\u65E0\u6CD5\u5728\u8BE5 API Key \u7684\u53EF\u7528\u751F\u6587\u6A21\u578B\u4E2D\u9009\u51FA\u9ED8\u8BA4\u6A21\u578B"));
|
|
1070
|
+
return 1;
|
|
1071
|
+
}
|
|
1072
|
+
if (allowed !== model) {
|
|
1073
|
+
p.note(
|
|
1074
|
+
`\u6A21\u578B ${model} \u4E0D\u5728\u8BE5 Key \u53EF\u7528\u5217\u8868\u4E2D\uFF0C\u5DF2\u6539\u4E3A ${allowed}\uFF08\u5171 ${verifiedModelIds.length} \u4E2A\u53EF\u7528\u751F\u6587\u6A21\u578B\uFF09`,
|
|
1075
|
+
"\u6743\u9650"
|
|
1076
|
+
);
|
|
1077
|
+
model = allowed;
|
|
1078
|
+
} else if (hint && hint !== model) {
|
|
660
1079
|
p.note(`\u540C\u7C7B\u578B\u63A8\u8350\u6A21\u578B: ${hint}`, "\u6A21\u578B");
|
|
661
1080
|
}
|
|
662
1081
|
} else {
|
|
663
1082
|
verifyFailed = true;
|
|
664
1083
|
spinner2.stop(pc.yellow(result.message));
|
|
665
1084
|
p.note(
|
|
666
|
-
"\u8FDE\u901A\u6027\u68C0\u67E5\u672A\u901A\u8FC7\uFF1B\u4ECD\u5C06\u5C1D\u8BD5\u5199\u5165\u672C\u5730\u914D\u7F6E\u3002\u8BF7\u68C0\u67E5 API Key\u3001\u7F51\u7EDC\uFF0C\u6216\u5728\u63A7\u5236\u53F0\u786E\u8BA4 Key \u6709\u6548\u3002",
|
|
1085
|
+
"\u8FDE\u901A\u6027\u68C0\u67E5\u672A\u901A\u8FC7\uFF1B\u4ECD\u5C06\u5C1D\u8BD5\u5199\u5165\u672C\u5730\u914D\u7F6E\uFF08\u4E0D\u4F1A\u540C\u6B65\u6A21\u578B\u5217\u8868\uFF09\u3002\u8BF7\u68C0\u67E5 API Key\u3001\u7F51\u7EDC\uFF0C\u6216\u5728\u63A7\u5236\u53F0\u786E\u8BA4 Key \u6709\u6548\u3002",
|
|
667
1086
|
"\u63D0\u793A"
|
|
668
1087
|
);
|
|
669
1088
|
}
|
|
1089
|
+
} else {
|
|
1090
|
+
p.note(
|
|
1091
|
+
"--skip-verify \u5DF2\u8DF3\u8FC7\uFF1A\u65E0\u6CD5\u6309 Key \u6743\u76CA\u62C9\u53D6\u53EF\u7528\u6A21\u578B\u5217\u8868\uFF0C\u4EC5\u5199\u5165\u6307\u5B9A\u9ED8\u8BA4\u6A21\u578B\u3002",
|
|
1092
|
+
"\u63D0\u793A"
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
if (toolType === "ccswitch" && ccApps) {
|
|
1096
|
+
const saved = [];
|
|
1097
|
+
for (const app of ccApps) {
|
|
1098
|
+
spinner2.start(`\u751F\u6210 CC Switch Deep Link\uFF08${app}\uFF09\u2026`);
|
|
1099
|
+
let appModel = model;
|
|
1100
|
+
let protocolRes2;
|
|
1101
|
+
try {
|
|
1102
|
+
protocolRes2 = await resolveProtocol({
|
|
1103
|
+
tool: protocolToolForCcSwitchApp(app),
|
|
1104
|
+
apiKey,
|
|
1105
|
+
model: appModel,
|
|
1106
|
+
rootBaseUrl: apiRoot,
|
|
1107
|
+
skipFetch: opts.skipVerify
|
|
1108
|
+
});
|
|
1109
|
+
} catch (firstError) {
|
|
1110
|
+
const fallback = defaultModelForCcSwitchApp(app);
|
|
1111
|
+
const canRetry = fallback !== appModel && (!verifiedModelIds || verifiedModelIds.includes(fallback));
|
|
1112
|
+
if (!canRetry) {
|
|
1113
|
+
spinner2.stop(pc.red(`${app} \u534F\u8BAE\u89E3\u6790\u5931\u8D25`));
|
|
1114
|
+
console.error(
|
|
1115
|
+
pc.red(
|
|
1116
|
+
firstError instanceof Error ? firstError.message : String(firstError)
|
|
1117
|
+
)
|
|
1118
|
+
);
|
|
1119
|
+
p.note(
|
|
1120
|
+
`\u591A App \u5171\u7528\u4E00\u4E2A --model \u65F6\uFF0C\u8BF7\u786E\u4FDD\u6A21\u578B\u534F\u8BAE\u5339\u914D\u5404\u69FD\u4F4D\uFF1B\u6216\u5206\u5F00\u6267\u884C\u4E0D\u540C --app / --model`,
|
|
1121
|
+
"\u63D0\u793A"
|
|
1122
|
+
);
|
|
1123
|
+
return 1;
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
protocolRes2 = await resolveProtocol({
|
|
1127
|
+
tool: protocolToolForCcSwitchApp(app),
|
|
1128
|
+
apiKey,
|
|
1129
|
+
model: fallback,
|
|
1130
|
+
rootBaseUrl: apiRoot,
|
|
1131
|
+
skipFetch: opts.skipVerify
|
|
1132
|
+
});
|
|
1133
|
+
p.note(
|
|
1134
|
+
`${app} \u69FD\uFF1A\u6A21\u578B ${appModel} \u4E0D\u517C\u5BB9\uFF0C\u5DF2\u6539\u7528 ${fallback}`,
|
|
1135
|
+
"\u6A21\u578B"
|
|
1136
|
+
);
|
|
1137
|
+
appModel = fallback;
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
spinner2.stop(pc.red(`${app} \u534F\u8BAE\u89E3\u6790\u5931\u8D25`));
|
|
1140
|
+
console.error(
|
|
1141
|
+
pc.red(error instanceof Error ? error.message : String(error))
|
|
1142
|
+
);
|
|
1143
|
+
return 1;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const protocol2 = protocolRes2.protocol;
|
|
1147
|
+
try {
|
|
1148
|
+
const link = buildCcSwitchDeepLink({
|
|
1149
|
+
app,
|
|
1150
|
+
apiKey,
|
|
1151
|
+
rootBaseUrl: apiRoot,
|
|
1152
|
+
model: appModel,
|
|
1153
|
+
protocol: protocol2
|
|
1154
|
+
});
|
|
1155
|
+
const files = persistCcSwitchDeepLink(app, link.url);
|
|
1156
|
+
saved.push(files.urlFile);
|
|
1157
|
+
spinner2.stop(
|
|
1158
|
+
`\u5DF2\u751F\u6210 ${app}\uFF1A${protocolLabel(protocol2)} \u2192 ${link.endpoint}`
|
|
1159
|
+
);
|
|
1160
|
+
p.note(link.notes, `${app} \u6CE8\u610F`);
|
|
1161
|
+
p.log.info(`Deep Link \u5DF2\u4FDD\u5B58: ${files.txtFile}`);
|
|
1162
|
+
if (link.url.length > 2e3) {
|
|
1163
|
+
p.note(
|
|
1164
|
+
"Deep Link \u8F83\u957F\uFF0C\u82E5\u7CFB\u7EDF\u65E0\u6CD5\u5524\u8D77\uFF0C\u8BF7\u53CC\u51FB\u843D\u76D8\u7684 .url \u6587\u4EF6\u5BFC\u5165",
|
|
1165
|
+
"\u63D0\u793A"
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
if (!opts.printOnly) {
|
|
1169
|
+
await openCcSwitchDeepLink(link.url);
|
|
1170
|
+
p.log.info("\u5DF2\u5C1D\u8BD5\u5524\u8D77 CC Switch \u5BFC\u5165\u5BF9\u8BDD\u6846\uFF08\u82E5\u672A\u5F39\u51FA\u8BF7\u53CC\u51FB .url \u6587\u4EF6\uFF09");
|
|
1171
|
+
} else {
|
|
1172
|
+
p.log.info(link.url);
|
|
1173
|
+
}
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
spinner2.stop(pc.red(`${app} Deep Link \u5931\u8D25`));
|
|
1176
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
1177
|
+
return 1;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (verifyFailed) {
|
|
1181
|
+
p.outro(pc.yellow("\u914D\u7F6E\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
|
|
1182
|
+
return 1;
|
|
1183
|
+
}
|
|
1184
|
+
p.note(
|
|
1185
|
+
[
|
|
1186
|
+
"\u5728 CC Switch \u5BFC\u5165\u786E\u8BA4\u6846\u4E2D\u6838\u5BF9\u540E Confirm",
|
|
1187
|
+
"\u5207\u6362\u5230\u5BF9\u5E94 App \u9875\u7B7E\uFF0CActivate\u300CTrinity\u300D",
|
|
1188
|
+
`\u843D\u76D8\u6587\u4EF6: ${saved.join(" | ")}`,
|
|
1189
|
+
opts.printOnly ? "\u5DF2 --print-only\uFF0C\u672A\u81EA\u52A8\u6253\u5F00\u5E94\u7528" : "\u82E5\u672A\u5F39\u7A97\uFF1A\u53CC\u51FB\u4E0A\u8FF0 .url\uFF0C\u6216\u68C0\u67E5 ccswitch:// \u534F\u8BAE\u6CE8\u518C"
|
|
1190
|
+
].join("\n"),
|
|
1191
|
+
"\u4E0B\u4E00\u6B65"
|
|
1192
|
+
);
|
|
1193
|
+
p.outro(pc.green("\u5B8C\u6210"));
|
|
1194
|
+
return 0;
|
|
670
1195
|
}
|
|
671
1196
|
spinner2.start("\u89E3\u6790\u6A21\u578B\u534F\u8BAE\u2026");
|
|
672
1197
|
let protocolRes;
|
|
673
1198
|
try {
|
|
674
1199
|
protocolRes = await resolveProtocol({
|
|
675
|
-
tool: toolType,
|
|
1200
|
+
tool: protocolToolFor(toolType),
|
|
676
1201
|
apiKey,
|
|
677
1202
|
model,
|
|
678
1203
|
rootBaseUrl: apiRoot,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
filterTextModelIds
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5WZW5Y4W.js";
|
|
4
4
|
|
|
5
5
|
// src/merge-openclaw-config.ts
|
|
6
6
|
import fs from "fs";
|
|
@@ -71,22 +71,13 @@ function mergeOpenClawConfig(options) {
|
|
|
71
71
|
modelsBlock.mode = modelsBlock.mode ?? "merge";
|
|
72
72
|
const providers = modelsBlock.providers && typeof modelsBlock.providers === "object" ? { ...modelsBlock.providers } : {};
|
|
73
73
|
const existing = providers[TRINITY_OPENCLAW_PROVIDER] ?? {};
|
|
74
|
-
const
|
|
75
|
-
const byId = /* @__PURE__ */ new Map();
|
|
76
|
-
for (const m of existingModels) {
|
|
77
|
-
if (m && typeof m === "object" && typeof m.id === "string") {
|
|
78
|
-
byId.set(m.id, m);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
for (const m of buildModelEntries(options.model, options.modelIds)) {
|
|
82
|
-
byId.set(m.id, { ...byId.get(m.id), ...m });
|
|
83
|
-
}
|
|
74
|
+
const nextModels = buildModelEntries(options.model, options.modelIds);
|
|
84
75
|
providers[TRINITY_OPENCLAW_PROVIDER] = {
|
|
85
76
|
...existing,
|
|
86
77
|
baseUrl: options.baseUrl.replace(/\/+$/, ""),
|
|
87
78
|
apiKey: options.apiKey,
|
|
88
79
|
api,
|
|
89
|
-
models:
|
|
80
|
+
models: nextModels
|
|
90
81
|
};
|
|
91
82
|
modelsBlock.providers = providers;
|
|
92
83
|
doc.models = modelsBlock;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
filterTextModelIds
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5WZW5Y4W.js";
|
|
4
4
|
|
|
5
5
|
// src/merge-opencode-config.ts
|
|
6
6
|
import fs from "fs";
|
|
@@ -65,7 +65,7 @@ function mergeOpenCodeConfig(options) {
|
|
|
65
65
|
doc.model = `${TRINITY_OPENCODE_PROVIDER}/${options.model}`;
|
|
66
66
|
const providers = doc.provider && typeof doc.provider === "object" ? { ...doc.provider } : {};
|
|
67
67
|
const existing = providers[TRINITY_OPENCODE_PROVIDER] ?? {};
|
|
68
|
-
const
|
|
68
|
+
const nextModels = buildModelMap(options.model, options.modelIds);
|
|
69
69
|
providers[TRINITY_OPENCODE_PROVIDER] = {
|
|
70
70
|
...existing,
|
|
71
71
|
npm,
|
|
@@ -75,10 +75,7 @@ function mergeOpenCodeConfig(options) {
|
|
|
75
75
|
baseURL: options.baseUrl.replace(/\/+$/, ""),
|
|
76
76
|
apiKey: options.apiKey
|
|
77
77
|
},
|
|
78
|
-
models:
|
|
79
|
-
...existingModels,
|
|
80
|
-
...buildModelMap(options.model, options.modelIds)
|
|
81
|
-
}
|
|
78
|
+
models: nextModels
|
|
82
79
|
};
|
|
83
80
|
doc.provider = providers;
|
|
84
81
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
filterTextModelIds
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5WZW5Y4W.js";
|
|
4
4
|
|
|
5
5
|
// src/merge-zcode-config.ts
|
|
6
6
|
import fs from "fs";
|
|
@@ -108,17 +108,13 @@ function mergeZCodeConfig(options) {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
const existing = providers[TRINITY_ZCODE_PROVIDER] ?? {};
|
|
111
|
-
const existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
|
|
112
111
|
const root = options.baseUrl.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
|
|
113
112
|
const withV1 = `${root}/v1`;
|
|
114
113
|
const withoutV1 = root;
|
|
115
114
|
const binding = zcodeProtocolBinding(options.protocol);
|
|
116
115
|
const primaryBase = options.protocol === "anthropic_messages" ? withoutV1 : withV1;
|
|
117
116
|
const now = Date.now();
|
|
118
|
-
const models =
|
|
119
|
-
...existingModels,
|
|
120
|
-
...buildModels(options.model, options.modelIds, binding)
|
|
121
|
-
};
|
|
117
|
+
const models = buildModels(options.model, options.modelIds, binding);
|
|
122
118
|
providers[TRINITY_ZCODE_PROVIDER] = {
|
|
123
119
|
...existing,
|
|
124
120
|
enabled: true,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trinity-config",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "一键把 Trinity API 接入 Claude Code、Codex、OpenCode、OpenClaw、ZCode",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "一键把 Trinity API 接入 Claude Code、Codex、OpenCode、OpenClaw、ZCode、CC Switch",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
"codex",
|
|
32
32
|
"opencode",
|
|
33
33
|
"openclaw",
|
|
34
|
+
"zcode",
|
|
35
|
+
"cc-switch",
|
|
34
36
|
"cli",
|
|
35
37
|
"ai-gateway",
|
|
36
38
|
"zcf"
|
package/dist/chunk-QS4QYSMM.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// src/text-models.ts
|
|
2
|
-
function isLikelyNonTextModel(id) {
|
|
3
|
-
const lower = id.toLowerCase();
|
|
4
|
-
return lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling") || lower.includes("og-image") || lower.includes("midjourney") || lower.includes("flux");
|
|
5
|
-
}
|
|
6
|
-
function filterTextModelIds(primaryModel, modelIds, limit = 80) {
|
|
7
|
-
const ids = [];
|
|
8
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9
|
-
const push = (raw) => {
|
|
10
|
-
const trimmed = raw?.trim();
|
|
11
|
-
if (!trimmed || seen.has(trimmed)) return;
|
|
12
|
-
if (ids.length > 0 && isLikelyNonTextModel(trimmed)) return;
|
|
13
|
-
seen.add(trimmed);
|
|
14
|
-
ids.push(trimmed);
|
|
15
|
-
};
|
|
16
|
-
push(primaryModel);
|
|
17
|
-
for (const id of modelIds ?? []) {
|
|
18
|
-
if (ids.length >= limit) break;
|
|
19
|
-
push(id);
|
|
20
|
-
}
|
|
21
|
-
return ids;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
filterTextModelIds
|
|
26
|
-
};
|