trinity-config 1.2.5 → 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.
Files changed (3) hide show
  1. package/README.md +21 -7
  2. package/dist/index.js +527 -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.0";
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;
46
96
  }
47
- function toolLabel(type) {
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
+ }
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,262 @@ 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
+ 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";
181
522
  function claudeSettingsPath() {
182
- return path.join(os.homedir(), ".claude", "settings.json");
523
+ return path2.join(os2.homedir(), ".claude", "settings.json");
183
524
  }
184
525
  function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
185
526
  const filePath = claudeSettingsPath();
186
- if (!fs.existsSync(filePath)) {
527
+ if (!fs2.existsSync(filePath)) {
187
528
  return;
188
529
  }
189
- const raw = fs.readFileSync(filePath, "utf8");
530
+ const raw = fs2.readFileSync(filePath, "utf8");
190
531
  let settings;
191
532
  try {
192
533
  settings = JSON.parse(raw);
@@ -199,8 +540,8 @@ function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
199
540
  ANTHROPIC_MODEL: claudeModel,
200
541
  ANTHROPIC_SMALL_FAST_MODEL: fastModel
201
542
  };
202
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
203
- fs.writeFileSync(filePath, `${JSON.stringify(settings, null, 2)}
543
+ fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
544
+ fs2.writeFileSync(filePath, `${JSON.stringify(settings, null, 2)}
204
545
  `, "utf8");
205
546
  }
206
547
 
@@ -366,7 +707,7 @@ function protocolLabel(protocol) {
366
707
  }
367
708
 
368
709
  // src/zcf.ts
369
- import { execa } from "execa";
710
+ import { execa as execa2 } from "execa";
370
711
  function sharedZcfArgs(verbose) {
371
712
  return [
372
713
  "zcf",
@@ -405,7 +746,7 @@ async function runZcfInit(options) {
405
746
  args.push("--api-haiku-model", options.fastModel);
406
747
  args.push("--api-sonnet-model", options.primaryModel);
407
748
  }
408
- await execa("npx", ["-y", ...args], {
749
+ await execa2("npx", ["-y", ...args], {
409
750
  stdio: options.verbose ? "inherit" : "pipe",
410
751
  reject: true,
411
752
  timeout: 18e4
@@ -581,11 +922,12 @@ async function promptToolType(initial) {
581
922
  const value = await p.select({
582
923
  message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
583
924
  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" }
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" }
589
931
  ]
590
932
  });
591
933
  if (p.isCancel(value)) {
@@ -594,13 +936,31 @@ async function promptToolType(initial) {
594
936
  }
595
937
  return value;
596
938
  }
597
- async function promptModel(type, initial) {
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) {
598
958
  if (initial?.trim()) {
599
959
  return initial.trim();
600
960
  }
601
- const fallback = defaultModelForType(type);
961
+ const fallback = defaultModelForType(type, app);
602
962
  const value = await p.text({
603
- message: `${toolLabel(type)} \u9ED8\u8BA4 model_code`,
963
+ message: `${toolLabel(type, app)} \u9ED8\u8BA4 model_code`,
604
964
  placeholder: fallback,
605
965
  initialValue: fallback,
606
966
  validate: (input) => {
@@ -613,8 +973,8 @@ async function promptModel(type, initial) {
613
973
  }
614
974
  return String(value).trim();
615
975
  }
616
- function recommendedHint(toolType, recommended) {
617
- switch (toolType) {
976
+ function recommendedHint(protocolTool, recommended) {
977
+ switch (protocolTool) {
618
978
  case "claude":
619
979
  return recommended.claude;
620
980
  case "codex":
@@ -627,6 +987,13 @@ function recommendedHint(toolType, recommended) {
627
987
  return recommended.zcode;
628
988
  }
629
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
+ }
630
997
  async function run() {
631
998
  let opts;
632
999
  try {
@@ -645,13 +1012,38 @@ async function run() {
645
1012
  }
646
1013
  p.intro(pc.bgCyan(pc.black(` ${PACKAGE_NAME} v${PACKAGE_VERSION} `)));
647
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];
648
1027
  const apiKey = await promptApiKey(opts.apiKey);
649
- let model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType) : await promptModel(toolType);
1028
+ let model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType, void 0, primaryApp) : await promptModel(toolType, primaryApp);
650
1029
  const apiRoot = rootBaseUrl(opts.baseUrl);
651
- p.log.info(`\u5DE5\u5177: ${toolLabel(toolType)} \xB7 \u6A21\u578B: ${model}`);
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
+ }
652
1043
  const spinner2 = p.spinner();
653
1044
  let verifiedModelIds;
654
1045
  let verifyFailed = false;
1046
+ const protocolToolHint = protocolToolFor(toolType, primaryApp);
655
1047
  if (!opts.skipVerify) {
656
1048
  spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
657
1049
  const result = await verifyTrinityApi(apiKey, apiRoot);
@@ -663,7 +1055,7 @@ async function run() {
663
1055
  return 1;
664
1056
  }
665
1057
  const recommended = pickRecommendedModels(verifiedModelIds);
666
- const hint = recommendedHint(toolType, recommended);
1058
+ const hint = recommendedHint(protocolToolHint, recommended);
667
1059
  const allowed = pickAllowedDefaultModel(model, verifiedModelIds, [
668
1060
  hint,
669
1061
  recommended.claude,
@@ -671,7 +1063,7 @@ async function run() {
671
1063
  recommended.opencode,
672
1064
  recommended.openclaw,
673
1065
  recommended.zcode,
674
- defaultModelForType(toolType)
1066
+ defaultModelForType(toolType, primaryApp)
675
1067
  ]);
676
1068
  if (!allowed) {
677
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"));
@@ -700,11 +1092,112 @@ async function run() {
700
1092
  "\u63D0\u793A"
701
1093
  );
702
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;
1195
+ }
703
1196
  spinner2.start("\u89E3\u6790\u6A21\u578B\u534F\u8BAE\u2026");
704
1197
  let protocolRes;
705
1198
  try {
706
1199
  protocolRes = await resolveProtocol({
707
- tool: toolType,
1200
+ tool: protocolToolFor(toolType),
708
1201
  apiKey,
709
1202
  model,
710
1203
  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.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"