meross-cli 0.3.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +46 -5
  3. package/cli/commands/control/execute.js +50 -7
  4. package/cli/commands/control/menu.js +3 -5
  5. package/cli/commands/control/params/index.js +16 -12
  6. package/cli/commands/control/params/light.js +55 -25
  7. package/cli/commands/control/params/thermostat.js +24 -22
  8. package/cli/commands/control/params/timer.js +18 -15
  9. package/cli/commands/control/params/trigger.js +24 -13
  10. package/cli/commands/info.js +39 -15
  11. package/cli/commands/sniffer/sniffer-menu.js +2 -2
  12. package/cli/commands/status/device-status.js +418 -1292
  13. package/cli/commands/status/hub-status.js +14 -6
  14. package/cli/control-registry.js +211 -406
  15. package/cli/helpers/client.js +16 -10
  16. package/cli/helpers/meross.js +18 -14
  17. package/cli/menu/main.js +170 -13
  18. package/cli/menu/settings.js +2 -2
  19. package/cli/meross-cli.js +13 -47
  20. package/cli/tests/README.md +2 -0
  21. package/cli/tests/test-alarm.js +22 -2
  22. package/cli/tests/test-child-lock.js +40 -10
  23. package/cli/tests/test-config.js +22 -2
  24. package/cli/tests/test-control.js +8 -8
  25. package/cli/tests/test-diffuser.js +7 -7
  26. package/cli/tests/test-dnd.js +87 -66
  27. package/cli/tests/test-electricity.js +37 -33
  28. package/cli/tests/test-encryption.js +13 -13
  29. package/cli/tests/test-garage.js +12 -14
  30. package/cli/tests/test-helper.js +1 -1
  31. package/cli/tests/test-hub-sensors.js +3 -3
  32. package/cli/tests/test-light.js +497 -105
  33. package/cli/tests/test-presence.js +10 -55
  34. package/cli/tests/test-registry.js +7 -1
  35. package/cli/tests/test-roller-shutter.js +78 -90
  36. package/cli/tests/test-screen.js +1 -1
  37. package/cli/tests/test-sensor-history.js +6 -2
  38. package/cli/tests/test-smoke-config.js +24 -4
  39. package/cli/tests/test-spray.js +11 -11
  40. package/cli/tests/test-system.js +375 -0
  41. package/cli/tests/test-temp-unit.js +22 -2
  42. package/cli/tests/test-template.js +61 -73
  43. package/cli/tests/test-thermostat.js +126 -89
  44. package/cli/tests/test-timer.js +8 -51
  45. package/cli/tests/test-toggle.js +49 -173
  46. package/cli/tests/test-trigger.js +7 -50
  47. package/cli/utils/error-handler.js +257 -0
  48. package/package.json +2 -2
