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.
- package/CHANGELOG.md +26 -0
- package/README.md +46 -5
- package/cli/commands/control/execute.js +50 -7
- package/cli/commands/control/menu.js +3 -5
- package/cli/commands/control/params/index.js +16 -12
- package/cli/commands/control/params/light.js +55 -25
- package/cli/commands/control/params/thermostat.js +24 -22
- package/cli/commands/control/params/timer.js +18 -15
- package/cli/commands/control/params/trigger.js +24 -13
- package/cli/commands/info.js +39 -15
- package/cli/commands/sniffer/sniffer-menu.js +2 -2
- package/cli/commands/status/device-status.js +418 -1292
- package/cli/commands/status/hub-status.js +14 -6
- package/cli/control-registry.js +211 -406
- package/cli/helpers/client.js +16 -10
- package/cli/helpers/meross.js +18 -14
- package/cli/menu/main.js +170 -13
- package/cli/menu/settings.js +2 -2
- package/cli/meross-cli.js +13 -47
- package/cli/tests/README.md +2 -0
- package/cli/tests/test-alarm.js +22 -2
- package/cli/tests/test-child-lock.js +40 -10
- package/cli/tests/test-config.js +22 -2
- package/cli/tests/test-control.js +8 -8
- package/cli/tests/test-diffuser.js +7 -7
- package/cli/tests/test-dnd.js +87 -66
- package/cli/tests/test-electricity.js +37 -33
- package/cli/tests/test-encryption.js +13 -13
- package/cli/tests/test-garage.js +12 -14
- package/cli/tests/test-helper.js +1 -1
- package/cli/tests/test-hub-sensors.js +3 -3
- package/cli/tests/test-light.js +497 -105
- package/cli/tests/test-presence.js +10 -55
- package/cli/tests/test-registry.js +7 -1
- package/cli/tests/test-roller-shutter.js +78 -90
- package/cli/tests/test-screen.js +1 -1
- package/cli/tests/test-sensor-history.js +6 -2
- package/cli/tests/test-smoke-config.js +24 -4
- package/cli/tests/test-spray.js +11 -11
- package/cli/tests/test-system.js +375 -0
- package/cli/tests/test-temp-unit.js +22 -2
- package/cli/tests/test-template.js +61 -73
- package/cli/tests/test-thermostat.js +126 -89
- package/cli/tests/test-timer.js +8 -51
- package/cli/tests/test-toggle.js +49 -173
- package/cli/tests/test-trigger.js +7 -50
- package/cli/utils/error-handler.js +257 -0
- package/package.json +2 -2
package/cli/tests/test-toggle.js
CHANGED
|
@@ -87,22 +87,19 @@ async function runTests(context) {
|
|
|
87
87
|
|
|
88
88
|
// Test 2: Get toggle state
|
|
89
89
|
try {
|
|
90
|
-
if (
|
|
91
|
-
const toggleState = await testDevice.
|
|
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: '
|
|
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
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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: '
|
|
158
|
+
error: 'Device did not turn on after set({ on: true })',
|
|
161
159
|
device: deviceName
|
|
162
160
|
});
|
|
163
161
|
} else {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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: '
|
|
181
|
+
error: 'Device did not turn off after set({ on: false })',
|
|
219
182
|
device: deviceName
|
|
220
183
|
});
|
|
221
184
|
} else {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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('
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
+
"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.
|
|
27
|
+
"meross-iot": "^0.6.0",
|
|
28
28
|
"ora": "^5.4.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|