meross-cli 0.1.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -0
  4. package/cli/commands/control/execute.js +23 -0
  5. package/cli/commands/control/index.js +12 -0
  6. package/cli/commands/control/menu.js +193 -0
  7. package/cli/commands/control/params/generic.js +229 -0
  8. package/cli/commands/control/params/index.js +56 -0
  9. package/cli/commands/control/params/light.js +188 -0
  10. package/cli/commands/control/params/thermostat.js +166 -0
  11. package/cli/commands/control/params/timer.js +242 -0
  12. package/cli/commands/control/params/trigger.js +206 -0
  13. package/cli/commands/dump.js +35 -0
  14. package/cli/commands/index.js +34 -0
  15. package/cli/commands/info.js +221 -0
  16. package/cli/commands/list.js +112 -0
  17. package/cli/commands/mqtt.js +187 -0
  18. package/cli/commands/sniffer/device-sniffer.js +217 -0
  19. package/cli/commands/sniffer/fake-app.js +233 -0
  20. package/cli/commands/sniffer/index.js +7 -0
  21. package/cli/commands/sniffer/message-queue.js +65 -0
  22. package/cli/commands/sniffer/sniffer-menu.js +676 -0
  23. package/cli/commands/stats.js +90 -0
  24. package/cli/commands/status/device-status.js +1403 -0
  25. package/cli/commands/status/hub-status.js +72 -0
  26. package/cli/commands/status/index.js +50 -0
  27. package/cli/commands/status/subdevices/hub-smoke-detector.js +82 -0
  28. package/cli/commands/status/subdevices/hub-temp-hum-sensor.js +43 -0
  29. package/cli/commands/status/subdevices/hub-thermostat-valve.js +83 -0
  30. package/cli/commands/status/subdevices/hub-water-leak-sensor.js +27 -0
  31. package/cli/commands/status/subdevices/index.js +23 -0
  32. package/cli/commands/test/index.js +185 -0
  33. package/cli/config/users.js +108 -0
  34. package/cli/control-registry.js +875 -0
  35. package/cli/helpers/client.js +89 -0
  36. package/cli/helpers/meross.js +106 -0
  37. package/cli/menu/index.js +10 -0
  38. package/cli/menu/main.js +648 -0
  39. package/cli/menu/settings.js +789 -0
  40. package/cli/meross-cli.js +547 -0
  41. package/cli/tests/README.md +365 -0
  42. package/cli/tests/test-alarm.js +144 -0
  43. package/cli/tests/test-child-lock.js +248 -0
  44. package/cli/tests/test-config.js +133 -0
  45. package/cli/tests/test-control.js +189 -0
  46. package/cli/tests/test-diffuser.js +505 -0
  47. package/cli/tests/test-dnd.js +246 -0
  48. package/cli/tests/test-electricity.js +209 -0
  49. package/cli/tests/test-encryption.js +281 -0
  50. package/cli/tests/test-garage.js +259 -0
  51. package/cli/tests/test-helper.js +313 -0
  52. package/cli/tests/test-hub-mts100.js +355 -0
  53. package/cli/tests/test-hub-sensors.js +489 -0
  54. package/cli/tests/test-light.js +253 -0
  55. package/cli/tests/test-presence.js +497 -0
  56. package/cli/tests/test-registry.js +419 -0
  57. package/cli/tests/test-roller-shutter.js +628 -0
  58. package/cli/tests/test-runner.js +415 -0
  59. package/cli/tests/test-runtime.js +234 -0
  60. package/cli/tests/test-screen.js +133 -0
  61. package/cli/tests/test-sensor-history.js +146 -0
  62. package/cli/tests/test-smoke-config.js +138 -0
  63. package/cli/tests/test-spray.js +131 -0
  64. package/cli/tests/test-temp-unit.js +133 -0
  65. package/cli/tests/test-template.js +238 -0
  66. package/cli/tests/test-thermostat.js +919 -0
  67. package/cli/tests/test-timer.js +372 -0
  68. package/cli/tests/test-toggle.js +342 -0
  69. package/cli/tests/test-trigger.js +279 -0
  70. package/cli/utils/display.js +86 -0
  71. package/cli/utils/terminal.js +137 -0
  72. package/package.json +53 -0
