@zhin.js/scaffold-wizard 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,4 +16,14 @@ export declare function configureOneBot11Bot(ctx: EndpointConfigureContext): Pro
16
16
  export declare function configureQQBot(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
17
17
  export declare function configureDiscordEndpoint(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
18
18
  export declare function configureSlackEndpoint(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
19
+ export declare function configureNapcatBot(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
20
+ export declare function configureOneBot12Bot(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
21
+ export declare function configureMilkyBot(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
22
+ export declare function configureSatoriBot(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
23
+ /**
24
+ * 微信 iLink(个人微信 / ClawBot)配置:默认走扫码绑定 —— 终端展示二维码,
25
+ * 微信扫码确认后 bot_token 写入 .env(`WEIXIN_ILINK_TOKEN`),
26
+ * zhin.config.yml 只留 `${WEIXIN_ILINK_TOKEN}` 引用;扫码失败可降级手动输入 token。
27
+ */
28
+ export declare function configureWeixinIlinkBot(ctx: EndpointConfigureContext): Promise<Record<string, unknown>>;
19
29
  //# sourceMappingURL=adapter-configurers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter-configurers.d.ts","sourceRoot":"","sources":["../src/adapter-configurers.ts"],"names":[],"mappings":"AAGA,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,kCAAkC,CAAC;AAEjE,uCAAuC;AACvC,eAAO,MAAM,kBAAkB,kCAAoB,CAAC;AAEpD,0BAA0B;AAC1B,eAAO,MAAM,uBAAuB,4CAA4C,CAAC;AAEjF,uCAAuC;AACvC,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,WAA2C,CAAC;AAEvF,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,oBAAoB,EAAE,MAAM,IAAI,CAAC;CAClC;AAED,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiF/G;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiJ7G;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsE1G;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsDpG;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmE9G;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAgE5G"}
1
+ {"version":3,"file":"adapter-configurers.d.ts","sourceRoot":"","sources":["../src/adapter-configurers.ts"],"names":[],"mappings":"AAIA,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,kCAAkC,CAAC;AAEjE,uCAAuC;AACvC,eAAO,MAAM,kBAAkB,kCAAoB,CAAC;AAEpD,0BAA0B;AAC1B,eAAO,MAAM,uBAAuB,4CAA4C,CAAC;AAEjF,uCAAuC;AACvC,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,WAA2C,CAAC;AAEvF,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,oBAAoB,EAAE,MAAM,IAAI,CAAC;CAClC;AAED,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiF/G;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmJ7G;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsE1G;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAyDpG;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmE9G;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAgE5G;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0FxG;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0F1G;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoEvG;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAkExG;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiD7G"}
@@ -1,5 +1,6 @@
1
1
  import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
+ import { IlinkQrLoginError, loginWithIlinkQr } from './weixin-ilink-login.js';
3
4
  /** 平台适配器文档索引 */
4
5
  export const ADAPTERS_DOCS_URL = 'https://zhin.js.org/adapters/';
5
6
  /** @deprecated 使用 ADAPTERS_DOCS_URL */
@@ -46,10 +47,10 @@ export async function configureTelegramEndpoint(ctx) {
46
47
  default: 'polling',
47
48
  },
48
49
  ]);
49
- const endpointConfig = {
50
- name: endpointName.trim(),
51
- token: '${TELEGRAM_TOKEN}',
50
+ // schema:polling / webhook 为顶层共享字段;name / token 为 endpoint 级
51
+ const config = {
52
52
  polling: transport === 'polling',
53
+ endpoints: [{ name: endpointName.trim(), token: '${TELEGRAM_TOKEN}' }],
53
54
  };
54
55
  if (transport === 'webhook') {
55
56
  console.log('');
@@ -76,13 +77,13 @@ export async function configureTelegramEndpoint(ctx) {
76
77
  default: '/telegram/webhook',
77
78
  },
78
79
  ]);
79
- endpointConfig.polling = false;
80
- endpointConfig.webhook = {
80
+ config.polling = false;
81
+ config.webhook = {
81
82
  domain: webhook.domain.trim(),
82
83
  path: webhook.path.trim(),
83
84
  };
84
85
  }
85
- return endpointConfig;
86
+ return config;
86
87
  }
87
88
  export async function configureGitHubEndpoint(ctx) {
88
89
  console.log(chalk.gray(' 文档: ' + adapterDocsUrl('github')));
@@ -114,9 +115,11 @@ export async function configureGitHubEndpoint(ctx) {
114
115
  default: 'github_app',
115
116
  },
116
117
  ]);
118
+ // schema:app_id / private_key / webhook_secret 为 endpoint 级;webhook_path / poll_interval 为顶层共享字段
117
119
  const endpointConfig = {
118
120
  name: endpointName.trim(),
119
121
  };
122
+ const sharedConfig = {};
120
123
  if (authMode === 'github_app') {
121
124
  console.log(chalk.gray(' 在 github.com/settings/apps 创建 App,记录 App ID 并下载 .pem 私钥'));
122
125
  const appConfig = await inquirer.prompt([
@@ -180,7 +183,7 @@ export async function configureGitHubEndpoint(ctx) {
180
183
  ]);
181
184
  ctx.envVars.GITHUB_WEBHOOK_SECRET = webhook.webhook_secret;
182
185
  endpointConfig.webhook_secret = '${GITHUB_WEBHOOK_SECRET}';
183
- endpointConfig.webhook_path = webhook.webhook_path.trim();
186
+ sharedConfig.webhook_path = webhook.webhook_path.trim();
184
187
  }
185
188
  else {
186
189
  const { poll_interval } = await inquirer.prompt([
@@ -197,7 +200,7 @@ export async function configureGitHubEndpoint(ctx) {
197
200
  },
198
201
  },
199
202
  ]);
200
- endpointConfig.poll_interval = parseInt(poll_interval, 10);
203
+ sharedConfig.poll_interval = parseInt(poll_interval, 10);
201
204
  }
202
205
  const { enableMcp } = await inquirer.prompt([
203
206
  {
@@ -218,7 +221,7 @@ export async function configureGitHubEndpoint(ctx) {
218
221
  ]);
219
222
  ctx.envVars.GITHUB_PERSONAL_ACCESS_TOKEN = pat;
220
223
  }
221
- return endpointConfig;
224
+ return { ...sharedConfig, endpoints: [endpointConfig] };
222
225
  }
223
226
  export async function configureOneBot11Bot(ctx) {
224
227
  console.log(chalk.gray(' 文档: ' + adapterDocsUrl('onebot11')));
@@ -243,8 +246,8 @@ export async function configureOneBot11Bot(ctx) {
243
246
  validate: (v) => (v.trim() ? true : '名称不能为空'),
244
247
  },
245
248
  ]);
249
+ // schema:connection 为顶层共享字段;name / url / path / access_token 为 endpoint 级
246
250
  const endpointConfig = {
247
- connection,
248
251
  name: endpointName.trim(),
249
252
  };
250
253
  if (connection === 'ws') {
@@ -284,7 +287,7 @@ export async function configureOneBot11Bot(ctx) {
284
287
  ctx.envVars.ONEBOT11_ACCESS_TOKEN = accessToken.trim();
285
288
  endpointConfig.access_token = '${ONEBOT11_ACCESS_TOKEN}';
286
289
  }
287
- return endpointConfig;
290
+ return { connection, endpoints: [endpointConfig] };
288
291
  }
289
292
  export async function configureQQBot(ctx) {
290
293
  console.log(chalk.gray(' 文档: ' + adapterDocsUrl('qq')));
@@ -329,12 +332,15 @@ export async function configureQQBot(ctx) {
329
332
  default: false,
330
333
  },
331
334
  ]);
335
+ // schema:mode / sandbox 为顶层共享字段;name / appid / secret 为 endpoint 级
332
336
  return {
333
- name: credentials.name.trim(),
334
- appid: '${QQ_APP_ID}',
335
- secret: '${QQ_SECRET}',
336
337
  mode,
337
338
  sandbox,
339
+ endpoints: [{
340
+ name: credentials.name.trim(),
341
+ appid: '${QQ_APP_ID}',
342
+ secret: '${QQ_SECRET}',
343
+ }],
338
344
  };
339
345
  }
