@lingyao037/openclaw-lingyao-cli 0.4.0 → 0.5.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/cli.mjs CHANGED
@@ -15,10 +15,14 @@ import { spawnSync, spawn, execSync } from 'child_process';
15
15
  import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from 'fs';
16
16
  import { join, dirname } from 'path';
17
17
  import { fileURLToPath } from 'url';
18
+ import https from 'https';
19
+ import QRCode from 'qrcode';
18
20
 
19
21
  const __filename = fileURLToPath(import.meta.url);
20
22
  const __dirname = dirname(__filename);
21
23
 
24
+ const LINGYAO_API = 'https://api.lingyao.live/lyoc';
25
+
22
26
  // 颜色
23
27
  const GREEN = '\x1b[32m';
24
28
  const YELLOW = '\x1b[33m';
@@ -33,6 +37,8 @@ class LingyaoInstaller {
33
37
  this.skipDeps = skipDeps;
34
38
  this.pluginName = '@lingyao/openclaw-lingyao-cli';
35
39
  this.channelId = 'lingyao';
40
+ this.gatewayId = null;
41
+ this.gatewayToken = null;
36
42
  }
37
43
 
38
44
  setOpenClawPath(path) {
@@ -317,11 +323,15 @@ class LingyaoInstaller {
317
323
  }
318
324
 
319
325
  // 启用 lingyao 插件(serverUrl 已内置,用户不可配置)
326
+ const gatewayId = this.gatewayId || this.generateGatewayId();
327
+ this.gatewayId = gatewayId;
328
+
320
329
  config.plugins.entries.lingyao = {
321
330
  enabled: true,
322
331
  config: {
323
332
  maxOfflineMessages: 100,
324
- tokenExpiryDays: 30
333
+ tokenExpiryDays: 30,
334
+ gatewayId,
325
335
  }
326
336
  };
327
337
 
@@ -382,7 +392,7 @@ class LingyaoInstaller {
382
392
  this.warn('注意事项:');
383
393
  this.log(' • 确保 OpenClaw Gateway 正在运行');
384
394
  this.log(' • 需要可访问的 lingyao.live 中转服务');
385
- this.log(' • 配对与设备接入通过灵爻 App 和中转服务完成');
395
+ this.log(' • 如需重新配对,请再次运行安装命令');
386
396
  this.log('');
387
397
 
388
398
  this.info('架构说明:');
@@ -396,6 +406,175 @@ class LingyaoInstaller {
396
406
  this.log('');
397
407
  }
398
408
 
409
+ // HTTP helpers
410
+ httpsPost(path, body) {
411
+ return new Promise((resolve, reject) => {
412
+ const data = JSON.stringify(body);
413
+ const url = new URL(path, LINGYAO_API);
414
+ const headers = { 'Content-Type': 'application/json' };
415
+ if (this.gatewayToken) {
416
+ headers['Authorization'] = `Bearer ${this.gatewayToken}`;
417
+ }
418
+ const req = https.request(url, { method: 'POST', headers }, (res) => {
419
+ let chunk = '';
420
+ res.on('data', (c) => chunk += c);
421
+ res.on('end', () => {
422
+ try { resolve(JSON.parse(chunk)); }
423
+ catch { reject(new Error(`Invalid JSON: ${chunk}`)); }
424
+ });
425
+ });
426
+ req.on('error', reject);
427
+ req.write(data);
428
+ req.end();
429
+ });
430
+ }
431
+
432
+ httpsGet(path) {
433
+ return new Promise((resolve, reject) => {
434
+ const url = new URL(path, LINGYAO_API);
435
+ https.get(url, (res) => {
436
+ let chunk = '';
437
+ res.on('data', (c) => chunk += c);
438
+ res.on('end', () => {
439
+ try { resolve(JSON.parse(chunk)); }
440
+ catch { reject(new Error(`Invalid JSON: ${chunk}`)); }
441
+ });
442
+ }).on('error', reject);
443
+ });
444
+ }
445
+
446
+ // Generate a stable gateway ID
447
+ generateGatewayId() {
448
+ const os = require('os');
449
+ const host = os.hostname().split('.')[0].replace(/[^a-z0-9]/gi, '').toLowerCase();
450
+ const suffix = Math.random().toString(36).substring(2, 8);
451
+ return `gw_openclaw_${host}_default_${suffix}`;
452
+ }
453
+
454
+ // Pair device flow: register gateway, init pairing, show QR, poll status
455
+ async pairDevice() {
456
+ this.log('\n═══════════════════════════════════════════════════', CYAN);
457
+ this.log(' 设备配对', CYAN);
458
+ this.log('═══════════════════════════════════════════════════\n', CYAN);
459
+
460
+ try {
461
+ // 1. Register gateway
462
+ this.info('正在注册到 lingyao.live...');
463
+ const regResult = await this.httpsPost('/gateway/register', {
464
+ gatewayId: this.gatewayId,
465
+ version: '0.4.1',
466
+ capabilities: { websocket: true },
467
+ });
468
+
469
+ if (!regResult.gatewayToken) {
470
+ this.error('Gateway 注册失败');
471
+ return false;
472
+ }
473
+
474
+ this.gatewayToken = regResult.gatewayToken;
475
+ this.success('Gateway 注册成功');
476
+
477
+ // 2. Init pairing
478
+ this.info('正在生成配对码...');
479
+ const pairResult = await this.httpsPost('/pairing/init', {});
480
+
481
+ if (!pairResult.session) {
482
+ this.error('配对码生成失败');
483
+ return false;
484
+ }
485
+
486
+ // 3. Display QR code and pairing code
487
+ this.log('');
488
+ this.log('╔═══════════════════════════════════════════════════════════╗', CYAN);
489
+ this.log('║ 请使用灵爻 App 扫描下方二维码 ║', CYAN);
490
+ this.log('╚═══════════════════════════════════════════════════════════╝', CYAN);
491
+ this.log('');
492
+
493
+ try {
494
+ const qr = await QRCode.toString(pairResult.qrData, {
495
+ type: 'terminal',
496
+ small: true,
497
+ });
498
+ this.log(qr);
499
+ } catch {
500
+ this.info(`配对链接: ${pairResult.qrData}`);
501
+ }
502
+
503
+ this.log('');
504
+ this.log(` 接入码: ${YELLOW}${pairResult.code}${NC}`, NC);
505
+ this.log('');
506
+ this.info('等待灵爻 App 扫码确认... (Ctrl+C 跳过)');
507
+ this.log('');
508
+
509
+ // 4. Poll pairing status
510
+ const pollInterval = 3000;
511
+ const maxAttempts = 600; // 30 minutes max
512
+ for (let i = 0; i < maxAttempts; i++) {
513
+ await new Promise(r => setTimeout(r, pollInterval));
514
+
515
+ try {
516
+ const status = await this.httpsGet(`/pairing/status/${pairResult.session}`);
517
+
518
+ if (status.status === 'completed') {
519
+ this.log('');
520
+ this.success('配对成功!');
521
+ this.log('');
522
+ this.log(` 设备 ID: ${status.deviceInfo?.name ?? 'unknown'}`);
523
+ this.log(` 平台: ${status.deviceInfo?.platform ?? 'unknown'}`);
524
+ this.log(` 版本: ${status.deviceInfo?.version ?? 'unknown'}`);
525
+ this.log('');
526
+
527
+ // Save device info to OpenClaw config
528
+ this.saveDeviceInfo(status.deviceInfo);
529
+ return true;
530
+ }
531
+
532
+ if (status.status === 'expired') {
533
+ this.error('配对码已过期');
534
+ return false;
535
+ }
536
+
537
+ // Still pending - show spinner dot
538
+ process.stdout.write('.');
539
+ } catch {
540
+ // Network error during polling, just retry
541
+ }
542
+ }
543
+
544
+ this.warn('\n配对等待超时,可稍后通过灵爻 App 重新配对');
545
+ return false;
546
+
547
+ } catch (error) {
548
+ this.error(`配对失败: ${error.message}`);
549
+ return false;
550
+ }
551
+ }
552
+
553
+ // Save paired device info to OpenClaw config
554
+ saveDeviceInfo(deviceInfo) {
555
+ if (!this.openclawPath) return;
556
+
557
+ const configFiles = [
558
+ join(this.openclawPath, 'openclaw.json'),
559
+ join(this.openclawPath, 'config.json'),
560
+ ];
561
+
562
+ for (const file of configFiles) {
563
+ if (!existsSync(file)) continue;
564
+ try {
565
+ const config = JSON.parse(readFileSync(file, 'utf-8'));
566
+ if (config.plugins?.entries?.lingyao?.config) {
567
+ config.plugins.entries.lingyao.config.pairedDevice = deviceInfo;
568
+ writeFileSync(file, JSON.stringify(config, null, 2), 'utf-8');
569
+ this.success('设备信息已保存到配置');
570
+ }
571
+ } catch {
572
+ // Ignore config write errors
573
+ }
574
+ break; // Only write to first found config file
575
+ }
576
+ }
577
+
399
578
  // 主安装流程
400
579
  async install() {
401
580
  try {
@@ -409,7 +588,7 @@ class LingyaoInstaller {
409
588
  return false;
410
589
  }
411
590
 
412
- // 步骤 3: 配置插件
591
+ // 步骤 3: 配置插件(含 gatewayId 生成)
413
592
  if (!this.configurePlugin()) {
414
593
  return false;
415
594
  }
@@ -417,7 +596,10 @@ class LingyaoInstaller {
417
596
  // 步骤 4: 重启网关
418
597
  const restarted = this.restartGateway();
419
598
 
420
- // 步骤 5: 显示说明
599
+ // 步骤 5: 设备配对
600
+ await this.pairDevice();
601
+
602
+ // 步骤 6: 显示说明
421
603
  this.showInstructions();
422
604
 
423
605
  if (!restarted) {
@@ -179,6 +179,7 @@ interface LingyaoAccountConfig {
179
179
  allowFrom?: string[];
180
180
  maxOfflineMessages?: number;
181
181
  tokenExpiryDays?: number;
182
+ gatewayId?: string;
182
183
  }
183
184
  /**
184
185
  * Channel configuration
@@ -299,6 +300,12 @@ declare class AccountManager {
299
300
  * Revoke an account
300
301
  */
301
302
  revokeAccount(deviceId: string): Promise<boolean>;
303
+ /**
304
+ * Manually add a device by deviceId (user-initiated pairing).
305
+ * No pairing code or deviceToken required — the user explicitly
306
+ * trusts this device from the OpenClaw CLI.
307
+ */
308
+ addDevice(deviceId: string, deviceInfo: DeviceInfo): Promise<LingyaoAccount>;
302
309
  /**
303
310
  * Refresh device token
304
311
  */
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { L as LingyaoRuntime, H as HealthStatus, A as AccountManager } from './accounts-AwHXj7VB.js';
1
+ import { L as LingyaoRuntime, H as HealthStatus, A as AccountManager } from './accounts-BNuShH7y.js';
2
2
 
3
3
  /**
4
4
  * Probe status levels
@@ -131,6 +131,10 @@ declare class LingyaoCLI {
131
131
  private accountManager;
132
132
  private probe;
133
133
  constructor(runtime: LingyaoRuntime, accountManager: AccountManager, probe: Probe);
134
+ /**
135
+ * Handle pair command - Add a device by deviceId
136
+ */
137
+ handlePair(args: PairArgs): Promise<CLIResult>;
134
138
  /**
135
139
  * Handle logout command - Revoke a device
136
140
  */
@@ -144,6 +148,16 @@ declare class LingyaoCLI {
144
148
  */
145
149
  listDevices(): Promise<CLIResult>;
146
150
  }
151
+ /**
152
+ * Pair command arguments
153
+ */
154
+ interface PairArgs {
155
+ channel?: string;
156
+ deviceId?: string;
157
+ device?: string;
158
+ name?: string;
159
+ platform?: string;
160
+ }
147
161
  /**
148
162
  * Logout command arguments
149
163
  */
@@ -172,4 +186,4 @@ interface CLIResult {
172
186
  */
173
187
  declare function executeCLICommand(command: string, args: Record<string, unknown>, runtime: LingyaoRuntime, accountManager: AccountManager, probe: Probe): Promise<CLIResult>;
174
188
 
175
- export { type CLIResult, LingyaoCLI, type LogoutArgs, type StatusArgs, executeCLICommand };
189
+ export { type CLIResult, LingyaoCLI, type LogoutArgs, type PairArgs, type StatusArgs, executeCLICommand };
package/dist/cli.js CHANGED
@@ -8,6 +8,45 @@ var LingyaoCLI = class {
8
8
  this.accountManager = accountManager;
9
9
  this.probe = probe;
10
10
  }
11
+ /**
12
+ * Handle pair command - Add a device by deviceId
13
+ */
14
+ async handlePair(args) {
15
+ try {
16
+ const { deviceId, device, name, platform } = args;
17
+ const targetId = deviceId ?? device;
18
+ if (!targetId) {
19
+ return {
20
+ success: false,
21
+ error: "Usage: openclaw channels pair --channel lingyao --device <deviceId> [--name <name>] [--platform <platform>]"
22
+ };
23
+ }
24
+ const deviceInfo = {
25
+ name: name ?? targetId,
26
+ platform: platform ?? "harmonyos",
27
+ version: ""
28
+ };
29
+ const account = await this.accountManager.addDevice(targetId, deviceInfo);
30
+ return {
31
+ success: true,
32
+ output: [
33
+ `Device paired successfully.`,
34
+ "",
35
+ ` Device ID: ${account.deviceId}`,
36
+ ` Name: ${account.deviceInfo.name}`,
37
+ ` Platform: ${account.deviceInfo.platform}`,
38
+ "",
39
+ "OpenClaw can now send notifications to this device via lingyao.live relay."
40
+ ].join("\n")
41
+ };
42
+ } catch (error) {
43
+ this.runtime.logger.error("Pair command failed", error);
44
+ return {
45
+ success: false,
46
+ error: `Pair command failed: ${error}`
47
+ };
48
+ }
49
+ }
11
50
  /**
12
51
  * Handle logout command - Revoke a device
13
52
  */
@@ -108,6 +147,7 @@ Usage: openclaw channels logout --channel lingyao --device <deviceId>`
108
147
  }
109
148
  }
110
149
  lines.push("Commands:");
150
+ lines.push(" openclaw channels pair --channel lingyao --device <deviceId>");
111
151
  lines.push(" openclaw channels logout --channel lingyao --device <deviceId>");
112
152
  lines.push(" openclaw channels status --channel lingyao");
113
153
  lines.push("");
@@ -161,6 +201,8 @@ Usage: openclaw channels logout --channel lingyao --device <deviceId>`
161
201
  async function executeCLICommand(command, args, runtime, accountManager, probe) {
162
202
  const cli = new LingyaoCLI(runtime, accountManager, probe);
163
203
  switch (command) {
204
+ case "pair":
205
+ return await cli.handlePair(args);
164
206
  case "logout":
165
207
  return await cli.handleLogout(args);
166
208
  case "status":
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import type { LingyaoRuntime } from \"./types.js\";\nimport { AccountManager } from \"./accounts.js\";\nimport { Probe } from \"./probe.js\";\n\n/**\n * CLI command handler for Lingyao plugin\n */\nexport class LingyaoCLI {\n private runtime: LingyaoRuntime;\n private accountManager: AccountManager;\n private probe: Probe;\n\n constructor(\n runtime: LingyaoRuntime,\n accountManager: AccountManager,\n probe: Probe\n ) {\n this.runtime = runtime;\n this.accountManager = accountManager;\n this.probe = probe;\n }\n\n /**\n * Handle logout command - Revoke a device\n */\n async handleLogout(args: LogoutArgs): Promise<CLIResult> {\n try {\n const { deviceId } = args;\n\n if (!deviceId) {\n // List devices and ask to specify\n const accounts = this.accountManager.getActiveAccounts();\n if (accounts.length === 0) {\n return {\n success: false,\n error: \"No paired devices found\",\n };\n }\n\n const list = accounts\n .map(\n (acc, i) =>\n `${i + 1}. ${acc.deviceId} (${acc.deviceInfo.name}) - Last seen: ${new Date(acc.lastSeenAt).toLocaleString()}`\n )\n .join(\"\\n\");\n\n return {\n success: false,\n error: `Please specify a device ID to revoke.\\n\\nPaired devices:\\n${list}\\n\\nUsage: openclaw channels logout --channel lingyao --device <deviceId>`,\n };\n }\n\n const account = this.accountManager.getAccount(deviceId);\n if (!account) {\n return {\n success: false,\n error: `Device not found: ${deviceId}`,\n };\n }\n\n const revoked = await this.accountManager.revokeAccount(deviceId);\n if (!revoked) {\n return {\n success: false,\n error: `Failed to revoke device: ${deviceId}`,\n };\n }\n\n return {\n success: true,\n output: `Device revoked: ${deviceId} (${account.deviceInfo.name})`,\n };\n } catch (error) {\n this.runtime.logger.error(\"Logout failed\", error);\n return {\n success: false,\n error: `Logout failed: ${error}`,\n };\n }\n }\n\n /**\n * Handle status command - Show channel status\n */\n async handleStatus(_args: StatusArgs): Promise<CLIResult> {\n try {\n const accounts = this.accountManager.getActiveAccounts();\n const healthStatus = await this.probe.getHealthStatus(\n 0, // activeConnections - would come from WebSocket manager\n 0 // queuedMessages - would come from outbound adapter\n );\n\n const statusColor = healthStatus.status === \"healthy\" ? \"✓\" : \"⚠\";\n\n const lines = [\n \"\",\n \"╔═══════════════════════════════════════════════════════════╗\",\n \"║ 灵爻 (Lingyao) Channel Status ║\",\n \"╚═══════════════════════════════════════════════════════════╝\",\n \"\",\n `Status: ${statusColor} ${healthStatus.status.toUpperCase()}`,\n `Uptime: ${this.probe.getUptimeString()}`,\n \"\",\n ];\n\n if (healthStatus.lastError) {\n lines.push(`Last Error: ${healthStatus.lastError}`);\n lines.push(\"\");\n }\n\n if (accounts.length === 0) {\n lines.push(\"No paired devices.\");\n lines.push(\"\");\n lines.push(\"Pairing is managed by the lingyao.live relay and Lingyao App.\");\n } else {\n lines.push(`Paired Devices (${accounts.length}):`);\n lines.push(\"\");\n\n for (const account of accounts) {\n const lastSeen = new Date(account.lastSeenAt).toLocaleString();\n const pairedAt = new Date(account.pairedAt).toLocaleString();\n\n lines.push(` Device: ${account.deviceId}`);\n lines.push(` Name: ${account.deviceInfo.name}`);\n lines.push(` Platform: ${account.deviceInfo.platform} ${account.deviceInfo.version}`);\n lines.push(` Status: ${account.status}`);\n lines.push(` Paired: ${pairedAt}`);\n lines.push(` Last Seen: ${lastSeen}`);\n lines.push(\"\");\n }\n }\n\n lines.push(\"Commands:\");\n lines.push(\" openclaw channels logout --channel lingyao --device <deviceId>\");\n lines.push(\" openclaw channels status --channel lingyao\");\n lines.push(\"\");\n\n return {\n success: true,\n output: lines.join(\"\\n\"),\n };\n } catch (error) {\n this.runtime.logger.error(\"Status command failed\", error);\n return {\n success: false,\n error: `Status command failed: ${error}`,\n };\n }\n }\n\n /**\n * List all paired devices\n */\n async listDevices(): Promise<CLIResult> {\n try {\n const accounts = this.accountManager.getActiveAccounts();\n\n if (accounts.length === 0) {\n return {\n success: true,\n output: \"No paired devices found.\",\n };\n }\n\n const lines = [\n `Paired Devices (${accounts.length}):`,\n \"\",\n ];\n\n for (const account of accounts) {\n lines.push(` ${account.deviceId}`);\n lines.push(` Name: ${account.deviceInfo.name}`);\n lines.push(` Platform: ${account.deviceInfo.platform}`);\n lines.push(` Status: ${account.status}`);\n lines.push(\"\");\n }\n\n return {\n success: true,\n output: lines.join(\"\\n\"),\n };\n } catch (error) {\n return {\n success: false,\n error: `Failed to list devices: ${error}`,\n };\n }\n }\n}\n\n/**\n * Logout command arguments\n */\nexport interface LogoutArgs {\n channel?: string;\n deviceId?: string;\n device?: string;\n}\n\n/**\n * Status command arguments\n */\nexport interface StatusArgs {\n channel?: string;\n verbose?: boolean;\n}\n\n/**\n * CLI command result\n */\nexport interface CLIResult {\n success: boolean;\n output?: string;\n error?: string;\n}\n\n/**\n * Execute CLI command\n */\nexport async function executeCLICommand(\n command: string,\n args: Record<string, unknown>,\n runtime: LingyaoRuntime,\n accountManager: AccountManager,\n probe: Probe\n): Promise<CLIResult> {\n const cli = new LingyaoCLI(runtime, accountManager, probe);\n\n switch (command) {\n case \"logout\":\n return await cli.handleLogout(args as LogoutArgs);\n\n case \"status\":\n return await cli.handleStatus(args as StatusArgs);\n\n case \"list\":\n return await cli.listDevices();\n\n default:\n return {\n success: false,\n error: `Unknown command: ${command}`,\n };\n }\n}\n"],"mappings":";AAOO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EAER,YACE,SACA,gBACA,OACA;AACA,SAAK,UAAU;AACf,SAAK,iBAAiB;AACtB,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,MAAsC;AACvD,QAAI;AACF,YAAM,EAAE,SAAS,IAAI;AAErB,UAAI,CAAC,UAAU;AAEb,cAAM,WAAW,KAAK,eAAe,kBAAkB;AACvD,YAAI,SAAS,WAAW,GAAG;AACzB,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO;AAAA,UACT;AAAA,QACF;AAEA,cAAM,OAAO,SACV;AAAA,UACC,CAAC,KAAK,MACJ,GAAG,IAAI,CAAC,KAAK,IAAI,QAAQ,KAAK,IAAI,WAAW,IAAI,kBAAkB,IAAI,KAAK,IAAI,UAAU,EAAE,eAAe,CAAC;AAAA,QAChH,EACC,KAAK,IAAI;AAEZ,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA;AAAA;AAAA,EAA6D,IAAI;AAAA;AAAA;AAAA,QAC1E;AAAA,MACF;AAEA,YAAM,UAAU,KAAK,eAAe,WAAW,QAAQ;AACvD,UAAI,CAAC,SAAS;AACZ,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,qBAAqB,QAAQ;AAAA,QACtC;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,KAAK,eAAe,cAAc,QAAQ;AAChE,UAAI,CAAC,SAAS;AACZ,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,4BAA4B,QAAQ;AAAA,QAC7C;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,mBAAmB,QAAQ,KAAK,QAAQ,WAAW,IAAI;AAAA,MACjE;AAAA,IACF,SAAS,OAAO;AACd,WAAK,QAAQ,OAAO,MAAM,iBAAiB,KAAK;AAChD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,kBAAkB,KAAK;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,OAAuC;AACxD,QAAI;AACF,YAAM,WAAW,KAAK,eAAe,kBAAkB;AACvD,YAAM,eAAe,MAAM,KAAK,MAAM;AAAA,QACpC;AAAA;AAAA,QACA;AAAA;AAAA,MACF;AAEA,YAAM,cAAc,aAAa,WAAW,YAAY,WAAM;AAE9D,YAAM,QAAQ;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,WAAW,IAAI,aAAa,OAAO,YAAY,CAAC;AAAA,QAC3D,WAAW,KAAK,MAAM,gBAAgB,CAAC;AAAA,QACvC;AAAA,MACF;AAEA,UAAI,aAAa,WAAW;AAC1B,cAAM,KAAK,eAAe,aAAa,SAAS,EAAE;AAClD,cAAM,KAAK,EAAE;AAAA,MACf;AAEA,UAAI,SAAS,WAAW,GAAG;AACzB,cAAM,KAAK,oBAAoB;AAC/B,cAAM,KAAK,EAAE;AACb,cAAM,KAAK,+DAA+D;AAAA,MAC5E,OAAO;AACL,cAAM,KAAK,mBAAmB,SAAS,MAAM,IAAI;AACjD,cAAM,KAAK,EAAE;AAEb,mBAAW,WAAW,UAAU;AAC9B,gBAAM,WAAW,IAAI,KAAK,QAAQ,UAAU,EAAE,eAAe;AAC7D,gBAAM,WAAW,IAAI,KAAK,QAAQ,QAAQ,EAAE,eAAe;AAE3D,gBAAM,KAAK,aAAa,QAAQ,QAAQ,EAAE;AAC1C,gBAAM,KAAK,aAAa,QAAQ,WAAW,IAAI,EAAE;AACjD,gBAAM,KAAK,iBAAiB,QAAQ,WAAW,QAAQ,IAAI,QAAQ,WAAW,OAAO,EAAE;AACvF,gBAAM,KAAK,eAAe,QAAQ,MAAM,EAAE;AAC1C,gBAAM,KAAK,eAAe,QAAQ,EAAE;AACpC,gBAAM,KAAK,kBAAkB,QAAQ,EAAE;AACvC,gBAAM,KAAK,EAAE;AAAA,QACf;AAAA,MACF;AAEA,YAAM,KAAK,WAAW;AACtB,YAAM,KAAK,kEAAkE;AAC7E,YAAM,KAAK,8CAA8C;AACzD,YAAM,KAAK,EAAE;AAEb,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,MAAM,KAAK,IAAI;AAAA,MACzB;AAAA,IACF,SAAS,OAAO;AACd,WAAK,QAAQ,OAAO,MAAM,yBAAyB,KAAK;AACxD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,0BAA0B,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAkC;AACtC,QAAI;AACF,YAAM,WAAW,KAAK,eAAe,kBAAkB;AAEvD,UAAI,SAAS,WAAW,GAAG;AACzB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AAEA,YAAM,QAAQ;AAAA,QACZ,mBAAmB,SAAS,MAAM;AAAA,QAClC;AAAA,MACF;AAEA,iBAAW,WAAW,UAAU;AAC9B,cAAM,KAAK,KAAK,QAAQ,QAAQ,EAAE;AAClC,cAAM,KAAK,aAAa,QAAQ,WAAW,IAAI,EAAE;AACjD,cAAM,KAAK,iBAAiB,QAAQ,WAAW,QAAQ,EAAE;AACzD,cAAM,KAAK,eAAe,QAAQ,MAAM,EAAE;AAC1C,cAAM,KAAK,EAAE;AAAA,MACf;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,MAAM,KAAK,IAAI;AAAA,MACzB;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,2BAA2B,KAAK;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;AA+BA,eAAsB,kBACpB,SACA,MACA,SACA,gBACA,OACoB;AACpB,QAAM,MAAM,IAAI,WAAW,SAAS,gBAAgB,KAAK;AAEzD,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,aAAO,MAAM,IAAI,aAAa,IAAkB;AAAA,IAElD,KAAK;AACH,aAAO,MAAM,IAAI,aAAa,IAAkB;AAAA,IAElD,KAAK;AACH,aAAO,MAAM,IAAI,YAAY;AAAA,IAE/B;AACE,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,oBAAoB,OAAO;AAAA,MACpC;AAAA,EACJ;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import type { LingyaoRuntime } from \"./types.js\";\nimport { AccountManager } from \"./accounts.js\";\nimport { Probe } from \"./probe.js\";\n\n/**\n * CLI command handler for Lingyao plugin\n */\nexport class LingyaoCLI {\n private runtime: LingyaoRuntime;\n private accountManager: AccountManager;\n private probe: Probe;\n\n constructor(\n runtime: LingyaoRuntime,\n accountManager: AccountManager,\n probe: Probe\n ) {\n this.runtime = runtime;\n this.accountManager = accountManager;\n this.probe = probe;\n }\n\n /**\n * Handle pair command - Add a device by deviceId\n */\n async handlePair(args: PairArgs): Promise<CLIResult> {\n try {\n const { deviceId, device, name, platform } = args;\n const targetId = deviceId ?? device;\n\n if (!targetId) {\n return {\n success: false,\n error: \"Usage: openclaw channels pair --channel lingyao --device <deviceId> [--name <name>] [--platform <platform>]\",\n };\n }\n\n const deviceInfo: { name: string; platform: string; version: string } = {\n name: name ?? targetId,\n platform: platform ?? 'harmonyos',\n version: '',\n };\n\n const account = await this.accountManager.addDevice(targetId, deviceInfo);\n\n return {\n success: true,\n output: [\n `Device paired successfully.`,\n \"\",\n ` Device ID: ${account.deviceId}`,\n ` Name: ${account.deviceInfo.name}`,\n ` Platform: ${account.deviceInfo.platform}`,\n \"\",\n \"OpenClaw can now send notifications to this device via lingyao.live relay.\",\n ].join(\"\\n\"),\n };\n } catch (error) {\n this.runtime.logger.error(\"Pair command failed\", error);\n return {\n success: false,\n error: `Pair command failed: ${error}`,\n };\n }\n }\n\n /**\n * Handle logout command - Revoke a device\n */\n async handleLogout(args: LogoutArgs): Promise<CLIResult> {\n try {\n const { deviceId } = args;\n\n if (!deviceId) {\n // List devices and ask to specify\n const accounts = this.accountManager.getActiveAccounts();\n if (accounts.length === 0) {\n return {\n success: false,\n error: \"No paired devices found\",\n };\n }\n\n const list = accounts\n .map(\n (acc, i) =>\n `${i + 1}. ${acc.deviceId} (${acc.deviceInfo.name}) - Last seen: ${new Date(acc.lastSeenAt).toLocaleString()}`\n )\n .join(\"\\n\");\n\n return {\n success: false,\n error: `Please specify a device ID to revoke.\\n\\nPaired devices:\\n${list}\\n\\nUsage: openclaw channels logout --channel lingyao --device <deviceId>`,\n };\n }\n\n const account = this.accountManager.getAccount(deviceId);\n if (!account) {\n return {\n success: false,\n error: `Device not found: ${deviceId}`,\n };\n }\n\n const revoked = await this.accountManager.revokeAccount(deviceId);\n if (!revoked) {\n return {\n success: false,\n error: `Failed to revoke device: ${deviceId}`,\n };\n }\n\n return {\n success: true,\n output: `Device revoked: ${deviceId} (${account.deviceInfo.name})`,\n };\n } catch (error) {\n this.runtime.logger.error(\"Logout failed\", error);\n return {\n success: false,\n error: `Logout failed: ${error}`,\n };\n }\n }\n\n /**\n * Handle status command - Show channel status\n */\n async handleStatus(_args: StatusArgs): Promise<CLIResult> {\n try {\n const accounts = this.accountManager.getActiveAccounts();\n const healthStatus = await this.probe.getHealthStatus(\n 0, // activeConnections - would come from WebSocket manager\n 0 // queuedMessages - would come from outbound adapter\n );\n\n const statusColor = healthStatus.status === \"healthy\" ? \"✓\" : \"⚠\";\n\n const lines = [\n \"\",\n \"╔═══════════════════════════════════════════════════════════╗\",\n \"║ 灵爻 (Lingyao) Channel Status ║\",\n \"╚═══════════════════════════════════════════════════════════╝\",\n \"\",\n `Status: ${statusColor} ${healthStatus.status.toUpperCase()}`,\n `Uptime: ${this.probe.getUptimeString()}`,\n \"\",\n ];\n\n if (healthStatus.lastError) {\n lines.push(`Last Error: ${healthStatus.lastError}`);\n lines.push(\"\");\n }\n\n if (accounts.length === 0) {\n lines.push(\"No paired devices.\");\n lines.push(\"\");\n lines.push(\"Pairing is managed by the lingyao.live relay and Lingyao App.\");\n } else {\n lines.push(`Paired Devices (${accounts.length}):`);\n lines.push(\"\");\n\n for (const account of accounts) {\n const lastSeen = new Date(account.lastSeenAt).toLocaleString();\n const pairedAt = new Date(account.pairedAt).toLocaleString();\n\n lines.push(` Device: ${account.deviceId}`);\n lines.push(` Name: ${account.deviceInfo.name}`);\n lines.push(` Platform: ${account.deviceInfo.platform} ${account.deviceInfo.version}`);\n lines.push(` Status: ${account.status}`);\n lines.push(` Paired: ${pairedAt}`);\n lines.push(` Last Seen: ${lastSeen}`);\n lines.push(\"\");\n }\n }\n\n lines.push(\"Commands:\");\n lines.push(\" openclaw channels pair --channel lingyao --device <deviceId>\");\n lines.push(\" openclaw channels logout --channel lingyao --device <deviceId>\");\n lines.push(\" openclaw channels status --channel lingyao\");\n lines.push(\"\");\n\n return {\n success: true,\n output: lines.join(\"\\n\"),\n };\n } catch (error) {\n this.runtime.logger.error(\"Status command failed\", error);\n return {\n success: false,\n error: `Status command failed: ${error}`,\n };\n }\n }\n\n /**\n * List all paired devices\n */\n async listDevices(): Promise<CLIResult> {\n try {\n const accounts = this.accountManager.getActiveAccounts();\n\n if (accounts.length === 0) {\n return {\n success: true,\n output: \"No paired devices found.\",\n };\n }\n\n const lines = [\n `Paired Devices (${accounts.length}):`,\n \"\",\n ];\n\n for (const account of accounts) {\n lines.push(` ${account.deviceId}`);\n lines.push(` Name: ${account.deviceInfo.name}`);\n lines.push(` Platform: ${account.deviceInfo.platform}`);\n lines.push(` Status: ${account.status}`);\n lines.push(\"\");\n }\n\n return {\n success: true,\n output: lines.join(\"\\n\"),\n };\n } catch (error) {\n return {\n success: false,\n error: `Failed to list devices: ${error}`,\n };\n }\n }\n}\n\n/**\n * Pair command arguments\n */\nexport interface PairArgs {\n channel?: string;\n deviceId?: string;\n device?: string;\n name?: string;\n platform?: string;\n}\n\n/**\n * Logout command arguments\n */\nexport interface LogoutArgs {\n channel?: string;\n deviceId?: string;\n device?: string;\n}\n\n/**\n * Status command arguments\n */\nexport interface StatusArgs {\n channel?: string;\n verbose?: boolean;\n}\n\n/**\n * CLI command result\n */\nexport interface CLIResult {\n success: boolean;\n output?: string;\n error?: string;\n}\n\n/**\n * Execute CLI command\n */\nexport async function executeCLICommand(\n command: string,\n args: Record<string, unknown>,\n runtime: LingyaoRuntime,\n accountManager: AccountManager,\n probe: Probe\n): Promise<CLIResult> {\n const cli = new LingyaoCLI(runtime, accountManager, probe);\n\n switch (command) {\n case \"pair\":\n return await cli.handlePair(args as PairArgs);\n\n case \"logout\":\n return await cli.handleLogout(args as LogoutArgs);\n\n case \"status\":\n return await cli.handleStatus(args as StatusArgs);\n\n case \"list\":\n return await cli.listDevices();\n\n default:\n return {\n success: false,\n error: `Unknown command: ${command}`,\n };\n }\n}\n"],"mappings":";AAOO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EAER,YACE,SACA,gBACA,OACA;AACA,SAAK,UAAU;AACf,SAAK,iBAAiB;AACtB,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,MAAoC;AACnD,QAAI;AACF,YAAM,EAAE,UAAU,QAAQ,MAAM,SAAS,IAAI;AAC7C,YAAM,WAAW,YAAY;AAE7B,UAAI,CAAC,UAAU;AACb,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AAEA,YAAM,aAAkE;AAAA,QACtE,MAAM,QAAQ;AAAA,QACd,UAAU,YAAY;AAAA,QACtB,SAAS;AAAA,MACX;AAEA,YAAM,UAAU,MAAM,KAAK,eAAe,UAAU,UAAU,UAAU;AAExE,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,UACA;AAAA,UACA,gBAAgB,QAAQ,QAAQ;AAAA,UAChC,gBAAgB,QAAQ,WAAW,IAAI;AAAA,UACvC,gBAAgB,QAAQ,WAAW,QAAQ;AAAA,UAC3C;AAAA,UACA;AAAA,QACF,EAAE,KAAK,IAAI;AAAA,MACb;AAAA,IACF,SAAS,OAAO;AACd,WAAK,QAAQ,OAAO,MAAM,uBAAuB,KAAK;AACtD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,wBAAwB,KAAK;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,MAAsC;AACvD,QAAI;AACF,YAAM,EAAE,SAAS,IAAI;AAErB,UAAI,CAAC,UAAU;AAEb,cAAM,WAAW,KAAK,eAAe,kBAAkB;AACvD,YAAI,SAAS,WAAW,GAAG;AACzB,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO;AAAA,UACT;AAAA,QACF;AAEA,cAAM,OAAO,SACV;AAAA,UACC,CAAC,KAAK,MACJ,GAAG,IAAI,CAAC,KAAK,IAAI,QAAQ,KAAK,IAAI,WAAW,IAAI,kBAAkB,IAAI,KAAK,IAAI,UAAU,EAAE,eAAe,CAAC;AAAA,QAChH,EACC,KAAK,IAAI;AAEZ,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA;AAAA;AAAA,EAA6D,IAAI;AAAA;AAAA;AAAA,QAC1E;AAAA,MACF;AAEA,YAAM,UAAU,KAAK,eAAe,WAAW,QAAQ;AACvD,UAAI,CAAC,SAAS;AACZ,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,qBAAqB,QAAQ;AAAA,QACtC;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,KAAK,eAAe,cAAc,QAAQ;AAChE,UAAI,CAAC,SAAS;AACZ,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,4BAA4B,QAAQ;AAAA,QAC7C;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,mBAAmB,QAAQ,KAAK,QAAQ,WAAW,IAAI;AAAA,MACjE;AAAA,IACF,SAAS,OAAO;AACd,WAAK,QAAQ,OAAO,MAAM,iBAAiB,KAAK;AAChD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,kBAAkB,KAAK;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,OAAuC;AACxD,QAAI;AACF,YAAM,WAAW,KAAK,eAAe,kBAAkB;AACvD,YAAM,eAAe,MAAM,KAAK,MAAM;AAAA,QACpC;AAAA;AAAA,QACA;AAAA;AAAA,MACF;AAEA,YAAM,cAAc,aAAa,WAAW,YAAY,WAAM;AAE9D,YAAM,QAAQ;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,WAAW,IAAI,aAAa,OAAO,YAAY,CAAC;AAAA,QAC3D,WAAW,KAAK,MAAM,gBAAgB,CAAC;AAAA,QACvC;AAAA,MACF;AAEA,UAAI,aAAa,WAAW;AAC1B,cAAM,KAAK,eAAe,aAAa,SAAS,EAAE;AAClD,cAAM,KAAK,EAAE;AAAA,MACf;AAEA,UAAI,SAAS,WAAW,GAAG;AACzB,cAAM,KAAK,oBAAoB;AAC/B,cAAM,KAAK,EAAE;AACb,cAAM,KAAK,+DAA+D;AAAA,MAC5E,OAAO;AACL,cAAM,KAAK,mBAAmB,SAAS,MAAM,IAAI;AACjD,cAAM,KAAK,EAAE;AAEb,mBAAW,WAAW,UAAU;AAC9B,gBAAM,WAAW,IAAI,KAAK,QAAQ,UAAU,EAAE,eAAe;AAC7D,gBAAM,WAAW,IAAI,KAAK,QAAQ,QAAQ,EAAE,eAAe;AAE3D,gBAAM,KAAK,aAAa,QAAQ,QAAQ,EAAE;AAC1C,gBAAM,KAAK,aAAa,QAAQ,WAAW,IAAI,EAAE;AACjD,gBAAM,KAAK,iBAAiB,QAAQ,WAAW,QAAQ,IAAI,QAAQ,WAAW,OAAO,EAAE;AACvF,gBAAM,KAAK,eAAe,QAAQ,MAAM,EAAE;AAC1C,gBAAM,KAAK,eAAe,QAAQ,EAAE;AACpC,gBAAM,KAAK,kBAAkB,QAAQ,EAAE;AACvC,gBAAM,KAAK,EAAE;AAAA,QACf;AAAA,MACF;AAEA,YAAM,KAAK,WAAW;AACtB,YAAM,KAAK,gEAAgE;AAC3E,YAAM,KAAK,kEAAkE;AAC7E,YAAM,KAAK,8CAA8C;AACzD,YAAM,KAAK,EAAE;AAEb,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,MAAM,KAAK,IAAI;AAAA,MACzB;AAAA,IACF,SAAS,OAAO;AACd,WAAK,QAAQ,OAAO,MAAM,yBAAyB,KAAK;AACxD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,0BAA0B,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAkC;AACtC,QAAI;AACF,YAAM,WAAW,KAAK,eAAe,kBAAkB;AAEvD,UAAI,SAAS,WAAW,GAAG;AACzB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,QACV;AAAA,MACF;AAEA,YAAM,QAAQ;AAAA,QACZ,mBAAmB,SAAS,MAAM;AAAA,QAClC;AAAA,MACF;AAEA,iBAAW,WAAW,UAAU;AAC9B,cAAM,KAAK,KAAK,QAAQ,QAAQ,EAAE;AAClC,cAAM,KAAK,aAAa,QAAQ,WAAW,IAAI,EAAE;AACjD,cAAM,KAAK,iBAAiB,QAAQ,WAAW,QAAQ,EAAE;AACzD,cAAM,KAAK,eAAe,QAAQ,MAAM,EAAE;AAC1C,cAAM,KAAK,EAAE;AAAA,MACf;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,MAAM,KAAK,IAAI;AAAA,MACzB;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,2BAA2B,KAAK;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;AA0CA,eAAsB,kBACpB,SACA,MACA,SACA,gBACA,OACoB;AACpB,QAAM,MAAM,IAAI,WAAW,SAAS,gBAAgB,KAAK;AAEzD,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,aAAO,MAAM,IAAI,WAAW,IAAgB;AAAA,IAE9C,KAAK;AACH,aAAO,MAAM,IAAI,aAAa,IAAkB;AAAA,IAElD,KAAK;AACH,aAAO,MAAM,IAAI,aAAa,IAAkB;AAAA,IAElD,KAAK;AACH,aAAO,MAAM,IAAI,YAAY;AAAA,IAE/B;AACE,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,oBAAoB,OAAO;AAAA,MACpC;AAAA,EACJ;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as openclaw_plugin_sdk_core from 'openclaw/plugin-sdk/core';
2
2
  import * as openclaw_plugin_sdk from 'openclaw/plugin-sdk';
3
- import { L as LingyaoRuntime, D as DeviceInfo, a as DeviceToken, A as AccountManager, S as SyncRequest, b as SyncResponse, c as LingyaoMessage, d as LingyaoConfig, N as NotifyPayload, H as HealthStatus } from './accounts-AwHXj7VB.js';
4
- export { e as AckRequest, f as DiarySyncPayload, F as FailedEntry, g as LINGYAO_SERVER_URL, h as LingyaoAccount, i as LingyaoAccountConfig, M as MemorySyncPayload, j as MessageType, k as NotifyAction, l as NotifyRequest, P as PairingCode, m as PairingConfirmRequest, n as PairingConfirmResponse, o as PollRequest, p as PollResponse, Q as QueuedMessage, T as TokenRefreshRequest, q as TokenRefreshResponse, W as WebSocketConnection } from './accounts-AwHXj7VB.js';
3
+ import { L as LingyaoRuntime, D as DeviceInfo, a as DeviceToken, A as AccountManager, S as SyncRequest, b as SyncResponse, c as LingyaoMessage, d as LingyaoConfig, N as NotifyPayload, H as HealthStatus } from './accounts-BNuShH7y.js';
4
+ export { e as AckRequest, f as DiarySyncPayload, F as FailedEntry, g as LINGYAO_SERVER_URL, h as LingyaoAccount, i as LingyaoAccountConfig, M as MemorySyncPayload, j as MessageType, k as NotifyAction, l as NotifyRequest, P as PairingCode, m as PairingConfirmRequest, n as PairingConfirmResponse, o as PollRequest, p as PollResponse, Q as QueuedMessage, T as TokenRefreshRequest, q as TokenRefreshResponse, W as WebSocketConnection } from './accounts-BNuShH7y.js';
5
5
 
6
6
  /**
7
7
  * 错误处理模块
@@ -431,6 +431,7 @@ declare enum WSMessageType {
431
431
  MESSAGE_FAILED = "message_failed",
432
432
  APP_MESSAGE = "app_message",// 来自 App 的消息
433
433
  DEVICE_ONLINE = "device_online",
434
+ PAIRING_COMPLETED = "pairing_completed",
434
435
  ERROR = "error"
435
436
  }
436
437
  /**
@@ -478,6 +479,15 @@ type WSClientEvent = {
478
479
  type: "appMessage";
479
480
  deviceId: string;
480
481
  message: AppMessage["payload"]["message"];
482
+ } | {
483
+ type: "pairing_completed";
484
+ deviceId: string;
485
+ deviceInfo: {
486
+ name: string;
487
+ platform: string;
488
+ version: string;
489
+ };
490
+ sessionId: string;
481
491
  };
482
492
  /**
483
493
  * WebSocket 客户端配置
@@ -574,6 +584,10 @@ declare class LingyaoWSClient {
574
584
  * 处理消息发送成功
575
585
  */
576
586
  private handleMessageDelivered;
587
+ /**
588
+ * 处理配对完成通知(来自 lingyao.live 服务器)
589
+ */
590
+ private handlePairingCompleted;
577
591
  /**
578
592
  * 处理消息发送失败
579
593
  */
package/dist/index.js CHANGED
@@ -14,11 +14,16 @@ function setRuntime(runtime) {
14
14
  function adaptPluginRuntime(pr) {
15
15
  const noop = (..._args) => {
16
16
  };
17
- const childLogger = pr.logging?.getChildLogger?.() ?? {
17
+ const rawLogger = pr.logging?.getChildLogger?.() ?? {
18
18
  info: console.info.bind(console),
19
19
  warn: console.warn.bind(console),
20
- error: console.error.bind(console),
21
- debug: noop
20
+ error: console.error.bind(console)
21
+ };
22
+ const childLogger = {
23
+ info: rawLogger.info,
24
+ warn: rawLogger.warn,
25
+ error: rawLogger.error,
26
+ debug: rawLogger.debug ?? noop
22
27
  };
23
28
  const stateDir = pr.state?.resolveStateDir?.() ?? join(process.cwd(), ".lingyao-data");
24
29
  const storeDir = join(stateDir, "lingyao");
@@ -92,6 +97,7 @@ function createConfigAdapter() {
92
97
  enabled: accountConfig?.enabled !== false,
93
98
  dmPolicy: accountConfig?.dmPolicy ?? "paired",
94
99
  allowFrom: accountConfig?.allowFrom ?? [],
100
+ gatewayId: accountConfig?.gatewayId,
95
101
  rawConfig: accountConfig
96
102
  };
97
103
  },
@@ -655,6 +661,7 @@ var LingyaoWSClient = class {
655
661
  this.registerMessageHandler("message_delivered" /* MESSAGE_DELIVERED */, this.handleMessageDelivered.bind(this));
656
662
  this.registerMessageHandler("message_failed" /* MESSAGE_FAILED */, this.handleMessageFailed.bind(this));
657
663
  this.registerMessageHandler("app_message" /* APP_MESSAGE */, this.handleAppMessage.bind(this));
664
+ this.registerMessageHandler("pairing_completed" /* PAIRING_COMPLETED */, this.handlePairingCompleted.bind(this));
658
665
  this.registerMessageHandler("error" /* ERROR */, this.handleError.bind(this));
659
666
  }
660
667
  /**
@@ -903,6 +910,24 @@ var LingyaoWSClient = class {
903
910
  messageId: message.id
904
911
  });
905
912
  }
913
+ /**
914
+ * 处理配对完成通知(来自 lingyao.live 服务器)
915
+ */
916
+ handlePairingCompleted(message) {
917
+ const payload = message.payload;
918
+ this.logger.info("Pairing completed", {
919
+ deviceId: payload?.deviceId,
920
+ sessionId: payload?.sessionId
921
+ });
922
+ if (this.config.eventHandler) {
923
+ this.config.eventHandler({
924
+ type: "pairing_completed",
925
+ deviceId: payload?.deviceId,
926
+ deviceInfo: payload?.deviceInfo,
927
+ sessionId: payload?.sessionId
928
+ });
929
+ }
930
+ }
906
931
  /**
907
932
  * 处理消息发送失败
908
933
  */
@@ -1125,6 +1150,42 @@ var AccountManager = class {
1125
1150
  this.runtime.logger.info(`Account revoked: ${deviceId}`);
1126
1151
  return true;
1127
1152
  }
1153
+ /**
1154
+ * Manually add a device by deviceId (user-initiated pairing).
1155
+ * No pairing code or deviceToken required — the user explicitly
1156
+ * trusts this device from the OpenClaw CLI.
1157
+ */
1158
+ async addDevice(deviceId, deviceInfo) {
1159
+ const existing = this.accounts.get(deviceId);
1160
+ if (existing) {
1161
+ existing.status = "active";
1162
+ existing.deviceInfo = deviceInfo;
1163
+ existing.lastSeenAt = Date.now();
1164
+ await this.saveAccounts();
1165
+ this.runtime.logger.info(`Device re-activated: ${deviceId} (${deviceInfo.name})`);
1166
+ return existing;
1167
+ }
1168
+ const now = Date.now();
1169
+ const account = {
1170
+ deviceId,
1171
+ deviceInfo,
1172
+ deviceToken: {
1173
+ deviceId,
1174
+ pairingId: `manual_${now}`,
1175
+ token: "",
1176
+ secret: "",
1177
+ expiresAt: 0,
1178
+ deviceInfo
1179
+ },
1180
+ pairedAt: now,
1181
+ lastSeenAt: now,
1182
+ status: "active"
1183
+ };
1184
+ this.accounts.set(deviceId, account);
1185
+ await this.saveAccounts();
1186
+ this.runtime.logger.info(`Device added: ${deviceId} (${deviceInfo.name})`);
1187
+ return account;
1188
+ }
1128
1189
  /**
1129
1190
  * Refresh device token
1130
1191
  */
@@ -2464,7 +2525,7 @@ var MultiAccountOrchestrator = class {
2464
2525
  return;
2465
2526
  }
2466
2527
  this.runtime.logger.info(`Starting account "${accountId}"`);
2467
- const gatewayId = generateGatewayId(accountId);
2528
+ const gatewayId = account.gatewayId ?? generateGatewayId(accountId);
2468
2529
  const storagePrefix = `lingyao:${accountId}`;
2469
2530
  const accountManager = new AccountManager(this.runtime);
2470
2531
  const messageProcessor = new MessageProcessor(this.runtime, accountManager);
@@ -2715,9 +2776,29 @@ var MultiAccountOrchestrator = class {
2715
2776
  });
2716
2777
  state.errorHandler.handleError(event.error);
2717
2778
  break;
2779
+ case "pairing_completed":
2780
+ this.handlePairingCompleted(state, event);
2781
+ break;
2718
2782
  }
2719
2783
  };
2720
2784
  }
2785
+ /**
2786
+ * Handle pairing completed event from WS — auto-bind device.
2787
+ */
2788
+ async handlePairingCompleted(state, event) {
2789
+ const { deviceId, deviceInfo } = event;
2790
+ this.runtime.logger.info(`[${state.accountId}] Pairing completed, auto-binding device`, { deviceId, deviceInfo });
2791
+ try {
2792
+ await state.accountManager.addDevice(deviceId, {
2793
+ name: deviceInfo?.name ?? deviceId,
2794
+ platform: deviceInfo?.platform ?? "harmonyos",
2795
+ version: deviceInfo?.version ?? ""
2796
+ });
2797
+ this.runtime.logger.info(`[${state.accountId}] Device auto-bound: ${deviceId}`);
2798
+ } catch (error) {
2799
+ this.runtime.logger.error(`[${state.accountId}] Failed to auto-bind device: ${deviceId}`, error);
2800
+ }
2801
+ }
2721
2802
  };
2722
2803
 
2723
2804
  // src/channel.ts