trinity-config 1.2.5 → 1.3.1
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 +21 -7
- package/dist/index.js +482 -34
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
|
@@ -8,15 +8,29 @@
|
|
|
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
|
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import pc from "picocolors";
|
|
|
9
9
|
|
|
10
10
|
// src/constants.ts
|
|
11
11
|
var PACKAGE_NAME = "trinity-config";
|
|
12
|
-
var PACKAGE_VERSION = "1.
|
|
12
|
+
var PACKAGE_VERSION = "1.3.1";
|
|
13
13
|
var TRINITY_BASE_URL = "https://api.trinitydesk.ai";
|
|
14
14
|
var DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
15
15
|
var DEFAULT_CLAUDE_FAST_MODEL = "claude-sonnet-4-6";
|
|
@@ -20,7 +20,23 @@ var DEFAULT_ZCODE_MODEL = "gpt-5.4";
|
|
|
20
20
|
var VERIFY_TIMEOUT_MS = 15e3;
|
|
21
21
|
|
|
22
22
|
// src/model.ts
|
|
23
|
-
|
|
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
|
+
}
|
|
24
40
|
switch (type) {
|
|
25
41
|
case "claude":
|
|
26
42
|
return DEFAULT_CLAUDE_MODEL;
|
|
@@ -34,17 +50,77 @@ function defaultModelForType(type) {
|
|
|
34
50
|
return DEFAULT_ZCODE_MODEL;
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
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);
|
|
39
67
|
}
|
|
40
68
|
function parseToolType(raw) {
|
|
41
69
|
const value = raw.trim().toLowerCase();
|
|
42
|
-
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") {
|
|
43
71
|
return value;
|
|
44
72
|
}
|
|
45
|
-
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;
|
|
96
|
+
}
|
|
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
|
+
}
|
|
46
112
|
}
|
|
47
|
-
function
|
|
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
|
+
}
|
|
48
124
|
switch (type) {
|
|
49
125
|
case "claude":
|
|
50
126
|
return "Claude Code";
|
|
@@ -70,6 +146,8 @@ function launchCommand(type) {
|
|
|
70
146
|
return "openclaw dashboard";
|
|
71
147
|
case "zcode":
|
|
72
148
|
return "ZCode \u684C\u9762\u5E94\u7528";
|
|
149
|
+
case "ccswitch":
|
|
150
|
+
return "CC Switch \u684C\u9762\u5E94\u7528";
|
|
73
151
|
}
|
|
74
152
|
}
|
|
75
153
|
|
|
@@ -79,27 +157,33 @@ function printHelp() {
|
|
|
79
157
|
trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF / Agent / \u684C\u9762\u7F16\u7A0B\u52A9\u624B
|
|
80
158
|
|
|
81
159
|
\u7528\u6CD5:
|
|
82
|
-
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]
|
|
83
161
|
|
|
84
162
|
\u9009\u9879:
|
|
85
163
|
-t, --type <\u2026> \u8981\u914D\u7F6E\u7684\u5DE5\u5177\uFF08\u975E\u4EA4\u4E92\u987B\u6307\u5B9A\uFF09
|
|
86
164
|
-k, --api-key <key> Trinity API Key\uFF08xh- \u524D\u7F00\uFF09
|
|
87
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
|
|
88
169
|
--base-url <url> API \u6839\u5730\u5740\uFF08\u9ED8\u8BA4 https://api.trinitydesk.ai\uFF09
|
|
89
170
|
--skip-verify \u8DF3\u8FC7\u8FDE\u901A\u6027 / \u8BE6\u60C5\u62C9\u53D6
|
|
90
|
-
-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
|
|
91
172
|
-h, --help \u663E\u793A\u5E2E\u52A9
|
|
92
173
|
-V, --version \u663E\u793A\u7248\u672C
|
|
93
174
|
|
|
94
175
|
\u793A\u4F8B:
|
|
95
176
|
npx -y trinity-config --type zcode -k xh-your-key --model gpt-5.4
|
|
96
|
-
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
|
|
97
180
|
`);
|
|
98
181
|
}
|
|
99
182
|
function parseArgs(argv) {
|
|
100
183
|
const opts = {
|
|
101
184
|
skipVerify: false,
|
|
102
185
|
verbose: false,
|
|
186
|
+
printOnly: false,
|
|
103
187
|
help: false,
|
|
104
188
|
version: false
|
|
105
189
|
};
|
|
@@ -123,6 +207,9 @@ function parseArgs(argv) {
|
|
|
123
207
|
case "--skip-verify":
|
|
124
208
|
opts.skipVerify = true;
|
|
125
209
|
break;
|
|
210
|
+
case "--print-only":
|
|
211
|
+
opts.printOnly = true;
|
|
212
|
+
break;
|
|
126
213
|
case "-k":
|
|
127
214
|
case "--api-key":
|
|
128
215
|
opts.apiKey = args.shift();
|
|
@@ -135,6 +222,9 @@ function parseArgs(argv) {
|
|
|
135
222
|
case "--model":
|
|
136
223
|
opts.model = args.shift();
|
|
137
224
|
break;
|
|
225
|
+
case "--app":
|
|
226
|
+
opts.apps = parseCcSwitchApps(String(args.shift() ?? ""));
|
|
227
|
+
break;
|
|
138
228
|
case "--base-url":
|
|
139
229
|
opts.baseUrl = args.shift();
|
|
140
230
|
break;
|
|
@@ -159,6 +249,17 @@ function parseArgs(argv) {
|
|
|
159
249
|
if (opts.help) {
|
|
160
250
|
printHelp();
|
|
161
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
|
+
}
|
|
162
263
|
return opts;
|
|
163
264
|
}
|
|
164
265
|
function normalizeApiKey(raw) {
|
|
@@ -171,22 +272,222 @@ function normalizeApiKey(raw) {
|
|
|
171
272
|
function requireType(type, hasApiKeyFlag) {
|
|
172
273
|
if (type) return type;
|
|
173
274
|
if (!hasApiKeyFlag) return void 0;
|
|
174
|
-
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
|
+
);
|
|
175
278
|
}
|
|
176
279
|
|
|
177
|
-
// src/
|
|
280
|
+
// src/ccswitch-deeplink.ts
|
|
178
281
|
import fs from "fs";
|
|
179
282
|
import os from "os";
|
|
180
283
|
import path from "path";
|
|
284
|
+
import { execa } from "execa";
|
|
285
|
+
var TRINITY_CCSWITCH_PROVIDER_NAME = "Trinity";
|
|
286
|
+
var CCSWITCH_URL_SAFE_MAX = 2e3;
|
|
287
|
+
function encodeConfigBase64(raw) {
|
|
288
|
+
return Buffer.from(raw, "utf8").toString("base64");
|
|
289
|
+
}
|
|
290
|
+
function buildClaudeConfigJson(apiKey, endpoint, model) {
|
|
291
|
+
return JSON.stringify({
|
|
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
|
+
}
|
|
302
|
+
function buildCodexConfigToml(_apiKey, endpointWithV1, model) {
|
|
303
|
+
const base = endpointWithV1.replace(/\/+$/, "");
|
|
304
|
+
return [
|
|
305
|
+
'model_provider = "trinity"',
|
|
306
|
+
`model = ${JSON.stringify(model)}`,
|
|
307
|
+
"disable_response_storage = true",
|
|
308
|
+
"",
|
|
309
|
+
"[model_providers.trinity]",
|
|
310
|
+
'name = "Trinity"',
|
|
311
|
+
`base_url = ${JSON.stringify(base)}`,
|
|
312
|
+
'wire_api = "responses"',
|
|
313
|
+
"requires_openai_auth = true"
|
|
314
|
+
].join("\n");
|
|
315
|
+
}
|
|
316
|
+
function buildOpenCodeConfigJson(apiKey, endpoint, model, protocol) {
|
|
317
|
+
const npm = protocol === "anthropic_messages" ? "@ai-sdk/anthropic" : protocol === "responses" ? "@ai-sdk/openai" : "@ai-sdk/openai-compatible";
|
|
318
|
+
return JSON.stringify({
|
|
319
|
+
$schema: "https://opencode.ai/config.json",
|
|
320
|
+
model: `trinity/${model}`,
|
|
321
|
+
provider: {
|
|
322
|
+
trinity: {
|
|
323
|
+
npm,
|
|
324
|
+
name: "Trinity",
|
|
325
|
+
options: {
|
|
326
|
+
baseURL: endpoint.replace(/\/+$/, ""),
|
|
327
|
+
apiKey
|
|
328
|
+
},
|
|
329
|
+
models: {
|
|
330
|
+
[model]: { name: model }
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function buildOpenClawConfigJson(apiKey, endpoint, model, protocol) {
|
|
337
|
+
const api = protocol === "anthropic_messages" ? "anthropic-messages" : protocol === "responses" ? "openai-responses" : "openai-completions";
|
|
338
|
+
return JSON.stringify({
|
|
339
|
+
models: {
|
|
340
|
+
mode: "merge",
|
|
341
|
+
providers: {
|
|
342
|
+
trinity: {
|
|
343
|
+
baseUrl: endpoint.replace(/\/+$/, ""),
|
|
344
|
+
apiKey,
|
|
345
|
+
api,
|
|
346
|
+
models: [
|
|
347
|
+
{
|
|
348
|
+
id: model,
|
|
349
|
+
name: model,
|
|
350
|
+
reasoning: false,
|
|
351
|
+
input: ["text"],
|
|
352
|
+
contextWindow: 128e3,
|
|
353
|
+
maxTokens: 8192
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
agents: {
|
|
360
|
+
defaults: {
|
|
361
|
+
model: { primary: `trinity/${model}` },
|
|
362
|
+
models: { "trinity/*": {} }
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function buildCcSwitchDeepLink(options) {
|
|
368
|
+
const root = (options.rootBaseUrl ?? TRINITY_BASE_URL).replace(/\/+$/, "");
|
|
369
|
+
const withoutV1 = root.replace(/\/v1\/?$/, "");
|
|
370
|
+
const withV1 = `${withoutV1}/v1`;
|
|
371
|
+
let endpoint;
|
|
372
|
+
let config;
|
|
373
|
+
let configFormat;
|
|
374
|
+
switch (options.app) {
|
|
375
|
+
case "claude": {
|
|
376
|
+
endpoint = withoutV1;
|
|
377
|
+
configFormat = "json";
|
|
378
|
+
config = buildClaudeConfigJson(options.apiKey, endpoint, options.model);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
case "codex": {
|
|
382
|
+
endpoint = withV1;
|
|
383
|
+
configFormat = "toml";
|
|
384
|
+
config = buildCodexConfigToml(options.apiKey, endpoint, options.model);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
case "opencode": {
|
|
388
|
+
endpoint = options.protocol === "anthropic_messages" ? withoutV1 : withV1;
|
|
389
|
+
configFormat = "json";
|
|
390
|
+
config = buildOpenCodeConfigJson(
|
|
391
|
+
options.apiKey,
|
|
392
|
+
endpoint,
|
|
393
|
+
options.model,
|
|
394
|
+
options.protocol
|
|
395
|
+
);
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case "openclaw": {
|
|
399
|
+
endpoint = options.protocol === "anthropic_messages" ? withoutV1 : withV1;
|
|
400
|
+
configFormat = "json";
|
|
401
|
+
config = buildOpenClawConfigJson(
|
|
402
|
+
options.apiKey,
|
|
403
|
+
endpoint,
|
|
404
|
+
options.model,
|
|
405
|
+
options.protocol
|
|
406
|
+
);
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
case "gemini":
|
|
410
|
+
throw new Error("gemini \u4E0D\u53D7\u652F\u6301");
|
|
411
|
+
}
|
|
412
|
+
const params = new URLSearchParams();
|
|
413
|
+
params.set("resource", "provider");
|
|
414
|
+
params.set("app", options.app);
|
|
415
|
+
params.set("name", TRINITY_CCSWITCH_PROVIDER_NAME);
|
|
416
|
+
params.set("endpoint", endpoint);
|
|
417
|
+
params.set("apiKey", options.apiKey);
|
|
418
|
+
params.set("homepage", "https://trinitydesk.ai");
|
|
419
|
+
params.set("model", options.model);
|
|
420
|
+
if (options.app === "claude") {
|
|
421
|
+
params.set("sonnetModel", options.model);
|
|
422
|
+
params.set("haikuModel", options.model);
|
|
423
|
+
}
|
|
424
|
+
params.set("config", encodeConfigBase64(config));
|
|
425
|
+
params.set("configFormat", configFormat);
|
|
426
|
+
if (options.enabled !== false) {
|
|
427
|
+
params.set("enabled", "true");
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
url: `ccswitch://v1/import?${params.toString()}`,
|
|
431
|
+
endpoint,
|
|
432
|
+
configFormat
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function ccSwitchImportDir() {
|
|
436
|
+
return path.join(os.homedir(), ".cc-switch", "trinity-imports");
|
|
437
|
+
}
|
|
438
|
+
function persistCcSwitchDeepLink(app, url) {
|
|
439
|
+
const dir = ccSwitchImportDir();
|
|
440
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
441
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
442
|
+
const base = path.join(dir, `trinity-${app}-${stamp}`);
|
|
443
|
+
const urlFile = `${base}.url`;
|
|
444
|
+
const txtFile = `${base}.txt`;
|
|
445
|
+
fs.writeFileSync(
|
|
446
|
+
urlFile,
|
|
447
|
+
`[InternetShortcut]\r
|
|
448
|
+
URL=${url}\r
|
|
449
|
+
`,
|
|
450
|
+
"utf8"
|
|
451
|
+
);
|
|
452
|
+
fs.writeFileSync(txtFile, `${url}
|
|
453
|
+
`, "utf8");
|
|
454
|
+
return { urlFile, txtFile };
|
|
455
|
+
}
|
|
456
|
+
async function openCcSwitchDeepLink(url, urlFile) {
|
|
457
|
+
if (url.length > CCSWITCH_URL_SAFE_MAX) {
|
|
458
|
+
return "too_long";
|
|
459
|
+
}
|
|
460
|
+
const platform = process.platform;
|
|
461
|
+
if (platform === "win32") {
|
|
462
|
+
const command = urlFile && fs.existsSync(urlFile) ? `Start-Process -LiteralPath ${JSON.stringify(urlFile)}` : `Start-Process ${JSON.stringify(url)}`;
|
|
463
|
+
await execa(
|
|
464
|
+
"powershell.exe",
|
|
465
|
+
["-NoProfile", "-NonInteractive", "-Command", command],
|
|
466
|
+
{ reject: false, windowsHide: true }
|
|
467
|
+
);
|
|
468
|
+
return "opened";
|
|
469
|
+
}
|
|
470
|
+
if (platform === "darwin") {
|
|
471
|
+
await execa("open", [url], { reject: false });
|
|
472
|
+
return "opened";
|
|
473
|
+
}
|
|
474
|
+
await execa("xdg-open", [url], { reject: false });
|
|
475
|
+
return "opened";
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/merge-claude-env.ts
|
|
479
|
+
import fs2 from "fs";
|
|
480
|
+
import os2 from "os";
|
|
481
|
+
import path2 from "path";
|
|
181
482
|
function claudeSettingsPath() {
|
|
182
|
-
return
|
|
483
|
+
return path2.join(os2.homedir(), ".claude", "settings.json");
|
|
183
484
|
}
|
|
184
485
|
function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
|
|
185
486
|
const filePath = claudeSettingsPath();
|
|
186
|
-
if (!
|
|
487
|
+
if (!fs2.existsSync(filePath)) {
|
|
187
488
|
return;
|
|
188
489
|
}
|
|
189
|
-
const raw =
|
|
490
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
190
491
|
let settings;
|
|
191
492
|
try {
|
|
192
493
|
settings = JSON.parse(raw);
|
|
@@ -199,8 +500,8 @@ function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
|
|
|
199
500
|
ANTHROPIC_MODEL: claudeModel,
|
|
200
501
|
ANTHROPIC_SMALL_FAST_MODEL: fastModel
|
|
201
502
|
};
|
|
202
|
-
|
|
203
|
-
|
|
503
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
504
|
+
fs2.writeFileSync(filePath, `${JSON.stringify(settings, null, 2)}
|
|
204
505
|
`, "utf8");
|
|
205
506
|
}
|
|
206
507
|
|
|
@@ -366,7 +667,7 @@ function protocolLabel(protocol) {
|
|
|
366
667
|
}
|
|
367
668
|
|
|
368
669
|
// src/zcf.ts
|
|
369
|
-
import { execa } from "execa";
|
|
670
|
+
import { execa as execa2 } from "execa";
|
|
370
671
|
function sharedZcfArgs(verbose) {
|
|
371
672
|
return [
|
|
372
673
|
"zcf",
|
|
@@ -405,7 +706,7 @@ async function runZcfInit(options) {
|
|
|
405
706
|
args.push("--api-haiku-model", options.fastModel);
|
|
406
707
|
args.push("--api-sonnet-model", options.primaryModel);
|
|
407
708
|
}
|
|
408
|
-
await
|
|
709
|
+
await execa2("npx", ["-y", ...args], {
|
|
409
710
|
stdio: options.verbose ? "inherit" : "pipe",
|
|
410
711
|
reject: true,
|
|
411
712
|
timeout: 18e4
|
|
@@ -581,11 +882,12 @@ async function promptToolType(initial) {
|
|
|
581
882
|
const value = await p.select({
|
|
582
883
|
message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
|
|
583
884
|
options: [
|
|
584
|
-
{ value: "claude", label: "Claude Code" },
|
|
585
|
-
{ value: "codex", label: "Codex CLI" },
|
|
586
|
-
{ value: "opencode", label: "OpenCode" },
|
|
587
|
-
{ value: "openclaw", label: "OpenClaw" },
|
|
588
|
-
{ value: "zcode", label: "ZCode" }
|
|
885
|
+
{ value: "claude", label: "Claude Code\uFF08\u76F4\u8FDE\uFF09" },
|
|
886
|
+
{ value: "codex", label: "Codex CLI\uFF08\u76F4\u8FDE\uFF09" },
|
|
887
|
+
{ value: "opencode", label: "OpenCode\uFF08\u76F4\u8FDE\uFF09" },
|
|
888
|
+
{ value: "openclaw", label: "OpenClaw\uFF08\u76F4\u8FDE\uFF09" },
|
|
889
|
+
{ value: "zcode", label: "ZCode" },
|
|
890
|
+
{ value: "ccswitch", label: "CC Switch\uFF08\u6309 App \u69FD\u4F4D\u5BFC\u5165\uFF09" }
|
|
589
891
|
]
|
|
590
892
|
});
|
|
591
893
|
if (p.isCancel(value)) {
|
|
@@ -594,13 +896,31 @@ async function promptToolType(initial) {
|
|
|
594
896
|
}
|
|
595
897
|
return value;
|
|
596
898
|
}
|
|
597
|
-
async function
|
|
899
|
+
async function promptCcSwitchApps(initial) {
|
|
900
|
+
if (initial && initial.length > 0) {
|
|
901
|
+
return initial;
|
|
902
|
+
}
|
|
903
|
+
const value = await p.multiselect({
|
|
904
|
+
message: "\u8981\u5199\u5165 CC Switch \u7684\u54EA\u4E9B App \u69FD\u4F4D\uFF1F\uFF08\u975E Universal\uFF09",
|
|
905
|
+
options: CC_SWITCH_SUPPORTED_APPS.map((app) => ({
|
|
906
|
+
value: app,
|
|
907
|
+
label: app
|
|
908
|
+
})),
|
|
909
|
+
required: true
|
|
910
|
+
});
|
|
911
|
+
if (p.isCancel(value)) {
|
|
912
|
+
p.cancel("\u5DF2\u53D6\u6D88");
|
|
913
|
+
process.exit(0);
|
|
914
|
+
}
|
|
915
|
+
return value;
|
|
916
|
+
}
|
|
917
|
+
async function promptModel(type, app, initial) {
|
|
598
918
|
if (initial?.trim()) {
|
|
599
919
|
return initial.trim();
|
|
600
920
|
}
|
|
601
|
-
const fallback = defaultModelForType(type);
|
|
921
|
+
const fallback = defaultModelForType(type, app);
|
|
602
922
|
const value = await p.text({
|
|
603
|
-
message: `${toolLabel(type)} \u9ED8\u8BA4 model_code`,
|
|
923
|
+
message: `${toolLabel(type, app)} \u9ED8\u8BA4 model_code`,
|
|
604
924
|
placeholder: fallback,
|
|
605
925
|
initialValue: fallback,
|
|
606
926
|
validate: (input) => {
|
|
@@ -613,8 +933,8 @@ async function promptModel(type, initial) {
|
|
|
613
933
|
}
|
|
614
934
|
return String(value).trim();
|
|
615
935
|
}
|
|
616
|
-
function recommendedHint(
|
|
617
|
-
switch (
|
|
936
|
+
function recommendedHint(protocolTool, recommended) {
|
|
937
|
+
switch (protocolTool) {
|
|
618
938
|
case "claude":
|
|
619
939
|
return recommended.claude;
|
|
620
940
|
case "codex":
|
|
@@ -627,6 +947,13 @@ function recommendedHint(toolType, recommended) {
|
|
|
627
947
|
return recommended.zcode;
|
|
628
948
|
}
|
|
629
949
|
}
|
|
950
|
+
function protocolToolFor(type, app) {
|
|
951
|
+
if (type === "ccswitch") {
|
|
952
|
+
if (!app) throw new Error("ccswitch \u7F3A\u5C11 --app");
|
|
953
|
+
return protocolToolForCcSwitchApp(app);
|
|
954
|
+
}
|
|
955
|
+
return type;
|
|
956
|
+
}
|
|
630
957
|
async function run() {
|
|
631
958
|
let opts;
|
|
632
959
|
try {
|
|
@@ -645,13 +972,38 @@ async function run() {
|
|
|
645
972
|
}
|
|
646
973
|
p.intro(pc.bgCyan(pc.black(` ${PACKAGE_NAME} v${PACKAGE_VERSION} `)));
|
|
647
974
|
const toolType = await promptToolType(opts.type);
|
|
975
|
+
const ccApps = toolType === "ccswitch" ? await promptCcSwitchApps(opts.apps) : void 0;
|
|
976
|
+
if (ccApps) {
|
|
977
|
+
for (const app of ccApps) {
|
|
978
|
+
try {
|
|
979
|
+
assertCcSwitchAppSupported(app);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
982
|
+
return 1;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
const primaryApp = ccApps?.[0];
|
|
648
987
|
const apiKey = await promptApiKey(opts.apiKey);
|
|
649
|
-
let model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType) : await promptModel(toolType);
|
|
988
|
+
let model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType, void 0, primaryApp) : await promptModel(toolType, primaryApp);
|
|
650
989
|
const apiRoot = rootBaseUrl(opts.baseUrl);
|
|
651
|
-
p.log.info(
|
|
990
|
+
p.log.info(
|
|
991
|
+
`\u5DE5\u5177: ${toolLabel(toolType, primaryApp)}` + (ccApps && ccApps.length > 1 ? `\uFF08${ccApps.join(",")}\uFF09` : "") + ` \xB7 \u6A21\u578B: ${model}`
|
|
992
|
+
);
|
|
993
|
+
if (toolType === "ccswitch") {
|
|
994
|
+
p.note(
|
|
995
|
+
[
|
|
996
|
+
"\u5C06\u751F\u6210 App-specific Provider Deep Link\uFF08\u975E Universal\uFF09",
|
|
997
|
+
"\u5BFC\u5165\u540E\u8BF7\u5728\u5BF9\u5E94 App \u4E0B Activate\u300CTrinity\u300D",
|
|
998
|
+
"\u8BF7\u52FF\u540C\u65F6\u5BF9\u672C\u5DE5\u5177\u518D\u8DD1 --type \u76F4\u8FDE\uFF0C\u4EE5\u514D\u914D\u7F6E\u4E92\u76F8\u8986\u76D6"
|
|
999
|
+
].join("\n"),
|
|
1000
|
+
"CC Switch"
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
652
1003
|
const spinner2 = p.spinner();
|
|
653
1004
|
let verifiedModelIds;
|
|
654
1005
|
let verifyFailed = false;
|
|
1006
|
+
const protocolToolHint = protocolToolFor(toolType, primaryApp);
|
|
655
1007
|
if (!opts.skipVerify) {
|
|
656
1008
|
spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
|
|
657
1009
|
const result = await verifyTrinityApi(apiKey, apiRoot);
|
|
@@ -663,7 +1015,7 @@ async function run() {
|
|
|
663
1015
|
return 1;
|
|
664
1016
|
}
|
|
665
1017
|
const recommended = pickRecommendedModels(verifiedModelIds);
|
|
666
|
-
const hint = recommendedHint(
|
|
1018
|
+
const hint = recommendedHint(protocolToolHint, recommended);
|
|
667
1019
|
const allowed = pickAllowedDefaultModel(model, verifiedModelIds, [
|
|
668
1020
|
hint,
|
|
669
1021
|
recommended.claude,
|
|
@@ -671,7 +1023,7 @@ async function run() {
|
|
|
671
1023
|
recommended.opencode,
|
|
672
1024
|
recommended.openclaw,
|
|
673
1025
|
recommended.zcode,
|
|
674
|
-
defaultModelForType(toolType)
|
|
1026
|
+
defaultModelForType(toolType, primaryApp)
|
|
675
1027
|
]);
|
|
676
1028
|
if (!allowed) {
|
|
677
1029
|
p.outro(pc.red("\u65E0\u6CD5\u5728\u8BE5 API Key \u7684\u53EF\u7528\u751F\u6587\u6A21\u578B\u4E2D\u9009\u51FA\u9ED8\u8BA4\u6A21\u578B"));
|
|
@@ -700,11 +1052,107 @@ async function run() {
|
|
|
700
1052
|
"\u63D0\u793A"
|
|
701
1053
|
);
|
|
702
1054
|
}
|
|
1055
|
+
if (toolType === "ccswitch" && ccApps) {
|
|
1056
|
+
const saved = [];
|
|
1057
|
+
for (const app of ccApps) {
|
|
1058
|
+
spinner2.start(`\u751F\u6210 CC Switch Deep Link\uFF08${app}\uFF09\u2026`);
|
|
1059
|
+
let appModel = model;
|
|
1060
|
+
let protocolRes2;
|
|
1061
|
+
try {
|
|
1062
|
+
protocolRes2 = await resolveProtocol({
|
|
1063
|
+
tool: protocolToolForCcSwitchApp(app),
|
|
1064
|
+
apiKey,
|
|
1065
|
+
model: appModel,
|
|
1066
|
+
rootBaseUrl: apiRoot,
|
|
1067
|
+
skipFetch: opts.skipVerify
|
|
1068
|
+
});
|
|
1069
|
+
} catch (firstError) {
|
|
1070
|
+
const fallback = defaultModelForCcSwitchApp(app);
|
|
1071
|
+
const canRetry = fallback !== appModel && (!verifiedModelIds || verifiedModelIds.includes(fallback));
|
|
1072
|
+
if (!canRetry) {
|
|
1073
|
+
spinner2.stop(pc.red(`${app} \u534F\u8BAE\u89E3\u6790\u5931\u8D25`));
|
|
1074
|
+
console.error(
|
|
1075
|
+
pc.red(
|
|
1076
|
+
firstError instanceof Error ? firstError.message : String(firstError)
|
|
1077
|
+
)
|
|
1078
|
+
);
|
|
1079
|
+
p.note(
|
|
1080
|
+
`\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`,
|
|
1081
|
+
"\u63D0\u793A"
|
|
1082
|
+
);
|
|
1083
|
+
return 1;
|
|
1084
|
+
}
|
|
1085
|
+
try {
|
|
1086
|
+
protocolRes2 = await resolveProtocol({
|
|
1087
|
+
tool: protocolToolForCcSwitchApp(app),
|
|
1088
|
+
apiKey,
|
|
1089
|
+
model: fallback,
|
|
1090
|
+
rootBaseUrl: apiRoot,
|
|
1091
|
+
skipFetch: opts.skipVerify
|
|
1092
|
+
});
|
|
1093
|
+
p.note(
|
|
1094
|
+
`${app} \u69FD\uFF1A\u6A21\u578B ${appModel} \u4E0D\u517C\u5BB9\uFF0C\u5DF2\u6539\u7528 ${fallback}`,
|
|
1095
|
+
"\u6A21\u578B"
|
|
1096
|
+
);
|
|
1097
|
+
appModel = fallback;
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
spinner2.stop(pc.red(`${app} \u534F\u8BAE\u89E3\u6790\u5931\u8D25`));
|
|
1100
|
+
console.error(
|
|
1101
|
+
pc.red(error instanceof Error ? error.message : String(error))
|
|
1102
|
+
);
|
|
1103
|
+
return 1;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
const protocol2 = protocolRes2.protocol;
|
|
1107
|
+
try {
|
|
1108
|
+
const link = buildCcSwitchDeepLink({
|
|
1109
|
+
app,
|
|
1110
|
+
apiKey,
|
|
1111
|
+
rootBaseUrl: apiRoot,
|
|
1112
|
+
model: appModel,
|
|
1113
|
+
protocol: protocol2
|
|
1114
|
+
});
|
|
1115
|
+
const files = persistCcSwitchDeepLink(app, link.url);
|
|
1116
|
+
saved.push(files.urlFile);
|
|
1117
|
+
spinner2.stop(
|
|
1118
|
+
`\u5DF2\u751F\u6210 ${app}\uFF1A${protocolLabel(protocol2)} \u2192 ${link.endpoint}`
|
|
1119
|
+
);
|
|
1120
|
+
p.log.info(`\u5BFC\u5165\u6587\u4EF6: ${files.urlFile}`);
|
|
1121
|
+
if (opts.printOnly) {
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
const openResult = await openCcSwitchDeepLink(link.url, files.urlFile);
|
|
1125
|
+
if (openResult === "too_long") {
|
|
1126
|
+
p.log.warn(
|
|
1127
|
+
`\u94FE\u63A5\u8FC7\u957F\uFF08${link.url.length}\uFF09\uFF0C\u672A\u81EA\u52A8\u5524\u8D77\uFF1B\u8BF7\u53CC\u51FB\u5BFC\u5165\u6587\u4EF6`
|
|
1128
|
+
);
|
|
1129
|
+
} else {
|
|
1130
|
+
p.log.info("\u5DF2\u5524\u8D77 CC Switch\uFF0C\u8BF7\u5728\u5BFC\u5165\u6846\u4E2D\u786E\u8BA4");
|
|
1131
|
+
}
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
spinner2.stop(pc.red(`${app} Deep Link \u5931\u8D25`));
|
|
1134
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
1135
|
+
return 1;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
if (verifyFailed) {
|
|
1139
|
+
p.outro(pc.yellow("\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
|
|
1140
|
+
return 1;
|
|
1141
|
+
}
|
|
1142
|
+
p.note(
|
|
1143
|
+
opts.printOnly ? `\u5DF2\u751F\u6210\u5BFC\u5165\u6587\u4EF6\uFF08\u672A\u5524\u8D77\uFF09:
|
|
1144
|
+
${saved.join("\n")}` : `\u8BF7\u5728 CC Switch \u5BFC\u5165\u6846\u70B9\u786E\u8BA4\uFF1B\u672A\u5F39\u7A97\u5219\u53CC\u51FB:
|
|
1145
|
+
${saved.join("\n")}`,
|
|
1146
|
+
"\u4E0B\u4E00\u6B65"
|
|
1147
|
+
);
|
|
1148
|
+
p.outro(pc.green("\u5B8C\u6210"));
|
|
1149
|
+
return 0;
|
|
1150
|
+
}
|
|
703
1151
|
spinner2.start("\u89E3\u6790\u6A21\u578B\u534F\u8BAE\u2026");
|
|
704
1152
|
let protocolRes;
|
|
705
1153
|
try {
|
|
706
1154
|
protocolRes = await resolveProtocol({
|
|
707
|
-
tool: toolType,
|
|
1155
|
+
tool: protocolToolFor(toolType),
|
|
708
1156
|
apiKey,
|
|
709
1157
|
model,
|
|
710
1158
|
rootBaseUrl: apiRoot,
|
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.1",
|
|
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"
|