340
346
  export async function configureDiscordEndpoint(ctx) {
@@ -367,10 +373,10 @@ export async function configureDiscordEndpoint(ctx) {
367
373
  },
368
374
  ]);
369
375
  ctx.envVars.DISCORD_TOKEN = token;
370
- const endpointConfig = {
376
+ // schema:connection / applicationId / publicKey / interactionsPath 为顶层共享字段;name / token 为 endpoint 级
377
+ const config = {
371
378
  connection,
372
- name: endpointName.trim(),
373
- token: '${DISCORD_TOKEN}',
379
+ endpoints: [{ name: endpointName.trim(), token: '${DISCORD_TOKEN}' }],
374
380
  };
375
381
  if (connection === 'interactions') {
376
382
  console.log(chalk.gray(' Interactions 模式由内置 HTTP Host 承接,需 Developer Portal 中的公钥'));
@@ -394,11 +400,11 @@ export async function configureDiscordEndpoint(ctx) {
394
400
  default: '/discord/interactions',
395
401
  },
396
402
  ]);
397
- endpointConfig.applicationId = interactions.applicationId.trim();
398
- endpointConfig.publicKey = interactions.publicKey.trim();
399
- endpointConfig.interactionsPath = interactions.interactionsPath.trim();
403
+ config.applicationId = interactions.applicationId.trim();
404
+ config.publicKey = interactions.publicKey.trim();
405
+ config.interactionsPath = interactions.interactionsPath.trim();
400
406
  }
