@zhin.js/scaffold-wizard 0.1.8 → 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.
package/src/adapter.ts CHANGED
@@ -5,24 +5,39 @@ import {
5
5
  adapterDocsUrl,
6
6
  configureDiscordEndpoint,
7
7
  configureGitHubEndpoint,
8
+ configureMilkyBot,
9
+ configureNapcatBot,
8
10
  configureOneBot11Bot,
11
+ configureOneBot12Bot,
9
12
  configureQQBot,
13
+ configureSatoriBot,
10
14
  configureSlackEndpoint,
11
15
  configureTelegramEndpoint,
16
+ configureWeixinIlinkBot,
12
17
  } from './adapter-configurers.js';
13
18
  import { ZHIN_STACK_VERSIONS } from './zhin-stack-deps.js';
14
19
 
20
+ /** 一个待挂载的子插件实例(package.json zhin.plugins 清单 + zhin.config plugins.<instanceKey> 配置) */
21
+ export interface AdapterPluginInstance {
22
+ /** 适配器/插件包名,如 @zhin.js/adapter-sandbox */
23
+ package: string;
24
+ /** 运行时实例键,如 sandbox / telegram;同名包多实例时需区分 */
25
+ instanceKey: string;
26
+ /** 写入 zhin.config plugins.<instanceKey> 的配置(对齐该插件 schema.json) */
27
+ config: Record<string, unknown>;
28
+ }
29
+
15
30
  export interface AdapterSetupResult {
16
31
  packages: string[];
17
32
  plugins: string[];
18
- endpoints: Array<Record<string, any>>;
33
+ instances: AdapterPluginInstance[];
19
34
  envVars: Record<string, string>;
20
35
  /** 所选适配器需要 database 插件(如 GitHub 订阅/OAuth 表) */
21
36
  requiresDatabase?: boolean;
22
37
  }
23
38
 
24
39
  // 适配器定义