@@ -87,22 +87,19 @@ async function runTests(context) {
87
87
 
88
88
  // Test 2: Get toggle state
89
89
  try {
90
- if (typeof testDevice.getToggleState === 'function') {
91
- const toggleState = await testDevice.getToggleState(0);
90
+ if (testDevice.toggle) {
91
+ const toggleState = await testDevice.toggle.get({ channel: 0 });
92
+ const isOn = testDevice.toggle.isOn({ channel: 0 });
92
93
 
93
94
  if (!toggleState) {
94
95
  results.push({
95
96
  name: 'should get toggle state',
96
97
  passed: false,
97
98
  skipped: false,
98
- error: 'getToggleState returned null or undefined',
99
+ error: 'toggle.get() returned null or undefined',
99
100
  device: deviceName
100
101
  });
101
102
  } else {
102
- // Check cached toggle state
103
- const cachedState = testDevice.getCachedToggleState(0);
104
- const isOn = testDevice.isOn(0);
105
-
106
103
  results.push({
107
104
  name: 'should get toggle state',
108
105
  passed: true,
@@ -111,7 +108,6 @@ async function runTests(context) {
111
108
  device: deviceName,
112
109
  details: {
113
110
  toggleState: toggleState,
114
- cachedState: cachedState,
115
111
  isOn: isOn
116
112
  }
117
113
  });
@@ -121,7 +117,7 @@ async function runTests(context) {
121
117
  name: 'should get toggle state',
122
118
  passed: false,
123
119
  skipped: true,
124
- error: 'Device does not support getToggleState',
120
+ error: 'Device does not support toggle feature',
125
121
  device: deviceName
126
122
  });
127
123
  }
@@ -137,141 +133,69 @@ async function runTests(context) {
137
133
 
138
134
  // Test 3: Control toggle state (turn on/off)
139
135
  try {
140
- // Get initial state
141
- let initialState = undefined;
142
- if (typeof testDevice.getToggleState === 'function') {
143
- const stateResponse = await testDevice.getToggleState(0);
144
- if (stateResponse && stateResponse.togglex) {
145
- initialState = stateResponse.togglex[0]?.onoff || stateResponse.togglex.onoff;
146
- }
147
- } else if (typeof testDevice.isOn === 'function') {
148
- initialState = testDevice.isOn(0);
149
- }
150
-
151
- // Test turnOn if available
152
- if (typeof testDevice.turnOn === 'function') {
153
- const turnOnResult = await testDevice.turnOn(0);
136
+ if (!testDevice.toggle) {
137
+ results.push({
138
+ name: 'should control toggle state',
139
+ passed: false,
140
+ skipped: true,
141
+ error: 'Device does not support toggle feature',
142
+ device: deviceName
143
+ });
144
+ } else {
145
+ // Get initial state
146
+ let initialState = testDevice.toggle.isOn({ channel: 0 });
154
147
 
155
- if (!turnOnResult) {
148
+ // Test turn on
149
+ await testDevice.toggle.set({ channel: 0, on: true });
150
+ await new Promise(resolve => setTimeout(resolve, 2000));
151
+
152
+ const isOnAfter = testDevice.toggle.isOn({ channel: 0 });
153
+ if (!isOnAfter) {
156
154
  results.push({
157
155
  name: 'should control toggle state (turn on)',
158
156
  passed: false,
159
157
  skipped: false,
160
- error: 'turnOn() returned null or undefined',
158
+ error: 'Device did not turn on after set({ on: true })',
161
159
  device: deviceName
162
160
  });
163
161
  } else {
164
- // Wait a bit for state to update
165
- await new Promise(resolve => setTimeout(resolve, 2000));
166
-
167
- // Verify state
168
- if (typeof testDevice.isOn === 'function') {
169
- const isOnAfter = testDevice.isOn(0);
170
- if (!isOnAfter) {
171
- results.push({
172
- name: 'should control toggle state (turn on)',
173
- passed: false,
174
- skipped: false,
175
- error: 'Device did not turn on after turnOn() call',
176
- device: deviceName
177
- });
178
- } else {
179
- results.push({
180
- name: 'should control toggle state (turn on)',
181
- passed: true,
182
- skipped: false,
183
- error: null,
184
- device: deviceName
185
- });
186
- }
187
- } else {
188
- // Can't verify, but command succeeded
189
- results.push({
190
- name: 'should control toggle state (turn on)',
191
- passed: true,
192
- skipped: false,
193
- error: null,
194
- device: deviceName,
195
- details: { note: 'turnOn succeeded but cannot verify state (isOn not available)' }
196
- });
197
- }
162
+ results.push({
163
+ name: 'should control toggle state (turn on)',
164
+ passed: true,
165
+ skipped: false,
166
+ error: null,
167
+ device: deviceName
168
+ });
198
169
  }
199
- } else {
200
- results.push({
201
- name: 'should control toggle state (turn on)',
202
- passed: false,
203
- skipped: true,
204
- error: 'Device does not support turnOn',
205
- device: deviceName
206
- });
207
- }
208
-
209
- // Test turnOff if available
210
- if (typeof testDevice.turnOff === 'function') {
211
- const turnOffResult = await testDevice.turnOff(0);
212
170
 
213
- if (!turnOffResult) {
171
+ // Test turn off
172
+ await testDevice.toggle.set({ channel: 0, on: false });
173
+ await new Promise(resolve => setTimeout(resolve, 2000));
174
+
175
+ const isOffAfter = testDevice.toggle.isOn({ channel: 0 });
176
+ if (isOffAfter) {
214
177
  results.push({
215
178
  name: 'should control toggle state (turn off)',
216
179
  passed: false,
217
180
  skipped: false,
218
- error: 'turnOff() returned null or undefined',
181
+ error: 'Device did not turn off after set({ on: false })',
219
182
  device: deviceName
220
183
  });
221
184
  } else {
222
- // Wait a bit for state to update
223
- await new Promise(resolve => setTimeout(resolve, 2000));
224
-
225
- // Verify state
226
- if (typeof testDevice.isOn === 'function') {
227
- const isOnAfter = testDevice.isOn(0);
228
- if (isOnAfter) {
229
- results.push({
230
- name: 'should control toggle state (turn off)',
231
- passed: false,
232
- skipped: false,
233
- error: 'Device did not turn off after turnOff() call',
234
- device: deviceName
235
- });
236
- } else {
237
- results.push({
238
- name: 'should control toggle state (turn off)',
239
- passed: true,
240
- skipped: false,
241
- error: null,
242
- device: deviceName
243
- });
244
- }
245
- } else {
246
- // Can't verify, but command succeeded
247
- results.push({
248
- name: 'should control toggle state (turn off)',
249
- passed: true,
250
- skipped: false,
251
- error: null,
252
- device: deviceName,
253
- details: { note: 'turnOff succeeded but cannot verify state (isOn not available)' }
254
- });
255
- }
185
+ results.push({
186
+ name: 'should control toggle state (turn off)',
187
+ passed: true,
188
+ skipped: false,
189
+ error: null,
190
+ device: deviceName
191
+ });
256
192
  }
257
- } else {
258
- results.push({
259
- name: 'should control toggle state (turn off)',
260
- passed: false,
261
- skipped: true,
262
- error: 'Device does not support turnOff',
263
- device: deviceName
264
- });
265
- }
266
-
267
- // Restore initial state if we changed it
268
- if (initialState !== undefined && typeof testDevice.turnOn === 'function' && typeof testDevice.turnOff === 'function') {
269
- if (initialState === 1 || initialState === true) {
270
- await testDevice.turnOn(0);
271
- } else {
272
- await testDevice.turnOff(0);
193
+
194
+ // Restore initial state if we changed it
195
+ if (initialState !== undefined) {
196
+ await testDevice.toggle.set({ channel: 0, on: initialState });
197
+ await new Promise(resolve => setTimeout(resolve, 2000));
273
198
  }
274
- await new Promise(resolve => setTimeout(resolve, 2000));
275
199
  }
276
200
 
277
201
  } catch (error) {
@@ -284,54 +208,6 @@ async function runTests(context) {
284
208
  });
285
209
  }
286
210
 
287
- // Test 4: Handle toggle push notifications
288
- try {
289
- const testDeviceForPush = toggleDevices[0];
290
-
291
- // Set up listener for toggle push notifications
292
- let receivedNotification = false;
293
- const notificationHandler = (notification) => {
294
- if (notification.namespace === 'Appliance.Control.ToggleX' ||
295
- notification.namespace === 'Appliance.Control.Toggle') {
296
- receivedNotification = true;
297
- }
298
- };
299
-
300
- testDeviceForPush.on('pushNotification', notificationHandler);
301
-
302
- // Get toggle state (may trigger a push notification)
303
- if (typeof testDeviceForPush.getToggleState === 'function') {
304
- await testDeviceForPush.getToggleState(0);
305
- }
306
-
307
- // Wait a bit for potential push notifications
308
- await new Promise(resolve => setTimeout(resolve, 5000));
309
-
310
- // Remove listener
311
- testDeviceForPush.removeListener('pushNotification', notificationHandler);
312
-
313
- // Note: We don't assert on receivedNotification since push notifications
314
- // are device-initiated and may not occur during testing
315
- // This test just verifies the listener mechanism works
316
- results.push({
317
- name: 'should handle toggle push notifications',
318
- passed: true,
319
- skipped: false,
320
- error: null,
321
- device: getDeviceName(testDeviceForPush),
322
- details: { notificationReceived: receivedNotification }
323
- });
324
-
325
- } catch (error) {
326
- results.push({
327
- name: 'should handle toggle push notifications',
328
- passed: false,
329
- skipped: false,
330
- error: error.message,
331
- device: toggleDevices.length > 0 ? getDeviceName(toggleDevices[0]) : null
332
- });
333
- }
334
-
335
211
  return results;
336
212
  }
337
213
 
@@ -81,7 +81,7 @@ async function runTests(context) {
81
81
  }
82
82
  }
83
83
  };
84
- testDevice.on('pushNotification', handler);
84
+ testDevice.on('pushNotificationReceived', handler);
85
85
 
86
86
  // Timeout after 5 seconds if no push notification arrives
87
87
  setTimeout(() => {
@@ -90,7 +90,7 @@ async function runTests(context) {
90
90
  }, 5000);
91
91
  });
92
92
 
93
- const createResult = await testDevice.setTriggerX(testTrigger);
93
+ const createResult = await testDevice.trigger.set({ triggerx: testTrigger });
94
94
 
95
95
  if (!createResult) {
96
96
  results.push({
@@ -114,7 +114,7 @@ async function runTests(context) {
114
114
  } else {
115
115
  // Wait a bit and query by alias to find it
116
116
  await new Promise(resolve => setTimeout(resolve, 2000));
117
- const triggers = await testDevice.getTriggerX(0);
117
+ const triggers = await testDevice.trigger.get({ channel: 0 });
118
118
  const foundTrigger = triggers?.triggerx?.find(t => t.alias === 'Test Trigger - CLI Test');
119
119
  if (foundTrigger && foundTrigger.id) {
120
120
  createdTriggerId = foundTrigger.id;
@@ -133,7 +133,7 @@ async function runTests(context) {
133
133
  }
134
134
 
135
135
  // Verify trigger exists by querying it
136
- const triggerInfo = await testDevice.getTriggerX(0);
136
+ const triggerInfo = await testDevice.trigger.get({ channel: 0 });
137
137
  const triggerExists = triggerInfo?.triggerx?.some(t => t.id === createdTriggerId);
138
138
 
139
139
  if (!triggerExists) {
@@ -148,7 +148,8 @@ async function runTests(context) {
148
148
  }
149
149
 
150
150
  // Verify cached trigger state
151
- const cachedTriggers = testDevice.getCachedTriggerX(0);
151
+ const triggerResponse = await testDevice.trigger.get({ channel: 0 });
152
+ const cachedTriggers = triggerResponse?.triggerx || [];
152
153
 
153
154
  results.push({
154
155
  name: 'should create trigger',
@@ -158,7 +159,7 @@ async function runTests(context) {
158
159
  device: deviceName,
159
160
  details: {
160
161
  triggerId: createdTriggerId,
161
- cachedTriggerCount: cachedTriggers ? cachedTriggers.length : 0
162
+ cachedTriggerCount: cachedTriggers.length
162
163
  }
163
164
  });
164
165
  } catch (error) {
@@ -226,50 +227,6 @@ async function runTests(context) {
226
227
  }
227
228
  }
228
229
 
229
- // Test 3: Handle trigger push notifications
230
- try {
231
- // Set up listener for trigger push notifications
232
- let receivedNotification = false;
233
- const notificationHandler = (notification) => {
234
- if (notification.namespace === 'Appliance.Control.TriggerX') {
235
- receivedNotification = true;
236
- }
237
- };
238
-
239
- testDevice.on('pushNotification', notificationHandler);
240
-
241
- // Get trigger info (may trigger a push notification)
242
- await testDevice.getTriggerX(0);
243
-
244
- // Wait a bit for potential push notifications
245
- await new Promise(resolve => setTimeout(resolve, 2000));
246
-
247
- // Remove listener
248
- testDevice.removeListener('pushNotification', notificationHandler);
249
-
250
- // Note: We don't assert on receivedNotification since push notifications
251
- // are device-initiated and may not occur during testing
252
- results.push({
253
- name: 'should handle trigger push notifications',
254
- passed: true,
255
- skipped: false,
256
- error: null,
257
- device: deviceName,
258
- details: {
259
- notificationReceived: receivedNotification,
260
- note: 'Push notifications are device-initiated and may not occur during testing'
261
- }
262
- });
263
- } catch (error) {
264
- results.push({
265
- name: 'should handle trigger push notifications',
266
- passed: false,
267
- skipped: false,
268
- error: error.message,
269
- device: deviceName
270
- });
271
- }
272
-
273
230
  return results;
274
231
  }
275
232
 
@@ -0,0 +1,257 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const ManagerMeross = require('meross-iot');
5
+
6
+ /**
7
+ * Formats error messages for display in the CLI.
8
+ *
9
+ * Provides user-friendly error messages based on error type, with specific
10
+ * guidance for common error scenarios like MFA, authentication, and command failures.
11
+ *
12
+ * @param {Error} error - The error to format
13
+ * @param {boolean} verbose - Whether to show detailed error information
14
+ * @returns {string} Formatted error message
15
+ */
16
+ function formatError(error, verbose = false) {
17
+ if (error instanceof ManagerMeross.MerossErrorMFARequired) {
18
+ return chalk.red('\n✗ MFA (Multi-Factor Authentication) is required.\n') +
19
+ chalk.dim(` Error Code: ${error.code}\n`) +
20
+ chalk.yellow(' Please provide MFA code using --mfa-code option or set MEROSS_MFA_CODE environment variable.\n');
21
+ }
22
+
23
+ if (error instanceof ManagerMeross.MerossErrorWrongMFA) {
24
+ return chalk.red('\n✗ MFA code is incorrect.\n') +
25
+ chalk.dim(` Error Code: ${error.code}\n`) +
26
+ chalk.yellow(' Please check your MFA code and try again.\n');
27
+ }
28
+
29
+ if (error instanceof ManagerMeross.MerossErrorAuthentication) {
30
+ return chalk.red('\n✗ Authentication failed.\n') +
31
+ chalk.dim(` Error Code: ${error.code}\n`) +
32
+ chalk.yellow(' Please check your email and password.\n');
33
+ }
34
+
35
+ if (error instanceof ManagerMeross.MerossErrorTokenExpired) {
36
+ return chalk.yellow('\n⚠ Authentication token has expired.\n') +
37
+ chalk.dim(` Error Code: ${error.code}\n`) +
38
+ chalk.dim(' The library will automatically attempt to login again.\n');
39
+ }
40
+
41
+ if (error instanceof ManagerMeross.MerossErrorBadDomain) {
42
+ return chalk.yellow('\n⚠ Bad domain error.\n') +
43
+ chalk.dim(` Error Code: ${error.code}\n`) +
44
+ chalk.dim(' The API domain may be incorrect. Auto-retry is enabled.\n');
45
+ }
46
+
47
+ if (error instanceof ManagerMeross.MerossErrorCommand) {
48
+ let message = chalk.red('\n✗ Device command failed.\n') +
49
+ chalk.dim(` Error Code: ${error.code}\n`);
50
+ if (error.deviceUuid) {
51
+ message += chalk.dim(` Device: ${error.deviceUuid}\n`);
52
+ }
53
+ if (error.errorPayload) {
54
+ message += chalk.dim(` Device Response: ${JSON.stringify(error.errorPayload, null, 2)}\n`);
55
+ }
56
+ return message;
57
+ }
58
+
59
+ if (error instanceof ManagerMeross.MerossErrorCommandTimeout) {
60
+ let message = chalk.yellow('\n⚠ Command timeout.\n') +
61
+ chalk.dim(` Error Code: ${error.code}\n`);
62
+ if (error.deviceUuid) {
63
+ message += chalk.dim(` Device: ${error.deviceUuid}\n`);
64
+ }
65
+ if (error.timeout) {
66
+ message += chalk.dim(` Timeout: ${error.timeout}ms\n`);
67
+ }
68
+ message += chalk.dim(' The device may be offline or experiencing network issues.\n');
69
+ return message;
70
+ }
71
+
72
+ if (error instanceof ManagerMeross.MerossErrorMqtt) {
73
+ let message = chalk.red('\n✗ MQTT error.\n') +
74
+ chalk.dim(` Error Code: ${error.code}\n`);
75
+ if (error.topic) {
76
+ message += chalk.dim(` Topic: ${error.topic}\n`);
77
+ }
78
+ return message;
79
+ }
80
+
81
+ if (error instanceof ManagerMeross.MerossErrorUnauthorized) {
82
+ return chalk.red('\n✗ Unauthorized access.\n') +
83
+ chalk.dim(` Error Code: ${error.code}\n`) +
84
+ chalk.dim(` HTTP Status: ${error.httpStatusCode || 401}\n`) +
85
+ chalk.yellow(' Authentication token may be invalid or expired.\n');
86
+ }
87
+
88
+ if (error instanceof ManagerMeross.MerossErrorHttpApi) {
89
+ let message = chalk.red('\n✗ HTTP API error.\n') +
90
+ chalk.dim(` Error Code: ${error.code}\n`);
91
+ if (error.httpStatusCode) {
92
+ message += chalk.dim(` HTTP Status: ${error.httpStatusCode}\n`);
93
+ }
94
+ if (error.cause && verbose) {
95
+ message += chalk.dim(` Caused by: ${error.cause.message}\n`);
96
+ }
97
+ return message;
98
+ }
99
+
100
+ if (error instanceof ManagerMeross.MerossErrorApiLimitReached) {
101
+ return chalk.yellow('\n⚠ API rate limit reached.\n') +
102
+ chalk.dim(` Error Code: ${error.code}\n`) +
103
+ chalk.dim(' Please wait before making more requests.\n');
104
+ }
105
+
106
+ if (error instanceof ManagerMeross.MerossErrorResourceAccessDenied) {
107
+ return chalk.red('\n✗ Resource access denied.\n') +
108
+ chalk.dim(` Error Code: ${error.code}\n`) +
109
+ chalk.yellow(' You may not have permission to access this resource.\n');
110
+ }
111
+
112
+ if (error instanceof ManagerMeross.MerossErrorUnconnected) {
113
+ return chalk.yellow('\n⚠ Device is not connected.\n') +
114
+ chalk.dim(` Error Code: ${error.code}\n`) +
115
+ chalk.dim(' Please wait for the device to connect before sending commands.\n');
116
+ }
117
+
118
+ if (error instanceof ManagerMeross.MerossErrorValidation) {
119
+ let message = chalk.red('\n✗ Validation error.\n') +
120
+ chalk.dim(` Error Code: ${error.code}\n`);
121
+ if (error.field) {
122
+ message += chalk.dim(` Field: ${error.field}\n`);
123
+ }
124
+ return message;
125
+ }
126
+
127
+ if (error instanceof ManagerMeross.MerossErrorNotFound) {
128
+ let message = chalk.red('\n✗ Resource not found.\n') +
129
+ chalk.dim(` Error Code: ${error.code}\n`);
130
+ if (error.resourceType) {
131
+ message += chalk.dim(` Type: ${error.resourceType}\n`);
132
+ }
133
+ if (error.resourceId) {
134
+ message += chalk.dim(` ID: ${error.resourceId}\n`);
135
+ }
136
+ return message;
137
+ }
138
+
139
+ if (error instanceof ManagerMeross.MerossErrorTooManyTokens) {
140
+ return chalk.red('\n✗ Too many authentication tokens.\n') +
141
+ chalk.dim(` Error Code: ${error.code}\n`) +
142
+ chalk.yellow(' You have issued too many tokens without logging out. Please log out from other sessions.\n');
143
+ }
144
+
145
+ if (error instanceof ManagerMeross.MerossErrorRateLimit) {
146
+ return chalk.yellow('\n⚠ Request rate limit exceeded.\n') +
147
+ chalk.dim(` Error Code: ${error.code}\n`) +
148
+ chalk.dim(' Please wait before making more requests.\n');
149
+ }
150
+
151
+ if (error instanceof ManagerMeross.MerossErrorOperationLocked) {
152
+ return chalk.yellow('\n⚠ Operation is locked.\n') +
153
+ chalk.dim(` Error Code: ${error.code}\n`) +
154
+ chalk.dim(' The operation may become available after a delay.\n');
155
+ }
156
+
157
+ if (error instanceof ManagerMeross.MerossErrorUnsupported) {
158
+ let message = chalk.red('\n✗ Unsupported operation.\n') +
159
+ chalk.dim(` Error Code: ${error.code}\n`);
160
+ if (error.operation) {
161
+ message += chalk.dim(` Operation: ${error.operation}\n`);
162
+ }
163
+ if (error.reason) {
164
+ message += chalk.dim(` Reason: ${error.reason}\n`);
165
+ }
166
+ return message;
167
+ }
168
+
169
+ if (error instanceof ManagerMeross.MerossErrorInitialization) {
170
+ let message = chalk.red('\n✗ Initialization failed.\n') +
171
+ chalk.dim(` Error Code: ${error.code}\n`);
172
+ if (error.component) {
173
+ message += chalk.dim(` Component: ${error.component}\n`);
174
+ }
175
+ if (error.reason) {
176
+ message += chalk.dim(` Reason: ${error.reason}\n`);
177
+ }
178
+ return message;
179
+ }
180
+
181
+ if (error instanceof ManagerMeross.MerossErrorNetworkTimeout) {
182
+ let message = chalk.yellow('\n⚠ Network request timeout.\n') +
183
+ chalk.dim(` Error Code: ${error.code}\n`);
184
+ if (error.timeout) {
185
+ message += chalk.dim(` Timeout: ${error.timeout}ms\n`);
186
+ }
187
+ if (error.url) {
188
+ message += chalk.dim(` URL: ${error.url}\n`);
189
+ }
190
+ return message;
191
+ }
192
+
193
+ if (error instanceof ManagerMeross.MerossErrorParse) {
194
+ let message = chalk.red('\n✗ Parse error.\n') +
195
+ chalk.dim(` Error Code: ${error.code}\n`);
196
+ if (error.format) {
197
+ message += chalk.dim(` Format: ${error.format}\n`);
198
+ }
199
+ return message;
200
+ }
201
+
202
+ if (error instanceof ManagerMeross.MerossErrorUnknownDeviceType) {
203
+ let message = chalk.red('\n✗ Unknown or unsupported device type.\n') +
204
+ chalk.dim(` Error Code: ${error.code}\n`);
205
+ if (error.deviceType) {
206
+ message += chalk.dim(` Device Type: ${error.deviceType}\n`);
207
+ }
208
+ return message;
209
+ }
210
+
211
+ if (error instanceof ManagerMeross.MerossError) {
212
+ let message = chalk.red(`\n✗ ${error.message}\n`);
213
+ if (error.code) {
214
+ message += chalk.dim(` Error Code: ${error.code}\n`);
215
+ }
216
+ if (error.errorCode !== null && error.errorCode !== undefined) {
217
+ message += chalk.dim(` API Error Code: ${error.errorCode}\n`);
218
+ }
219
+ return message;
220
+ }
221
+
222
+ // Generic error fallback
223
+ return chalk.red(`\n✗ ${error.message}\n`);
224
+ }
225
+
226
+ /**
227
+ * Handles and displays errors with appropriate formatting.
228
+ *
229
+ * Formats the error message and optionally displays the stack trace
230
+ * if verbose mode is enabled.
231
+ *
232
+ * @param {Error} error - The error to handle
233
+ * @param {Object} options - Options for error handling
234
+ * @param {boolean} [options.verbose=false] - Whether to show stack trace
235
+ * @param {boolean} [options.exit=false] - Whether to exit the process after displaying error
236
+ * @param {number} [options.exitCode=1] - Exit code to use if exiting
237
+ */
238
+ function handleError(error, options = {}) {
239
+ const { verbose = false, exit = false, exitCode = 1 } = options;
240
+
241
+ const formattedMessage = formatError(error, verbose);
242
+ console.error(formattedMessage);
243
+
244
+ if (verbose && error.stack) {
245
+ console.error(chalk.dim('\nStack trace:'));
246
+ console.error(chalk.dim(error.stack));
247
+ }
248
+
249
+ if (exit) {
250
+ process.exit(exitCode);
251
+ }
252
+ }
253
+
254
+ module.exports = {
255
+ formatError,
256
+ handleError
257
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meross-cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Command-line interface for controlling Meross smart home devices",
5
5
  "author": "Abe Haverkamp",
6
6
  "homepage": "https://github.com/Doekse/merossiot#readme",
@@ -24,7 +24,7 @@
24
24
  "chalk": "^4.1.2",
25
25
  "commander": "^12.1.0",
26
26
  "inquirer": "^8.2.6",
27
- "meross-iot": "^0.4.0",
27
+ "meross-iot": "^0.6.0",
28
28
  "ora": "^5.4.1"
29
29
  },
30
30
  "devDependencies": {