401
- return endpointConfig;
407
+ return config;
402
408
  }
403
409
  export async function configureSlackEndpoint(ctx) {
404
410
  console.log(chalk.gray(' 文档: ' + adapterDocsUrl('slack')));
@@ -437,11 +443,11 @@ export async function configureSlackEndpoint(ctx) {
437
443
  ]);
438
444
  ctx.envVars.SLACK_BOT_TOKEN = token;
439
445
  ctx.envVars.SLACK_SIGNING_SECRET = signingSecret;
446
+ // schema:socketMode 为顶层共享字段;name / token / signingSecret / appToken 为 endpoint 级
440
447
  const endpointConfig = {
441
448
  name: endpointName.trim(),
442
449
  token: '${SLACK_BOT_TOKEN}',
443
450
  signingSecret: '${SLACK_SIGNING_SECRET}',
444
- socketMode: socketMode === 'true',
445
451
  };
446
452
  if (socketMode === 'true') {
447
453
  const { appToken } = await inquirer.prompt([
@@ -458,6 +464,404 @@ export async function configureSlackEndpoint(ctx) {
458
464
  else {
459
465
  console.log(chalk.yellow(' HTTP 模式由内置 HTTP Host 承接,需公网可达并配置 Slack Event Subscriptions URL'));
460
466
  }
461
- return endpointConfig;
467
+ return { socketMode: socketMode === 'true', endpoints: [endpointConfig] };
468
+ }
469
+ export async function configureNapcatBot(ctx) {
470
+ console.log(chalk.gray(' 文档: ' + adapterDocsUrl('napcat')));
471
+ const { connection } = await inquirer.prompt([
472
+ {
473
+ type: 'select',
474
+ name: 'connection',
475
+ message: ' 连接方式:',
476
+ choices: [
477
+ { name: '正向 WebSocket (ws) — 本 Bot 连接 NapCat', value: 'ws' },
478
+ { name: '反向 WebSocket (wss) — NapCat 连本 Bot(由内置 HTTP Host 承接)', value: 'wss' },
479
+ { name: 'HTTP (http) — POST 上报入站 + HTTP API 出站(由内置 HTTP Host 承接)', value: 'http' },
480
+ ],
481
+ default: 'ws',
482
+ },
483
+ ]);
484
+ const { endpointName } = await inquirer.prompt([
485
+ {
486
+ type: 'input',
487
+ name: 'endpointName',
488
+ message: ' Endpoint 标识名称:',
489
+ default: 'napcat-bot',
490
+ validate: (v) => (v.trim() ? true : '名称不能为空'),
491
+ },
492
+ ]);
493
+ // schema:connection 为顶层共享字段;name / url / path / http_url / post_path / access_token 为 endpoint 级
494
+ const endpointConfig = {
495
+ name: endpointName.trim(),
496
+ };
497
+ if (connection === 'ws') {
498
+ const { url } = await inquirer.prompt([
499
+ {
500
+ type: 'input',
501
+ name: 'url',
502
+ message: ' NapCat WS 地址:',
503
+ default: 'ws://127.0.0.1:3001',
504
+ validate: (v) => (v.trim() ? true : '地址不能为空'),
505
+ },
506
+ ]);
507
+ endpointConfig.url = url.trim();
508
+ }
509
+ else if (connection === 'wss') {
510
+ console.log(chalk.gray(' 反向 WS 由内置 HTTP Host 承接,NapCat 配置连接到此 Bot'));
511
+ const { path: wsPath } = await inquirer.prompt([
512
+ {
513
+ type: 'input',
514
+ name: 'path',
515
+ message: ' 反向 WS 路径:',
516
+ default: '/napcat/ws',
517
+ validate: (v) => (v.trim() ? true : '路径不能为空'),
518
+ },
519
+ ]);
520
+ endpointConfig.path = wsPath.trim();
521
+ }
522
+ else {
523
+ const http = await inquirer.prompt([
524
+ {
525
+ type: 'input',
526
+ name: 'http_url',
527
+ message: ' NapCat HTTP API 地址(出站):',
528
+ default: 'http://127.0.0.1:3000',
529
+ validate: (v) => (v.trim() ? true : '地址不能为空'),
530
+ },
531
+ {
532
+ type: 'input',
533
+ name: 'post_path',
534
+ message: ' HTTP POST 上报路径(入站):',
535
+ default: '/napcat/post',
536
+ validate: (v) => (v.trim() ? true : '路径不能为空'),
537
+ },
538
+ ]);
539
+ endpointConfig.http_url = http.http_url.trim();
540
+ endpointConfig.post_path = http.post_path.trim();
541
+ }
542
+ const { accessToken } = await inquirer.prompt([
543
+ {
544
+ type: 'password',
545
+ name: 'accessToken',
546
+ message: ' access_token(无则留空):',
547
+ default: '',
548
+ },
549
+ ]);
550
+ if (accessToken?.trim()) {
551
+ ctx.envVars.NAPCAT_TOKEN = accessToken.trim();
552
+ endpointConfig.access_token = '${NAPCAT_TOKEN}';
553
+ }
554
+ return { connection, endpoints: [endpointConfig] };
555
+ }
556
+ export async function configureOneBot12Bot(ctx) {
557
+ console.log(chalk.gray(' 文档: ' + adapterDocsUrl('onebot12')));
558
+ const { connection } = await inquirer.prompt([
559
+ {
560
+ type: 'select',
561
+ name: 'connection',
562
+ message: ' 连接方式:',
563
+ choices: [
564
+ { name: '正向 WebSocket (ws) — 本 Bot 连接 OneBot 实现', value: 'ws' },
565
+ { name: 'HTTP Webhook (webhook) — POST 入站 + api_url HTTP 出站(由内置 HTTP Host 承接)', value: 'webhook' },
566
+ { name: '反向 WebSocket (wss) — OneBot 实现连本 Bot(由内置 HTTP Host 承接)', value: 'wss' },
567
+ ],
568
+ default: 'ws',
569
+ },
570
+ ]);
571
+ const { endpointName } = await inquirer.prompt([
572
+ {
573
+ type: 'input',
574
+ name: 'endpointName',
575
+ message: ' Endpoint 标识名称:',
576
+ default: 'onebot12-bot',
577
+ validate: (v) => (v.trim() ? true : '名称不能为空'),
578
+ },
579
+ ]);
580
+ // schema:connection 为顶层共享字段;name / url / path / api_url / access_token 为 endpoint 级
581
+ const endpointConfig = {
582
+ name: endpointName.trim(),
583
+ };
584
+ if (connection === 'ws') {
585
+ const { url } = await inquirer.prompt([
586
+ {
587
+ type: 'input',
588
+ name: 'url',
589
+ message: ' OneBot WS 地址:',
590
+ default: 'ws://127.0.0.1:6700',
591
+ validate: (v) => (v.trim() ? true : '地址不能为空'),
592
+ },
593
+ ]);
594
+ endpointConfig.url = url.trim();
595
+ }
596
+ else if (connection === 'webhook') {
597
+ const webhook = await inquirer.prompt([
598
+ {
599
+ type: 'input',
600
+ name: 'path',
601
+ message: ' Webhook POST 路径(入站):',
602
+ default: '/onebot12/webhook',
603
+ validate: (v) => (v.trim() ? true : '路径不能为空'),
604
+ },
605
+ {
606
+ type: 'input',
607
+ name: 'api_url',
608
+ message: ' HTTP action 地址(出站):',
609
+ default: 'http://127.0.0.1:6700',
610
+ validate: (v) => (v.trim() ? true : '地址不能为空'),
611
+ },
612
+ ]);
613
+ endpointConfig.path = webhook.path.trim();
614
+ endpointConfig.api_url = webhook.api_url.trim();
615
+ }
616
+ else {
617
+ console.log(chalk.gray(' 反向 WS 由内置 HTTP Host 承接,OneBot 实现配置连接到此 Bot'));
618
+ const { path: wsPath } = await inquirer.prompt([
619
+ {
620
+ type: 'input',
621
+ name: 'path',
622
+ message: ' 反向 WS 路径:',
623
+ default: '/onebot12/ws',
624
+ validate: (v) => (v.trim() ? true : '路径不能为空'),
625
+ },
626
+ ]);
627
+ endpointConfig.path = wsPath.trim();
628
+ }
629
+ const { accessToken } = await inquirer.prompt([
630
+ {
631
+ type: 'password',
632
+ name: 'accessToken',
633
+ message: ' access_token(无则留空):',
634
+ default: '',
635
+ },
636
+ ]);
637
+ if (accessToken?.trim()) {
638
+ ctx.envVars.ONEBOT12_ACCESS_TOKEN = accessToken.trim();
639
+ endpointConfig.access_token = '${ONEBOT12_ACCESS_TOKEN}';
640
+ }
641
+ return { connection, endpoints: [endpointConfig] };
642
+ }
643
+ export async function configureMilkyBot(ctx) {
644
+ console.log(chalk.gray(' 文档: ' + adapterDocsUrl('milky')));
645
+ const { connection } = await inquirer.prompt([
646
+ {
647
+ type: 'select',
648
+ name: 'connection',
649
+ message: ' 连接方式:',
650
+ choices: [
651
+ { name: '正向 WebSocket (ws) — 本 Bot 连协议端 ws(s)://baseUrl/event', value: 'ws' },
652
+ { name: 'SSE (sse) — HTTP GET baseUrl/event(text/event-stream)', value: 'sse' },
653
+ { name: 'Webhook (webhook) — POST 入站 + baseUrl HTTP API 出站(由内置 HTTP Host 承接)', value: 'webhook' },
654
+ { name: '反向 WebSocket (wss) — 由内置 HTTP Host 承接', value: 'wss' },
655
+ ],
656
+ default: 'ws',
657
+ },
658
+ ]);
659
+ const { endpointName, baseUrl } = await inquirer.prompt([
660
+ {
661
+ type: 'input',
662
+ name: 'endpointName',
663
+ message: ' Endpoint 标识名称:',
664
+ default: 'milky-bot',
665
+ validate: (v) => (v.trim() ? true : '名称不能为空'),
666
+ },
667
+ {
668
+ type: 'input',
669
+ name: 'baseUrl',
670
+ message: ' Milky HTTP API baseUrl:',
671
+ default: 'http://127.0.0.1:8080',
672
+ validate: (v) => (v.trim() ? true : 'baseUrl 不能为空'),
673
+ },
674
+ ]);
675
+ // schema:connection 为顶层共享字段;name / baseUrl / path / access_token 为 endpoint 级
676
+ const endpointConfig = {
677
+ name: endpointName.trim(),
678
+ baseUrl: baseUrl.trim(),
679
+ };
680
+ if (connection === 'webhook' || connection === 'wss') {
681
+ const { path: hookPath } = await inquirer.prompt([
682
+ {
683
+ type: 'input',
684
+ name: 'path',
685
+ message: ` ${connection === 'webhook' ? 'Webhook POST' : '反向 WS'} 路径:`,
686
+ default: connection === 'webhook' ? '/milky/webhook' : '/milky/ws',
687
+ validate: (v) => (v.trim() ? true : '路径不能为空'),
688
+ },
689
+ ]);
690
+ endpointConfig.path = hookPath.trim();
691
+ }
692
+ const { accessToken } = await inquirer.prompt([
693
+ {
694
+ type: 'password',
695
+ name: 'accessToken',
696
+ message: ' access_token(无则留空):',
697
+ default: '',
698
+ },
699
+ ]);
700
+ if (accessToken?.trim()) {
701
+ ctx.envVars.MILKY_ACCESS_TOKEN = accessToken.trim();
702
+ endpointConfig.access_token = '${MILKY_ACCESS_TOKEN}';
703
+ }
704
+ return { connection, endpoints: [endpointConfig] };
705
+ }
706
+ export async function configureSatoriBot(ctx) {
707
+ console.log(chalk.gray(' 文档: ' + adapterDocsUrl('satori')));
708
+ const { connection } = await inquirer.prompt([
709
+ {
710
+ type: 'select',
711
+ name: 'connection',
712
+ message: ' 连接方式:',
713
+ choices: [
714
+ { name: '正向 WebSocket (ws) — 本 Bot 连 Satori SDK', value: 'ws' },
715
+ { name: 'Webhook (webhook) — SDK POST 事件到 path(由内置 HTTP Host 承接)', value: 'webhook' },
716
+ ],
717
+ default: 'ws',
718
+ },
719
+ ]);
720
+ const { endpointName, baseUrl } = await inquirer.prompt([
721
+ {
722
+ type: 'input',
723
+ name: 'endpointName',
724
+ message: ' Endpoint 标识名称:',
725
+ default: 'satori-bot',
726
+ validate: (v) => (v.trim() ? true : '名称不能为空'),
727
+ },
728
+ {
729
+ type: 'input',
730
+ name: 'baseUrl',
731
+ message: ' Satori SDK baseUrl:',
732
+ default: 'http://127.0.0.1:5140',
733
+ validate: (v) => (v.trim() ? true : 'baseUrl 不能为空'),
734
+ },
735
+ ]);
736
+ // schema:connection 为顶层共享字段;name / baseUrl / token / path 为 endpoint 级
737
+ const endpointConfig = {
738
+ name: endpointName.trim(),
739
+ baseUrl: baseUrl.trim(),
740
+ };
741
+ if (connection === 'webhook') {
742
+ const { path: hookPath } = await inquirer.prompt([
743
+ {
744
+ type: 'input',
745
+ name: 'path',
746
+ message: ' Webhook POST 路径:',
747
+ default: '/satori/webhook',
748
+ validate: (v) => (v.trim() ? true : '路径不能为空'),
749
+ },
750
+ ]);
751
+ endpointConfig.path = hookPath.trim();
752
+ }
753
+ const { token } = await inquirer.prompt([
754
+ {
755
+ type: 'password',
756
+ name: 'token',
757
+ message: ' Bearer Token(无则留空):',
758
+ default: '',
759
+ },
760
+ ]);
761
+ if (token?.trim()) {
762
+ ctx.envVars.SATORI_TOKEN = token.trim();
763
+ endpointConfig.token = '${SATORI_TOKEN}';
764
+ }
765
+ return { connection, endpoints: [endpointConfig] };
766
+ }
767
+ /**
768
+ * 微信 iLink(个人微信 / ClawBot)配置:默认走扫码绑定 —— 终端展示二维码,
769
+ * 微信扫码确认后 bot_token 写入 .env(`WEIXIN_ILINK_TOKEN`),
770
+ * zhin.config.yml 只留 `${WEIXIN_ILINK_TOKEN}` 引用;扫码失败可降级手动输入 token。
771
+ */
772
+ export async function configureWeixinIlinkBot(ctx) {
773
+ console.log(chalk.gray(' 文档: ' + adapterDocsUrl('weixin-ilink')));
774
+ console.log(chalk.gray(' 需最新版微信 + ClawBot 灰度资格(仅私聊)'));
775
+ const { endpointName } = await inquirer.prompt([
776
+ {
777
+ type: 'input',
778
+ name: 'endpointName',
779
+ message: ' Endpoint 标识名称:',
780
+ default: 'weixin-bot',
781
+ validate: (v) => (v.trim() ? true : '名称不能为空'),
782
+ },
783
+ ]);
784
+ const { method } = await inquirer.prompt([
785
+ {
786
+ type: 'select',
787
+ name: 'method',
788
+ message: ' 登录方式:',
789
+ choices: [
790
+ { name: '扫码绑定(微信 ClawBot 入口扫码,自动获取 bot_token)', value: 'qr' },
791
+ { name: '手动输入 bot_token', value: 'manual' },
792
+ ],
793
+ default: 'qr',
794
+ },
795
+ ]);
796
+ if (method === 'qr') {
797
+ const scanned = await tryQrLogin(ctx);
798
+ if (scanned) {
799
+ return {
800
+ endpoints: [{ name: endpointName.trim(), botToken: '${WEIXIN_ILINK_TOKEN}' }],
801
+ };
802
+ }
803
+ console.log(chalk.yellow(' 扫码未完成,降级为手动输入 bot_token'));
804
+ }
805
+ const { token } = await inquirer.prompt([
806
+ {
807
+ type: 'password',
808
+ name: 'token',
809
+ message: ' iLink Bot Token:',
810
+ validate: (v) => (v.trim() ? true : 'Token 不能为空'),
811
+ },
812
+ ]);
813
+ ctx.envVars.WEIXIN_ILINK_TOKEN = token.trim();
814
+ return {
815
+ endpoints: [{ name: endpointName.trim(), botToken: '${WEIXIN_ILINK_TOKEN}' }],
816
+ };
817
+ }
818
+ /** 扫码尝试:成功写入 envVars 并返回 true;用户放弃/过期/超时返回 false。 */
819
+ async function tryQrLogin(ctx) {
820
+ // 动态加载 qrcode:扫码路径才需要,失败则降级为纯链接展示
821
+ let renderQr;
822
+ try {
823
+ const qrcode = await import('qrcode');
824
+ renderQr = (text) => qrcode.toString(text, { type: 'terminal', small: true });
825
+ }
826
+ catch {
827
+ renderQr = undefined;
828
+ }
829
+ for (;;) {
830
+ try {
831
+ const creds = await loginWithIlinkQr({
832
+ onQrCode: async (url) => {
833
+ console.log('');
834
+ if (renderQr) {
835
+ console.log(await renderQr(url));
836
+ }
837
+ console.log(chalk.cyan(' 请用微信扫描上方二维码(或打开链接完成绑定):'));
838
+ console.log(chalk.cyan(` ${url}`));
839
+ console.log('');
840
+ },
841
+ onStatus: (status) => {
842
+ if (status === 'scaned') {
843
+ console.log(chalk.green(' 已扫码,请在微信中确认…'));
844
+ }
845
+ },
846
+ });
847
+ ctx.envVars.WEIXIN_ILINK_TOKEN = creds.botToken;
848
+ console.log(chalk.green(' 绑定成功,bot_token 已写入 .env(WEIXIN_ILINK_TOKEN)'));
849
+ return true;
850
+ }
851
+ catch (error) {
852
+ const message = error instanceof Error ? error.message : String(error);
853
+ console.log(chalk.yellow(` 扫码登录失败:${message}`));
854
+ const { retry } = await inquirer.prompt([
855
+ {
856
+ type: 'confirm',
857
+ name: 'retry',
858
+ message: ' 重新获取二维码再试一次?(选否则手动输入 token)',
859
+ default: error instanceof IlinkQrLoginError && error.reason === 'expired',
860
+ },
861
+ ]);
862
+ if (!retry)
863
+ return false;
864
+ }
865
+ }
462
866
  }
463
867
  //# sourceMappingURL=adapter-configurers.js.map