25
- interface AdapterDefinition {
40
+ export interface AdapterDefinition {
26
41
  name: string;
27
42
  value: string;
28
43
  package: string;
@@ -38,7 +53,7 @@ interface AdapterDefinition {
38
53
  fields: AdapterField[];
39
54
  }
40
55
 
41
- interface AdapterField {
56
+ export interface AdapterField {
42
57
  key: string;
43
58
  message: string;
44
59
  type?: 'input' | 'password' | 'select';
@@ -46,6 +61,8 @@ interface AdapterField {
46
61
  required?: boolean;
47
62
  envKey?: string; // 如果是敏感信息,对应的环境变量名
48
63
  choices?: { name: string; value: string }[];
64
+ /** 字段归属:endpoint 级(默认,进 endpoints[i])或 shared(顶层共享字段) */
65
+ scope?: 'endpoint' | 'shared';
49
66
  }
50
67
 
51
68
  const ADAPTERS: AdapterDefinition[] = [
@@ -56,7 +73,7 @@ const ADAPTERS: AdapterDefinition[] = [
56
73
  plugin: '@zhin.js/adapter-sandbox',
57
74
  needsHttp: true,
58
75
  description: '终端 + Remote Console 沙盒页,本地开发首选',
59
- setupHint: '无需额外凭据;pnpm dev 后可在终端或 Console 沙盒页直接对话。',
76
+ setupHint: '无需额外凭据;pnpm dev(zhin runtime start)后可在 Console 沙盒页直接对话。',
60
77
  fields: [],
61
78
  },
62
79
  {
@@ -70,6 +87,8 @@ const ADAPTERS: AdapterDefinition[] = [
70
87
  docUrl: adapterDocsUrl('icqq'),
71
88
  fields: [
72
89
  { key: 'name', message: 'QQ 号(与 icqq login 时一致):', required: true, envKey: 'ICQQ_ACCOUNT' },
90
+ // schema 顶层 required: master(所有 endpoint 共享)
91
+ { key: 'master', message: '主人 QQ 号(master,/approve 等管理命令):', required: true, scope: 'shared' },
73
92
  ],
74
93
  },
75
94
  {
@@ -143,6 +162,8 @@ const ADAPTERS: AdapterDefinition[] = [
143
162
  fields: [
144
163
  { key: 'appKey', message: 'App Key:', required: true, envKey: 'DINGTALK_APP_KEY' },
145
164
  { key: 'appSecret', message: 'App Secret:', required: true, type: 'password', envKey: 'DINGTALK_APP_SECRET' },
165
+ // schema endpoints[i] required 含 robotCode(主动发送 /robot/send 需要)
166
+ { key: 'robotCode', message: 'Robot Code:', required: true, envKey: 'DINGTALK_ROBOT_CODE' },
146
167
  { key: 'webhookPath', message: 'Webhook 路径:', default: '/dingtalk/webhook' },
147
168
  ],
148
169
  },
@@ -157,7 +178,24 @@ const ADAPTERS: AdapterDefinition[] = [
157
178
  fields: [
158
179
  { key: 'appId', message: 'App ID:', required: true, envKey: 'LARK_APP_ID' },
159
180
  { key: 'appSecret', message: 'App Secret:', required: true, type: 'password', envKey: 'LARK_APP_SECRET' },
160
- { key: 'webhookPath', message: 'Webhook 路径:', default: '/lark/webhook' },
181
+ // schema 顶层共享字段(endpoints[i] 不含 webhookPath)
182
+ { key: 'webhookPath', message: 'Webhook 路径:', default: '/lark/webhook', scope: 'shared' },
183
+ ],
184
+ },
185
+ {
186
+ name: 'LINE',
187
+ value: 'line',
188
+ package: '@zhin.js/adapter-line',
189
+ plugin: '@zhin.js/adapter-line',
190
+ needsHttp: true,
191
+ description: 'LINE Messaging API',
192
+ setupHint: '在 LINE Developers Console 创建 Messaging API Channel;Webhook URL 需 HTTPS 公网可达,并关闭 Auto-reply。',
193
+ docUrl: adapterDocsUrl('line'),
194
+ fields: [
195
+ { key: 'channelSecret', message: 'Channel Secret:', required: true, type: 'password', envKey: 'LINE_CHANNEL_SECRET' },
196
+ { key: 'channelAccessToken', message: 'Channel Access Token:', required: true, type: 'password', envKey: 'LINE_CHANNEL_ACCESS_TOKEN' },
197
+ // schema 顶层共享字段(endpoints[i] 不含 webhookPath)
198
+ { key: 'webhookPath', message: 'Webhook 路径:', default: '/line/webhook', scope: 'shared' },
161
199
  ],
162
200
  },
163
201
  {
@@ -167,11 +205,59 @@ const ADAPTERS: AdapterDefinition[] = [
167
205
  plugin: '@zhin.js/adapter-onebot11',
168
206
  needsHttp: false,
169
207
  description: 'OneBot v11(ws 正向 / wss 反向)',
170
- setupHint: 'connection: ws 连 OneBot 实现;connection: wss 反向连接(需 host-router)。',
208
+ setupHint: 'connection: ws 连 OneBot 实现;connection: wss 反向连接(由内置 HTTP Host 承接)。',
171
209
  docUrl: adapterDocsUrl('onebot11'),
172
210
  configure: configureOneBot11Bot,
173
211
  fields: [],
174
212
  },
213
+ {
214
+ name: 'OneBot v12',
215
+ value: 'onebot12',
216
+ package: '@zhin.js/adapter-onebot12',
217
+ plugin: '@zhin.js/adapter-onebot12',
218
+ needsHttp: false,
219
+ description: 'OneBot v12(ws / webhook / wss)',
220
+ setupHint: 'connection: ws 连 OneBot 实现;webhook / wss 由内置 HTTP Host 承接。',
221
+ docUrl: adapterDocsUrl('onebot12'),
222
+ configure: configureOneBot12Bot,
223
+ fields: [],
224
+ },
225
+ {
226
+ name: 'NapCat',
227
+ value: 'napcat',
228
+ package: '@zhin.js/adapter-napcat',
229
+ plugin: '@zhin.js/adapter-napcat',
230
+ needsHttp: false,
231
+ description: 'NapCat(OneBot 11 + NapCat 扩展)',
232
+ setupHint: 'connection: ws 连 NapCat(默认 ws://127.0.0.1:3001);wss / http 由内置 HTTP Host 承接。',
233
+ docUrl: adapterDocsUrl('napcat'),
234
+ configure: configureNapcatBot,
235
+ fields: [],
236
+ },
237
+ {
238
+ name: 'Milky',
239
+ value: 'milky',
240
+ package: '@zhin.js/adapter-milky',
241
+ plugin: '@zhin.js/adapter-milky',
242
+ needsHttp: false,
243
+ description: 'Milky 协议(ws / sse / webhook / wss)',
244
+ setupHint: 'connection: ws 连协议端 ws(s)://baseUrl/event;webhook / wss 由内置 HTTP Host 承接。',
245
+ docUrl: adapterDocsUrl('milky'),
246
+ configure: configureMilkyBot,
247
+ fields: [],
248
+ },
249
+ {
250
+ name: 'Satori',
251
+ value: 'satori',
252
+ package: '@zhin.js/adapter-satori',
253
+ plugin: '@zhin.js/adapter-satori',
254
+ needsHttp: false,
255
+ description: 'Satori 聊天协议(ws / webhook)',
256
+ setupHint: 'connection: ws 连 Satori SDK(默认 http://127.0.0.1:5140);webhook 由内置 HTTP Host 承接。',
257
+ docUrl: adapterDocsUrl('satori'),
258
+ configure: configureSatoriBot,
259
+ fields: [],
260
+ },
175
261
  {
176
262
  name: '微信公众号',
177
263
  value: 'wechat-mp',
@@ -184,7 +270,26 @@ const ADAPTERS: AdapterDefinition[] = [
184
270
  { key: 'appId', message: 'App ID:', required: true, envKey: 'WECHAT_APP_ID' },
185
271
  { key: 'appSecret', message: 'App Secret:', required: true, type: 'password', envKey: 'WECHAT_APP_SECRET' },
186
272
  { key: 'token', message: '验证 Token:', required: true, envKey: 'WECHAT_TOKEN' },
187
- { key: 'webhookPath', message: 'Webhook 路径:', default: '/wechat/webhook' },
273
+ // schema 顶层共享字段名为 path(endpoints[i] 不含此项)
274
+ { key: 'webhookPath', message: 'Webhook 路径:', default: '/wechat/webhook', scope: 'shared' },
275
+ ],
276
+ },
277
+ {
278
+ name: '企业微信',
279
+ value: 'wecom',
280
+ package: '@zhin.js/adapter-wecom',
281
+ plugin: '@zhin.js/adapter-wecom',
282
+ needsHttp: true,
283
+ description: '企业微信自建应用',
284
+ setupHint: '管理后台创建自建应用并配置「接收消息」URL(需公网可达);Token / EncodingAESKey 与后台一致。',
285
+ docUrl: adapterDocsUrl('wecom'),
286
+ fields: [
287
+ { key: 'corpId', message: 'Corp ID:', required: true, envKey: 'WECOM_CORP_ID' },
288
+ { key: 'agentSecret', message: '应用 Secret:', required: true, type: 'password', envKey: 'WECOM_AGENT_SECRET' },
289
+ { key: 'token', message: '回调 Token:', required: true, type: 'password', envKey: 'WECOM_TOKEN' },
290
+ { key: 'encodingAESKey', message: 'EncodingAESKey(43 字符):', required: true, type: 'password', envKey: 'WECOM_AES_KEY' },
291
+ // schema 顶层共享字段(endpoints[i] 不含 webhookPath)
292
+ { key: 'webhookPath', message: '回调路径:', default: '/wecom/callback', scope: 'shared' },
188
293
  ],
189
294
  },
190
295
  {
@@ -211,12 +316,24 @@ const ADAPTERS: AdapterDefinition[] = [
211
316
  plugin: '@zhin.js/adapter-github',
212
317
  needsHttp: true,
213
318
  description: 'GitHub Issue/PR 聊天 + App 认证',
214
- setupHint: 'GitHub App 或 gh CLI;Webhook 用 /pub/github/webhook;需 database 存订阅与 OAuth。',
319
+ setupHint: 'GitHub App 或 gh CLI;Webhook 由内置 HTTP Host 承接(默认 /github/webhook);需 database 存订阅与 OAuth。',
215
320
  docUrl: adapterDocsUrl('github'),
216
321
  requiresDatabase: true,
217
322
  configure: configureGitHubEndpoint,
218
323
  fields: [],
219
324
  },
325
+ {
326
+ name: '微信 iLink(个人微信)',
327
+ value: 'weixin-ilink',
328
+ package: '@zhin.js/adapter-weixin-ilink',
329
+ plugin: '@zhin.js/adapter-weixin-ilink',
330
+ needsHttp: false,
331
+ description: '个人微信(iLink / ClawBot 灰度入口,长轮询,仅私聊)',
332
+ setupHint: '需最新版微信 + ClawBot 灰度资格;默认扫码绑定(bot_token 写入 .env),也可手动输入或放 data/weixin-ilink/<name>.json 侧车凭证文件。',
333
+ docUrl: adapterDocsUrl('weixin-ilink'),
334
+ configure: configureWeixinIlinkBot,
335
+ fields: [],
336
+ },
220
337
  ];
221
338
 
222
339
  /**
@@ -243,7 +360,7 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
243
360
  if (input.length === 0) return '至少选择一个适配器';
244
361
  return true;
245
362
  },
246
- pageSize: 14,
363
+ pageSize: 22,
247
364
  }
248
365
  ]);
249
366
 
@@ -261,13 +378,13 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
261
378
  for (const adapter of httpAdapters) {
262
379
  console.log(chalk.gray(` • ${adapter.name.split(' (')[0]}`));
263
380
  }
264
- console.log(chalk.gray(' 开发时可先用 Sandbox;上线前请确保 http 端口(默认 8086)可从平台回调。'));
381
+ console.log(chalk.gray(' 开发时可先用 Sandbox;上线前请确保 http 端口(默认 8068)可从平台回调。'));
265
382
  }
266
383
 
267
384
  const result: AdapterSetupResult = {
268
385
  packages: [],
269
386
  plugins: [],
270
- endpoints: [],
387
+ instances: [],
271
388
  envVars: {},
272
389
  requiresDatabase: false,
273
390
  };
@@ -287,10 +404,23 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
287
404
  result.requiresDatabase = true;
288
405
  }
289
406
 
290
- if (adapterValue === 'sandbox') continue;
407
+ if (adapterValue === 'sandbox') {
408
+ // 对齐 examples/test-bot:Console 沙盒页与本地调试共用的默认 Endpoint
409
+ result.instances.push({
410
+ package: adapterDef.package,
411
+ instanceKey: 'sandbox',
412
+ config: {
413
+ endpoints: [{ context: 'sandbox', name: 'sandbox-bot', owner: 'sandbox-user' }],
414
+ },
415
+ });
416
+ continue;
417
+ }
291
418
 
292
419
  const hasConfig = adapterDef.configure || adapterDef.fields.length > 0;
293
- if (!hasConfig) continue;
420
+ if (!hasConfig) {
421
+ result.instances.push({ package: adapterDef.package, instanceKey: adapterDef.value, config: {} });
422
+ continue;
423
+ }
294
424
 
295
425
  console.log('');
296
426
  console.log(chalk.yellow(` 📝 配置 ${adapterDef.name.split(' (')[0]} Bot`));
@@ -304,7 +434,7 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
304
434
 
305
435
  if (adapterDef.configure) {
306
436
  let markedDb = false;
307
- const endpointConfig = await adapterDef.configure({
437
+ const instanceConfig = await adapterDef.configure({
308
438
  envVars: result.envVars,
309
439
  markRequiresDatabase: () => {
310
440
  markedDb = true;
@@ -312,13 +442,11 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
312
442
  },
313
443
  });
314
444
  if (markedDb) result.requiresDatabase = true;
315
- result.endpoints.push(endpointConfig);
445
+ result.instances.push({ package: adapterDef.package, instanceKey: adapterDef.value, config: instanceConfig });
316
446
  continue;
317
447
  }
318
448
 
319
- const endpointConfig: Record<string, any> = {
320
- context: adapterDef.value,
321
- };
449
+ const fieldValues: Record<string, any> = {};
322
450
 
323
451
  for (const field of adapterDef.fields) {
324
452
  const promptConfig: any = {
@@ -344,127 +472,108 @@ export async function configureAdapters(): Promise<AdapterSetupResult> {
344
472
  if (field.envKey) {
345
473
  // 敏感信息存 .env,配置引用环境变量
346
474
  result.envVars[field.envKey] = value || '';
347
- endpointConfig[field.key] = `\${${field.envKey}}`;
475
+ fieldValues[field.key] = `\${${field.envKey}}`;
348
476
  } else {
349
- endpointConfig[field.key] = value;
477
+ fieldValues[field.key] = value;
350
478
  }
351
479
  }
352
480
 
353
- result.endpoints.push(endpointConfig);
481
+ result.instances.push({
482
+ package: adapterDef.package,
483
+ instanceKey: adapterDef.value,
484
+ config: buildFieldBasedInstanceConfig(adapterDef, fieldValues),
485
+ });
354
486
  }
355
487
 
356
488
  return result;
357
489
  }
358
490
 
359
491
  /**
360
- * 获取适配器配置需要写入 .env 的环境变量文本
492
+ * 字段式适配器(无自定义 configure)的实例配置整形,对齐各插件 schema.json 新格式:
493
+ * - endpoint 级字段(name + 凭据等,字段默认 scope: 'endpoint')进 `endpoints: [{ name, ... }]`
494
+ * - scope: 'shared' 的字段(如 lark/wechat-mp 的 webhookPath)留顶层
495
+ * - wechat-mp:顶层字段名为 path(向导沿用 webhookPath 提问)
496
+ * - email:smtp/imap 平铺字段 → endpoint 内 smtp/imap 嵌套对象
497
+ * - 未单独询问 name 的适配器用 `<adapter>-bot` 兜底
361
498
  */
362
- export function generateAdapterEnvVars(result: AdapterSetupResult): string {
363
- const entries = Object.entries(result.envVars);
364
- if (entries.length === 0) return '';
365
-
366
- const lines: string[] = ['', '# 适配器配置'];
367
- for (const [key, value] of entries) {
368
- lines.push(`${key}=${value}`);
499
+ export function buildFieldBasedInstanceConfig(adapter: AdapterDefinition, values: Record<string, any>): Record<string, unknown> {
500
+ if (adapter.value === 'email') {
501
+ const user = values.user ?? '';
502
+ const password = values.password ?? '';
503
+ return {
504
+ endpoints: [{
505
+ name: 'email-bot',
506
+ smtp: {
507
+ host: values.smtpHost,
508
+ port: Number(values.smtpPort) || 465,
509
+ secure: true,
510
+ auth: { user, pass: password },
511
+ },
512
+ imap: {
513
+ host: values.imapHost,
514
+ port: Number(values.imapPort) || 993,
515
+ tls: true,
516
+ user,
517
+ password,
518
+ },
519
+ }],
520
+ };
369
521
  }
370
- return lines.join('\n');
371
- }
372
522
 
373
- /**
374
- * 将 Endpoint 配置项格式化为 YAML 行
375
- */
376
- function appendYamlValue(lines: string[], key: string, value: unknown, indent: number): void {
377
- const pad = ' '.repeat(indent);
378
- if (value === undefined || value === null) return;
379
-
380
- if (typeof value === 'object' && !Array.isArray(value)) {
381
- lines.push(`${pad}${key}:`);
382
- for (const [subKey, subValue] of Object.entries(value)) {
383
- appendYamlValue(lines, subKey, subValue, indent + 1);
523
+ const endpoint: Record<string, unknown> = {};
524
+ const shared: Record<string, unknown> = {};
525
+ for (const field of adapter.fields) {
526
+ const value = values[field.key];
527
+ if (value === undefined) continue;
528
+ if (field.scope === 'shared') {
529
+ shared[field.key] = value;
530
+ } else {
531
+ endpoint[field.key] = value;
384
532
  }
385
- return;
386
533
  }
387
-
388
- if (Array.isArray(value)) {
389
- lines.push(`${pad}${key}:`);
390
- for (const item of value) {
391
- if (typeof item === 'string') {
392
- lines.push(`${pad} - "${item}"`);
393
- } else {
394
- lines.push(`${pad} - ${item}`);
395
- }
396
- }
397
- return;
534
+ if (adapter.value === 'wechat-mp' && shared.webhookPath !== undefined) {
535
+ shared.path = shared.webhookPath;
536
+ delete shared.webhookPath;
398
537
  }
399
-
400
- if (typeof value === 'boolean' || typeof value === 'number') {
401
- lines.push(`${pad}${key}: ${value}`);
402
- return;
538
+ if (!endpoint.name) {
539
+ endpoint.name = `${adapter.value}-bot`;
403
540
  }
404
-
405
- lines.push(`${pad}${key}: ${value}`);
541
+ return { ...shared, endpoints: [endpoint] };
406
542
  }
407
543
 
408
544
  /**
409
- * 生成 YAML 格式的 endpoints 配置段
545
+ * 获取适配器配置需要写入 .env 的环境变量文本
410
546
  */
411
- export function generateEndpointsConfigYaml(result: AdapterSetupResult): string {
412
- if (result.endpoints.length === 0) {
413
- return `
414
-
415
- # Sandbox:Remote Console 打开「沙盒」页并通过 WebSocket 连接时自动创建 Endpoint
416
- endpoints: []
417
- `;
418
- }
547
+ export function generateAdapterEnvVars(result: AdapterSetupResult): string {
548
+ const entries = Object.entries(result.envVars);
549
+ if (entries.length === 0) return '';
419
550
 
420
- const lines: string[] = ['', 'endpoints:'];
421
- for (const entry of result.endpoints) {
422
- const { context, ...config } = entry;
423
- lines.push(` - context: ${context}`);
424
- for (const [key, value] of Object.entries(config)) {
425
- appendYamlValue(lines, key, value, 2);
426
- }
551
+ const lines: string[] = ['', '# 适配器配置'];
552
+ for (const [key, value] of entries) {
553
+ lines.push(`${key}=${value}`);
427
554
  }
428
-
429
555
  return lines.join('\n');
430
556
  }
431
557
 
432
558
  /**
433
- * 生成 TOML 格式的 endpoints 配置段
559
+ * 汇总适配器实例为 zhin.config `plugins.<instanceKey>` 配置映射(新 Plugin Runtime 格式)
434
560
  */
435
- export function generateEndpointsConfigToml(result: AdapterSetupResult): string {
436
- if (result.endpoints.length === 0) return '';
437
-
438
- const lines: string[] = [''];
439
- for (const entry of result.endpoints) {
440
- lines.push('[[endpoints]]');
441
- for (const [key, value] of Object.entries(entry)) {
442
- if (typeof value === 'string') {
443
- lines.push(`${key} = "${escapeTomlString(value)}"`);
444
- } else {
445
- lines.push(`${key} = ${JSON.stringify(value)}`);
446
- }
447
- }
448
- lines.push('');
449
- }
450
- return lines.join('\n');
451
- }
452
-
453
- function escapeTomlString(value: string): string {
454
- let out = '';
455
- for (const ch of value) {
456
- if (ch === '"' || ch === '\\') out += `\\${ch}`;
457
- else out += ch;
561
+ export function collectAdapterPluginConfigs(result: AdapterSetupResult): Record<string, Record<string, unknown>> {
562
+ const plugins: Record<string, Record<string, unknown>> = {};
563
+ for (const instance of result.instances) {
564
+ plugins[instance.instanceKey] = instance.config;
458
565
  }
459
- return out;
566
+ return plugins;
460
567
  }
461
568
 
462
569
  /**
463
- * 生成 JSON 格式的 endpoints 配置段
570
+ * 汇总适配器实例为 package.json `zhin.plugins` 清单条目
464
571
  */
465
- export function generateEndpointsConfigJSON(result: AdapterSetupResult): string {
466
- if (result.endpoints.length === 0) return '';
467
- return ` "endpoints": ${JSON.stringify(result.endpoints, null, 4).replace(/^/gm, ' ').trimStart()},`;
572
+ export function collectAdapterPluginManifest(result: AdapterSetupResult): Array<{ package: string; instanceKey: string }> {
573
+ return result.instances.map((instance) => ({
574
+ package: instance.package,
575
+ instanceKey: instance.instanceKey,
576
+ }));
468
577
  }
469
578
 
470
579
  /**
@@ -472,21 +581,24 @@ export function generateEndpointsConfigJSON(result: AdapterSetupResult): string
472
581
  */
473
582
  export function getAdapterSetupNotes(result: AdapterSetupResult): string[] {
474
583
  const notes: string[] = [];
475
- for (const entry of result.endpoints) {
476
- const context = entry.context as string;
584
+ for (const instance of result.instances) {
585
+ const context = instance.instanceKey;
586
+ const entry = instance.config;
477
587
  if (context === 'telegram' && entry.polling === false) {
478
- notes.push('Telegram Webhook: 确保 HTTPS 域名可从公网访问,且 webhook.port 已放行');
588
+ notes.push('Telegram Webhook: 确保 HTTPS 域名可从公网访问,且 http 端口已放行');
479
589
  }
480
590
  if (context === 'telegram' && entry.polling !== false) {
481
591
  notes.push('Telegram polling: 本地开发可直接 pnpm dev,无需公网');
482
592
  }
483
593
  if (context === 'github') {
484
- if (entry.webhook_secret) {
485
- notes.push('GitHub Webhook: App 设置 URL 为 https://<域名>/pub/github/webhook');
594
+ const endpoints = Array.isArray(entry.endpoints) ? entry.endpoints as Array<Record<string, unknown>> : [];
595
+ const firstEndpoint = endpoints[0] ?? {};
596
+ if (firstEndpoint.webhook_secret) {
597
+ notes.push('GitHub Webhook: App 设置 URL 为 https://<域名>/github/webhook');
486
598
  } else {
487
599
  notes.push('GitHub polling: 无 Webhook 时将轮询 Events API(默认 60s)');
488
600
  }
489
- if (entry.app_id) {
601
+ if (firstEndpoint.app_id) {
490
602
  notes.push('GitHub App: 私钥路径需存在,且 App 已安装到目标仓库');
491
603
  } else {
492
604
  notes.push('GitHub gh CLI: 运行 gh auth login 完成认证');