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.
Files changed (3) hide show
  1. package/README.md +21 -7
  2. package/dist/index.js +482 -34
  3. 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.2.5";
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
- function defaultModelForType(type) {
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 resolveModel(type, model) {
38
- return model?.trim() || defaultModelForType(type);
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("--type \u987B\u4E3A claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
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 toolLabel(type) {
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 openclaw -k xh-your-key
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("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
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/merge-claude-env.ts
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 path.join(os.homedir(), ".claude", "settings.json");
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 (!fs.existsSync(filePath)) {
487
+ if (!fs2.existsSync(filePath)) {
187
488
  return;
188
489
  }
189
- const raw = fs.readFileSync(filePath, "utf8");
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
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
203
- fs.writeFileSync(filePath, `${JSON.stringify(settings, null, 2)}
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 execa("npx", ["-y", ...args], {
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 promptModel(type, initial) {
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(toolType, recommended) {
617
- switch (toolType) {
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(`\u5DE5\u5177: ${toolLabel(toolType)} \xB7 \u6A21\u578B: ${model}`);
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(toolType, recommended);
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.2.5",
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"