meross-cli 0.3.0 → 0.4.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 +15 -0
- package/README.md +19 -4
- package/cli/commands/control/execute.js +16 -3
- package/cli/commands/control/menu.js +3 -5
- package/cli/commands/info.js +1 -1
- 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 +12 -46
- package/cli/utils/error-handler.js +257 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-01-19
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **BREAKING**: Updated to use new manager module structure from `meross-iot` v0.5.0
|
|
12
|
+
- Updated to use manager properties (`manager.devices`, `manager.mqtt`, `manager.http`, etc.) instead of direct methods
|
|
13
|
+
- Updated all commands and helpers to use new property-based access patterns
|
|
14
|
+
- **BREAKING**: Updated to use standardized error handling from `meross-iot` v0.5.0
|
|
15
|
+
- Updated to use new `MerossError*` error class names
|
|
16
|
+
- Replaced inline error handling with centralized `handleError()` function
|
|
17
|
+
- All error handling now uses the new error handler utility for consistent, user-friendly formatted messages
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
|
|
21
|
+
- Enhanced error display with better context and user-friendly formatting
|
|
22
|
+
|
|
8
23
|
## [0.3.0] - 2026-01-16
|
|
9
24
|
|
|
10
25
|
### Changed
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Command-line interface for controlling and managing Meross smart home devices.
|
|
|
23
23
|
npm install -g meross-cli@alpha
|
|
24
24
|
|
|
25
25
|
# Or install specific version
|
|
26
|
-
npm install -g meross-cli@0.
|
|
26
|
+
npm install -g meross-cli@0.4.0
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Or use via npx:
|
|
@@ -77,6 +77,24 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
|
|
|
77
77
|
|
|
78
78
|
## Changelog
|
|
79
79
|
|
|
80
|
+
### [0.4.0] - 2026-01-19
|
|
81
|
+
|
|
82
|
+
#### Changed
|
|
83
|
+
- **BREAKING**: Updated to use new manager module structure from `meross-iot` v0.5.0
|
|
84
|
+
- Updated to use manager properties (`manager.devices`, `manager.mqtt`, `manager.http`, etc.) instead of direct methods
|
|
85
|
+
- Updated all commands and helpers to use new property-based access patterns
|
|
86
|
+
- **BREAKING**: Updated to use standardized error handling from `meross-iot` v0.5.0
|
|
87
|
+
- Updated to use new `MerossError*` error class names
|
|
88
|
+
- Replaced inline error handling with centralized `handleError()` function
|
|
89
|
+
- All error handling now uses the new error handler utility for consistent, user-friendly formatted messages
|
|
90
|
+
|
|
91
|
+
#### Added
|
|
92
|
+
- Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
|
|
93
|
+
- Enhanced error display with better context and user-friendly formatting
|
|
94
|
+
|
|
95
|
+
<details>
|
|
96
|
+
<summary>Older</summary>
|
|
97
|
+
|
|
80
98
|
### [0.3.0] - 2026-01-16
|
|
81
99
|
|
|
82
100
|
#### Changed
|
|
@@ -86,9 +104,6 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
|
|
|
86
104
|
- Updated to use camelCase property names consistently
|
|
87
105
|
- Updated all tests and commands to use new API patterns
|
|
88
106
|
|
|
89
|
-
<details>
|
|
90
|
-
<summary>Older</summary>
|
|
91
|
-
|
|
92
107
|
### [0.2.0] - 2026-01-15
|
|
93
108
|
|
|
94
109
|
#### Changed
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const ManagerMeross = require('meross-iot');
|
|
4
|
+
|
|
3
5
|
async function executeControlCommand(manager, uuid, methodName, params) {
|
|
4
6
|
const device = manager.devices.get(uuid);
|
|
5
7
|
|
|
6
8
|
if (!device) {
|
|
7
|
-
throw new
|
|
9
|
+
throw new ManagerMeross.MerossErrorNotFound(
|
|
10
|
+
`Device not found: ${uuid}`,
|
|
11
|
+
'device',
|
|
12
|
+
uuid
|
|
13
|
+
);
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
if (!device.deviceConnected) {
|
|
11
|
-
throw new
|
|
17
|
+
throw new ManagerMeross.MerossErrorUnconnected(
|
|
18
|
+
'Device is not connected. Please wait for device to connect.',
|
|
19
|
+
uuid
|
|
20
|
+
);
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
if (typeof device[methodName] !== 'function') {
|
|
15
|
-
throw new
|
|
24
|
+
throw new ManagerMeross.MerossErrorUnsupported(
|
|
25
|
+
`Control method not available: ${methodName}`,
|
|
26
|
+
methodName,
|
|
27
|
+
'Method not supported by this device'
|
|
28
|
+
);
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
// All methods now use unified options pattern, so we can call directly with params
|
|
@@ -143,7 +143,7 @@ async function controlDeviceMenu(manager, rl, currentUser = null) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// Check error budget if using LAN HTTP transport modes
|
|
146
|
-
const transportMode = manager.
|
|
146
|
+
const transportMode = manager.transport.defaultMode;
|
|
147
147
|
const usesLanHttp = transportMode === TransportMode.LAN_HTTP_FIRST ||
|
|
148
148
|
transportMode === TransportMode.LAN_HTTP_FIRST_ONLY_GET;
|
|
149
149
|
if (usesLanHttp) {
|
|
@@ -170,10 +170,8 @@ async function controlDeviceMenu(manager, rl, currentUser = null) {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
} catch (error) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
console.error(error.stack);
|
|
176
|
-
}
|
|
173
|
+
const { handleError } = require('../../utils/error-handler');
|
|
174
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
177
175
|
}
|
|
178
176
|
|
|
179
177
|
const { continueControl } = await inquirer.prompt([{
|
package/cli/commands/info.js
CHANGED
|
@@ -34,7 +34,7 @@ function _buildBasicDeviceInfo(device, manager) {
|
|
|
34
34
|
|
|
35
35
|
info.push(['Connected', status]);
|
|
36
36
|
info.push(['Online', onlineStatus]);
|
|
37
|
-
info.push(['Transport', getTransportModeName(manager.
|
|
37
|
+
info.push(['Transport', getTransportModeName(manager.transport.defaultMode)]);
|
|
38
38
|
|
|
39
39
|
return info;
|
|
40
40
|
}
|
package/cli/helpers/client.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { MerossHttpClient, TransportMode } = require('meross-iot');
|
|
5
|
+
const { handleError } = require('../utils/error-handler');
|
|
5
6
|
|
|
6
7
|
async function processOptionsAndCreateHttpClient(opts) {
|
|
7
8
|
const email = opts.email || process.env.MEROSS_EMAIL || null;
|
|
@@ -33,16 +34,21 @@ async function processOptionsAndCreateHttpClient(opts) {
|
|
|
33
34
|
maxStatsSamples: 1000
|
|
34
35
|
});
|
|
35
36
|
} else if (email && password) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
try {
|
|
38
|
+
httpClient = await MerossHttpClient.fromUserPassword({
|
|
39
|
+
email,
|
|
40
|
+
password,
|
|
41
|
+
mfaCode,
|
|
42
|
+
logger: verbose ? console.log : null,
|
|
43
|
+
timeout,
|
|
44
|
+
autoRetryOnBadDomain: true,
|
|
45
|
+
enableStats,
|
|
46
|
+
maxStatsSamples: 1000
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// Re-throw with better context for MFA/auth errors
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
46
52
|
} else {
|
|
47
53
|
throw new Error('Email and password are required (or provide token data).\nUse --email and --password options or set MEROSS_EMAIL and MEROSS_PASSWORD environment variables.');
|
|
48
54
|
}
|
package/cli/helpers/meross.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const ManagerMeross = require('meross-iot');
|
|
4
4
|
const { MerossHttpClient, TransportMode } = require('meross-iot');
|
|
5
5
|
const ora = require('ora');
|
|
6
|
+
const { handleError } = require('../utils/error-handler');
|
|
6
7
|
|
|
7
8
|
async function createMerossInstance(optionsOrEmail, password, mfaCode, transportMode, timeout, enableStats, verbose) {
|
|
8
9
|
let httpClient;
|
|
@@ -29,16 +30,21 @@ async function createMerossInstance(optionsOrEmail, password, mfaCode, transport
|
|
|
29
30
|
finalVerbose = verbose || false;
|
|
30
31
|
|
|
31
32
|
// Create HTTP client
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
try {
|
|
34
|
+
httpClient = await MerossHttpClient.fromUserPassword({
|
|
35
|
+
email,
|
|
36
|
+
password,
|
|
37
|
+
mfaCode,
|
|
38
|
+
logger: finalVerbose ? console.log : null,
|
|
39
|
+
timeout: finalTimeout,
|
|
40
|
+
autoRetryOnBadDomain: true,
|
|
41
|
+
enableStats: finalEnableStats,
|
|
42
|
+
maxStatsSamples: 1000
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Re-throw to let caller handle with proper error formatting
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
const instance = new ManagerMeross({
|
|
@@ -79,10 +85,8 @@ async function connectMeross(manager) {
|
|
|
79
85
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
80
86
|
return true;
|
|
81
87
|
} catch (error) {
|
|
82
|
-
spinner.
|
|
83
|
-
|
|
84
|
-
console.error(error.stack);
|
|
85
|
-
}
|
|
88
|
+
spinner.stop();
|
|
89
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
86
90
|
return false;
|
|
87
91
|
}
|
|
88
92
|
}
|
package/cli/menu/main.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
+
const ora = require('ora');
|
|
5
6
|
const { MerossHubDevice, MerossSubDevice, createDebugUtils, TransportMode } = require('meross-iot');
|
|
6
7
|
const testRunner = require('../tests/test-runner');
|
|
7
8
|
const { clearScreen, renderLogoAtTop, renderSimpleHeader, clearMenuArea, CONTENT_START_LINE, SIMPLE_CONTENT_START_LINE, createRL, question, promptForPassword } = require('../utils/terminal');
|
|
8
9
|
const { formatDevice } = require('../utils/display');
|
|
9
10
|
const { listDevices, showStats, dumpRegistry, listMqttConnections, getDeviceStatus, showDeviceInfo, controlDeviceMenu, runTestCommand, snifferMenu } = require('../commands');
|
|
10
11
|
const { addUser, getUser, listUsers } = require('../config/users');
|
|
11
|
-
const { createMerossInstance,
|
|
12
|
+
const { createMerossInstance, disconnectMeross } = require('../helpers/meross');
|
|
12
13
|
const { showSettingsMenu } = require('./settings');
|
|
13
14
|
|
|
14
15
|
// Helper functions
|
|
@@ -169,6 +170,164 @@ async function _saveCredentialsPrompt(rl, currentCredentials) {
|
|
|
169
170
|
return null;
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Prompts user to select devices and subdevices to initialize.
|
|
175
|
+
*
|
|
176
|
+
* Discovers available devices and subdevices, presents them in a hierarchical
|
|
177
|
+
* selection UI with subdevices indented under their hubs, and initializes
|
|
178
|
+
* only the selected items.
|
|
179
|
+
*
|
|
180
|
+
* @param {ManagerMeross} manager - Meross manager instance
|
|
181
|
+
* @returns {Promise<boolean>} True if initialization succeeded, false otherwise
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
async function _selectDevicesToInitialize(manager) {
|
|
185
|
+
const spinner = ora('Discovering available devices...').start();
|
|
186
|
+
try {
|
|
187
|
+
const [baseDevices, subdevices] = await Promise.all([
|
|
188
|
+
manager.devices.discover({ onlineOnly: true }),
|
|
189
|
+
manager.devices.discoverSubdevices({ onlineOnly: true })
|
|
190
|
+
]);
|
|
191
|
+
spinner.stop();
|
|
192
|
+
|
|
193
|
+
if ((!baseDevices || baseDevices.length === 0) && (!subdevices || subdevices.length === 0)) {
|
|
194
|
+
console.log(chalk.yellow('\nNo online devices or subdevices found.'));
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Group subdevices by hub UUID to display them hierarchically
|
|
199
|
+
const subdevicesByHub = new Map();
|
|
200
|
+
if (subdevices && subdevices.length > 0) {
|
|
201
|
+
for (const subdevice of subdevices) {
|
|
202
|
+
const hubUuid = subdevice.hubUuid;
|
|
203
|
+
if (!subdevicesByHub.has(hubUuid)) {
|
|
204
|
+
subdevicesByHub.set(hubUuid, []);
|
|
205
|
+
}
|
|
206
|
+
subdevicesByHub.get(hubUuid).push(subdevice);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const deviceChoices = [];
|
|
211
|
+
const deviceUuids = new Set();
|
|
212
|
+
|
|
213
|
+
if (baseDevices && baseDevices.length > 0) {
|
|
214
|
+
for (const device of baseDevices) {
|
|
215
|
+
const hasSubdevices = subdevicesByHub.has(device.uuid);
|
|
216
|
+
if (hasSubdevices) {
|
|
217
|
+
deviceUuids.add(device.uuid);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
deviceChoices.push({
|
|
221
|
+
name: `${device.devName || 'Unknown'} (${device.deviceType}) - ${chalk.grey(device.uuid)}`,
|
|
222
|
+
value: `device:${device.uuid}`,
|
|
223
|
+
checked: true
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Display subdevices indented under their hub for visual hierarchy
|
|
227
|
+
if (hasSubdevices) {
|
|
228
|
+
const hubSubdevices = subdevicesByHub.get(device.uuid);
|
|
229
|
+
for (const subdevice of hubSubdevices) {
|
|
230
|
+
deviceChoices.push({
|
|
231
|
+
name: ` └─ ${subdevice.subdeviceName || 'Unknown'} (${subdevice.subdeviceType}) - ${chalk.grey(subdevice.subdeviceId)}`,
|
|
232
|
+
value: `subdevice:${subdevice.hubUuid}:${subdevice.subdeviceId}`,
|
|
233
|
+
checked: true
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Handle edge case where subdevices exist but their hub isn't in base device list
|
|
241
|
+
for (const [hubUuid, hubSubdevices] of subdevicesByHub) {
|
|
242
|
+
if (!deviceUuids.has(hubUuid)) {
|
|
243
|
+
for (const subdevice of hubSubdevices) {
|
|
244
|
+
deviceChoices.push({
|
|
245
|
+
name: ` └─ ${subdevice.subdeviceName || 'Unknown'} (${subdevice.subdeviceType}) - ${chalk.grey(subdevice.subdeviceId)} [Hub: ${chalk.grey(subdevice.hubUuid)}]`,
|
|
246
|
+
value: `subdevice:${subdevice.hubUuid}:${subdevice.subdeviceId}`,
|
|
247
|
+
checked: true
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (deviceChoices.length === 0) {
|
|
254
|
+
console.log(chalk.yellow('\nNo devices or subdevices found.'));
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const { selectedItems } = await inquirer.prompt([{
|
|
259
|
+
type: 'checkbox',
|
|
260
|
+
name: 'selectedItems',
|
|
261
|
+
message: 'Select devices/subdevices to initialize (use space to toggle, enter to confirm):',
|
|
262
|
+
choices: deviceChoices,
|
|
263
|
+
pageSize: 20
|
|
264
|
+
}]);
|
|
265
|
+
|
|
266
|
+
if (!selectedItems || selectedItems.length === 0) {
|
|
267
|
+
console.log(chalk.yellow('\nNo devices selected. Skipping initialization.'));
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Parse selection into base devices and subdevices for different initialization paths
|
|
272
|
+
const baseDeviceUuids = [];
|
|
273
|
+
const subdeviceIdentifiers = [];
|
|
274
|
+
|
|
275
|
+
for (const item of selectedItems) {
|
|
276
|
+
if (item.startsWith('device:')) {
|
|
277
|
+
baseDeviceUuids.push(item.replace('device:', ''));
|
|
278
|
+
} else if (item.startsWith('subdevice:')) {
|
|
279
|
+
const parts = item.replace('subdevice:', '').split(':');
|
|
280
|
+
if (parts.length === 2) {
|
|
281
|
+
subdeviceIdentifiers.push({ hubUuid: parts[0], id: parts[1] });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
spinner.start('Initializing selected devices...');
|
|
287
|
+
try {
|
|
288
|
+
let totalInitialized = 0;
|
|
289
|
+
|
|
290
|
+
if (baseDeviceUuids.length > 0) {
|
|
291
|
+
const deviceCount = await manager.devices.initialize({ uuids: baseDeviceUuids });
|
|
292
|
+
totalInitialized += deviceCount;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Initialize subdevices individually since they require hub context
|
|
296
|
+
for (const subdeviceId of subdeviceIdentifiers) {
|
|
297
|
+
try {
|
|
298
|
+
const subdevice = await manager.devices.initializeDevice(subdeviceId);
|
|
299
|
+
if (subdevice) {
|
|
300
|
+
totalInitialized++;
|
|
301
|
+
}
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (manager.options && manager.options.logger) {
|
|
304
|
+
manager.options.logger(`Failed to initialize subdevice ${subdeviceId.id}: ${error.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
spinner.succeed(`Initialized ${totalInitialized} device(s)`);
|
|
310
|
+
|
|
311
|
+
// Allow time for MQTT connections to establish
|
|
312
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
313
|
+
|
|
314
|
+
manager.authenticated = true;
|
|
315
|
+
|
|
316
|
+
return true;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
spinner.stop();
|
|
319
|
+
const { handleError } = require('../utils/error-handler');
|
|
320
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
spinner.stop();
|
|
325
|
+
const { handleError } = require('../utils/error-handler');
|
|
326
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
172
331
|
async function _selectDevice(manager, message = 'Select device:') {
|
|
173
332
|
const devices = manager.devices.list().filter(d => !(d instanceof MerossSubDevice));
|
|
174
333
|
if (devices.length === 0) {
|
|
@@ -384,12 +543,12 @@ function _buildSettingsCallbacks(
|
|
|
384
543
|
enableStatsRef.current,
|
|
385
544
|
verboseRef.current
|
|
386
545
|
);
|
|
387
|
-
const connected = await
|
|
546
|
+
const connected = await _selectDevicesToInitialize(managerRef.current);
|
|
388
547
|
if (connected) {
|
|
389
548
|
currentUserRef.current = userName;
|
|
390
549
|
return { success: true };
|
|
391
550
|
}
|
|
392
|
-
return { success: false, error: 'Failed to
|
|
551
|
+
return { success: false, error: 'Failed to initialize devices with new user' };
|
|
393
552
|
},
|
|
394
553
|
onSaveCredentials: async (name) => {
|
|
395
554
|
if (!currentCredentials) {
|
|
@@ -408,15 +567,15 @@ function _buildSettingsCallbacks(
|
|
|
408
567
|
}
|
|
409
568
|
|
|
410
569
|
async function _handleSettingsCommand(manager, rl, currentUserRef, currentCredentials) {
|
|
411
|
-
const transportModeRef = { current: manager.
|
|
570
|
+
const transportModeRef = { current: manager.transport.defaultMode || TransportMode.MQTT_ONLY };
|
|
412
571
|
const timeoutRef = { current: 10000 };
|
|
413
|
-
const enableStatsRef = { current: manager.
|
|
572
|
+
const enableStatsRef = { current: manager.statistics.isEnabled() };
|
|
414
573
|
const verboseRef = { current: manager.options && manager.options.logger !== null };
|
|
415
574
|
const managerRef = { current: manager };
|
|
416
575
|
|
|
417
576
|
const setTransportMode = (mode) => {
|
|
418
577
|
transportModeRef.current = mode;
|
|
419
|
-
managerRef.current.
|
|
578
|
+
managerRef.current.transport.defaultMode = mode;
|
|
420
579
|
};
|
|
421
580
|
const setTimeout = (newTimeout) => {
|
|
422
581
|
timeoutRef.current = newTimeout;
|
|
@@ -540,10 +699,10 @@ async function menuMode() {
|
|
|
540
699
|
currentCredentials = result.credentials;
|
|
541
700
|
}
|
|
542
701
|
|
|
543
|
-
//
|
|
544
|
-
const connected = await
|
|
702
|
+
// Discover and select devices to initialize
|
|
703
|
+
const connected = await _selectDevicesToInitialize(currentManager);
|
|
545
704
|
if (!connected) {
|
|
546
|
-
console.error('Failed to
|
|
705
|
+
console.error('Failed to initialize devices. Exiting.');
|
|
547
706
|
rl.close();
|
|
548
707
|
return;
|
|
549
708
|
}
|
|
@@ -622,10 +781,8 @@ async function menuMode() {
|
|
|
622
781
|
}
|
|
623
782
|
}
|
|
624
783
|
} catch (error) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
console.error(error.stack);
|
|
628
|
-
}
|
|
784
|
+
const { handleError } = require('../utils/error-handler');
|
|
785
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
629
786
|
await question(rl, '\nPress Enter to return to menu...');
|
|
630
787
|
}
|
|
631
788
|
}
|
package/cli/menu/settings.js
CHANGED
|
@@ -32,7 +32,7 @@ async function showSettingsMenu(rl, currentManager, currentUser, timeout, enable
|
|
|
32
32
|
const debug = currentManager ? createDebugUtils(currentManager) : null;
|
|
33
33
|
const currentStatsEnabled = debug ? debug.isStatsEnabled() : enableStats;
|
|
34
34
|
const currentTransportMode = currentManager
|
|
35
|
-
? getTransportModeName(currentManager.
|
|
35
|
+
? getTransportModeName(currentManager.transport.defaultMode)
|
|
36
36
|
: getTransportModeName(TransportMode.MQTT_ONLY);
|
|
37
37
|
const currentVerboseState = currentManager && currentManager.options ? (currentManager.options.logger !== null) : verbose;
|
|
38
38
|
|
|
@@ -111,7 +111,7 @@ async function showTransportModeSettings(rl, currentManager, currentUser, setTra
|
|
|
111
111
|
type: 'list',
|
|
112
112
|
name: 'mode',
|
|
113
113
|
message: 'Transport Mode',
|
|
114
|
-
default: currentManager.
|
|
114
|
+
default: currentManager.transport.defaultMode,
|
|
115
115
|
choices: [
|
|
116
116
|
{
|
|
117
117
|
name: 'MQTT Only (default, works remotely)',
|
package/cli/meross-cli.js
CHANGED
|
@@ -16,6 +16,7 @@ const packageJson = require('../package.json');
|
|
|
16
16
|
// Import from new modules
|
|
17
17
|
const { processOptionsAndCreateHttpClient } = require('./helpers/client');
|
|
18
18
|
const { printLogo, printVersion } = require('./utils/display');
|
|
19
|
+
const { handleError } = require('./utils/error-handler');
|
|
19
20
|
const { listDevices, dumpRegistry, listMqttConnections, getDeviceStatus, showDeviceInfo, executeControlCommand, collectControlParameters, runTestCommand } = require('./commands');
|
|
20
21
|
const { menuMode } = require('./menu');
|
|
21
22
|
|
|
@@ -112,7 +113,8 @@ Examples:
|
|
|
112
113
|
const deviceCount = await manager.connect();
|
|
113
114
|
spinner.succeed(chalk.green(`Connected to ${deviceCount} device(s)`));
|
|
114
115
|
} catch (error) {
|
|
115
|
-
spinner.
|
|
116
|
+
spinner.stop();
|
|
117
|
+
handleError(error, { verbose: config.verbose });
|
|
116
118
|
throw error;
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -136,11 +138,7 @@ Examples:
|
|
|
136
138
|
console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
|
|
137
139
|
}
|
|
138
140
|
} catch (error) {
|
|
139
|
-
|
|
140
|
-
if (opts.verbose) {
|
|
141
|
-
console.error(error.stack);
|
|
142
|
-
}
|
|
143
|
-
process.exit(1);
|
|
141
|
+
handleError(error, { verbose: opts.verbose, exit: true });
|
|
144
142
|
}
|
|
145
143
|
});
|
|
146
144
|
|
|
@@ -164,11 +162,7 @@ Examples:
|
|
|
164
162
|
console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
|
|
165
163
|
}
|
|
166
164
|
} catch (error) {
|
|
167
|
-
|
|
168
|
-
if (opts.verbose) {
|
|
169
|
-
console.error(error.stack);
|
|
170
|
-
}
|
|
171
|
-
process.exit(1);
|
|
165
|
+
handleError(error, { verbose: opts.verbose, exit: true });
|
|
172
166
|
}
|
|
173
167
|
});
|
|
174
168
|
|
|
@@ -186,11 +180,7 @@ Examples:
|
|
|
186
180
|
console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
|
|
187
181
|
}
|
|
188
182
|
} catch (error) {
|
|
189
|
-
|
|
190
|
-
if (opts.verbose) {
|
|
191
|
-
console.error(error.stack);
|
|
192
|
-
}
|
|
193
|
-
process.exit(1);
|
|
183
|
+
handleError(error, { verbose: opts.verbose, exit: true });
|
|
194
184
|
}
|
|
195
185
|
});
|
|
196
186
|
|
|
@@ -221,11 +211,7 @@ Examples:
|
|
|
221
211
|
console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
|
|
222
212
|
}
|
|
223
213
|
} catch (error) {
|
|
224
|
-
|
|
225
|
-
if (opts.verbose) {
|
|
226
|
-
console.error(error.stack);
|
|
227
|
-
}
|
|
228
|
-
process.exit(1);
|
|
214
|
+
handleError(error, { verbose: opts.verbose, exit: true });
|
|
229
215
|
}
|
|
230
216
|
});
|
|
231
217
|
|
|
@@ -248,11 +234,7 @@ Examples:
|
|
|
248
234
|
console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
|
|
249
235
|
}
|
|
250
236
|
} catch (error) {
|
|
251
|
-
|
|
252
|
-
if (opts.verbose) {
|
|
253
|
-
console.error(error.stack);
|
|
254
|
-
}
|
|
255
|
-
process.exit(1);
|
|
237
|
+
handleError(error, { verbose: opts.verbose, exit: true });
|
|
256
238
|
}
|
|
257
239
|
});
|
|
258
240
|
|
|
@@ -443,11 +425,7 @@ Examples:
|
|
|
443
425
|
console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
|
|
444
426
|
}
|
|
445
427
|
} catch (error) {
|
|
446
|
-
|
|
447
|
-
if (opts.verbose) {
|
|
448
|
-
console.error(error.stack);
|
|
449
|
-
}
|
|
450
|
-
process.exit(1);
|
|
428
|
+
handleError(error, { verbose: opts.verbose, exit: true });
|
|
451
429
|
}
|
|
452
430
|
});
|
|
453
431
|
|
|
@@ -476,11 +454,7 @@ Examples:
|
|
|
476
454
|
console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
|
|
477
455
|
}
|
|
478
456
|
} catch (error) {
|
|
479
|
-
|
|
480
|
-
if (opts.verbose) {
|
|
481
|
-
console.error(error.stack);
|
|
482
|
-
}
|
|
483
|
-
process.exit(1);
|
|
457
|
+
handleError(error, { verbose: opts.verbose, exit: true });
|
|
484
458
|
}
|
|
485
459
|
});
|
|
486
460
|
|
|
@@ -520,11 +494,7 @@ Examples:
|
|
|
520
494
|
await menuMode();
|
|
521
495
|
return;
|
|
522
496
|
} catch (error) {
|
|
523
|
-
|
|
524
|
-
if (error.stack) {
|
|
525
|
-
console.error(error.stack);
|
|
526
|
-
}
|
|
527
|
-
process.exit(1);
|
|
497
|
+
handleError(error, { verbose: true, exit: true });
|
|
528
498
|
}
|
|
529
499
|
}
|
|
530
500
|
|
|
@@ -535,11 +505,7 @@ Examples:
|
|
|
535
505
|
// Run if called directly
|
|
536
506
|
if (require.main === module) {
|
|
537
507
|
main().catch(error => {
|
|
538
|
-
|
|
539
|
-
if (error.stack) {
|
|
540
|
-
console.error(error.stack);
|
|
541
|
-
}
|
|
542
|
-
process.exit(1);
|
|
508
|
+
handleError(error, { verbose: true, exit: true });
|
|
543
509
|
});
|
|
544
510
|
}
|
|
545
511
|
|
|
@@ -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.4.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.5.0",
|
|
28
28
|
"ora": "^5.4.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|