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 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
- 写入客户端的模型列表来自 `GET /v1/models?modality=text`(仅生文/多模态,不含生图/生视频)。
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.2.4";
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
- 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
+ }
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 resolveModel(type, model) {
34
- 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);
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("--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;
42
96
  }
43
- 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
+ }
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 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
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("\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
+ );
171
278
  }
172
279
 
173
- // src/merge-claude-env.ts
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 path.join(os.homedir(), ".claude", "settings.json");
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 (!fs.existsSync(filePath)) {
527
+ if (!fs2.existsSync(filePath)) {
183
528
  return;
184
529
  }
185
- const raw = fs.readFileSync(filePath, "utf8");
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
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
199
- 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)}
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 execa("npx", ["-y", ...args], {
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-XGIZYPOW.js");
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-KMG3FYK6.js");
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-PTPY6RXN.js");
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${modelIds.length} \u4E2A\u751F\u6587\u6A21\u578B\uFF09`,
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 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) {
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(toolType, recommended) {
613
- switch (toolType) {
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
- const 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);
646
1029
  const apiRoot = rootBaseUrl(opts.baseUrl);
647
- 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
+ }
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
- const recommended = pickRecommendedModels(result.modelIds ?? []);
658
- const hint = recommendedHint(toolType, recommended);
659
- if (hint && hint !== model) {
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-QS4QYSMM.js";
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 existingModels = Array.isArray(existing.models) ? existing.models : [];
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: [...byId.values()]
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-QS4QYSMM.js";
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 existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
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-QS4QYSMM.js";
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.2.4",
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"
@@ -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
- };