trinity-config 1.1.1 → 1.2.2

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,58 +1,21 @@
1
1
  # trinity-config
2
2
 
3
- > 一键把 [Trinity API](https://trinitydesk.ai) 接入 **Claude Code** **Codex CLI** —— 指定工具类型 + 粘贴 `xh-` Key 即用
3
+ 一键接入 TrinityClaude Code / Codex / OpenCode / OpenClaw / ZCode。
4
4
 
5
- ## 快速开始
6
-
7
- ### 交互式
8
-
9
- ```bash
10
- npx -y trinity-config
11
- ```
12
-
13
- 按提示选择 **工具类型**、粘贴 Key、确认 **model_code**。
14
-
15
- ### 一行命令(推荐)
5
+ 协议按 `GET /v1/models/{code}` 的 `capabilities.text.interfaces` **自动择优**(Anthropic → Responses → Completions),再与工具能力求交。
16
6
 
17
7
  ```bash
18
- # Claude Code
19
- npx -y trinity-config --type claude -k xh-你的key
20
-
21
- # Codex CLI
22
- npx -y trinity-config --type codex -k xh-你的key --model gpt-5.4
8
+ npx -y trinity-config --type zcode -k xh-你的key --model gpt-5.4
23
9
  ```
24
10
 
25
- 每次只配置 **一个** 工具;两个都要用时各执行一次。
26
-
27
- ## 命令行参数
28
-
29
- | 参数 | 简写 | 说明 |
30
- | --- | --- | --- |
31
- | `--type` | `-t` | **必填(非交互)**:`claude` \| `codex` |
32
- | `--api-key` | `-k` | Trinity API Key |
33
- | `--model` | `-M` | 默认 model_code(省略则按 type 内置推荐) |
34
- | `--base-url` | | 默认 `https://api.trinitydesk.ai` |
35
- | `--skip-verify` | | 跳过连通性验证 |
36
- | `--verbose` | `-v` | 显示 zcf 详细输出 |
37
-
38
- ### 按 type 的默认模型
39
-
40
- | `--type` | 默认 `--model` |
11
+ | type | 配置文件 |
41
12
  | --- | --- |
42
- | `claude` | `claude-sonnet-4-6` |
43
- | `codex` | `gpt-5.4` |
44
-
45
- ## 扩展新工具
46
-
47
- 后续增加 Cursor、OpenCode 等时,只需新增 `type` 枚举与对应写入逻辑,**不再**为每个工具增加 `--xxx-model` 参数。
48
-
49
- ## 注意
50
-
51
- - 配置后**所有模型调用走 Trinity 计费**。
52
- - 非交互模式必须同时提供 `--type` 与 `-k`。
53
- - **Claude Code**:经 zcf 写入 `~/.claude/settings.json`
54
- - **Codex CLI**:**直接** merge `~/.codex/config.toml` + `auth.json`(不调用 zcf;zcf cx 会重置为 ChatGPT 官方登录)
13
+ | claude | `~/.claude/settings.json` |
14
+ | codex | `~/.codex/config.toml` |
15
+ | opencode | `~/.config/opencode/opencode.json` |
16
+ | openclaw | `~/.openclaw/openclaw.json` |
17
+ | zcode | `~/.zcode/v2/config.json` |
55
18
 
56
- ## License
19
+ Anthropic Base **不带** `/v1`;Responses / Completions **带** `/v1`。
57
20
 
58
- MIT
21
+ License: MIT
package/dist/index.js CHANGED
@@ -5,53 +5,91 @@ import pc from "picocolors";
5
5
 
6
6
  // src/constants.ts
7
7
  var PACKAGE_NAME = "trinity-config";
8
- var PACKAGE_VERSION = "1.1.1";
8
+ var PACKAGE_VERSION = "1.2.2";
9
9
  var TRINITY_BASE_URL = "https://api.trinitydesk.ai";
10
10
  var DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
11
11
  var DEFAULT_CLAUDE_FAST_MODEL = "claude-sonnet-4-6";
12
12
  var DEFAULT_CODEX_MODEL = "gpt-5.4";
13
+ var DEFAULT_OPENCODE_MODEL = "gpt-5.4";
14
+ var DEFAULT_OPENCLAW_MODEL = "gpt-5.4";
15
+ var DEFAULT_ZCODE_MODEL = "gpt-5.4";
13
16
  var VERIFY_TIMEOUT_MS = 15e3;
14
17
 
15
18
  // src/model.ts
16
19
  function defaultModelForType(type) {
17
- return type === "claude" ? DEFAULT_CLAUDE_MODEL : DEFAULT_CODEX_MODEL;
20
+ switch (type) {
21
+ case "claude":
22
+ return DEFAULT_CLAUDE_MODEL;
23
+ case "codex":
24
+ return DEFAULT_CODEX_MODEL;
25
+ case "opencode":
26
+ return DEFAULT_OPENCODE_MODEL;
27
+ case "openclaw":
28
+ return DEFAULT_OPENCLAW_MODEL;
29
+ case "zcode":
30
+ return DEFAULT_ZCODE_MODEL;
31
+ }
18
32
  }
19
33
  function resolveModel(type, model) {
20
34
  return model?.trim() || defaultModelForType(type);
21
35
  }
22
36
  function parseToolType(raw) {
23
37
  const value = raw.trim().toLowerCase();
24
- if (value === "claude" || value === "codex") {
38
+ if (value === "claude" || value === "codex" || value === "opencode" || value === "openclaw" || value === "zcode") {
25
39
  return value;
26
40
  }
27
- throw new Error("--type \u987B\u4E3A claude \u6216 codex");
41
+ throw new Error("--type \u987B\u4E3A claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
28
42
  }
29
43
  function toolLabel(type) {
30
- return type === "claude" ? "Claude Code" : "Codex CLI";
44
+ switch (type) {
45
+ case "claude":
46
+ return "Claude Code";
47
+ case "codex":
48
+ return "Codex CLI";
49
+ case "opencode":
50
+ return "OpenCode";
51
+ case "openclaw":
52
+ return "OpenClaw";
53
+ case "zcode":
54
+ return "ZCode";
55
+ }
56
+ }
57
+ function launchCommand(type) {
58
+ switch (type) {
59
+ case "claude":
60
+ return "claude";
61
+ case "codex":
62
+ return "codex";
63
+ case "opencode":
64
+ return "opencode";
65
+ case "openclaw":
66
+ return "openclaw dashboard";
67
+ case "zcode":
68
+ return "ZCode \u684C\u9762\u5E94\u7528";
69
+ }
31
70
  }
32
71
 
33
72
  // src/args.ts
34
73
  function printHelp() {
35
74
  console.log(`
36
- trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF\u7F16\u7A0B\u52A9\u624B
75
+ trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF / Agent / \u684C\u9762\u7F16\u7A0B\u52A9\u624B
37
76
 
38
77
  \u7528\u6CD5:
39
- npx -y trinity-config --type <claude|codex> [\u9009\u9879]
78
+ npx -y trinity-config --type <claude|codex|opencode|openclaw|zcode> [\u9009\u9879]
40
79
 
41
80
  \u9009\u9879:
42
- -t, --type <claude|codex> \u8981\u914D\u7F6E\u7684\u5DE5\u5177\uFF08\u975E\u4EA4\u4E92\u987B\u6307\u5B9A\uFF1B\u4EA4\u4E92\u5F0F\u53EF\u7701\u7565\uFF09
43
- -k, --api-key <key> Trinity API Key\uFF08xh- \u524D\u7F00\uFF09
44
- -M, --model <model_code> \u9ED8\u8BA4\u6A21\u578B\uFF08\u7701\u7565\u5219\u6309 type \u4F7F\u7528\u5185\u7F6E\u63A8\u8350\uFF09
45
- --base-url <url> API \u6839\u5730\u5740\uFF08\u9ED8\u8BA4 https://api.trinitydesk.ai\uFF09
46
- --skip-verify \u8DF3\u8FC7 GET /v1/models \u8FDE\u901A\u6027\u68C0\u67E5
47
- -v, --verbose \u663E\u793A zcf \u8BE6\u7EC6\u8F93\u51FA
48
- -h, --help \u663E\u793A\u5E2E\u52A9
49
- -V, --version \u663E\u793A\u7248\u672C
81
+ -t, --type <\u2026> \u8981\u914D\u7F6E\u7684\u5DE5\u5177\uFF08\u975E\u4EA4\u4E92\u987B\u6307\u5B9A\uFF09
82
+ -k, --api-key <key> Trinity API Key\uFF08xh- \u524D\u7F00\uFF09
83
+ -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
84
+ --base-url <url> API \u6839\u5730\u5740\uFF08\u9ED8\u8BA4 https://api.trinitydesk.ai\uFF09
85
+ --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
87
+ -h, --help \u663E\u793A\u5E2E\u52A9
88
+ -V, --version \u663E\u793A\u7248\u672C
50
89
 
51
90
  \u793A\u4F8B:
52
- npx -y trinity-config --type claude -k xh-your-key
53
- npx -y trinity-config --type codex -k xh-your-key --model gpt-5.4
54
- npx -y trinity-config
91
+ 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
55
93
  `);
56
94
  }
57
95
  function parseArgs(argv) {
@@ -98,7 +136,7 @@ function parseArgs(argv) {
98
136
  break;
99
137
  case "--only": {
100
138
  const legacy = args.shift();
101
- console.warn("\u8B66\u544A: --only \u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u6539\u7528 --type claude \u6216 --type codex");
139
+ console.warn("\u8B66\u544A: --only \u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u6539\u7528 --type");
102
140
  opts.type = parseToolType(String(legacy ?? ""));
103
141
  break;
104
142
  }
@@ -129,7 +167,7 @@ function normalizeApiKey(raw) {
129
167
  function requireType(type, hasApiKeyFlag) {
130
168
  if (type) return type;
131
169
  if (!hasApiKeyFlag) return void 0;
132
- throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude \u6216 --type codex");
170
+ throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude\u3001codex\u3001opencode\u3001openclaw \u6216 zcode");
133
171
  }
134
172
 
135
173
  // src/merge-claude-env.ts
@@ -162,6 +200,167 @@ function mergeClaudeEnv(claudeModel, fastModel, baseUrl) {
162
200
  `, "utf8");
163
201
  }
164
202
 
203
+ // src/protocol.ts
204
+ import https from "https";
205
+ var PROTOCOL_PRIORITY = [
206
+ "anthropic_messages",
207
+ "responses",
208
+ "chat_completions"
209
+ ];
210
+ var TOOL_SUPPORTED_PROTOCOLS = {
211
+ claude: ["anthropic_messages"],
212
+ codex: ["responses"],
213
+ opencode: ["anthropic_messages", "responses", "chat_completions"],
214
+ openclaw: ["anthropic_messages", "responses", "chat_completions"],
215
+ zcode: ["anthropic_messages", "responses", "chat_completions"]
216
+ };
217
+ var INTERFACE_ALIASES = {
218
+ "text.anthropic_messages": "anthropic_messages",
219
+ anthropic_messages: "anthropic_messages",
220
+ "text.responses": "responses",
221
+ responses: "responses",
222
+ "text.chat_completions": "chat_completions",
223
+ chat_completions: "chat_completions",
224
+ chat: "chat_completions"
225
+ };
226
+ function getJson(url, apiKey) {
227
+ return new Promise((resolve, reject) => {
228
+ const target = new URL(url);
229
+ const req = https.request(
230
+ {
231
+ protocol: target.protocol,
232
+ hostname: target.hostname,
233
+ port: target.port || 443,
234
+ path: `${target.pathname}${target.search}`,
235
+ method: "GET",
236
+ headers: {
237
+ Accept: "application/json",
238
+ Authorization: `Bearer ${apiKey}`
239
+ },
240
+ timeout: VERIFY_TIMEOUT_MS
241
+ },
242
+ (res) => {
243
+ const chunks = [];
244
+ res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
245
+ res.on("end", () => {
246
+ const text2 = Buffer.concat(chunks).toString("utf8");
247
+ try {
248
+ resolve({
249
+ statusCode: res.statusCode ?? 0,
250
+ body: JSON.parse(text2)
251
+ });
252
+ } catch {
253
+ reject(new Error(`\u54CD\u5E94\u4E0D\u662F JSON\uFF08HTTP ${res.statusCode}\uFF09`));
254
+ }
255
+ });
256
+ }
257
+ );
258
+ req.on("timeout", () => {
259
+ req.destroy();
260
+ reject(new Error(`\u8FDE\u63A5\u8D85\u65F6\uFF08>${VERIFY_TIMEOUT_MS}ms\uFF09`));
261
+ });
262
+ req.on("error", reject);
263
+ req.end();
264
+ });
265
+ }
266
+ function normalizeInterface(raw) {
267
+ if (typeof raw !== "string" || !raw.trim()) return null;
268
+ return INTERFACE_ALIASES[raw.trim().toLowerCase()] ?? null;
269
+ }
270
+ function parseInterfacesFromDetail(body) {
271
+ if (!body || typeof body !== "object") return [];
272
+ const root = body;
273
+ const caps = root.capabilities;
274
+ if (!caps || typeof caps !== "object") return [];
275
+ const text2 = caps.text;
276
+ if (!text2 || typeof text2 !== "object") return [];
277
+ const textObj = text2;
278
+ const rawList = textObj.interfaces ?? textObj.protocols;
279
+ if (!Array.isArray(rawList)) return [];
280
+ const out = [];
281
+ const seen = /* @__PURE__ */ new Set();
282
+ for (const item of rawList) {
283
+ const proto = normalizeInterface(item);
284
+ if (proto && !seen.has(proto)) {
285
+ seen.add(proto);
286
+ out.push(proto);
287
+ }
288
+ }
289
+ return out;
290
+ }
291
+ function rootBaseUrl(baseUrl) {
292
+ const root = (baseUrl ?? TRINITY_BASE_URL).replace(/\/+$/, "");
293
+ return root.endsWith("/v1") ? root.slice(0, -3) : root;
294
+ }
295
+ function baseUrlForProtocol(protocol, root) {
296
+ const cleaned = rootBaseUrl(root);
297
+ return protocol === "anthropic_messages" ? cleaned : `${cleaned}/v1`;
298
+ }
299
+ function defaultProtocolForTool(tool) {
300
+ return TOOL_SUPPORTED_PROTOCOLS[tool][0];
301
+ }
302
+ function pickProtocol(tool, modelProtocols) {
303
+ const supported = TOOL_SUPPORTED_PROTOCOLS[tool];
304
+ const candidates = PROTOCOL_PRIORITY.filter(
305
+ (p2) => modelProtocols.includes(p2) && supported.includes(p2)
306
+ );
307
+ if (candidates.length > 0) {
308
+ return { protocol: candidates[0] };
309
+ }
310
+ if (modelProtocols.length === 0) {
311
+ return {
312
+ protocol: defaultProtocolForTool(tool),
313
+ note: "\u6A21\u578B\u8BE6\u60C5\u672A\u8FD4\u56DE interfaces\uFF0C\u5DF2\u6309\u5DE5\u5177\u9ED8\u8BA4\u534F\u8BAE\u964D\u7EA7"
314
+ };
315
+ }
316
+ throw new Error(
317
+ `\u6A21\u578B\u4E0D\u652F\u6301 ${tool} \u53EF\u7528\u534F\u8BAE\uFF08\u6A21\u578B: ${modelProtocols.join(", ") || "\u65E0"}\uFF1B\u5DE5\u5177: ${supported.join(", ")}\uFF09`
318
+ );
319
+ }
320
+ async function fetchModelProtocols(apiKey, modelCode, root) {
321
+ const base = rootBaseUrl(root);
322
+ const url = `${base}/v1/models/${encodeURIComponent(modelCode)}`;
323
+ const { statusCode, body } = await getJson(url, apiKey);
324
+ if (statusCode < 200 || statusCode >= 300) {
325
+ return [];
326
+ }
327
+ return parseInterfacesFromDetail(body);
328
+ }
329
+ async function resolveProtocol(options) {
330
+ let modelProtocols = [];
331
+ let fromApi = false;
332
+ if (!options.skipFetch) {
333
+ try {
334
+ modelProtocols = await fetchModelProtocols(
335
+ options.apiKey,
336
+ options.model,
337
+ options.rootBaseUrl
338
+ );
339
+ fromApi = modelProtocols.length > 0;
340
+ } catch {
341
+ modelProtocols = [];
342
+ }
343
+ }
344
+ const picked = pickProtocol(options.tool, modelProtocols);
345
+ return {
346
+ protocol: picked.protocol,
347
+ baseUrl: baseUrlForProtocol(picked.protocol, options.rootBaseUrl),
348
+ modelProtocols,
349
+ fromApi,
350
+ note: picked.note
351
+ };
352
+ }
353
+ function protocolLabel(protocol) {
354
+ switch (protocol) {
355
+ case "anthropic_messages":
356
+ return "Anthropic Messages\uFF08/v1/messages\uFF09";
357
+ case "responses":
358
+ return "OpenAI Responses\uFF08/v1/responses\uFF09";
359
+ case "chat_completions":
360
+ return "Chat Completions\uFF08/v1/chat/completions\uFF09";
361
+ }
362
+ }
363
+
165
364
  // src/zcf.ts
166
365
  import { execa } from "execa";
167
366
  function sharedZcfArgs(verbose) {
@@ -222,23 +421,43 @@ async function configureCodex(apiKey, codexBaseUrl, codexModel, _verbose) {
222
421
  const { configureCodexDirect } = await import("./merge-codex-config-GC7ZDP7R.js");
223
422
  return configureCodexDirect(apiKey, codexBaseUrl, codexModel);
224
423
  }
225
- function resolveBaseUrls(baseUrl) {
226
- const root = (baseUrl ?? TRINITY_BASE_URL).replace(/\/+$/, "");
227
- const codexBaseUrl = root.endsWith("/v1") ? root : `${root}/v1`;
228
- const claudeBaseUrl = root.endsWith("/v1") ? root.slice(0, -3) : root;
229
- return { claudeBaseUrl, codexBaseUrl };
424
+ async function configureOpenCode(apiKey, openCodeBaseUrl, model, modelIds, protocol) {
425
+ const { configureOpenCodeDirect } = await import("./merge-opencode-config-24P7MV7L.js");
426
+ return configureOpenCodeDirect({
427
+ apiKey,
428
+ baseUrl: openCodeBaseUrl,
429
+ model,
430
+ modelIds,
431
+ protocol
432
+ });
230
433
  }
231
- function baseUrlForType(type, baseUrl) {
232
- const { claudeBaseUrl, codexBaseUrl } = resolveBaseUrls(baseUrl);
233
- return type === "claude" ? claudeBaseUrl : codexBaseUrl;
434
+ async function configureOpenClaw(apiKey, openClawBaseUrl, model, modelIds, protocol) {
435
+ const { configureOpenClawDirect } = await import("./merge-openclaw-config-BNKTK35F.js");
436
+ return configureOpenClawDirect({
437
+ apiKey,
438
+ baseUrl: openClawBaseUrl,
439
+ model,
440
+ modelIds,
441
+ protocol
442
+ });
443
+ }
444
+ async function configureZCode(apiKey, zcodeBaseUrl, model, modelIds, protocol) {
445
+ const { configureZCodeDirect } = await import("./merge-zcode-config-U4V7XCYT.js");
446
+ return configureZCodeDirect({
447
+ apiKey,
448
+ baseUrl: zcodeBaseUrl,
449
+ model,
450
+ modelIds,
451
+ protocol
452
+ });
234
453
  }
235
454
 
236
455
  // src/verify.ts
237
- import https from "https";
238
- function getJson(url, apiKey) {
456
+ import https2 from "https";
457
+ function getJson2(url, apiKey) {
239
458
  return new Promise((resolve, reject) => {
240
459
  const target = new URL(url);
241
- const req = https.request(
460
+ const req = https2.request(
242
461
  {
243
462
  protocol: target.protocol,
244
463
  hostname: target.hostname,
@@ -279,7 +498,7 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
279
498
  const root = baseUrl.replace(/\/+$/, "");
280
499
  const modelsUrl = `${root}/v1/models`;
281
500
  try {
282
- const { statusCode, body } = await getJson(modelsUrl, apiKey);
501
+ const { statusCode, body } = await getJson2(modelsUrl, apiKey);
283
502
  if (statusCode === 401 || statusCode === 403) {
284
503
  return {
285
504
  ok: false,
@@ -327,7 +546,10 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
327
546
  function pickRecommendedModels(modelIds) {
328
547
  const claude = modelIds.find((id) => id === DEFAULT_CLAUDE_MODEL) ?? modelIds.find((id) => id.includes("claude-sonnet")) ?? modelIds.find((id) => id.toLowerCase().includes("claude"));
329
548
  const codex = modelIds.find((id) => id === DEFAULT_CODEX_MODEL) ?? modelIds.find((id) => id === "gpt-5.4") ?? modelIds.find((id) => id.startsWith("gpt-5")) ?? modelIds.find((id) => id.toLowerCase().includes("gpt"));
330
- return { claude, codex };
549
+ const opencode = modelIds.find((id) => id === DEFAULT_OPENCODE_MODEL) ?? codex ?? modelIds.find((id) => id.toLowerCase().includes("qwen"));
550
+ const openclaw = modelIds.find((id) => id === DEFAULT_OPENCLAW_MODEL) ?? opencode ?? codex;
551
+ const zcode = modelIds.find((id) => id === DEFAULT_ZCODE_MODEL) ?? opencode ?? codex;
552
+ return { claude, codex, opencode, openclaw, zcode };
331
553
  }
332
554
 
333
555
  // src/index.ts
@@ -355,8 +577,11 @@ async function promptToolType(initial) {
355
577
  const value = await p.select({
356
578
  message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
357
579
  options: [
358
- { value: "claude", label: "Claude Code\uFF08/v1/messages\uFF09" },
359
- { value: "codex", label: "Codex CLI\uFF08/v1/responses\uFF09" }
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" }
360
585
  ]
361
586
  });
362
587
  if (p.isCancel(value)) {
@@ -384,6 +609,20 @@ async function promptModel(type, initial) {
384
609
  }
385
610
  return String(value).trim();
386
611
  }
612
+ function recommendedHint(toolType, recommended) {
613
+ switch (toolType) {
614
+ case "claude":
615
+ return recommended.claude;
616
+ case "codex":
617
+ return recommended.codex;
618
+ case "opencode":
619
+ return recommended.opencode;
620
+ case "openclaw":
621
+ return recommended.openclaw;
622
+ case "zcode":
623
+ return recommended.zcode;
624
+ }
625
+ }
387
626
  async function run() {
388
627
  let opts;
389
628
  try {
@@ -404,67 +643,161 @@ async function run() {
404
643
  const toolType = await promptToolType(opts.type);
405
644
  const apiKey = await promptApiKey(opts.apiKey);
406
645
  const model = opts.model?.trim() ? opts.model.trim() : opts.apiKey ? resolveModel(toolType) : await promptModel(toolType);
407
- const apiBaseUrl = baseUrlForType(toolType, opts.baseUrl);
408
- const { claudeBaseUrl } = resolveBaseUrls(opts.baseUrl);
646
+ const apiRoot = rootBaseUrl(opts.baseUrl);
409
647
  p.log.info(`\u5DE5\u5177: ${toolLabel(toolType)} \xB7 \u6A21\u578B: ${model}`);
410
648
  const spinner2 = p.spinner();
649
+ let verifiedModelIds;
650
+ let verifyFailed = false;
651
+ if (!opts.skipVerify) {
652
+ spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
653
+ const result = await verifyTrinityApi(apiKey, apiRoot);
654
+ if (result.ok) {
655
+ 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) {
660
+ p.note(`\u540C\u7C7B\u578B\u63A8\u8350\u6A21\u578B: ${hint}`, "\u6A21\u578B");
661
+ }
662
+ } else {
663
+ verifyFailed = true;
664
+ spinner2.stop(pc.yellow(result.message));
665
+ 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",
667
+ "\u63D0\u793A"
668
+ );
669
+ }
670
+ }
671
+ spinner2.start("\u89E3\u6790\u6A21\u578B\u534F\u8BAE\u2026");
672
+ let protocolRes;
673
+ try {
674
+ protocolRes = await resolveProtocol({
675
+ tool: toolType,
676
+ apiKey,
677
+ model,
678
+ rootBaseUrl: apiRoot,
679
+ skipFetch: opts.skipVerify
680
+ });
681
+ spinner2.stop(
682
+ `\u534F\u8BAE: ${protocolLabel(protocolRes.protocol)}` + (protocolRes.fromApi ? "" : "\uFF08\u964D\u7EA7\uFF09")
683
+ );
684
+ if (protocolRes.note) {
685
+ p.note(protocolRes.note, "\u534F\u8BAE");
686
+ }
687
+ } catch (error) {
688
+ spinner2.stop(pc.red("\u534F\u8BAE\u89E3\u6790\u5931\u8D25"));
689
+ console.error(pc.red(error instanceof Error ? error.message : String(error)));
690
+ return 1;
691
+ }
692
+ const writeBase = protocolRes.baseUrl;
693
+ const protocol = protocolRes.protocol;
411
694
  if (toolType === "claude") {
412
695
  spinner2.start("\u914D\u7F6E Claude Code\u2026");
413
696
  try {
414
- await configureClaude(apiKey, apiBaseUrl, model, opts.verbose);
415
- mergeClaudeEnv(model, model, apiBaseUrl);
697
+ await configureClaude(apiKey, writeBase, model, opts.verbose);
698
+ mergeClaudeEnv(model, model, writeBase);
416
699
  spinner2.stop("Claude Code \u914D\u7F6E\u5DF2\u5199\u5165 ~/.claude/settings.json");
417
700
  } catch (error) {
418
701
  spinner2.stop(pc.red("Claude Code \u914D\u7F6E\u5931\u8D25"));
419
- console.error(formatZcfError(error));
702
+ console.error(formatConfigError(error));
420
703
  return 1;
421
704
  }
422
- } else {
705
+ } else if (toolType === "codex") {
423
706
  spinner2.start("\u914D\u7F6E Codex CLI\u2026");
424
707
  try {
425
- const backupDir = await configureCodex(apiKey, apiBaseUrl, model, opts.verbose);
708
+ const backupDir = await configureCodex(apiKey, writeBase, model, opts.verbose);
426
709
  const backupHint = backupDir ? `
427
710
  \u5907\u4EFD: ${backupDir}` : "";
428
711
  spinner2.stop(`Codex \u914D\u7F6E\u5DF2\u5199\u5165 ~/.codex/config.toml \u4E0E auth.json${backupHint}`);
429
712
  } catch (error) {
430
713
  spinner2.stop(pc.red("Codex \u914D\u7F6E\u5931\u8D25"));
431
- console.error(formatZcfError(error));
714
+ console.error(formatConfigError(error));
432
715
  return 1;
433
716
  }
434
- }
435
- if (!opts.skipVerify) {
436
- spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
437
- const result = await verifyTrinityApi(apiKey, claudeBaseUrl);
438
- if (result.ok) {
439
- spinner2.stop(pc.green(result.message));
440
- const recommended = pickRecommendedModels(result.modelIds ?? []);
441
- const hint = toolType === "claude" ? recommended.claude : recommended.codex;
442
- if (hint && hint !== model) {
443
- p.note(`\u540C\u7C7B\u578B\u63A8\u8350\u6A21\u578B: ${hint}`, "\u6A21\u578B");
444
- }
445
- } else {
446
- spinner2.stop(pc.yellow(result.message));
447
- p.note(
448
- "\u672C\u5730\u914D\u7F6E\u5DF2\u5199\u5165\uFF0C\u4F46\u8FDE\u901A\u6027\u68C0\u67E5\u672A\u901A\u8FC7\u3002\u8BF7\u68C0\u67E5 API Key\u3001\u7F51\u7EDC\uFF0C\u6216\u5728\u63A7\u5236\u53F0\u786E\u8BA4 Key \u6709\u6548\u3002",
449
- "\u63D0\u793A"
717
+ } else if (toolType === "opencode") {
718
+ spinner2.start("\u914D\u7F6E OpenCode\u2026");
719
+ try {
720
+ const backupDir = await configureOpenCode(
721
+ apiKey,
722
+ writeBase,
723
+ model,
724
+ verifiedModelIds,
725
+ protocol
726
+ );
727
+ const backupHint = backupDir ? `
728
+ \u5907\u4EFD: ${backupDir}` : "";
729
+ spinner2.stop(
730
+ `OpenCode \u914D\u7F6E\u5DF2\u5199\u5165 ~/.config/opencode/opencode.json${backupHint}`
731
+ );
732
+ } catch (error) {
733
+ spinner2.stop(pc.red("OpenCode \u914D\u7F6E\u5931\u8D25"));
734
+ console.error(formatConfigError(error));
735
+ return 1;
736
+ }
737
+ } else if (toolType === "openclaw") {
738
+ spinner2.start("\u914D\u7F6E OpenClaw\u2026");
739
+ try {
740
+ const backupDir = await configureOpenClaw(
741
+ apiKey,
742
+ writeBase,
743
+ model,
744
+ verifiedModelIds,
745
+ protocol
746
+ );
747
+ const backupHint = backupDir ? `
748
+ \u5907\u4EFD: ${backupDir}` : "";
749
+ spinner2.stop(
750
+ `OpenClaw \u914D\u7F6E\u5DF2\u5199\u5165 ~/.openclaw/openclaw.json${backupHint}`
751
+ );
752
+ } catch (error) {
753
+ spinner2.stop(pc.red("OpenClaw \u914D\u7F6E\u5931\u8D25"));
754
+ console.error(formatConfigError(error));
755
+ return 1;
756
+ }
757
+ } else {
758
+ spinner2.start("\u914D\u7F6E ZCode\u2026");
759
+ try {
760
+ const backupDir = await configureZCode(
761
+ apiKey,
762
+ writeBase,
763
+ model,
764
+ verifiedModelIds,
765
+ protocol
766
+ );
767
+ const backupHint = backupDir ? `
768
+ \u5907\u4EFD: ${backupDir}` : "";
769
+ spinner2.stop(
770
+ `ZCode \u914D\u7F6E\u5DF2\u5199\u5165 ~/.zcode/v2/config.json${backupHint}`
450
771
  );
451
- p.outro(pc.yellow("\u914D\u7F6E\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
772
+ } catch (error) {
773
+ spinner2.stop(pc.red("ZCode \u914D\u7F6E\u5931\u8D25"));
774
+ console.error(formatConfigError(error));
452
775
  return 1;
453
776
  }
454
777
  }
455
- const launchCmd = toolType === "claude" ? "claude" : "codex";
456
- p.note(
457
- [
458
- `\u8FD0\u884C ${launchCmd} \u542F\u52A8 ${toolLabel(toolType)}`,
459
- "\u5728\u5DE5\u5177\u5185\u7528 /model \u5207\u6362\u4E3A Trinity \u5DF2\u4E0A\u67B6\u7684 model_code",
460
- "\u914D\u7F6E\u540E\u8BF7\u6C42\u8D70 Trinity \u8BA1\u8D39\uFF0C\u4E0D\u518D\u4F7F\u7528 Anthropic/OpenAI \u5B98\u65B9\u989D\u5EA6"
461
- ].join("\n"),
462
- "\u4E0B\u4E00\u6B65"
463
- );
778
+ if (verifyFailed) {
779
+ p.outro(pc.yellow("\u914D\u7F6E\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
780
+ return 1;
781
+ }
782
+ const nextSteps = [
783
+ `\u8FD0\u884C ${launchCommand(toolType)} \u542F\u52A8 ${toolLabel(toolType)}`,
784
+ `\u5DF2\u6309\u534F\u8BAE\u63A5\u5165: ${protocolLabel(protocol)}`
785
+ ];
786
+ if (toolType === "opencode") {
787
+ nextSteps.push("\u5728 OpenCode \u5185\u7528 /models \u9009\u62E9 trinity/<model_code>");
788
+ } else if (toolType === "openclaw") {
789
+ nextSteps.push("\u6539\u5B8C\u914D\u7F6E\u540E\u8BF7\u91CD\u542F Gateway");
790
+ } else if (toolType === "zcode") {
791
+ nextSteps.push("\u91CD\u542F ZCode \u540E\uFF0C\u5728\u6A21\u578B\u8BBE\u7F6E\u4E2D\u542F\u7528 Trinity \u4F9B\u5E94\u5546");
792
+ } else {
793
+ nextSteps.push("\u5728\u5DE5\u5177\u5185\u7528 /model \u5207\u6362\u4E3A Trinity \u5DF2\u4E0A\u67B6\u7684 model_code");
794
+ }
795
+ nextSteps.push("\u914D\u7F6E\u540E\u8BF7\u6C42\u8D70 Trinity \u8BA1\u8D39\uFF0C\u4E0D\u518D\u4F7F\u7528\u5B98\u65B9\u989D\u5EA6");
796
+ p.note(nextSteps.join("\n"), "\u4E0B\u4E00\u6B65");
464
797
  p.outro(pc.green("\u5B8C\u6210"));
465
798
  return 0;
466
799
  }
467
- function formatZcfError(error) {
800
+ function formatConfigError(error) {
468
801
  if (error instanceof Error && "timed out" in error) {
469
802
  return "zcf \u6267\u884C\u8D85\u65F6\uFF08>3 \u5206\u949F\uFF09\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5\u3002";
470
803
  }
@@ -0,0 +1,128 @@
1
+ // src/merge-openclaw-config.ts
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ var TRINITY_OPENCLAW_PROVIDER = "trinity";
6
+ function openclawHome() {
7
+ if (process.env.OPENCLAW_HOME?.trim()) {
8
+ return process.env.OPENCLAW_HOME.trim();
9
+ }
10
+ return path.join(os.homedir(), ".openclaw");
11
+ }
12
+ function openclawConfigPath() {
13
+ if (process.env.OPENCLAW_CONFIG?.trim()) {
14
+ return process.env.OPENCLAW_CONFIG.trim();
15
+ }
16
+ return path.join(openclawHome(), "openclaw.json");
17
+ }
18
+ function timestampLabel() {
19
+ const now = /* @__PURE__ */ new Date();
20
+ const pad = (n) => String(n).padStart(2, "0");
21
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
22
+ }
23
+ function backupOpenClawConfig() {
24
+ const configPath = openclawConfigPath();
25
+ if (!fs.existsSync(configPath)) {
26
+ return null;
27
+ }
28
+ const backupDir = path.join(openclawHome(), "backup", `trinity-config-${timestampLabel()}`);
29
+ fs.mkdirSync(backupDir, { recursive: true });
30
+ fs.copyFileSync(configPath, path.join(backupDir, path.basename(configPath)));
31
+ return backupDir;
32
+ }
33
+ function readOpenClawConfig() {
34
+ const configPath = openclawConfigPath();
35
+ if (!fs.existsSync(configPath)) {
36
+ return {};
37
+ }
38
+ const raw = fs.readFileSync(configPath, "utf8");
39
+ if (!raw.trim()) {
40
+ return {};
41
+ }
42
+ try {
43
+ return JSON.parse(raw);
44
+ } catch {
45
+ throw new Error(
46
+ `\u65E0\u6CD5\u89E3\u6790 ${configPath}\u3002\u82E5\u6587\u4EF6\u4E3A JSON5\uFF0C\u8BF7\u5148\u6539\u4E3A\u6807\u51C6 JSON\uFF0C\u6216\u5907\u4EFD\u540E\u5220\u9664\u518D\u91CD\u8DD1\u3002`
47
+ );
48
+ }
49
+ }
50
+ function isLikelyNonTextModel(id) {
51
+ const lower = id.toLowerCase();
52
+ return lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling");
53
+ }
54
+ function buildModelEntries(primaryModel, modelIds) {
55
+ const ids = [primaryModel];
56
+ const seen = new Set(ids);
57
+ for (const id of modelIds ?? []) {
58
+ const trimmed = id?.trim();
59
+ if (!trimmed || seen.has(trimmed) || isLikelyNonTextModel(trimmed)) continue;
60
+ seen.add(trimmed);
61
+ ids.push(trimmed);
62
+ if (ids.length >= 80) break;
63
+ }
64
+ return ids.map((id) => ({
65
+ id,
66
+ name: id,
67
+ reasoning: false,
68
+ input: ["text"],
69
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
70
+ contextWindow: 128e3,
71
+ maxTokens: 8192
72
+ }));
73
+ }
74
+ function mergeOpenClawConfig(options) {
75
+ const configPath = openclawConfigPath();
76
+ const doc = readOpenClawConfig();
77
+ const protocol = options.protocol ?? "chat_completions";
78
+ const api = protocol === "anthropic_messages" ? "anthropic-messages" : protocol === "responses" ? "openai-responses" : "openai-completions";
79
+ const modelsBlock = doc.models && typeof doc.models === "object" ? { ...doc.models } : {};
80
+ modelsBlock.mode = modelsBlock.mode ?? "merge";
81
+ const providers = modelsBlock.providers && typeof modelsBlock.providers === "object" ? { ...modelsBlock.providers } : {};
82
+ const existing = providers[TRINITY_OPENCLAW_PROVIDER] ?? {};
83
+ const existingModels = Array.isArray(existing.models) ? existing.models : [];
84
+ const byId = /* @__PURE__ */ new Map();
85
+ for (const m of existingModels) {
86
+ if (m && typeof m === "object" && typeof m.id === "string") {
87
+ byId.set(m.id, m);
88
+ }
89
+ }
90
+ for (const m of buildModelEntries(options.model, options.modelIds)) {
91
+ byId.set(m.id, { ...byId.get(m.id), ...m });
92
+ }
93
+ providers[TRINITY_OPENCLAW_PROVIDER] = {
94
+ ...existing,
95
+ baseUrl: options.baseUrl.replace(/\/+$/, ""),
96
+ apiKey: options.apiKey,
97
+ api,
98
+ models: [...byId.values()]
99
+ };
100
+ modelsBlock.providers = providers;
101
+ doc.models = modelsBlock;
102
+ const agents = doc.agents && typeof doc.agents === "object" ? { ...doc.agents } : {};
103
+ const defaults = agents.defaults && typeof agents.defaults === "object" ? { ...agents.defaults } : {};
104
+ const modelCfg = defaults.model && typeof defaults.model === "object" ? { ...defaults.model } : {};
105
+ modelCfg.primary = `${TRINITY_OPENCLAW_PROVIDER}/${options.model}`;
106
+ defaults.model = modelCfg;
107
+ const allow = defaults.models && typeof defaults.models === "object" ? { ...defaults.models } : {};
108
+ allow[`${TRINITY_OPENCLAW_PROVIDER}/*`] = allow[`${TRINITY_OPENCLAW_PROVIDER}/*`] ?? {};
109
+ defaults.models = allow;
110
+ agents.defaults = defaults;
111
+ doc.agents = agents;
112
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
113
+ fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
114
+ `, "utf8");
115
+ }
116
+ function configureOpenClawDirect(options) {
117
+ const backupDir = backupOpenClawConfig();
118
+ mergeOpenClawConfig(options);
119
+ return backupDir;
120
+ }
121
+ export {
122
+ TRINITY_OPENCLAW_PROVIDER,
123
+ backupOpenClawConfig,
124
+ configureOpenClawDirect,
125
+ mergeOpenClawConfig,
126
+ openclawConfigPath,
127
+ openclawHome
128
+ };
@@ -0,0 +1,109 @@
1
+ // src/merge-opencode-config.ts
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ var TRINITY_OPENCODE_PROVIDER = "trinity";
6
+ function opencodeConfigDir() {
7
+ if (process.env.OPENCODE_CONFIG_DIR?.trim()) {
8
+ return process.env.OPENCODE_CONFIG_DIR.trim();
9
+ }
10
+ return path.join(os.homedir(), ".config", "opencode");
11
+ }
12
+ function opencodeConfigPath() {
13
+ if (process.env.OPENCODE_CONFIG?.trim()) {
14
+ return process.env.OPENCODE_CONFIG.trim();
15
+ }
16
+ return path.join(opencodeConfigDir(), "opencode.json");
17
+ }
18
+ function timestampLabel() {
19
+ const now = /* @__PURE__ */ new Date();
20
+ const pad = (n) => String(n).padStart(2, "0");
21
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
22
+ }
23
+ function backupOpenCodeConfig() {
24
+ const configPath = opencodeConfigPath();
25
+ if (!fs.existsSync(configPath)) {
26
+ return null;
27
+ }
28
+ const backupDir = path.join(opencodeConfigDir(), "backup", `trinity-config-${timestampLabel()}`);
29
+ fs.mkdirSync(backupDir, { recursive: true });
30
+ fs.copyFileSync(configPath, path.join(backupDir, path.basename(configPath)));
31
+ return backupDir;
32
+ }
33
+ function readOpenCodeConfig() {
34
+ const configPath = opencodeConfigPath();
35
+ if (!fs.existsSync(configPath)) {
36
+ return {};
37
+ }
38
+ const raw = fs.readFileSync(configPath, "utf8");
39
+ if (!raw.trim()) {
40
+ return {};
41
+ }
42
+ try {
43
+ return JSON.parse(raw);
44
+ } catch {
45
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 ${configPath}\uFF0C\u8BF7\u68C0\u67E5 JSON \u8BED\u6CD5\u540E\u91CD\u8BD5\u3002`);
46
+ }
47
+ }
48
+ function buildModelMap(primaryModel, modelIds) {
49
+ const ids = [primaryModel];
50
+ const seen = new Set(ids);
51
+ for (const id of modelIds ?? []) {
52
+ const trimmed = id?.trim();
53
+ if (!trimmed || seen.has(trimmed)) continue;
54
+ const lower = trimmed.toLowerCase();
55
+ if (lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling")) {
56
+ continue;
57
+ }
58
+ seen.add(trimmed);
59
+ ids.push(trimmed);
60
+ if (ids.length >= 80) break;
61
+ }
62
+ const models = {};
63
+ for (const id of ids) {
64
+ models[id] = { name: id };
65
+ }
66
+ return models;
67
+ }
68
+ function mergeOpenCodeConfig(options) {
69
+ const configPath = opencodeConfigPath();
70
+ const doc = readOpenCodeConfig();
71
+ const protocol = options.protocol ?? "chat_completions";
72
+ const npm = protocol === "anthropic_messages" ? "@ai-sdk/anthropic" : protocol === "responses" ? "@ai-sdk/openai" : "@ai-sdk/openai-compatible";
73
+ doc.$schema = doc.$schema ?? "https://opencode.ai/config.json";
74
+ doc.model = `${TRINITY_OPENCODE_PROVIDER}/${options.model}`;
75
+ const providers = doc.provider && typeof doc.provider === "object" ? { ...doc.provider } : {};
76
+ const existing = providers[TRINITY_OPENCODE_PROVIDER] ?? {};
77
+ const existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
78
+ providers[TRINITY_OPENCODE_PROVIDER] = {
79
+ ...existing,
80
+ npm,
81
+ name: "Trinity",
82
+ options: {
83
+ ...existing.options ?? {},
84
+ baseURL: options.baseUrl.replace(/\/+$/, ""),
85
+ apiKey: options.apiKey
86
+ },
87
+ models: {
88
+ ...existingModels,
89
+ ...buildModelMap(options.model, options.modelIds)
90
+ }
91
+ };
92
+ doc.provider = providers;
93
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
94
+ fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
95
+ `, "utf8");
96
+ }
97
+ function configureOpenCodeDirect(options) {
98
+ const backupDir = backupOpenCodeConfig();
99
+ mergeOpenCodeConfig(options);
100
+ return backupDir;
101
+ }
102
+ export {
103
+ TRINITY_OPENCODE_PROVIDER,
104
+ backupOpenCodeConfig,
105
+ configureOpenCodeDirect,
106
+ mergeOpenCodeConfig,
107
+ opencodeConfigDir,
108
+ opencodeConfigPath
109
+ };
@@ -0,0 +1,114 @@
1
+ // src/merge-zcode-config.ts
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ var TRINITY_ZCODE_PROVIDER = "trinity";
6
+ function zcodeHome() {
7
+ if (process.env.ZCODE_HOME?.trim()) {
8
+ return process.env.ZCODE_HOME.trim();
9
+ }
10
+ return path.join(os.homedir(), ".zcode");
11
+ }
12
+ function zcodeConfigPath() {
13
+ if (process.env.ZCODE_CONFIG?.trim()) {
14
+ return process.env.ZCODE_CONFIG.trim();
15
+ }
16
+ return path.join(zcodeHome(), "v2", "config.json");
17
+ }
18
+ function timestampLabel() {
19
+ const now = /* @__PURE__ */ new Date();
20
+ const pad = (n) => String(n).padStart(2, "0");
21
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
22
+ }
23
+ function backupZCodeConfig() {
24
+ const configPath = zcodeConfigPath();
25
+ if (!fs.existsSync(configPath)) {
26
+ return null;
27
+ }
28
+ const backupDir = path.join(zcodeHome(), "backup", `trinity-config-${timestampLabel()}`);
29
+ fs.mkdirSync(backupDir, { recursive: true });
30
+ fs.copyFileSync(configPath, path.join(backupDir, "config.json"));
31
+ return backupDir;
32
+ }
33
+ function readZCodeConfig() {
34
+ const configPath = zcodeConfigPath();
35
+ if (!fs.existsSync(configPath)) {
36
+ return {};
37
+ }
38
+ const raw = fs.readFileSync(configPath, "utf8");
39
+ if (!raw.trim()) return {};
40
+ try {
41
+ return JSON.parse(raw);
42
+ } catch {
43
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 ${configPath}\uFF0C\u8BF7\u68C0\u67E5 JSON \u8BED\u6CD5\u540E\u91CD\u8BD5\u3002`);
44
+ }
45
+ }
46
+ function buildModels(primary, modelIds) {
47
+ const ids = /* @__PURE__ */ new Set([primary]);
48
+ for (const id of modelIds ?? []) {
49
+ const t = id?.trim();
50
+ if (!t) continue;
51
+ const lower = t.toLowerCase();
52
+ if (lower.includes("image") || lower.includes("video") || lower.includes("seedance") || lower.includes("wan-") || lower.includes("kling")) {
53
+ continue;
54
+ }
55
+ ids.add(t);
56
+ if (ids.size >= 80) break;
57
+ }
58
+ const models = {};
59
+ for (const id of ids) {
60
+ models[id] = { name: id };
61
+ }
62
+ return models;
63
+ }
64
+ function mergeZCodeConfig(options) {
65
+ const configPath = zcodeConfigPath();
66
+ const doc = readZCodeConfig();
67
+ const providers = doc.provider && typeof doc.provider === "object" ? { ...doc.provider } : {};
68
+ for (const [id, provider] of Object.entries(providers)) {
69
+ if (id === TRINITY_ZCODE_PROVIDER) continue;
70
+ if (provider && typeof provider === "object") {
71
+ providers[id] = { ...provider, enabled: false };
72
+ }
73
+ }
74
+ const existing = providers[TRINITY_ZCODE_PROVIDER] ?? {};
75
+ const existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
76
+ const root = options.baseUrl.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
77
+ const withV1 = `${root}/v1`;
78
+ const withoutV1 = root;
79
+ const primaryBase = options.protocol === "anthropic_messages" ? withoutV1 : withV1;
80
+ providers[TRINITY_ZCODE_PROVIDER] = {
81
+ ...existing,
82
+ enabled: true,
83
+ name: "Trinity",
84
+ options: {
85
+ ...existing.options ?? {},
86
+ baseURL: primaryBase,
87
+ apiKey: options.apiKey,
88
+ openaiBaseURL: withV1,
89
+ anthropicBaseURL: withoutV1
90
+ },
91
+ models: {
92
+ ...existingModels,
93
+ ...buildModels(options.model, options.modelIds)
94
+ }
95
+ };
96
+ doc.provider = providers;
97
+ doc.model = `${TRINITY_ZCODE_PROVIDER}/${options.model}`;
98
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
99
+ fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
100
+ `, "utf8");
101
+ }
102
+ function configureZCodeDirect(options) {
103
+ const backupDir = backupZCodeConfig();
104
+ mergeZCodeConfig(options);
105
+ return backupDir;
106
+ }
107
+ export {
108
+ TRINITY_ZCODE_PROVIDER,
109
+ backupZCodeConfig,
110
+ configureZCodeDirect,
111
+ mergeZCodeConfig,
112
+ zcodeConfigPath,
113
+ zcodeHome
114
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trinity-config",
3
- "version": "1.1.1",
4
- "description": "一键把 Trinity API 接入 Claude CodeCodex CLI",
3
+ "version": "1.2.2",
4
+ "description": "一键把 Trinity API 接入 Claude CodeCodex、OpenCode、OpenClaw",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -29,6 +29,8 @@
29
29
  "trinitydesk",
30
30
  "claude-code",
31
31
  "codex",
32
+ "opencode",
33
+ "openclaw",
32
34
  "cli",
33
35
  "ai-gateway",
34
36
  "zcf"