trinity-config 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,56 +1,24 @@
1
1
  # trinity-config
2
2
 
3
- > 一键把 [Trinity API](https://trinitydesk.ai) 接入 **Claude Code** **Codex CLI** —— 指定工具类型 + 粘贴 `xh-` Key 即用
3
+ > 一键把 [Trinity API](https://trinitydesk.ai) 接入 **Claude Code** / **Codex CLI** / **OpenCode** / **OpenClaw**
4
4
 
5
5
  ## 快速开始
6
6
 
7
- ### 交互式
8
-
9
7
  ```bash
10
- npx -y trinity-config
11
- ```
12
-
13
- 按提示选择 **工具类型**、粘贴 Key、确认 **model_code**。
14
-
15
- ### 一行命令(推荐)
16
-
17
- ```bash
18
- # Claude Code
19
8
  npx -y trinity-config --type claude -k xh-你的key
20
-
21
- # Codex CLI
22
9
  npx -y trinity-config --type codex -k xh-你的key --model gpt-5.4
10
+ npx -y trinity-config --type opencode -k xh-你的key --model gpt-5.4
11
+ npx -y trinity-config --type openclaw -k xh-你的key --model gpt-5.4
23
12
  ```
24
13
 
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` |
41
- | --- | --- |
42
- | `claude` | `claude-sonnet-4-6` |
43
- | `codex` | `gpt-5.4` |
44
-
45
- ## 扩展新工具
46
-
47
- 后续增加 Cursor、OpenCode 等时,只需新增 `type` 枚举与对应写入逻辑,**不再**为每个工具增加 `--xxx-model` 参数。
48
-
49
- ## 注意
14
+ | `--type` | 默认模型 | 接口 | 写入 |
15
+ | --- | --- | --- | --- |
16
+ | `claude` | `claude-sonnet-4-6` | `/v1/messages` | `~/.claude/settings.json` |
17
+ | `codex` | `gpt-5.4` | `/v1/responses` | `~/.codex/config.toml` + `auth.json` |
18
+ | `opencode` | `gpt-5.4` | `/v1/chat/completions` | `~/.config/opencode/opencode.json` |
19
+ | `openclaw` | `gpt-5.4` | `/v1/chat/completions` | `~/.openclaw/openclaw.json` |
50
20
 
51
- - 配置后**所有模型调用走 Trinity 计费**。
52
- - 非交互模式必须同时提供 `--type` 与 `-k`。
53
- - Codex 侧 zcf 写入仍可能有已知限制,见设计文档。
21
+ OpenClaw 改完后请重启 Gateway(或重新 `openclaw dashboard`)。
54
22
 
55
23
  ## License
56
24
 
package/dist/index.js CHANGED
@@ -5,52 +5,86 @@ import pc from "picocolors";
5
5
 
6
6
  // src/constants.ts
7
7
  var PACKAGE_NAME = "trinity-config";
8
- var PACKAGE_VERSION = "1.1.0";
8
+ var PACKAGE_VERSION = "1.2.1";
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";
13
15
  var VERIFY_TIMEOUT_MS = 15e3;
14
16
 
15
17
  // src/model.ts
16
18
  function defaultModelForType(type) {
17
- return type === "claude" ? DEFAULT_CLAUDE_MODEL : DEFAULT_CODEX_MODEL;
19
+ switch (type) {
20
+ case "claude":
21
+ return DEFAULT_CLAUDE_MODEL;
22
+ case "codex":
23
+ return DEFAULT_CODEX_MODEL;
24
+ case "opencode":
25
+ return DEFAULT_OPENCODE_MODEL;
26
+ case "openclaw":
27
+ return DEFAULT_OPENCLAW_MODEL;
28
+ }
18
29
  }
19
30
  function resolveModel(type, model) {
20
31
  return model?.trim() || defaultModelForType(type);
21
32
  }
22
33
  function parseToolType(raw) {
23
34
  const value = raw.trim().toLowerCase();
24
- if (value === "claude" || value === "codex") {
35
+ if (value === "claude" || value === "codex" || value === "opencode" || value === "openclaw") {
25
36
  return value;
26
37
  }
27
- throw new Error("--type \u987B\u4E3A claude \u6216 codex");
38
+ throw new Error("--type \u987B\u4E3A claude\u3001codex\u3001opencode \u6216 openclaw");
28
39
  }
29
40
  function toolLabel(type) {
30
- return type === "claude" ? "Claude Code" : "Codex CLI";
41
+ switch (type) {
42
+ case "claude":
43
+ return "Claude Code";
44
+ case "codex":
45
+ return "Codex CLI";
46
+ case "opencode":
47
+ return "OpenCode";
48
+ case "openclaw":
49
+ return "OpenClaw";
50
+ }
51
+ }
52
+ function launchCommand(type) {
53
+ switch (type) {
54
+ case "claude":
55
+ return "claude";
56
+ case "codex":
57
+ return "codex";
58
+ case "opencode":
59
+ return "opencode";
60
+ case "openclaw":
61
+ return "openclaw dashboard";
62
+ }
31
63
  }
32
64
 
33
65
  // src/args.ts
34
66
  function printHelp() {
35
67
  console.log(`
36
- trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF\u7F16\u7A0B\u52A9\u624B
68
+ trinity-config \u2014 \u4E00\u952E\u628A Trinity API \u63A5\u5165\u7EC8\u7AEF\u7F16\u7A0B\u52A9\u624B / Agent \u7F51\u5173
37
69
 
38
70
  \u7528\u6CD5:
39
- npx -y trinity-config --type <claude|codex> [\u9009\u9879]
71
+ npx -y trinity-config --type <claude|codex|opencode|openclaw> [\u9009\u9879]
40
72
 
41
73
  \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
74
+ -t, --type <claude|codex|opencode|openclaw> \u8981\u914D\u7F6E\u7684\u5DE5\u5177\uFF08\u975E\u4EA4\u4E92\u987B\u6307\u5B9A\uFF09
75
+ -k, --api-key <key> Trinity API Key\uFF08xh- \u524D\u7F00\uFF09
76
+ -M, --model <model_code> \u9ED8\u8BA4\u6A21\u578B\uFF08\u7701\u7565\u5219\u6309 type \u63A8\u8350\uFF09
77
+ --base-url <url> API \u6839\u5730\u5740\uFF08\u9ED8\u8BA4 https://api.trinitydesk.ai\uFF09
78
+ --skip-verify \u8DF3\u8FC7 GET /v1/models \u8FDE\u901A\u6027\u68C0\u67E5
79
+ -v, --verbose \u663E\u793A zcf \u8BE6\u7EC6\u8F93\u51FA\uFF08\u4EC5 Claude\uFF09
80
+ -h, --help \u663E\u793A\u5E2E\u52A9
81
+ -V, --version \u663E\u793A\u7248\u672C
50
82
 
51
83
  \u793A\u4F8B:
52
84
  npx -y trinity-config --type claude -k xh-your-key
53
85
  npx -y trinity-config --type codex -k xh-your-key --model gpt-5.4
86
+ npx -y trinity-config --type opencode -k xh-your-key --model gpt-5.4
87
+ npx -y trinity-config --type openclaw -k xh-your-key --model gpt-5.4
54
88
  npx -y trinity-config
55
89
  `);
56
90
  }
@@ -98,7 +132,7 @@ function parseArgs(argv) {
98
132
  break;
99
133
  case "--only": {
100
134
  const legacy = args.shift();
101
- console.warn("\u8B66\u544A: --only \u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u6539\u7528 --type claude \u6216 --type codex");
135
+ console.warn("\u8B66\u544A: --only \u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u6539\u7528 --type");
102
136
  opts.type = parseToolType(String(legacy ?? ""));
103
137
  break;
104
138
  }
@@ -129,7 +163,7 @@ function normalizeApiKey(raw) {
129
163
  function requireType(type, hasApiKeyFlag) {
130
164
  if (type) return type;
131
165
  if (!hasApiKeyFlag) return void 0;
132
- throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude \u6216 --type codex");
166
+ throw new Error("\u975E\u4EA4\u4E92\u6A21\u5F0F\u987B\u6307\u5B9A --type claude\u3001codex\u3001opencode \u6216 openclaw");
133
167
  }
134
168
 
135
169
  // src/merge-claude-env.ts
@@ -218,14 +252,9 @@ async function configureClaude(apiKey, baseUrl, claudeModel, verbose) {
218
252
  verbose
219
253
  });
220
254
  }
221
- async function configureCodex(apiKey, codexBaseUrl, codexModel, verbose) {
222
- await runZcfInit({
223
- codeType: "cx",
224
- apiKey,
225
- baseUrl: codexBaseUrl,
226
- primaryModel: codexModel,
227
- verbose
228
- });
255
+ async function configureCodex(apiKey, codexBaseUrl, codexModel, _verbose) {
256
+ const { configureCodexDirect } = await import("./merge-codex-config-GC7ZDP7R.js");
257
+ return configureCodexDirect(apiKey, codexBaseUrl, codexModel);
229
258
  }
230
259
  function resolveBaseUrls(baseUrl) {
231
260
  const root = (baseUrl ?? TRINITY_BASE_URL).replace(/\/+$/, "");
@@ -237,6 +266,24 @@ function baseUrlForType(type, baseUrl) {
237
266
  const { claudeBaseUrl, codexBaseUrl } = resolveBaseUrls(baseUrl);
238
267
  return type === "claude" ? claudeBaseUrl : codexBaseUrl;
239
268
  }
269
+ async function configureOpenCode(apiKey, openCodeBaseUrl, model, modelIds) {
270
+ const { configureOpenCodeDirect } = await import("./merge-opencode-config-P4QJ63DF.js");
271
+ return configureOpenCodeDirect({
272
+ apiKey,
273
+ baseUrl: openCodeBaseUrl,
274
+ model,
275
+ modelIds
276
+ });
277
+ }
278
+ async function configureOpenClaw(apiKey, openClawBaseUrl, model, modelIds) {
279
+ const { configureOpenClawDirect } = await import("./merge-openclaw-config-XV25MSSC.js");
280
+ return configureOpenClawDirect({
281
+ apiKey,
282
+ baseUrl: openClawBaseUrl,
283
+ model,
284
+ modelIds
285
+ });
286
+ }
240
287
 
241
288
  // src/verify.ts
242
289
  import https from "https";
@@ -332,7 +379,9 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
332
379
  function pickRecommendedModels(modelIds) {
333
380
  const claude = modelIds.find((id) => id === DEFAULT_CLAUDE_MODEL) ?? modelIds.find((id) => id.includes("claude-sonnet")) ?? modelIds.find((id) => id.toLowerCase().includes("claude"));
334
381
  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"));
335
- return { claude, codex };
382
+ const opencode = modelIds.find((id) => id === DEFAULT_OPENCODE_MODEL) ?? codex ?? modelIds.find((id) => id.toLowerCase().includes("qwen"));
383
+ const openclaw = modelIds.find((id) => id === DEFAULT_OPENCLAW_MODEL) ?? opencode ?? codex;
384
+ return { claude, codex, opencode, openclaw };
336
385
  }
337
386
 
338
387
  // src/index.ts
@@ -361,7 +410,9 @@ async function promptToolType(initial) {
361
410
  message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
362
411
  options: [
363
412
  { value: "claude", label: "Claude Code\uFF08/v1/messages\uFF09" },
364
- { value: "codex", label: "Codex CLI\uFF08/v1/responses\uFF09" }
413
+ { value: "codex", label: "Codex CLI\uFF08/v1/responses\uFF09" },
414
+ { value: "opencode", label: "OpenCode\uFF08/v1/chat/completions\uFF09" },
415
+ { value: "openclaw", label: "OpenClaw\uFF08/v1/chat/completions\uFF09" }
365
416
  ]
366
417
  });
367
418
  if (p.isCancel(value)) {
@@ -389,6 +440,18 @@ async function promptModel(type, initial) {
389
440
  }
390
441
  return String(value).trim();
391
442
  }
443
+ function recommendedHint(toolType, recommended) {
444
+ switch (toolType) {
445
+ case "claude":
446
+ return recommended.claude;
447
+ case "codex":
448
+ return recommended.codex;
449
+ case "opencode":
450
+ return recommended.opencode;
451
+ case "openclaw":
452
+ return recommended.openclaw;
453
+ }
454
+ }
392
455
  async function run() {
393
456
  let opts;
394
457
  try {
@@ -413,6 +476,26 @@ async function run() {
413
476
  const { claudeBaseUrl } = resolveBaseUrls(opts.baseUrl);
414
477
  p.log.info(`\u5DE5\u5177: ${toolLabel(toolType)} \xB7 \u6A21\u578B: ${model}`);
415
478
  const spinner2 = p.spinner();
479
+ let verifiedModelIds;
480
+ if (!opts.skipVerify) {
481
+ spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
482
+ const result = await verifyTrinityApi(apiKey, claudeBaseUrl);
483
+ if (result.ok) {
484
+ spinner2.stop(pc.green(result.message));
485
+ verifiedModelIds = result.modelIds;
486
+ const recommended = pickRecommendedModels(result.modelIds ?? []);
487
+ const hint = recommendedHint(toolType, recommended);
488
+ if (hint && hint !== model) {
489
+ p.note(`\u540C\u7C7B\u578B\u63A8\u8350\u6A21\u578B: ${hint}`, "\u6A21\u578B");
490
+ }
491
+ } else {
492
+ spinner2.stop(pc.yellow(result.message));
493
+ p.note(
494
+ "\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",
495
+ "\u63D0\u793A"
496
+ );
497
+ }
498
+ }
416
499
  if (toolType === "claude") {
417
500
  spinner2.start("\u914D\u7F6E Claude Code\u2026");
418
501
  try {
@@ -421,53 +504,80 @@ async function run() {
421
504
  spinner2.stop("Claude Code \u914D\u7F6E\u5DF2\u5199\u5165 ~/.claude/settings.json");
422
505
  } catch (error) {
423
506
  spinner2.stop(pc.red("Claude Code \u914D\u7F6E\u5931\u8D25"));
424
- console.error(formatZcfError(error));
507
+ console.error(formatConfigError(error));
425
508
  return 1;
426
509
  }
427
- } else {
510
+ } else if (toolType === "codex") {
428
511
  spinner2.start("\u914D\u7F6E Codex CLI\u2026");
429
512
  try {
430
- await configureCodex(apiKey, apiBaseUrl, model, opts.verbose);
431
- spinner2.stop("Codex \u914D\u7F6E\u5DF2\u5199\u5165 ~/.codex/");
513
+ const backupDir = await configureCodex(apiKey, apiBaseUrl, model, opts.verbose);
514
+ const backupHint = backupDir ? `
515
+ \u5907\u4EFD: ${backupDir}` : "";
516
+ spinner2.stop(`Codex \u914D\u7F6E\u5DF2\u5199\u5165 ~/.codex/config.toml \u4E0E auth.json${backupHint}`);
432
517
  } catch (error) {
433
518
  spinner2.stop(pc.red("Codex \u914D\u7F6E\u5931\u8D25"));
434
- console.error(formatZcfError(error));
519
+ console.error(formatConfigError(error));
435
520
  return 1;
436
521
  }
437
- }
438
- if (!opts.skipVerify) {
439
- spinner2.start("\u9A8C\u8BC1 Trinity API \u8FDE\u901A\u6027\u2026");
440
- const result = await verifyTrinityApi(apiKey, claudeBaseUrl);
441
- if (result.ok) {
442
- spinner2.stop(pc.green(result.message));
443
- const recommended = pickRecommendedModels(result.modelIds ?? []);
444
- const hint = toolType === "claude" ? recommended.claude : recommended.codex;
445
- if (hint && hint !== model) {
446
- p.note(`\u540C\u7C7B\u578B\u63A8\u8350\u6A21\u578B: ${hint}`, "\u6A21\u578B");
447
- }
448
- } else {
449
- spinner2.stop(pc.yellow(result.message));
450
- p.note(
451
- "\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",
452
- "\u63D0\u793A"
522
+ } else if (toolType === "opencode") {
523
+ spinner2.start("\u914D\u7F6E OpenCode\u2026");
524
+ try {
525
+ const backupDir = await configureOpenCode(
526
+ apiKey,
527
+ apiBaseUrl,
528
+ model,
529
+ verifiedModelIds
530
+ );
531
+ const backupHint = backupDir ? `
532
+ \u5907\u4EFD: ${backupDir}` : "";
533
+ spinner2.stop(
534
+ `OpenCode \u914D\u7F6E\u5DF2\u5199\u5165 ~/.config/opencode/opencode.json${backupHint}`
453
535
  );
454
- p.outro(pc.yellow("\u914D\u7F6E\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
536
+ } catch (error) {
537
+ spinner2.stop(pc.red("OpenCode \u914D\u7F6E\u5931\u8D25"));
538
+ console.error(formatConfigError(error));
455
539
  return 1;
456
540
  }
541
+ } else {
542
+ spinner2.start("\u914D\u7F6E OpenClaw\u2026");
543
+ try {
544
+ const backupDir = await configureOpenClaw(
545
+ apiKey,
546
+ apiBaseUrl,
547
+ model,
548
+ verifiedModelIds
549
+ );
550
+ const backupHint = backupDir ? `
551
+ \u5907\u4EFD: ${backupDir}` : "";
552
+ spinner2.stop(
553
+ `OpenClaw \u914D\u7F6E\u5DF2\u5199\u5165 ~/.openclaw/openclaw.json${backupHint}`
554
+ );
555
+ } catch (error) {
556
+ spinner2.stop(pc.red("OpenClaw \u914D\u7F6E\u5931\u8D25"));
557
+ console.error(formatConfigError(error));
558
+ return 1;
559
+ }
560
+ }
561
+ if (!opts.skipVerify && verifiedModelIds === void 0) {
562
+ p.outro(pc.yellow("\u914D\u7F6E\u5B8C\u6210\uFF08\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF09"));
563
+ return 1;
564
+ }
565
+ const nextSteps = [
566
+ `\u8FD0\u884C ${launchCommand(toolType)} \u542F\u52A8 ${toolLabel(toolType)}`
567
+ ];
568
+ if (toolType === "opencode") {
569
+ nextSteps.push("\u5728 OpenCode \u5185\u7528 /models \u9009\u62E9 trinity/<model_code>");
570
+ } else if (toolType === "openclaw") {
571
+ nextSteps.push("\u9ED8\u8BA4\u6A21\u578B\u4E3A trinity/<model_code>\uFF1B\u6539\u5B8C\u914D\u7F6E\u540E\u8BF7\u91CD\u542F Gateway");
572
+ } else {
573
+ nextSteps.push("\u5728\u5DE5\u5177\u5185\u7528 /model \u5207\u6362\u4E3A Trinity \u5DF2\u4E0A\u67B6\u7684 model_code");
457
574
  }
458
- const launchCmd = toolType === "claude" ? "claude" : "codex";
459
- p.note(
460
- [
461
- `\u8FD0\u884C ${launchCmd} \u542F\u52A8 ${toolLabel(toolType)}`,
462
- "\u5728\u5DE5\u5177\u5185\u7528 /model \u5207\u6362\u4E3A Trinity \u5DF2\u4E0A\u67B6\u7684 model_code",
463
- "\u914D\u7F6E\u540E\u8BF7\u6C42\u8D70 Trinity \u8BA1\u8D39\uFF0C\u4E0D\u518D\u4F7F\u7528 Anthropic/OpenAI \u5B98\u65B9\u989D\u5EA6"
464
- ].join("\n"),
465
- "\u4E0B\u4E00\u6B65"
466
- );
575
+ nextSteps.push("\u914D\u7F6E\u540E\u8BF7\u6C42\u8D70 Trinity \u8BA1\u8D39\uFF0C\u4E0D\u518D\u4F7F\u7528\u5B98\u65B9\u989D\u5EA6");
576
+ p.note(nextSteps.join("\n"), "\u4E0B\u4E00\u6B65");
467
577
  p.outro(pc.green("\u5B8C\u6210"));
468
578
  return 0;
469
579
  }
470
- function formatZcfError(error) {
580
+ function formatConfigError(error) {
471
581
  if (error instanceof Error && "timed out" in error) {
472
582
  return "zcf \u6267\u884C\u8D85\u65F6\uFF08>3 \u5206\u949F\uFF09\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5\u3002";
473
583
  }
@@ -0,0 +1,107 @@
1
+ // src/merge-codex-config.ts
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ import { parse, stringify } from "smol-toml";
6
+ var TRINITY_CODEX_PROVIDER = "trinity";
7
+ var TRINITY_CODEX_TEMP_ENV_KEY = "TRINITY_API_KEY";
8
+ function codexHome() {
9
+ return process.env.CODEX_HOME?.trim() || path.join(os.homedir(), ".codex");
10
+ }
11
+ function codexConfigPath() {
12
+ return path.join(codexHome(), "config.toml");
13
+ }
14
+ function codexAuthPath() {
15
+ return path.join(codexHome(), "auth.json");
16
+ }
17
+ function timestampLabel() {
18
+ const now = /* @__PURE__ */ new Date();
19
+ const pad = (n) => String(n).padStart(2, "0");
20
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
21
+ }
22
+ function backupCodexFiles() {
23
+ const home = codexHome();
24
+ const configPath = codexConfigPath();
25
+ const authPath = codexAuthPath();
26
+ if (!fs.existsSync(configPath) && !fs.existsSync(authPath)) {
27
+ return null;
28
+ }
29
+ const backupDir = path.join(home, "backup", `trinity-config-${timestampLabel()}`);
30
+ fs.mkdirSync(backupDir, { recursive: true });
31
+ for (const src of [configPath, authPath]) {
32
+ if (fs.existsSync(src)) {
33
+ fs.copyFileSync(src, path.join(backupDir, path.basename(src)));
34
+ }
35
+ }
36
+ return backupDir;
37
+ }
38
+ function readCodexConfigDoc() {
39
+ const configPath = codexConfigPath();
40
+ if (!fs.existsSync(configPath)) {
41
+ return {};
42
+ }
43
+ const raw = fs.readFileSync(configPath, "utf8");
44
+ if (!raw.trim()) {
45
+ return {};
46
+ }
47
+ try {
48
+ return parse(raw);
49
+ } catch {
50
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 ${configPath}\uFF0C\u8BF7\u68C0\u67E5 TOML \u8BED\u6CD5\u540E\u91CD\u8BD5\u3002`);
51
+ }
52
+ }
53
+ function buildTrinityProvider(baseUrl) {
54
+ return {
55
+ name: "Trinity API",
56
+ base_url: baseUrl.replace(/\/+$/, ""),
57
+ wire_api: "responses",
58
+ temp_env_key: TRINITY_CODEX_TEMP_ENV_KEY,
59
+ requires_openai_auth: false
60
+ };
61
+ }
62
+ function mergeCodexConfig(model, baseUrl) {
63
+ const configPath = codexConfigPath();
64
+ const doc = readCodexConfigDoc();
65
+ doc.model = model;
66
+ doc.model_provider = TRINITY_CODEX_PROVIDER;
67
+ doc.disable_response_storage = true;
68
+ const providers = doc.model_providers && typeof doc.model_providers === "object" ? { ...doc.model_providers } : {};
69
+ providers[TRINITY_CODEX_PROVIDER] = buildTrinityProvider(baseUrl);
70
+ doc.model_providers = providers;
71
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
72
+ fs.writeFileSync(configPath, `${stringify(doc)}
73
+ `, "utf8");
74
+ }
75
+ function mergeCodexAuth(apiKey) {
76
+ const authPath = codexAuthPath();
77
+ let auth = {};
78
+ if (fs.existsSync(authPath)) {
79
+ try {
80
+ auth = JSON.parse(fs.readFileSync(authPath, "utf8"));
81
+ } catch {
82
+ auth = {};
83
+ }
84
+ }
85
+ auth[TRINITY_CODEX_TEMP_ENV_KEY] = apiKey;
86
+ auth.OPENAI_API_KEY = apiKey;
87
+ fs.mkdirSync(path.dirname(authPath), { recursive: true });
88
+ fs.writeFileSync(authPath, `${JSON.stringify(auth, null, 2)}
89
+ `, "utf8");
90
+ }
91
+ function configureCodexDirect(apiKey, codexBaseUrl, model) {
92
+ const backupDir = backupCodexFiles();
93
+ mergeCodexConfig(model, codexBaseUrl);
94
+ mergeCodexAuth(apiKey);
95
+ return backupDir;
96
+ }
97
+ export {
98
+ TRINITY_CODEX_PROVIDER,
99
+ TRINITY_CODEX_TEMP_ENV_KEY,
100
+ backupCodexFiles,
101
+ codexAuthPath,
102
+ codexConfigPath,
103
+ codexHome,
104
+ configureCodexDirect,
105
+ mergeCodexAuth,
106
+ mergeCodexConfig
107
+ };
@@ -0,0 +1,126 @@
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 modelsBlock = doc.models && typeof doc.models === "object" ? { ...doc.models } : {};
78
+ modelsBlock.mode = modelsBlock.mode ?? "merge";
79
+ const providers = modelsBlock.providers && typeof modelsBlock.providers === "object" ? { ...modelsBlock.providers } : {};
80
+ const existing = providers[TRINITY_OPENCLAW_PROVIDER] ?? {};
81
+ const existingModels = Array.isArray(existing.models) ? existing.models : [];
82
+ const byId = /* @__PURE__ */ new Map();
83
+ for (const m of existingModels) {
84
+ if (m && typeof m === "object" && typeof m.id === "string") {
85
+ byId.set(m.id, m);
86
+ }
87
+ }
88
+ for (const m of buildModelEntries(options.model, options.modelIds)) {
89
+ byId.set(m.id, { ...byId.get(m.id), ...m });
90
+ }
91
+ providers[TRINITY_OPENCLAW_PROVIDER] = {
92
+ ...existing,
93
+ baseUrl: options.baseUrl.replace(/\/+$/, ""),
94
+ apiKey: options.apiKey,
95
+ api: "openai-completions",
96
+ models: [...byId.values()]
97
+ };
98
+ modelsBlock.providers = providers;
99
+ doc.models = modelsBlock;
100
+ const agents = doc.agents && typeof doc.agents === "object" ? { ...doc.agents } : {};
101
+ const defaults = agents.defaults && typeof agents.defaults === "object" ? { ...agents.defaults } : {};
102
+ const modelCfg = defaults.model && typeof defaults.model === "object" ? { ...defaults.model } : {};
103
+ modelCfg.primary = `${TRINITY_OPENCLAW_PROVIDER}/${options.model}`;
104
+ defaults.model = modelCfg;
105
+ const allow = defaults.models && typeof defaults.models === "object" ? { ...defaults.models } : {};
106
+ allow[`${TRINITY_OPENCLAW_PROVIDER}/*`] = allow[`${TRINITY_OPENCLAW_PROVIDER}/*`] ?? {};
107
+ defaults.models = allow;
108
+ agents.defaults = defaults;
109
+ doc.agents = agents;
110
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
111
+ fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
112
+ `, "utf8");
113
+ }
114
+ function configureOpenClawDirect(options) {
115
+ const backupDir = backupOpenClawConfig();
116
+ mergeOpenClawConfig(options);
117
+ return backupDir;
118
+ }
119
+ export {
120
+ TRINITY_OPENCLAW_PROVIDER,
121
+ backupOpenClawConfig,
122
+ configureOpenClawDirect,
123
+ mergeOpenClawConfig,
124
+ openclawConfigPath,
125
+ openclawHome
126
+ };
@@ -0,0 +1,107 @@
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
+ doc.$schema = doc.$schema ?? "https://opencode.ai/config.json";
72
+ doc.model = `${TRINITY_OPENCODE_PROVIDER}/${options.model}`;
73
+ const providers = doc.provider && typeof doc.provider === "object" ? { ...doc.provider } : {};
74
+ const existing = providers[TRINITY_OPENCODE_PROVIDER] ?? {};
75
+ const existingModels = existing.models && typeof existing.models === "object" ? existing.models : {};
76
+ providers[TRINITY_OPENCODE_PROVIDER] = {
77
+ ...existing,
78
+ npm: "@ai-sdk/openai-compatible",
79
+ name: "Trinity",
80
+ options: {
81
+ ...existing.options ?? {},
82
+ baseURL: options.baseUrl.replace(/\/+$/, ""),
83
+ apiKey: options.apiKey
84
+ },
85
+ models: {
86
+ ...existingModels,
87
+ ...buildModelMap(options.model, options.modelIds)
88
+ }
89
+ };
90
+ doc.provider = providers;
91
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
92
+ fs.writeFileSync(configPath, `${JSON.stringify(doc, null, 2)}
93
+ `, "utf8");
94
+ }
95
+ function configureOpenCodeDirect(options) {
96
+ const backupDir = backupOpenCodeConfig();
97
+ mergeOpenCodeConfig(options);
98
+ return backupDir;
99
+ }
100
+ export {
101
+ TRINITY_OPENCODE_PROVIDER,
102
+ backupOpenCodeConfig,
103
+ configureOpenCodeDirect,
104
+ mergeOpenCodeConfig,
105
+ opencodeConfigDir,
106
+ opencodeConfigPath
107
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trinity-config",
3
- "version": "1.1.0",
4
- "description": "一键把 Trinity API 接入 Claude CodeCodex CLI",
3
+ "version": "1.2.1",
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"
@@ -42,7 +44,8 @@
42
44
  "dependencies": {
43
45
  "@clack/prompts": "^0.11.0",
44
46
  "execa": "^9.5.2",
45
- "picocolors": "^1.1.1"
47
+ "picocolors": "^1.1.1",
48
+ "smol-toml": "^1.3.1"
46
49
  },
47
50
  "devDependencies": {
48
51
  "@types/node": "^22.10.0",