@openclaw-china/shared 0.1.35 → 0.1.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw-china/shared",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -38,50 +38,50 @@ type RegisterCliLike = (
38
38
 
39
39
  type WriteConfigLike = (cfg: ConfigRoot) => Promise<void>;
40
40
 
41
- type ApiLike = {
42
- registerCli?: RegisterCliLike;
43
- runtime?: unknown;
44
- logger?: LoggerLike;
45
- };
41
+ type ApiLike = {
42
+ registerCli?: RegisterCliLike;
43
+ runtime?: unknown;
44
+ logger?: LoggerLike;
45
+ };
46
46
 
47
47
  type ConfigRecord = Record<string, unknown>;
48
48
 
49
- type ConfigRoot = {
50
- channels?: ConfigRecord;
51
- [key: string]: unknown;
52
- };
53
-
54
- export type ChannelId = "dingtalk" | "feishu-china" | "wecom" | "wecom-app" | "qqbot";
55
-
56
- export type RegisterChinaSetupCliOptions = {
57
- channels?: readonly ChannelId[];
58
- };
59
-
60
- type Option<T extends string> = {
61
- key?: string;
62
- value: T;
63
- label: string;
64
- };
65
-
66
- const PROJECT_REPO = "https://github.com/BytePioneer-AI/moltbot-china";
67
- const GUIDES_BASE = "https://github.com/BytePioneer-AI/openclaw-china/tree/main/doc/guides";
68
- const OPENCLAW_HOME = join(homedir(), ".openclaw");
69
- const DEFAULT_PLUGIN_PATH = join(OPENCLAW_HOME, "extensions");
70
- const LEGACY_PLUGIN_PATH = join(OPENCLAW_HOME, "plugins");
71
- const CONFIG_FILE_PATH = join(OPENCLAW_HOME, "openclaw.json");
72
- const ANSI_RESET = "\u001b[0m";
73
- const ANSI_LINK = "\u001b[1;4;96m";
74
- const ANSI_BORDER = "\u001b[92m";
75
- const CHANNEL_ORDER: readonly ChannelId[] = [
76
- "dingtalk",
77
- "qqbot",
78
- "wecom",
79
- "wecom-app",
80
- "feishu-china",
81
- ];
82
- const CHANNEL_DISPLAY_LABELS: Record<ChannelId, string> = {
83
- dingtalk: "DingTalk(钉钉)",
84
- "feishu-china": "Feishu(飞书)",
49
+ type ConfigRoot = {
50
+ channels?: ConfigRecord;
51
+ [key: string]: unknown;
52
+ };
53
+
54
+ export type ChannelId = "dingtalk" | "feishu-china" | "wecom" | "wecom-app" | "qqbot";
55
+
56
+ export type RegisterChinaSetupCliOptions = {
57
+ channels?: readonly ChannelId[];
58
+ };
59
+
60
+ type Option<T extends string> = {
61
+ key?: string;
62
+ value: T;
63
+ label: string;
64
+ };
65
+
66
+ const PROJECT_REPO = "https://github.com/BytePioneer-AI/openclaw-china";
67
+ const GUIDES_BASE = "https://github.com/BytePioneer-AI/openclaw-china/tree/main/doc/guides";
68
+ const OPENCLAW_HOME = join(homedir(), ".openclaw");
69
+ const DEFAULT_PLUGIN_PATH = join(OPENCLAW_HOME, "extensions");
70
+ const LEGACY_PLUGIN_PATH = join(OPENCLAW_HOME, "plugins");
71
+ const CONFIG_FILE_PATH = join(OPENCLAW_HOME, "openclaw.json");
72
+ const ANSI_RESET = "\u001b[0m";
73
+ const ANSI_LINK = "\u001b[1;4;96m";
74
+ const ANSI_BORDER = "\u001b[92m";
75
+ const CHANNEL_ORDER: readonly ChannelId[] = [
76
+ "dingtalk",
77
+ "qqbot",
78
+ "wecom",
79
+ "wecom-app",
80
+ "feishu-china",
81
+ ];
82
+ const CHANNEL_DISPLAY_LABELS: Record<ChannelId, string> = {
83
+ dingtalk: "DingTalk(钉钉)",
84
+ "feishu-china": "Feishu(飞书)",
85
85
  wecom: "WeCom(企业微信-智能机器人)",
86
86
  "wecom-app": "WeCom App(自建应用-可接入微信)",
87
87
  qqbot: "QQBot(QQ 机器人)",
@@ -90,71 +90,71 @@ const CHANNEL_GUIDE_LINKS: Record<ChannelId, string> = {
90
90
  dingtalk: `${GUIDES_BASE}/dingtalk/configuration.md`,
91
91
  "feishu-china": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/README.md",
92
92
  wecom: `${GUIDES_BASE}/wecom/configuration.md`,
93
- "wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
94
- qqbot: `${GUIDES_BASE}/qqbot/configuration.md`,
95
- };
96
- const CHINA_CLI_STATE_KEY = Symbol.for("@openclaw-china/china-cli-state");
97
-
98
- type ChinaCliState = {
99
- channels: Set<ChannelId>;
100
- cliRegistered: boolean;
101
- };
102
-
103
- class PromptCancelledError extends Error {
104
- constructor() {
105
- super("prompt-cancelled");
106
- }
107
- }
108
-
109
- function isChannelId(value: unknown): value is ChannelId {
110
- return typeof value === "string" && CHANNEL_ORDER.includes(value as ChannelId);
111
- }
112
-
113
- function getChinaCliState(): ChinaCliState {
114
- const root = globalThis as Record<PropertyKey, unknown>;
115
- const cached = root[CHINA_CLI_STATE_KEY];
116
-
117
- if (isRecord(cached)) {
118
- const channels = cached.channels;
119
- const cliRegistered = cached.cliRegistered;
120
- if (channels instanceof Set && typeof cliRegistered === "boolean") {
121
- return {
122
- channels: channels as Set<ChannelId>,
123
- cliRegistered,
124
- };
125
- }
126
- }
127
-
128
- const created: ChinaCliState = {
129
- channels: new Set<ChannelId>(),
130
- cliRegistered: false,
131
- };
132
- root[CHINA_CLI_STATE_KEY] = created;
133
- return created;
134
- }
135
-
136
- function normalizeChannels(channels?: readonly ChannelId[]): ChannelId[] {
137
- const selected = channels && channels.length > 0 ? channels : CHANNEL_ORDER;
138
- const unique = new Set<ChannelId>();
139
- for (const channelId of selected) {
140
- if (isChannelId(channelId)) {
141
- unique.add(channelId);
142
- }
143
- }
144
- return CHANNEL_ORDER.filter((channelId) => unique.has(channelId));
145
- }
146
-
147
- function getInstalledChannels(state: ChinaCliState): ChannelId[] {
148
- return CHANNEL_ORDER.filter((channelId) => state.channels.has(channelId));
149
- }
150
-
151
- function guardCancel<T>(value: T | symbol): T {
152
- if (isCancel(value)) {
153
- clackCancel("已取消配置。");
154
- throw new PromptCancelledError();
155
- }
156
- return value as T;
157
- }
93
+ "wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
94
+ qqbot: `${GUIDES_BASE}/qqbot/configuration.md`,
95
+ };
96
+ const CHINA_CLI_STATE_KEY = Symbol.for("@openclaw-china/china-cli-state");
97
+
98
+ type ChinaCliState = {
99
+ channels: Set<ChannelId>;
100
+ cliRegistered: boolean;
101
+ };
102
+
103
+ class PromptCancelledError extends Error {
104
+ constructor() {
105
+ super("prompt-cancelled");
106
+ }
107
+ }
108
+
109
+ function isChannelId(value: unknown): value is ChannelId {
110
+ return typeof value === "string" && CHANNEL_ORDER.includes(value as ChannelId);
111
+ }
112
+
113
+ function getChinaCliState(): ChinaCliState {
114
+ const root = globalThis as Record<PropertyKey, unknown>;
115
+ const cached = root[CHINA_CLI_STATE_KEY];
116
+
117
+ if (isRecord(cached)) {
118
+ const channels = cached.channels;
119
+ const cliRegistered = cached.cliRegistered;
120
+ if (channels instanceof Set && typeof cliRegistered === "boolean") {
121
+ return {
122
+ channels: channels as Set<ChannelId>,
123
+ cliRegistered,
124
+ };
125
+ }
126
+ }
127
+
128
+ const created: ChinaCliState = {
129
+ channels: new Set<ChannelId>(),
130
+ cliRegistered: false,
131
+ };
132
+ root[CHINA_CLI_STATE_KEY] = created;
133
+ return created;
134
+ }
135
+
136
+ function normalizeChannels(channels?: readonly ChannelId[]): ChannelId[] {
137
+ const selected = channels && channels.length > 0 ? channels : CHANNEL_ORDER;
138
+ const unique = new Set<ChannelId>();
139
+ for (const channelId of selected) {
140
+ if (isChannelId(channelId)) {
141
+ unique.add(channelId);
142
+ }
143
+ }
144
+ return CHANNEL_ORDER.filter((channelId) => unique.has(channelId));
145
+ }
146
+
147
+ function getInstalledChannels(state: ChinaCliState): ChannelId[] {
148
+ return CHANNEL_ORDER.filter((channelId) => state.channels.has(channelId));
149
+ }
150
+
151
+ function guardCancel<T>(value: T | symbol): T {
152
+ if (isCancel(value)) {
153
+ clackCancel("已取消配置。");
154
+ throw new PromptCancelledError();
155
+ }
156
+ return value as T;
157
+ }
158
158
 
159
159
  function warn(text: string): void {
160
160
  output.write(`\n[warn] ${text}\n`);
@@ -174,25 +174,25 @@ function resolvePluginPath(): string {
174
174
  return DEFAULT_PLUGIN_PATH;
175
175
  }
176
176
 
177
- function renderReadyMessage(): string {
178
- return [
179
- `${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
180
- " OpenClaw China Channels 已就绪!",
181
- `${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
177
+ function renderReadyMessage(): string {
178
+ return [
179
+ `${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
180
+ " OpenClaw China Channels 已就绪!",
181
+ `${ANSI_BORDER}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ANSI_RESET}`,
182
182
  "",
183
183
  "插件路径:",
184
184
  ` ${resolvePluginPath()}`,
185
185
  "",
186
- "配置文件:",
187
- ` ${CONFIG_FILE_PATH}`,
188
- "",
189
- "更新插件:",
190
- " openclaw plugins update <plugin-id>",
191
- "",
192
- "项目仓库:",
193
- ` ${ANSI_LINK}${PROJECT_REPO}${ANSI_RESET}`,
186
+ "配置文件:",
187
+ ` ${CONFIG_FILE_PATH}`,
194
188
  "",
195
- "⭐ 如果这个项目对你有帮助,请给我们一个 Star!⭐",
189
+ "更新插件:",
190
+ " openclaw plugins update <plugin-id>",
191
+ "",
192
+ "项目仓库:",
193
+ ` ${ANSI_LINK}${PROJECT_REPO}${ANSI_RESET}`,
194
+ "",
195
+ "⭐ 如果这个项目对你有帮助,请给我们一个 Star!⭐",
196
196
  "",
197
197
  "下一步:",
198
198
  " openclaw gateway --port 18789 --verbose",
@@ -209,23 +209,23 @@ function showGuideLink(channelId: ChannelId): void {
209
209
  clackNote(`配置文档:${url}`, "Docs");
210
210
  }
211
211
 
212
- function isRecord(value: unknown): value is ConfigRecord {
213
- return typeof value === "object" && value !== null && !Array.isArray(value);
214
- }
215
-
216
- function resolveWriteConfig(runtime: unknown): WriteConfigLike | undefined {
217
- if (!isRecord(runtime)) {
218
- return undefined;
219
- }
220
- const config = runtime.config;
221
- if (!isRecord(config)) {
222
- return undefined;
223
- }
224
- if (typeof config.writeConfigFile !== "function") {
225
- return undefined;
226
- }
227
- return config.writeConfigFile as WriteConfigLike;
228
- }
212
+ function isRecord(value: unknown): value is ConfigRecord {
213
+ return typeof value === "object" && value !== null && !Array.isArray(value);
214
+ }
215
+
216
+ function resolveWriteConfig(runtime: unknown): WriteConfigLike | undefined {
217
+ if (!isRecord(runtime)) {
218
+ return undefined;
219
+ }
220
+ const config = runtime.config;
221
+ if (!isRecord(config)) {
222
+ return undefined;
223
+ }
224
+ if (typeof config.writeConfigFile !== "function") {
225
+ return undefined;
226
+ }
227
+ return config.writeConfigFile as WriteConfigLike;
228
+ }
229
229
 
230
230
  function isCommandLike(value: unknown): value is CommandLike {
231
231
  if (!isRecord(value)) {
@@ -424,28 +424,28 @@ class SetupPrompter {
424
424
  }
425
425
  }
426
426
 
427
- async askSelect<T extends string>(
428
- message: string,
429
- options: Array<Option<T>>,
430
- defaultValue: T
431
- ): Promise<T> {
432
- const initial = options.some((opt) => opt.value === defaultValue)
433
- ? defaultValue
434
- : options[0]?.value;
435
- const selectOptions = options.map((option) => ({
436
- value: option.value,
437
- label: option.label,
438
- })) as Parameters<typeof clackSelect<T>>[0]["options"];
439
-
440
- return guardCancel(
441
- await clackSelect<T>({
442
- message,
443
- options: selectOptions,
444
- initialValue: initial,
445
- })
446
- );
447
- }
448
- }
427
+ async askSelect<T extends string>(
428
+ message: string,
429
+ options: Array<Option<T>>,
430
+ defaultValue: T
431
+ ): Promise<T> {
432
+ const initial = options.some((opt) => opt.value === defaultValue)
433
+ ? defaultValue
434
+ : options[0]?.value;
435
+ const selectOptions = options.map((option) => ({
436
+ value: option.value,
437
+ label: option.label,
438
+ })) as Parameters<typeof clackSelect<T>>[0]["options"];
439
+
440
+ return guardCancel(
441
+ await clackSelect<T>({
442
+ message,
443
+ options: selectOptions,
444
+ initialValue: initial,
445
+ })
446
+ );
447
+ }
448
+ }
449
449
 
450
450
  async function configureDingtalk(prompter: SetupPrompter, cfg: ConfigRoot): Promise<ConfigRoot> {
451
451
  section("配置 DingTalk(钉钉)");
@@ -531,6 +531,7 @@ async function configureWecomApp(prompter: SetupPrompter, cfg: ConfigRoot): Prom
531
531
  section("配置 WeCom App(自建应用-可接入微信)");
532
532
  showGuideLink("wecom-app");
533
533
  const existing = getChannelConfig(cfg, "wecom-app");
534
+ const existingAsr = isRecord(existing.asr) ? existing.asr : {};
534
535
 
535
536
  const webhookPath = await prompter.askText({
536
537
  label: "Webhook 路径(需与企业微信后台配置一致,默认 /wecom-app)",
@@ -572,6 +573,38 @@ async function configureWecomApp(prompter: SetupPrompter, cfg: ConfigRoot): Prom
572
573
  patch.corpId = corpId;
573
574
  patch.corpSecret = corpSecret;
574
575
  patch.agentId = agentId;
576
+ const asrEnabled = await prompter.askConfirm(
577
+ "启用 ASR(支持入站语音自动转文字)",
578
+ toBoolean(existingAsr.enabled, false)
579
+ );
580
+ const asr: ConfigRecord = {
581
+ enabled: asrEnabled,
582
+ };
583
+ if (asrEnabled) {
584
+ clackNote(
585
+ [
586
+ "ASR 开通方式请查看配置文档:步骤七(可选):开启语音转文本(ASR)",
587
+ "https://github.com/BytePioneer-AI/openclaw-china/blob/main/doc/guides/wecom-app/configuration.md",
588
+ ].join("\n"),
589
+ "提示"
590
+ );
591
+ asr.appId = await prompter.askText({
592
+ label: "ASR appId(腾讯云)",
593
+ defaultValue: toTrimmedString(existingAsr.appId),
594
+ required: true,
595
+ });
596
+ asr.secretId = await prompter.askSecret({
597
+ label: "ASR secretId(腾讯云)",
598
+ existingValue: toTrimmedString(existingAsr.secretId),
599
+ required: true,
600
+ });
601
+ asr.secretKey = await prompter.askSecret({
602
+ label: "ASR secretKey(腾讯云)",
603
+ existingValue: toTrimmedString(existingAsr.secretKey),
604
+ required: true,
605
+ });
606
+ }
607
+ patch.asr = asr;
575
608
 
576
609
  return mergeChannelConfig(cfg, "wecom-app", patch);
577
610
  }
@@ -580,6 +613,7 @@ async function configureQQBot(prompter: SetupPrompter, cfg: ConfigRoot): Promise
580
613
  section("配置 QQBot(QQ 机器人)");
581
614
  showGuideLink("qqbot");
582
615
  const existing = getChannelConfig(cfg, "qqbot");
616
+ const existingAsr = isRecord(existing.asr) ? existing.asr : {};
583
617
 
584
618
  const appId = await prompter.askText({
585
619
  label: "QQBot appId",
@@ -591,20 +625,46 @@ async function configureQQBot(prompter: SetupPrompter, cfg: ConfigRoot): Promise
591
625
  existingValue: toTrimmedString(existing.clientSecret),
592
626
  required: true,
593
627
  });
628
+ clackNote(
629
+ "QQ 的 Markdown 体验很好,但需要先申请开通,详情请查看配置文档。",
630
+ "提示"
631
+ );
594
632
  const markdownSupport = await prompter.askConfirm(
595
633
  "启用 Markdown 支持",
596
634
  toBoolean(existing.markdownSupport, false)
597
635
  );
598
- const replyFinalOnly = await prompter.askConfirm(
599
- "仅发送最终回复(关闭流式分片)",
600
- toBoolean(existing.replyFinalOnly, false)
636
+ const asrEnabled = await prompter.askConfirm(
637
+ "启用 ASR(支持入站语音自动转文字)",
638
+ toBoolean(existingAsr.enabled, false)
601
639
  );
602
640
 
641
+ const asr: ConfigRecord = {
642
+ enabled: asrEnabled,
643
+ };
644
+ if (asrEnabled) {
645
+ clackNote("ASR 开通方式详情请查看配置文档。", "提示");
646
+ asr.appId = await prompter.askText({
647
+ label: "ASR appId(腾讯云)",
648
+ defaultValue: toTrimmedString(existingAsr.appId),
649
+ required: true,
650
+ });
651
+ asr.secretId = await prompter.askSecret({
652
+ label: "ASR secretId(腾讯云)",
653
+ existingValue: toTrimmedString(existingAsr.secretId),
654
+ required: true,
655
+ });
656
+ asr.secretKey = await prompter.askSecret({
657
+ label: "ASR secretKey(腾讯云)",
658
+ existingValue: toTrimmedString(existingAsr.secretKey),
659
+ required: true,
660
+ });
661
+ }
662
+
603
663
  return mergeChannelConfig(cfg, "qqbot", {
604
664
  appId,
605
665
  clientSecret,
606
666
  markdownSupport,
607
- replyFinalOnly,
667
+ asr,
608
668
  });
609
669
  }
610
670
 
@@ -629,16 +689,16 @@ async function configureSingleChannel(
629
689
  }
630
690
  }
631
691
 
632
- async function runChinaSetup(params: {
633
- initialConfig: ConfigRoot;
634
- writeConfig?: WriteConfigLike;
635
- logger: LoggerLike;
636
- availableChannels: readonly ChannelId[];
637
- }): Promise<void> {
638
- if (!input.isTTY || !output.isTTY) {
639
- params.logger.error?.("交互式配置需要在 TTY 终端中运行。");
640
- return;
641
- }
692
+ async function runChinaSetup(params: {
693
+ initialConfig: ConfigRoot;
694
+ writeConfig?: WriteConfigLike;
695
+ logger: LoggerLike;
696
+ availableChannels: readonly ChannelId[];
697
+ }): Promise<void> {
698
+ if (!input.isTTY || !output.isTTY) {
699
+ params.logger.error?.("交互式配置需要在 TTY 终端中运行。");
700
+ return;
701
+ }
642
702
 
643
703
  const prompter = new SetupPrompter();
644
704
  const touched = new Set<ChannelId>();
@@ -646,37 +706,37 @@ async function runChinaSetup(params: {
646
706
 
647
707
  try {
648
708
  clackIntro("OpenClaw China 配置向导");
649
- clackNote(
650
- [
651
- "使用方向键选择,按 Enter 确认。",
652
- `项目仓库:${ANSI_LINK}${PROJECT_REPO}${ANSI_RESET}`,
653
- ].join("\n"),
654
- "欢迎"
655
- );
656
-
657
- if (params.availableChannels.length === 0) {
658
- params.logger.error?.("未检测到可配置的 China 渠道插件。");
659
- return;
660
- }
661
-
662
- const channelOptions = params.availableChannels.map((channelId, index) => ({
663
- key: index === 0 ? "recommended" : "",
664
- value: channelId,
665
- label: withConfiguredSuffix(next, channelId),
666
- }));
667
- const defaultChannel = channelOptions[0]?.value ?? "save";
668
-
669
- let continueLoop = true;
670
- while (continueLoop) {
671
- const selected = await prompter.askSelect<ChannelId | "save" | "cancel">(
672
- "请选择要配置的渠道",
673
- [
674
- ...channelOptions,
675
- { key: "", value: "save", label: "保存并退出" },
676
- { key: "", value: "cancel", label: "不保存并退出" },
677
- ],
678
- defaultChannel
679
- );
709
+ clackNote(
710
+ [
711
+ "使用方向键选择,按 Enter 确认。",
712
+ `项目仓库:${ANSI_LINK}${PROJECT_REPO}${ANSI_RESET}`,
713
+ ].join("\n"),
714
+ "欢迎"
715
+ );
716
+
717
+ if (params.availableChannels.length === 0) {
718
+ params.logger.error?.("未检测到可配置的 China 渠道插件。");
719
+ return;
720
+ }
721
+
722
+ const channelOptions = params.availableChannels.map((channelId, index) => ({
723
+ key: index === 0 ? "recommended" : "",
724
+ value: channelId,
725
+ label: withConfiguredSuffix(next, channelId),
726
+ }));
727
+ const defaultChannel = channelOptions[0]?.value ?? "save";
728
+
729
+ let continueLoop = true;
730
+ while (continueLoop) {
731
+ const selected = await prompter.askSelect<ChannelId | "save" | "cancel">(
732
+ "请选择要配置的渠道",
733
+ [
734
+ ...channelOptions,
735
+ { key: "", value: "save", label: "保存并退出" },
736
+ { key: "", value: "cancel", label: "不保存并退出" },
737
+ ],
738
+ defaultChannel
739
+ );
680
740
 
681
741
  if (selected === "cancel") {
682
742
  clackCancel("已取消,未写入任何配置。");
@@ -722,21 +782,21 @@ async function runChinaSetup(params: {
722
782
  }
723
783
  }
724
784
 
725
- export function registerChinaSetupCli(api: ApiLike, opts?: RegisterChinaSetupCliOptions): void {
726
- const state = getChinaCliState();
727
- for (const channelId of normalizeChannels(opts?.channels)) {
728
- state.channels.add(channelId);
729
- }
730
-
731
- if (state.cliRegistered || typeof api.registerCli !== "function") {
732
- return;
733
- }
734
- state.cliRegistered = true;
735
-
736
- const writeConfig = resolveWriteConfig(api.runtime);
737
- const fallbackLogger: LoggerLike = {
738
- info: (message) => output.write(`${message}\n`),
739
- warn: (message) => warn(message),
785
+ export function registerChinaSetupCli(api: ApiLike, opts?: RegisterChinaSetupCliOptions): void {
786
+ const state = getChinaCliState();
787
+ for (const channelId of normalizeChannels(opts?.channels)) {
788
+ state.channels.add(channelId);
789
+ }
790
+
791
+ if (state.cliRegistered || typeof api.registerCli !== "function") {
792
+ return;
793
+ }
794
+ state.cliRegistered = true;
795
+
796
+ const writeConfig = resolveWriteConfig(api.runtime);
797
+ const fallbackLogger: LoggerLike = {
798
+ info: (message) => output.write(`${message}\n`),
799
+ warn: (message) => warn(message),
740
800
  error: (message) => warn(message),
741
801
  };
742
802
 
@@ -752,30 +812,30 @@ export function registerChinaSetupCli(api: ApiLike, opts?: RegisterChinaSetupCli
752
812
 
753
813
  root
754
814
  .command("setup")
755
- .description("中国渠道交互式配置向导")
756
- .action(async () => {
757
- const logger = ctx.logger ?? api.logger ?? fallbackLogger;
758
- const availableChannels = getInstalledChannels(state);
759
- await runChinaSetup({
760
- initialConfig: isRecord(ctx.config) ? (ctx.config as ConfigRoot) : {},
761
- writeConfig,
762
- logger,
763
- availableChannels,
764
- });
765
- });
766
-
767
- root.command("about").description("显示项目信息").action(() => {
768
- const installed = getInstalledChannels(state);
769
- clackIntro("OpenClaw China 渠道插件");
770
- clackNote(
771
- installed.length > 0
772
- ? `当前已安装渠道:${installed.map((channelId) => CHANNEL_DISPLAY_LABELS[channelId]).join("、")}`
773
- : "OpenClaw China 渠道插件",
774
- "关于"
775
- );
776
- clackOutro(PROJECT_REPO);
777
- showReadyMessage();
778
- });
815
+ .description("中国渠道交互式配置向导")
816
+ .action(async () => {
817
+ const logger = ctx.logger ?? api.logger ?? fallbackLogger;
818
+ const availableChannels = getInstalledChannels(state);
819
+ await runChinaSetup({
820
+ initialConfig: isRecord(ctx.config) ? (ctx.config as ConfigRoot) : {},
821
+ writeConfig,
822
+ logger,
823
+ availableChannels,
824
+ });
825
+ });
826
+
827
+ root.command("about").description("显示项目信息").action(() => {
828
+ const installed = getInstalledChannels(state);
829
+ clackIntro("OpenClaw China 渠道插件");
830
+ clackNote(
831
+ installed.length > 0
832
+ ? `当前已安装渠道:${installed.map((channelId) => CHANNEL_DISPLAY_LABELS[channelId]).join("、")}`
833
+ : "OpenClaw China 渠道插件",
834
+ "关于"
835
+ );
836
+ clackOutro(PROJECT_REPO);
837
+ showReadyMessage();
838
+ });
779
839
  },
780
840
  { commands: ["china"] }
781
841
  );
package/src/cli/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from "./china-setup.js";
2
- export * from "./install-hint.js";
1
+ export * from "./china-setup.js";
2
+ export * from "./install-hint.js";