trinity-config 1.1.1 → 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,57 +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
- - **Claude Code**:经 zcf 写入 `~/.claude/settings.json`
54
- - **Codex CLI**:**直接** merge `~/.codex/config.toml` + `auth.json`(不调用 zcf;zcf cx 会重置为 ChatGPT 官方登录)
21
+ OpenClaw 改完后请重启 Gateway(或重新 `openclaw dashboard`)。
55
22
 
56
23
  ## License
57
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.1";
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
@@ -232,6 +266,24 @@ function baseUrlForType(type, baseUrl) {
232
266
  const { claudeBaseUrl, codexBaseUrl } = resolveBaseUrls(baseUrl);
233
267
  return type === "claude" ? claudeBaseUrl : codexBaseUrl;
234
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
+ }
235
287
 
236
288
  // src/verify.ts
237
289
  import https from "https";
@@ -327,7 +379,9 @@ async function verifyTrinityApi(apiKey, baseUrl = TRINITY_BASE_URL) {
327
379
  function pickRecommendedModels(modelIds) {
328
380
  const claude = modelIds.find((id) => id === DEFAULT_CLAUDE_MODEL) ?? modelIds.find((id) => id.includes("claude-sonnet")) ?? modelIds.find((id) => id.toLowerCase().includes("claude"));
329
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"));
330
- 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 };
331
385
  }
332
386
 
333
387
  // src/index.ts
@@ -356,7 +410,9 @@ async function promptToolType(initial) {
356
410
  message: "\u8981\u914D\u7F6E\u54EA\u4E2A\u5DE5\u5177\uFF1F",
357
411
  options: [
358
412
  { value: "claude", label: "Claude Code\uFF08/v1/messages\uFF09" },
359
- { 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" }
360
416
  ]
361
417
  });
362
418
  if (p.isCancel(value)) {
@@ -384,6 +440,18 @@ async function promptModel(type, initial) {
384
440
  }
385
441
  return String(value).trim();
386
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
+ }
387
455
  async function run() {
388
456
  let opts;
389
457
  try {
@@ -408,6 +476,26 @@ async function run() {
408
476
  const { claudeBaseUrl } = resolveBaseUrls(opts.baseUrl);
409
477
  p.log.info(`\u5DE5\u5177: ${toolLabel(toolType)} \xB7 \u6A21\u578B: ${model}`);
410
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
+ }
411
499
  if (toolType === "claude") {
412
500
  spinner2.start("\u914D\u7F6E Claude Code\u2026");
413
501
  try {
@@ -416,10 +504,10 @@ async function run() {
416
504
  spinner2.stop("Claude Code \u914D\u7F6E\u5DF2\u5199\u5165 ~/.claude/settings.json");
417
505
  } catch (error) {
418
506
  spinner2.stop(pc.red("Claude Code \u914D\u7F6E\u5931\u8D25"));
419
- console.error(formatZcfError(error));
507
+ console.error(formatConfigError(error));
420
508
  return 1;
421
509
  }
422
- } else {
510
+ } else if (toolType === "codex") {
423
511
  spinner2.start("\u914D\u7F6E Codex CLI\u2026");
424
512
  try {
425
513
  const backupDir = await configureCodex(apiKey, apiBaseUrl, model, opts.verbose);
@@ -428,43 +516,68 @@ async function run() {
428
516
  spinner2.stop(`Codex \u914D\u7F6E\u5DF2\u5199\u5165 ~/.codex/config.toml \u4E0E auth.json${backupHint}`);
429
517
  } catch (error) {
430
518
  spinner2.stop(pc.red("Codex \u914D\u7F6E\u5931\u8D25"));
431
- console.error(formatZcfError(error));
519
+ console.error(formatConfigError(error));
432
520
  return 1;
433
521
  }
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"
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}`
450
535
  );
451
- 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));
452
539
  return 1;
453
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");
454
574
  }
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
- );
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");
464
577
  p.outro(pc.green("\u5B8C\u6210"));
465
578
  return 0;
466
579
  }
467
- function formatZcfError(error) {
580
+ function formatConfigError(error) {
468
581
  if (error instanceof Error && "timed out" in error) {
469
582
  return "zcf \u6267\u884C\u8D85\u65F6\uFF08>3 \u5206\u949F\uFF09\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5\u3002";
470
583
  }
@@ -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.1",
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"