@@ -0,0 +1,648 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const inquirer = require('inquirer');
5
+ const { MerossHubDevice, MerossSubDevice, createDebugUtils, TransportMode } = require('meross-iot');
6
+ const testRunner = require('../tests/test-runner');
7
+ const { clearScreen, renderLogoAtTop, renderSimpleHeader, clearMenuArea, CONTENT_START_LINE, SIMPLE_CONTENT_START_LINE, createRL, question, promptForPassword } = require('../utils/terminal');
8
+ const { formatDevice } = require('../utils/display');
9
+ const { listDevices, showStats, dumpRegistry, listMqttConnections, getDeviceStatus, showDeviceInfo, controlDeviceMenu, runTestCommand, snifferMenu } = require('../commands');
10
+ const { addUser, getUser, listUsers } = require('../config/users');
11
+ const { createMerossInstance, connectMeross, disconnectMeross } = require('../helpers/meross');
12
+ const { showSettingsMenu } = require('./settings');
13
+
14
+ // Helper functions
15
+ function _getDeviceCount(manager) {
16
+ if (!manager) {return 0;}
17
+ return manager.getAllDevices().filter(d => !(d instanceof MerossSubDevice)).length;
18
+ }
19
+
20
+ function _renderMainMenuHeader(currentUser, manager) {
21
+ clearScreen();
22
+ const deviceCount = _getDeviceCount(manager);
23
+ renderSimpleHeader(currentUser, deviceCount);
24
+ }
25
+
26
+ async function _promptForCredentials(rl) {
27
+ const email = await question(rl, 'Email: ');
28
+ const password = await promptForPassword(rl, 'Password: ');
29
+ const mfaCode = await question(rl, 'MFA Code (optional, press Enter to skip): ');
30
+
31
+ return {
32
+ email: email.trim(),
33
+ password,
34
+ mfaCode: mfaCode.trim() || null
35
+ };
36
+ }
37
+
38
+ async function _handleStoredUserLogin(loginChoice) {
39
+ const userName = loginChoice.replace('user_', '');
40
+ const userData = getUser(userName);
41
+ if (!userData) {
42
+ return { success: false };
43
+ }
44
+
45
+ const manager = await createMerossInstance(
46
+ userData.email,
47
+ userData.password,
48
+ userData.mfaCode,
49
+ TransportMode.MQTT_ONLY,
50
+ 10000,
51
+ false,
52
+ false
53
+ );
54
+
55
+ return { success: true, manager, userName };
56
+ }
57
+
58
+ async function _handleAddNewUser(rl) {
59
+ const name = await question(rl, 'User name: ');
60
+ if (!name || !name.trim()) {
61
+ console.error('\nError: User name cannot be empty.');
62
+ return { success: false };
63
+ }
64
+
65
+ const email = await question(rl, 'Email: ');
66
+ if (!email || !email.trim()) {
67
+ console.error('\nError: Email cannot be empty.');
68
+ return { success: false };
69
+ }
70
+
71
+ const password = await promptForPassword(rl, 'Password: ');
72
+ if (!password) {
73
+ console.error('\nError: Password cannot be empty.');
74
+ return { success: false };
75
+ }
76
+
77
+ const mfaCode = await question(rl, 'MFA Code (optional, press Enter to skip): ');
78
+
79
+ const result = addUser(name.trim(), email.trim(), password, mfaCode.trim() || null);
80
+ if (!result.success) {
81
+ console.error(`\nError: ${result.error}`);
82
+ return { success: false };
83
+ }
84
+
85
+ console.log(`\nUser "${name.trim()}" added successfully.`);
86
+ const userData = getUser(name.trim());
87
+ if (!userData) {
88
+ return { success: false };
89
+ }
90
+
91
+ const manager = await createMerossInstance(
92
+ userData.email,
93
+ userData.password,
94
+ userData.mfaCode,
95
+ TransportMode.MQTT_ONLY,
96
+ 10000,
97
+ false,
98
+ false
99
+ );
100
+
101
+ return { success: true, manager, userName: name.trim() };
102
+ }
103
+
104
+ async function _handleManualLogin(rl) {
105
+ const credentials = await _promptForCredentials(rl);
106
+ const manager = await createMerossInstance(
107
+ credentials.email,
108
+ credentials.password,
109
+ credentials.mfaCode,
110
+ TransportMode.MQTT_ONLY,
111
+ 10000,
112
+ false,
113
+ false
114
+ );
115
+
116
+ return { success: true, manager, credentials };
117
+ }
118
+
119
+ async function _handleNoStoredUsersLogin(rl) {
120
+ process.stdout.write('\nNo stored users found. Please enter credentials:\n\n');
121
+ const credentials = await _promptForCredentials(rl);
122
+ const manager = await createMerossInstance(
123
+ credentials.email,
124
+ credentials.password,
125
+ credentials.mfaCode,
126
+ TransportMode.MQTT_ONLY,
127
+ 10000,
128
+ false,
129
+ false
130
+ );
131
+
132
+ return { success: true, manager, credentials };
133
+ }
134
+
135
+ async function _saveCredentialsPrompt(rl, currentCredentials) {
136
+ const { saveCredentials } = await inquirer.prompt([{
137
+ type: 'confirm',
138
+ name: 'saveCredentials',
139
+ message: 'Would you like to save these credentials for future use?',
140
+ default: false
141
+ }]);
142
+
143
+ if (!saveCredentials) {
144
+ return null;
145
+ }
146
+
147
+ const { userName } = await inquirer.prompt([{
148
+ type: 'input',
149
+ name: 'userName',
150
+ message: 'Enter a name for this user account:',
151
+ validate: (input) => {
152
+ if (!input || !input.trim()) {
153
+ return 'User name cannot be empty';
154
+ }
155
+ return true;
156
+ }
157
+ }]);
158
+
159
+ if (!userName || !userName.trim()) {
160
+ return null;
161
+ }
162
+
163
+ const result = addUser(userName.trim(), currentCredentials.email, currentCredentials.password, currentCredentials.mfaCode);
164
+ if (result.success) {
165
+ console.log(`\nCredentials saved as user "${userName.trim()}".\n`);
166
+ return userName.trim();
167
+ }
168
+
169
+ return null;
170
+ }
171
+
172
+ async function _selectDevice(manager, message = 'Select device:') {
173
+ const devices = manager.getAllDevices().filter(d => !(d instanceof MerossSubDevice));
174
+ if (devices.length === 0) {
175
+ return null;
176
+ }
177
+
178
+ const deviceChoices = devices.map((device) => {
179
+ const info = formatDevice(device);
180
+ return {
181
+ name: `${info.name} (${info.uuid})`,
182
+ value: info.uuid
183
+ };
184
+ });
185
+
186
+ const result = await inquirer.prompt([{
187
+ type: 'list',
188
+ name: 'uuid',
189
+ message,
190
+ choices: deviceChoices
191
+ }]);
192
+
193
+ return result.uuid;
194
+ }
195
+
196
+ async function _selectDeviceOrAll(manager) {
197
+ const devices = manager.getAllDevices().filter(d => !(d instanceof MerossSubDevice));
198
+ if (devices.length === 0) {
199
+ return null;
200
+ }
201
+
202
+ const deviceChoices = [
203
+ { name: 'All devices', value: null },
204
+ ...devices.map((device) => {
205
+ const info = formatDevice(device);
206
+ return {
207
+ name: `${info.name} (${info.uuid})`,
208
+ value: info.uuid
209
+ };
210
+ })
211
+ ];
212
+
213
+ const result = await inquirer.prompt([{
214
+ type: 'list',
215
+ name: 'uuid',
216
+ message: 'Select device (or All devices):',
217
+ choices: deviceChoices
218
+ }]);
219
+
220
+ return result.uuid;
221
+ }
222
+
223
+ async function _selectSubdevice(manager, uuid) {
224
+ if (!uuid) {
225
+ return null;
226
+ }
227
+
228
+ const device = manager.getDevice(uuid);
229
+ if (!(device instanceof MerossHubDevice)) {
230
+ return null;
231
+ }
232
+
233
+ const subdevices = device.getSubdevices();
234
+ if (subdevices.length === 0) {
235
+ return null;
236
+ }
237
+
238
+ const subdeviceChoices = [
239
+ { name: 'All subdevices', value: null },
240
+ ...subdevices.map((subdevice) => {
241
+ const subName = subdevice.name || subdevice.subdeviceId;
242
+ return {
243
+ name: `${subName} (${subdevice.subdeviceId})`,
244
+ value: subdevice.subdeviceId
245
+ };
246
+ })
247
+ ];
248
+
249
+ const { subId } = await inquirer.prompt([{
250
+ type: 'list',
251
+ name: 'subId',
252
+ message: 'Select subdevice (or All subdevices):',
253
+ choices: subdeviceChoices
254
+ }]);
255
+
256
+ return subId;
257
+ }
258
+
259
+ async function _handleListCommand(manager, rl) {
260
+ await listDevices(manager);
261
+ await question(rl, '\nPress Enter to return to menu...');
262
+ }
263
+
264
+ async function _handleInfoCommand(manager, rl) {
265
+ const devices = manager.getAllDevices().filter(d => !(d instanceof MerossSubDevice));
266
+ if (devices.length === 0) {
267
+ console.log('\nNo devices found.');
268
+ await question(rl, '\nPress Enter to return to menu...');
269
+ return;
270
+ }
271
+
272
+ const uuid = await _selectDevice(manager);
273
+ if (uuid) {
274
+ await showDeviceInfo(manager, uuid);
275
+ await question(rl, '\nPress Enter to return to menu...');
276
+ }
277
+ }
278
+
279
+ async function _handleStatusCommand(manager, rl) {
280
+ const devices = manager.getAllDevices().filter(d => !(d instanceof MerossSubDevice));
281
+ if (devices.length === 0) {
282
+ console.log('\nNo devices found.');
283
+ await question(rl, '\nPress Enter to return to menu...');
284
+ return;
285
+ }
286
+
287
+ const uuid = await _selectDeviceOrAll(manager);
288
+ if (uuid === undefined) {
289
+ return;
290
+ }
291
+
292
+ const subdeviceId = await _selectSubdevice(manager, uuid);
293
+ await getDeviceStatus(manager, uuid, subdeviceId);
294
+ await question(rl, '\nPress Enter to return to menu...');
295
+ }
296
+
297
+ async function _handleControlCommand(manager, rl, currentUser) {
298
+ await controlDeviceMenu(manager, rl, currentUser);
299
+ }
300
+
301
+ async function _handleStatsCommand(manager, rl) {
302
+ const { action } = await inquirer.prompt([{
303
+ type: 'list',
304
+ name: 'action',
305
+ message: 'Statistics action:',
306
+ choices: [
307
+ { name: 'Show statistics', value: 'get' },
308
+ { name: 'Enable statistics', value: 'on' },
309
+ { name: 'Disable statistics', value: 'off' }
310
+ ]
311
+ }]);
312
+
313
+ const debug = createDebugUtils(manager);
314
+ if (action === 'get') {
315
+ showStats(manager);
316
+ } else if (action === 'on') {
317
+ debug.enableStats();
318
+ console.log('\nStatistics tracking enabled');
319
+ } else {
320
+ debug.disableStats();
321
+ console.log('\nStatistics tracking disabled');
322
+ }
323
+ await question(rl, '\nPress Enter to return to menu...');
324
+ }
325
+
326
+ async function _handleMqttConnectionsCommand(manager, rl) {
327
+ const currentVerboseState = manager && manager.options ? (manager.options.logger !== null) : false;
328
+ await listMqttConnections(manager, {
329
+ verbose: currentVerboseState,
330
+ json: false
331
+ });
332
+ await question(rl, '\nPress Enter to return to menu...');
333
+ }
334
+
335
+ async function _handleDumpCommand(manager, rl) {
336
+ const { filename } = await inquirer.prompt([{
337
+ type: 'input',
338
+ name: 'filename',
339
+ message: 'Filename (default: device-registry.json):',
340
+ default: 'device-registry.json'
341
+ }]);
342
+
343
+ await dumpRegistry(manager, filename || 'device-registry.json');
344
+ await question(rl, '\nPress Enter to return to menu...');
345
+ }
346
+
347
+ async function _handleTestCommand(manager, rl) {
348
+ const availableTypes = testRunner.getAvailableTestTypes();
349
+ const testChoices = availableTypes.map(type => {
350
+ const description = testRunner.getTestDescription(type);
351
+ return {
352
+ name: `${type} - ${description}`,
353
+ value: type
354
+ };
355
+ });
356
+
357
+ const { testType } = await inquirer.prompt([{
358
+ type: 'list',
359
+ name: 'testType',
360
+ message: 'Select test type:',
361
+ choices: testChoices
362
+ }]);
363
+
364
+ await runTestCommand(manager, testType, true, rl);
365
+ await question(rl, '\nPress Enter to return to menu...');
366
+ }
367
+
368
+ async function _handleSnifferCommand(manager, rl, currentUser = null) {
369
+ await snifferMenu(manager, rl, currentUser);
370
+ }
371
+
372
+ function _buildSettingsCallbacks(
373
+ manager, rl, currentCredentials, currentUserRef, managerRef, transportModeRef, timeoutRef, enableStatsRef, verboseRef
374
+ ) {
375
+ return {
376
+ onSwitchUser: async (userName, userData) => {
377
+ await disconnectMeross(managerRef.current);
378
+ managerRef.current = await createMerossInstance(
379
+ userData.email,
380
+ userData.password,
381
+ userData.mfaCode,
382
+ transportModeRef.current,
383
+ timeoutRef.current,
384
+ enableStatsRef.current,
385
+ verboseRef.current
386
+ );
387
+ const connected = await connectMeross(managerRef.current, rl);
388
+ if (connected) {
389
+ currentUserRef.current = userName;
390
+ return { success: true };
391
+ }
392
+ return { success: false, error: 'Failed to connect with new user' };
393
+ },
394
+ onSaveCredentials: async (name) => {
395
+ if (!currentCredentials) {
396
+ return { success: false, error: 'No credentials available to save' };
397
+ }
398
+ if (currentUserRef.current) {
399
+ return { success: false, error: `You are already using a stored user account: "${currentUserRef.current}"` };
400
+ }
401
+ const result = addUser(name, currentCredentials.email, currentCredentials.password, currentCredentials.mfaCode);
402
+ if (result.success) {
403
+ currentUserRef.current = name;
404
+ }
405
+ return result;
406
+ }
407
+ };
408
+ }
409
+
410
+ async function _handleSettingsCommand(manager, rl, currentUserRef, currentCredentials) {
411
+ const transportModeRef = { current: manager.defaultTransportMode || TransportMode.MQTT_ONLY };
412
+ const timeoutRef = { current: 10000 };
413
+ const enableStatsRef = { current: manager._mqttStatsCounter !== null || manager.httpClient._httpStatsCounter !== null };
414
+ const verboseRef = { current: manager.options && manager.options.logger !== null };
415
+ const managerRef = { current: manager };
416
+
417
+ const setTransportMode = (mode) => {
418
+ transportModeRef.current = mode;
419
+ managerRef.current.defaultTransportMode = mode;
420
+ };
421
+ const setTimeout = (newTimeout) => {
422
+ timeoutRef.current = newTimeout;
423
+ };
424
+ const setEnableStats = (enabled) => {
425
+ enableStatsRef.current = enabled;
426
+ const debug = createDebugUtils(managerRef.current);
427
+ if (enabled) {
428
+ debug.enableStats();
429
+ } else {
430
+ debug.disableStats();
431
+ }
432
+ };
433
+ const setVerbose = (enabled) => {
434
+ verboseRef.current = enabled;
435
+ if (managerRef.current.options) {
436
+ managerRef.current.options.logger = enabled ? console.log : null;
437
+ }
438
+ if (managerRef.current.httpClient && managerRef.current.httpClient.options) {
439
+ managerRef.current.httpClient.options.logger = enabled ? console.log : null;
440
+ }
441
+ };
442
+
443
+ const userManagementCallbacks = _buildSettingsCallbacks(
444
+ manager, rl, currentCredentials, currentUserRef, managerRef,
445
+ transportModeRef, timeoutRef, enableStatsRef, verboseRef
446
+ );
447
+
448
+ await showSettingsMenu(
449
+ rl,
450
+ managerRef.current,
451
+ currentUserRef.current,
452
+ timeoutRef.current,
453
+ enableStatsRef.current,
454
+ verboseRef.current,
455
+ setTransportMode,
456
+ setTimeout,
457
+ setEnableStats,
458
+ setVerbose,
459
+ userManagementCallbacks
460
+ );
461
+
462
+ return { manager: managerRef.current, user: currentUserRef.current };
463
+ }
464
+
465
+ async function menuMode() {
466
+ const rl = createRL();
467
+
468
+ // Clear screen and render logo at top
469
+ clearScreen();
470
+ renderLogoAtTop('menu');
471
+
472
+ // Prompt for login
473
+ let currentManager = null;
474
+ let currentUser = null;
475
+ let currentCredentials = null;
476
+
477
+ const users = listUsers();
478
+ const hasStoredUsers = users.length > 0;
479
+
480
+ // Clear menu area and show login prompt
481
+ clearMenuArea(CONTENT_START_LINE);
482
+
483
+ // Handle login flow
484
+ if (hasStoredUsers) {
485
+ const choices = [
486
+ ...users.map(user => ({
487
+ name: `${user.name} (${user.email})`,
488
+ value: `user_${user.name}`
489
+ })),
490
+ new inquirer.Separator(),
491
+ {
492
+ name: 'Add new user',
493
+ value: 'add_user'
494
+ },
495
+ {
496
+ name: 'Enter credentials manually',
497
+ value: 'manual'
498
+ }
499
+ ];
500
+
501
+ const { loginChoice } = await inquirer.prompt([{
502
+ type: 'list',
503
+ name: 'loginChoice',
504
+ message: 'Select login option:',
505
+ choices
506
+ }]);
507
+
508
+ if (loginChoice.startsWith('user_')) {
509
+ const result = await _handleStoredUserLogin(loginChoice);
510
+ if (!result.success) {
511
+ rl.close();
512
+ return;
513
+ }
514
+ currentManager = result.manager;
515
+ currentUser = result.userName;
516
+ } else if (loginChoice === 'add_user') {
517
+ const result = await _handleAddNewUser(rl);
518
+ if (!result.success) {
519
+ rl.close();
520
+ return;
521
+ }
522
+ currentManager = result.manager;
523
+ currentUser = result.userName;
524
+ } else if (loginChoice === 'manual') {
525
+ const result = await _handleManualLogin(rl);
526
+ if (!result.success) {
527
+ rl.close();
528
+ return;
529
+ }
530
+ currentManager = result.manager;
531
+ currentCredentials = result.credentials;
532
+ }
533
+ } else {
534
+ const result = await _handleNoStoredUsersLogin(rl);
535
+ if (!result.success) {
536
+ rl.close();
537
+ return;
538
+ }
539
+ currentManager = result.manager;
540
+ currentCredentials = result.credentials;
541
+ }
542
+
543
+ // Connect
544
+ const connected = await connectMeross(currentManager, rl);
545
+ if (!connected) {
546
+ console.error('Failed to connect. Exiting.');
547
+ rl.close();
548
+ return;
549
+ }
550
+
551
+ // Offer to save credentials if logged in manually
552
+ if (currentCredentials && !currentUser) {
553
+ const savedUserName = await _saveCredentialsPrompt(rl, currentCredentials);
554
+ if (savedUserName) {
555
+ currentUser = savedUserName;
556
+ }
557
+ }
558
+
559
+ // Render simple header after successful login (no big logo)
560
+ _renderMainMenuHeader(currentUser, currentManager);
561
+
562
+ // Main menu loop
563
+ const currentUserRef = { current: currentUser };
564
+ while (true) {
565
+ _renderMainMenuHeader(currentUserRef.current, currentManager);
566
+ clearMenuArea(SIMPLE_CONTENT_START_LINE);
567
+ process.stdout.write(chalk.bold('=== Meross CLI Menu ===\n\n'));
568
+
569
+ const { command } = await inquirer.prompt([{
570
+ type: 'list',
571
+ name: 'command',
572
+ message: 'Select a command:',
573
+ choices: [
574
+ { name: 'List devices', value: 'list' },
575
+ { name: 'Device info', value: 'info' },
576
+ { name: 'Device status', value: 'status' },
577
+ { name: 'Control device', value: 'control' },
578
+ { name: 'Statistics', value: 'stats' },
579
+ { name: 'MQTT connections', value: 'mqtt-connections' },
580
+ { name: 'Dump registry', value: 'dump' },
581
+ { name: 'Run tests', value: 'test' },
582
+ { name: 'Device Sniffer', value: 'sniffer' },
583
+ { name: 'Settings', value: 'settings' },
584
+ new inquirer.Separator(),
585
+ { name: 'Exit', value: 'exit' }
586
+ ]
587
+ }]);
588
+
589
+ if (command === 'exit') {
590
+ break;
591
+ }
592
+
593
+ try {
594
+ _renderMainMenuHeader(currentUserRef.current, currentManager);
595
+ clearMenuArea(SIMPLE_CONTENT_START_LINE);
596
+
597
+ if (command === 'list') {
598
+ await _handleListCommand(currentManager, rl);
599
+ } else if (command === 'info') {
600
+ await _handleInfoCommand(currentManager, rl);
601
+ } else if (command === 'status') {
602
+ await _handleStatusCommand(currentManager, rl);
603
+ } else if (command === 'control') {
604
+ await _handleControlCommand(currentManager, rl, currentUserRef.current);
605
+ } else if (command === 'stats') {
606
+ await _handleStatsCommand(currentManager, rl);
607
+ } else if (command === 'mqtt-connections') {
608
+ await _handleMqttConnectionsCommand(currentManager, rl);
609
+ } else if (command === 'dump') {
610
+ await _handleDumpCommand(currentManager, rl);
611
+ } else if (command === 'test') {
612
+ await _handleTestCommand(currentManager, rl);
613
+ } else if (command === 'sniffer') {
614
+ await _handleSnifferCommand(currentManager, rl, currentUserRef.current);
615
+ } else if (command === 'settings') {
616
+ const result = await _handleSettingsCommand(currentManager, rl, currentUserRef, currentCredentials);
617
+ if (result.manager) {
618
+ currentManager = result.manager;
619
+ }
620
+ if (result.user) {
621
+ currentUserRef.current = result.user;
622
+ }
623
+ }
624
+ } catch (error) {
625
+ console.error(`\nError: ${error.message}`);
626
+ if (error.stack && process.env.MEROSS_VERBOSE) {
627
+ console.error(error.stack);
628
+ }
629
+ await question(rl, '\nPress Enter to return to menu...');
630
+ }
631
+ }
632
+
633
+ // Cleanup
634
+ rl.close();
635
+ console.log('\nLogging out from Meross cloud...');
636
+ try {
637
+ await disconnectMeross(currentManager);
638
+ console.log('Logged out successfully.');
639
+ } catch (error) {
640
+ console.error(`Error during logout: ${error.message}`);
641
+ }
642
+ console.log('Goodbye!');
643
+ }
644
+
645
+ module.exports = {
646
+ menuMode
647
+ };
648
+