@lingyao037/openclaw-lingyao-cli 0.3.0-alpha.1

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.
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Lingyao relay server URL (internal constant, not user-configurable)
3
+ */
4
+ declare const LINGYAO_SERVER_URL = "https://api.lingyao.live";
5
+ /**
6
+ * Message type enumeration for all Lingyao message types
7
+ */
8
+ declare enum MessageType {
9
+ SYNC_DIARY = "sync_diary",
10
+ SYNC_MEMORY = "sync_memory",
11
+ SYNC_ACK = "sync_ack",
12
+ NOTIFY_TEXT = "notify_text",
13
+ NOTIFY_ACTION = "notify_action",
14
+ HEARTBEAT = "heartbeat",
15
+ PAIRING_REQUEST = "pairing_request",
16
+ PAIRING_CONFIRM = "pairing_confirm"
17
+ }
18
+ /**
19
+ * Base message structure for all Lingyao messages
20
+ */
21
+ interface LingyaoMessage {
22
+ id: string;
23
+ type: MessageType;
24
+ timestamp: number;
25
+ from: string;
26
+ to: string;
27
+ payload: unknown;
28
+ signature?: string;
29
+ }
30
+ /**
31
+ * Diary sync payload
32
+ */
33
+ interface DiarySyncPayload {
34
+ diaryId: string;
35
+ title?: string;
36
+ content: string;
37
+ emotion?: string;
38
+ tags?: string[];
39
+ mediaUrls?: string[];
40
+ createdAt: number;
41
+ updatedAt: number;
42
+ }
43
+ /**
44
+ * Memory sync payload
45
+ */
46
+ interface MemorySyncPayload {
47
+ memoryId: string;
48
+ type: "diary" | "fact" | "preference" | "decision";
49
+ content: string;
50
+ importance: number;
51
+ metadata?: Record<string, unknown>;
52
+ timestamp: number;
53
+ }
54
+ /**
55
+ * Notification payload
56
+ */
57
+ interface NotifyPayload {
58
+ title: string;
59
+ body: string;
60
+ action?: NotifyAction;
61
+ }
62
+ /**
63
+ * Notification action descriptor
64
+ */
65
+ interface NotifyAction {
66
+ type: "open_memory" | "view_diary" | "custom";
67
+ params: Record<string, unknown>;
68
+ }
69
+ /**
70
+ * Device token structure
71
+ */
72
+ interface DeviceToken {
73
+ deviceId: string;
74
+ pairingId: string;
75
+ token: string;
76
+ secret: string;
77
+ expiresAt: number;
78
+ deviceInfo: DeviceInfo;
79
+ }
80
+ /**
81
+ * Device information
82
+ */
83
+ interface DeviceInfo {
84
+ name: string;
85
+ platform: string;
86
+ version: string;
87
+ }
88
+ /**
89
+ * Pairing code structure
90
+ */
91
+ interface PairingCode {
92
+ code: string;
93
+ timestamp: number;
94
+ nonce: string;
95
+ expiresAt: number;
96
+ }
97
+ /**
98
+ * Sync request from App
99
+ */
100
+ interface SyncRequest {
101
+ deviceToken: string;
102
+ messages: LingyaoMessage[];
103
+ lastSyncAt?: number;
104
+ }
105
+ /**
106
+ * Sync response from Gateway
107
+ */
108
+ interface SyncResponse {
109
+ processed: string[];
110
+ failed: FailedEntry[];
111
+ }
112
+ /**
113
+ * Failed sync entry
114
+ */
115
+ interface FailedEntry {
116
+ id: string;
117
+ error: string;
118
+ }
119
+ /**
120
+ * Poll request from App
121
+ */
122
+ interface PollRequest {
123
+ deviceToken: string;
124
+ timeout?: number;
125
+ }
126
+ /**
127
+ * Poll response from Gateway
128
+ */
129
+ interface PollResponse {
130
+ messages: LingyaoMessage[];
131
+ hasMore: boolean;
132
+ }
133
+ /**
134
+ * Ack request from App
135
+ */
136
+ interface AckRequest {
137
+ deviceToken: string;
138
+ messageIds: string[];
139
+ }
140
+ /**
141
+ * Pairing confirm request from App
142
+ */
143
+ interface PairingConfirmRequest {
144
+ pairingCode: string;
145
+ deviceInfo: DeviceInfo;
146
+ }
147
+ /**
148
+ * Pairing confirm response from Gateway
149
+ */
150
+ interface PairingConfirmResponse {
151
+ pairingId: string;
152
+ deviceToken: DeviceToken;
153
+ }
154
+ /**
155
+ * Token refresh request
156
+ */
157
+ interface TokenRefreshRequest {
158
+ currentToken: string;
159
+ }
160
+ /**
161
+ * Token refresh response
162
+ */
163
+ interface TokenRefreshResponse {
164
+ deviceToken: DeviceToken;
165
+ }
166
+ /**
167
+ * Notify request (Agent to Gateway)
168
+ */
169
+ interface NotifyRequest {
170
+ deviceId: string;
171
+ notification: NotifyPayload;
172
+ }
173
+ /**
174
+ * Per-account configuration (under channels.lingyao.accounts.<id>)
175
+ */
176
+ interface LingyaoAccountConfig {
177
+ enabled?: boolean;
178
+ dmPolicy?: "paired" | "open" | "deny";
179
+ allowFrom?: string[];
180
+ maxOfflineMessages?: number;
181
+ tokenExpiryDays?: number;
182
+ }
183
+ /**
184
+ * Channel configuration
185
+ */
186
+ interface LingyaoConfig {
187
+ enabled: boolean;
188
+ maxOfflineMessages?: number;
189
+ tokenExpiryDays?: number;
190
+ dmPolicy?: "paired" | "open" | "deny";
191
+ allowFrom?: string[];
192
+ accounts?: Record<string, LingyaoAccountConfig>;
193
+ }
194
+ /**
195
+ * Plugin runtime API
196
+ */
197
+ interface LingyaoRuntime {
198
+ config: LingyaoConfig;
199
+ logger: {
200
+ info: (message: string, ...args: unknown[]) => void;
201
+ warn: (message: string, ...args: unknown[]) => void;
202
+ error: (message: string, ...args: unknown[]) => void;
203
+ debug: (message: string, ...args: unknown[]) => void;
204
+ };
205
+ storage: {
206
+ get: (key: string) => Promise<unknown | null>;
207
+ set: (key: string, value: unknown) => Promise<void>;
208
+ delete: (key: string) => Promise<void>;
209
+ };
210
+ tools: {
211
+ call: (name: string, params: Record<string, unknown>) => Promise<unknown>;
212
+ };
213
+ }
214
+ /**
215
+ * Account information
216
+ */
217
+ interface LingyaoAccount {
218
+ deviceId: string;
219
+ deviceInfo: DeviceInfo;
220
+ deviceToken: DeviceToken;
221
+ pairedAt: number;
222
+ lastSeenAt: number;
223
+ status: "active" | "inactive" | "revoked";
224
+ }
225
+ /**
226
+ * Message queue entry
227
+ */
228
+ interface QueuedMessage {
229
+ message: LingyaoMessage;
230
+ status: "pending" | "delivered" | "failed";
231
+ retryCount: number;
232
+ createdAt: number;
233
+ }
234
+ /**
235
+ * WebSocket connection info
236
+ */
237
+ interface WebSocketConnection {
238
+ deviceId: string;
239
+ ws: unknown;
240
+ connectedAt: number;
241
+ lastHeartbeat: number;
242
+ }
243
+ /**
244
+ * Health status
245
+ */
246
+ interface HealthStatus {
247
+ status: "healthy" | "degraded" | "unhealthy";
248
+ uptime: number;
249
+ activeConnections: number;
250
+ queuedMessages: number;
251
+ lastError?: string;
252
+ }
253
+
254
+ /**
255
+ * Account storage and management
256
+ */
257
+ declare class AccountManager {
258
+ private runtime;
259
+ private accounts;
260
+ private pendingPairings;
261
+ constructor(runtime: LingyaoRuntime);
262
+ /**
263
+ * Initialize account manager from storage
264
+ */
265
+ initialize(): Promise<void>;
266
+ /**
267
+ * Get account by device ID
268
+ */
269
+ getAccount(deviceId: string): LingyaoAccount | undefined;
270
+ /**
271
+ * Get account by device token
272
+ */
273
+ getAccountByToken(token: string): LingyaoAccount | undefined;
274
+ /**
275
+ * Get all active accounts
276
+ */
277
+ getActiveAccounts(): LingyaoAccount[];
278
+ /**
279
+ * Create a new pairing session
280
+ */
281
+ createPairingSession(code: string, expiresAt: number): Promise<void>;
282
+ /**
283
+ * Get pairing session by code
284
+ */
285
+ getPairingSession(code: string): PairingSession | undefined;
286
+ /**
287
+ * Remove a pending pairing (e.g. expired or cancelled)
288
+ */
289
+ deletePendingPairing(code: string): Promise<void>;
290
+ /**
291
+ * Confirm pairing and create account
292
+ */
293
+ confirmPairing(pairingCode: string, deviceToken: DeviceToken, deviceInfo: DeviceInfo): Promise<LingyaoAccount | null>;
294
+ /**
295
+ * Update account's last seen timestamp
296
+ */
297
+ updateLastSeen(deviceId: string): Promise<void>;
298
+ /**
299
+ * Revoke an account
300
+ */
301
+ revokeAccount(deviceId: string): Promise<boolean>;
302
+ /**
303
+ * Refresh device token
304
+ */
305
+ refreshDeviceToken(deviceId: string, newToken: DeviceToken): Promise<boolean>;
306
+ /**
307
+ * Clean up expired accounts
308
+ */
309
+ cleanupExpired(): Promise<void>;
310
+ /**
311
+ * Save accounts to storage
312
+ */
313
+ private saveAccounts;
314
+ /**
315
+ * Save pending pairings to storage
316
+ */
317
+ private savePendingPairings;
318
+ }
319
+ interface PairingSession {
320
+ code: string;
321
+ createdAt: number;
322
+ expiresAt: number;
323
+ }
324
+
325
+ export { AccountManager as A, type DeviceInfo as D, type FailedEntry as F, type HealthStatus as H, type LingyaoRuntime as L, type MemorySyncPayload as M, type NotifyPayload as N, type PairingCode as P, type QueuedMessage as Q, type SyncRequest as S, type TokenRefreshRequest as T, type WebSocketConnection as W, type LingyaoAccountConfig as a, type SyncResponse as b, type LingyaoMessage as c, type DeviceToken as d, type LingyaoConfig as e, type AckRequest as f, type DiarySyncPayload as g, LINGYAO_SERVER_URL as h, type LingyaoAccount as i, MessageType as j, type NotifyAction as k, type NotifyRequest as l, type PairingConfirmRequest as m, type PairingConfirmResponse as n, type PollRequest as o, type PollResponse as p, type TokenRefreshResponse as q };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,175 @@
1
+ import { L as LingyaoRuntime, H as HealthStatus, A as AccountManager } from './accounts-Bkwmg14Q.js';
2
+
3
+ /**
4
+ * Probe status levels
5
+ */
6
+ declare enum ProbeStatus {
7
+ HEALTHY = "healthy",
8
+ DEGRADED = "degraded",
9
+ UNHEALTHY = "unhealthy"
10
+ }
11
+ /**
12
+ * Health check result
13
+ */
14
+ interface HealthCheckResult {
15
+ status: ProbeStatus;
16
+ checks: Map<string, CheckResult>;
17
+ timestamp: number;
18
+ }
19
+ /**
20
+ * Individual check result
21
+ */
22
+ interface CheckResult {
23
+ passed: boolean;
24
+ message?: string;
25
+ duration: number;
26
+ error?: Error;
27
+ }
28
+ /**
29
+ * Channel status for reporting
30
+ */
31
+ interface ChannelStatus {
32
+ configured: boolean;
33
+ running: boolean;
34
+ lastError?: string;
35
+ activeAccounts: number;
36
+ uptime: number;
37
+ status: ProbeStatus;
38
+ }
39
+ /**
40
+ * Probe - Channel health status and monitoring
41
+ */
42
+ declare class Probe {
43
+ private runtime;
44
+ private startTime;
45
+ private lastError;
46
+ private lastErrorTime;
47
+ private errorCounts;
48
+ private healthChecks;
49
+ constructor(runtime: LingyaoRuntime);
50
+ /**
51
+ * Register a custom health check
52
+ */
53
+ registerHealthCheck(name: string, check: HealthCheckFn): void;
54
+ /**
55
+ * Run all health checks
56
+ */
57
+ runHealthChecks(): Promise<HealthCheckResult>;
58
+ /**
59
+ * Get channel status for reporting
60
+ */
61
+ getChannelStatus(configured: boolean, running: boolean, activeAccounts?: number): ChannelStatus;
62
+ /**
63
+ * Get health status for API endpoint
64
+ */
65
+ getHealthStatus(activeConnections?: number, queuedMessages?: number): Promise<HealthStatus>;
66
+ /**
67
+ * Record an error
68
+ */
69
+ recordError(error: string, category?: string): void;
70
+ /**
71
+ * Get last error
72
+ */
73
+ getLastError(): string | null;
74
+ /**
75
+ * Clear last error
76
+ */
77
+ clearLastError(): void;
78
+ /**
79
+ * Get error counts by category
80
+ */
81
+ getErrorCounts(): Record<string, number>;
82
+ /**
83
+ * Reset error counts
84
+ */
85
+ resetErrorCounts(): void;
86
+ /**
87
+ * Get uptime in milliseconds
88
+ */
89
+ getUptime(): number;
90
+ /**
91
+ * Get uptime formatted as human-readable string
92
+ */
93
+ getUptimeString(): string;
94
+ /**
95
+ * Health check: uptime
96
+ */
97
+ private checkUptime;
98
+ /**
99
+ * Health check: errors
100
+ */
101
+ private checkErrors;
102
+ /**
103
+ * Create status adapter for OpenClaw integration
104
+ */
105
+ createStatusAdapter(configured: boolean): {
106
+ getStatus: (running: boolean, activeAccounts?: number, activeConnections?: number, queuedMessages?: number) => Promise<{
107
+ activeConnections: number;
108
+ queuedMessages: number;
109
+ configured: boolean;
110
+ running: boolean;
111
+ lastError?: string;
112
+ activeAccounts: number;
113
+ uptime: number;
114
+ status: ProbeStatus;
115
+ }>;
116
+ };
117
+ }
118
+ /**
119
+ * Health check function signature
120
+ */
121
+ type HealthCheckFn = () => Promise<{
122
+ passed: boolean;
123
+ message?: string;
124
+ }>;
125
+
126
+ /**
127
+ * CLI command handler for Lingyao plugin
128
+ */
129
+ declare class LingyaoCLI {
130
+ private runtime;
131
+ private accountManager;
132
+ private probe;
133
+ constructor(runtime: LingyaoRuntime, accountManager: AccountManager, probe: Probe);
134
+ /**
135
+ * Handle logout command - Revoke a device
136
+ */
137
+ handleLogout(args: LogoutArgs): Promise<CLIResult>;
138
+ /**
139
+ * Handle status command - Show channel status
140
+ */
141
+ handleStatus(_args: StatusArgs): Promise<CLIResult>;
142
+ /**
143
+ * List all paired devices
144
+ */
145
+ listDevices(): Promise<CLIResult>;
146
+ }
147
+ /**
148
+ * Logout command arguments
149
+ */
150
+ interface LogoutArgs {
151
+ channel?: string;
152
+ deviceId?: string;
153
+ device?: string;
154
+ }
155
+ /**
156
+ * Status command arguments
157
+ */
158
+ interface StatusArgs {
159
+ channel?: string;
160
+ verbose?: boolean;
161
+ }
162
+ /**
163
+ * CLI command result
164
+ */
165
+ interface CLIResult {
166
+ success: boolean;
167
+ output?: string;
168
+ error?: string;
169
+ }
170
+ /**
171
+ * Execute CLI command
172
+ */
173
+ declare function executeCLICommand(command: string, args: Record<string, unknown>, runtime: LingyaoRuntime, accountManager: AccountManager, probe: Probe): Promise<CLIResult>;
174
+
175
+ export { type CLIResult, LingyaoCLI, type LogoutArgs, type StatusArgs, executeCLICommand };
package/dist/cli.js ADDED
@@ -0,0 +1,181 @@
1
+ // src/cli.ts
2
+ var LingyaoCLI = class {
3
+ runtime;
4
+ accountManager;
5
+ probe;
6
+ constructor(runtime, accountManager, probe) {
7
+ this.runtime = runtime;
8
+ this.accountManager = accountManager;
9
+ this.probe = probe;
10
+ }
11
+ /**
12
+ * Handle logout command - Revoke a device
13
+ */
14
+ async handleLogout(args) {
15
+ try {
16
+ const { deviceId } = args;
17
+ if (!deviceId) {
18
+ const accounts = this.accountManager.getActiveAccounts();
19
+ if (accounts.length === 0) {
20
+ return {
21
+ success: false,
22
+ error: "No paired devices found"
23
+ };
24
+ }
25
+ const list = accounts.map(
26
+ (acc, i) => `${i + 1}. ${acc.deviceId} (${acc.deviceInfo.name}) - Last seen: ${new Date(acc.lastSeenAt).toLocaleString()}`
27
+ ).join("\n");
28
+ return {
29
+ success: false,
30
+ error: `Please specify a device ID to revoke.
31
+
32
+ Paired devices:
33
+ ${list}
34
+
35
+ Usage: openclaw channels logout --channel lingyao --device <deviceId>`
36
+ };
37
+ }
38
+ const account = this.accountManager.getAccount(deviceId);
39
+ if (!account) {
40
+ return {
41
+ success: false,
42
+ error: `Device not found: ${deviceId}`
43
+ };
44
+ }
45
+ const revoked = await this.accountManager.revokeAccount(deviceId);
46
+ if (!revoked) {
47
+ return {
48
+ success: false,
49
+ error: `Failed to revoke device: ${deviceId}`
50
+ };
51
+ }
52
+ return {
53
+ success: true,
54
+ output: `Device revoked: ${deviceId} (${account.deviceInfo.name})`
55
+ };
56
+ } catch (error) {
57
+ this.runtime.logger.error("Logout failed", error);
58
+ return {
59
+ success: false,
60
+ error: `Logout failed: ${error}`
61
+ };
62
+ }
63
+ }
64
+ /**
65
+ * Handle status command - Show channel status
66
+ */
67
+ async handleStatus(_args) {
68
+ try {
69
+ const accounts = this.accountManager.getActiveAccounts();
70
+ const healthStatus = await this.probe.getHealthStatus(
71
+ 0,
72
+ // activeConnections - would come from WebSocket manager
73
+ 0
74
+ // queuedMessages - would come from outbound adapter
75
+ );
76
+ const statusColor = healthStatus.status === "healthy" ? "\u2713" : "\u26A0";
77
+ const lines = [
78
+ "",
79
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
80
+ "\u2551 \u7075\u723B (Lingyao) Channel Status \u2551",
81
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
82
+ "",
83
+ `Status: ${statusColor} ${healthStatus.status.toUpperCase()}`,
84
+ `Uptime: ${this.probe.getUptimeString()}`,
85
+ ""
86
+ ];
87
+ if (healthStatus.lastError) {
88
+ lines.push(`Last Error: ${healthStatus.lastError}`);
89
+ lines.push("");
90
+ }
91
+ if (accounts.length === 0) {
92
+ lines.push("No paired devices.");
93
+ lines.push("");
94
+ lines.push("Pairing is managed by the lingyao.live relay and Lingyao App.");
95
+ } else {
96
+ lines.push(`Paired Devices (${accounts.length}):`);
97
+ lines.push("");
98
+ for (const account of accounts) {
99
+ const lastSeen = new Date(account.lastSeenAt).toLocaleString();
100
+ const pairedAt = new Date(account.pairedAt).toLocaleString();
101
+ lines.push(` Device: ${account.deviceId}`);
102
+ lines.push(` Name: ${account.deviceInfo.name}`);
103
+ lines.push(` Platform: ${account.deviceInfo.platform} ${account.deviceInfo.version}`);
104
+ lines.push(` Status: ${account.status}`);
105
+ lines.push(` Paired: ${pairedAt}`);
106
+ lines.push(` Last Seen: ${lastSeen}`);
107
+ lines.push("");
108
+ }
109
+ }
110
+ lines.push("Commands:");
111
+ lines.push(" openclaw channels logout --channel lingyao --device <deviceId>");
112
+ lines.push(" openclaw channels status --channel lingyao");
113
+ lines.push("");
114
+ return {
115
+ success: true,
116
+ output: lines.join("\n")
117
+ };
118
+ } catch (error) {
119
+ this.runtime.logger.error("Status command failed", error);
120
+ return {
121
+ success: false,
122
+ error: `Status command failed: ${error}`
123
+ };
124
+ }
125
+ }
126
+ /**
127
+ * List all paired devices
128
+ */
129
+ async listDevices() {
130
+ try {
131
+ const accounts = this.accountManager.getActiveAccounts();
132
+ if (accounts.length === 0) {
133
+ return {
134
+ success: true,
135
+ output: "No paired devices found."
136
+ };
137
+ }
138
+ const lines = [
139
+ `Paired Devices (${accounts.length}):`,
140
+ ""
141
+ ];
142
+ for (const account of accounts) {
143
+ lines.push(` ${account.deviceId}`);
144
+ lines.push(` Name: ${account.deviceInfo.name}`);
145
+ lines.push(` Platform: ${account.deviceInfo.platform}`);
146
+ lines.push(` Status: ${account.status}`);
147
+ lines.push("");
148
+ }
149
+ return {
150
+ success: true,
151
+ output: lines.join("\n")
152
+ };
153
+ } catch (error) {
154
+ return {
155
+ success: false,
156
+ error: `Failed to list devices: ${error}`
157
+ };
158
+ }
159
+ }
160
+ };
161
+ async function executeCLICommand(command, args, runtime, accountManager, probe) {
162
+ const cli = new LingyaoCLI(runtime, accountManager, probe);
163
+ switch (command) {
164
+ case "logout":
165
+ return await cli.handleLogout(args);
166
+ case "status":
167
+ return await cli.handleStatus(args);
168
+ case "list":
169
+ return await cli.listDevices();
170
+ default:
171
+ return {
172
+ success: false,
173
+ error: `Unknown command: ${command}`
174
+ };
175
+ }
176
+ }
177
+ export {
178
+ LingyaoCLI,
179
+ executeCLICommand
180
+ };
181
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +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":[]}