@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 +186 -4
- package/dist/{accounts-AwHXj7VB.d.ts → accounts-BNuShH7y.d.ts} +7 -0
- package/dist/cli.d.ts +16 -2
- package/dist/cli.js +42 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.js +85 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(' •
|
|
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-
|
|
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":"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","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":"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","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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|