meross-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -0
  4. package/cli/commands/control/execute.js +23 -0
  5. package/cli/commands/control/index.js +12 -0
  6. package/cli/commands/control/menu.js +193 -0
  7. package/cli/commands/control/params/generic.js +229 -0
  8. package/cli/commands/control/params/index.js +56 -0
  9. package/cli/commands/control/params/light.js +188 -0
  10. package/cli/commands/control/params/thermostat.js +166 -0
  11. package/cli/commands/control/params/timer.js +242 -0
  12. package/cli/commands/control/params/trigger.js +206 -0
  13. package/cli/commands/dump.js +35 -0
  14. package/cli/commands/index.js +34 -0
  15. package/cli/commands/info.js +221 -0
  16. package/cli/commands/list.js +112 -0
  17. package/cli/commands/mqtt.js +187 -0
  18. package/cli/commands/sniffer/device-sniffer.js +217 -0
  19. package/cli/commands/sniffer/fake-app.js +233 -0
  20. package/cli/commands/sniffer/index.js +7 -0
  21. package/cli/commands/sniffer/message-queue.js +65 -0
  22. package/cli/commands/sniffer/sniffer-menu.js +676 -0
  23. package/cli/commands/stats.js +90 -0
  24. package/cli/commands/status/device-status.js +1403 -0
  25. package/cli/commands/status/hub-status.js +72 -0
  26. package/cli/commands/status/index.js +50 -0
  27. package/cli/commands/status/subdevices/hub-smoke-detector.js +82 -0
  28. package/cli/commands/status/subdevices/hub-temp-hum-sensor.js +43 -0
  29. package/cli/commands/status/subdevices/hub-thermostat-valve.js +83 -0
  30. package/cli/commands/status/subdevices/hub-water-leak-sensor.js +27 -0
  31. package/cli/commands/status/subdevices/index.js +23 -0
  32. package/cli/commands/test/index.js +185 -0
  33. package/cli/config/users.js +108 -0
  34. package/cli/control-registry.js +875 -0
  35. package/cli/helpers/client.js +89 -0
  36. package/cli/helpers/meross.js +106 -0
  37. package/cli/menu/index.js +10 -0
  38. package/cli/menu/main.js +648 -0
  39. package/cli/menu/settings.js +789 -0
  40. package/cli/meross-cli.js +547 -0
  41. package/cli/tests/README.md +365 -0
  42. package/cli/tests/test-alarm.js +144 -0
  43. package/cli/tests/test-child-lock.js +248 -0
  44. package/cli/tests/test-config.js +133 -0
  45. package/cli/tests/test-control.js +189 -0
  46. package/cli/tests/test-diffuser.js +505 -0
  47. package/cli/tests/test-dnd.js +246 -0
  48. package/cli/tests/test-electricity.js +209 -0
  49. package/cli/tests/test-encryption.js +281 -0
  50. package/cli/tests/test-garage.js +259 -0
  51. package/cli/tests/test-helper.js +313 -0
  52. package/cli/tests/test-hub-mts100.js +355 -0
  53. package/cli/tests/test-hub-sensors.js +489 -0
  54. package/cli/tests/test-light.js +253 -0
  55. package/cli/tests/test-presence.js +497 -0
  56. package/cli/tests/test-registry.js +419 -0
  57. package/cli/tests/test-roller-shutter.js +628 -0
  58. package/cli/tests/test-runner.js +415 -0
  59. package/cli/tests/test-runtime.js +234 -0
  60. package/cli/tests/test-screen.js +133 -0
  61. package/cli/tests/test-sensor-history.js +146 -0
  62. package/cli/tests/test-smoke-config.js +138 -0
  63. package/cli/tests/test-spray.js +131 -0
  64. package/cli/tests/test-temp-unit.js +133 -0
  65. package/cli/tests/test-template.js +238 -0
  66. package/cli/tests/test-thermostat.js +919 -0
  67. package/cli/tests/test-timer.js +372 -0
  68. package/cli/tests/test-toggle.js +342 -0
  69. package/cli/tests/test-trigger.js +279 -0
  70. package/cli/utils/display.js +86 -0
  71. package/cli/utils/terminal.js +137 -0
  72. package/package.json +53 -0
@@ -0,0 +1,547 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const MerossManager = require('meross-iot');
5
+ const { MerossHubDevice } = require('meross-iot');
6
+ const path = require('path');
7
+ const testRunner = require('./tests/test-runner');
8
+ const { Command } = require('commander');
9
+ const chalk = require('chalk');
10
+ const ora = require('ora');
11
+ const { detectControlMethods } = require('./control-registry');
12
+
13
+ // Load package.json for version info
14
+ const packageJson = require('../package.json');
15
+
16
+ // Import from new modules
17
+ const { processOptionsAndCreateHttpClient } = require('./helpers/client');
18
+ const { printLogo, printVersion } = require('./utils/display');
19
+ const { listDevices, dumpRegistry, listMqttConnections, getDeviceStatus, showDeviceInfo, executeControlCommand, collectControlParameters, runTestCommand } = require('./commands');
20
+ const { menuMode } = require('./menu');
21
+
22
+ // Functions moved to modules - imported above
23
+ // menuMode moved to menu/main.js
24
+ // Settings menu functions moved to menu/settings.js
25
+ // Terminal control functions moved to utils/terminal.js
26
+ // question, promptForPassword, createRL moved to utils/terminal.js
27
+
28
+ // All menu functions moved to menu/ directory
29
+
30
+ async function main() {
31
+ const program = new Command();
32
+
33
+ // Set program name and version
34
+ program
35
+ .name('meross-cli')
36
+ .description('Control and manage your Meross smart home devices from the command line')
37
+ .version(packageJson.version, '-V, --version', 'display version number');
38
+
39
+ // Customize help to show logo
40
+ program.configureHelp({
41
+ helpWidth: 120
42
+ });
43
+
44
+ // Add custom help text with environment variables
45
+ program.addHelpText('after', `
46
+ Environment Variables:
47
+ MEROSS_EMAIL Meross account email
48
+ MEROSS_PASSWORD Meross account password
49
+ MEROSS_MFA_CODE Multi-factor authentication code
50
+ MEROSS_TOKEN_DATA Path to token data JSON file
51
+ MEROSS_API_URL Meross API base URL (optional)
52
+
53
+ Examples:
54
+ meross-cli list --email user@example.com --password mypass
55
+ meross-cli info abc123 --email user@example.com --password mypass
56
+ meross-cli status --email user@example.com --password mypass
57
+ meross-cli listen --email user@example.com --password mypass
58
+ meross-cli test light --email user@example.com --password mypass
59
+ meross-cli Start menu mode
60
+ `);
61
+
62
+ // Global options
63
+ program
64
+ .option('-e, --email <email>', 'Meross account email (or set MEROSS_EMAIL env var)')
65
+ .option('-p, --password <password>', 'Meross account password (or set MEROSS_PASSWORD env var)')
66
+ .option('-m, --mfa-code <code>', 'Multi-factor authentication code (or set MEROSS_MFA_CODE env var)')
67
+ .option('-t, --token-data <path>', 'Path to token data JSON file (or set MEROSS_TOKEN_DATA env var)')
68
+ .option('-T, --transport-mode <mode>', 'Transport mode: mqtt_only, lan_http_first, lan_http_first_only_get', 'mqtt_only')
69
+ .option('--timeout <ms>', 'Request timeout in milliseconds', '10000')
70
+ .option('--enable-stats', 'Enable statistics tracking')
71
+ .option('-v, --verbose', 'Enable verbose logging');
72
+
73
+ // Helper function to connect and setup meross instance
74
+ async function connectMerossInstance(opts) {
75
+ const processed = await processOptionsAndCreateHttpClient({
76
+ email: opts.email,
77
+ password: opts.password,
78
+ mfaCode: opts.mfaCode,
79
+ tokenData: opts.tokenData,
80
+ transportMode: opts.transportMode,
81
+ timeout: opts.timeout,
82
+ enableStats: opts.enableStats,
83
+ verbose: opts.verbose
84
+ });
85
+
86
+ const { options, config } = processed;
87
+
88
+ const manager = new MerossManager(options);
89
+
90
+ // Handle device events
91
+ manager.on('deviceInitialized', (deviceId) => {
92
+ if (config.verbose) {
93
+ console.log(`Device initialized: ${deviceId}`);
94
+ }
95
+ });
96
+
97
+ manager.on('connected', (deviceId) => {
98
+ if (config.verbose) {
99
+ console.log(`Device connected: ${deviceId}`);
100
+ }
101
+ });
102
+
103
+ manager.on('error', (error, deviceId) => {
104
+ if (config.verbose) {
105
+ console.error(chalk.red(`Error${deviceId ? ` (${deviceId})` : ''}: ${error.message}`));
106
+ }
107
+ });
108
+
109
+ // Connect
110
+ const spinner = ora('Connecting to Meross cloud...').start();
111
+ try {
112
+ const deviceCount = await manager.connect();
113
+ spinner.succeed(chalk.green(`Connected to ${deviceCount} device(s)`));
114
+ } catch (error) {
115
+ spinner.fail(chalk.red(`Connection failed: ${error.message}`));
116
+ throw error;
117
+ }
118
+
119
+ // Wait a bit for devices to connect
120
+ await new Promise(resolve => setTimeout(resolve, 2000));
121
+
122
+ return { manager, config };
123
+ }
124
+
125
+ // List command
126
+ program
127
+ .command('list')
128
+ .description('List all devices')
129
+ .action(async () => {
130
+ const opts = program.opts();
131
+ try {
132
+ const { manager, config } = await connectMerossInstance(opts);
133
+ await listDevices(manager);
134
+ const logoutResponse = await manager.logout();
135
+ if (config.verbose && logoutResponse) {
136
+ console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
137
+ }
138
+ } catch (error) {
139
+ console.error(chalk.red(`Error: ${error.message}`));
140
+ if (opts.verbose) {
141
+ console.error(error.stack);
142
+ }
143
+ process.exit(1);
144
+ }
145
+ });
146
+
147
+ // Info command
148
+ program
149
+ .command('info <uuid>')
150
+ .description('Show detailed information about a device')
151
+ .action(async (uuid) => {
152
+ const opts = program.opts();
153
+ try {
154
+ // Validate UUID
155
+ if (!uuid || uuid.trim() === '') {
156
+ console.error(chalk.red('Error: UUID required for info command'));
157
+ console.error('Usage: meross-cli info <uuid> [options]');
158
+ process.exit(1);
159
+ }
160
+ const { manager, config } = await connectMerossInstance(opts);
161
+ await showDeviceInfo(manager, uuid.trim());
162
+ const logoutResponse = await manager.logout();
163
+ if (config.verbose && logoutResponse) {
164
+ console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
165
+ }
166
+ } catch (error) {
167
+ console.error(chalk.red(`Error: ${error.message}`));
168
+ if (opts.verbose) {
169
+ console.error(error.stack);
170
+ }
171
+ process.exit(1);
172
+ }
173
+ });
174
+
175
+ // Status command
176
+ program
177
+ .command('status [uuid] [subdevice-id]')
178
+ .description('Get device status with sensors and configuration (optional: filter by UUID and/or subdevice ID)')
179
+ .action(async (uuid, subdeviceId) => {
180
+ const opts = program.opts();
181
+ try {
182
+ const { manager, config } = await connectMerossInstance(opts);
183
+ await getDeviceStatus(manager, uuid || null, subdeviceId || null);
184
+ const logoutResponse = await manager.logout();
185
+ if (config.verbose && logoutResponse) {
186
+ console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
187
+ }
188
+ } catch (error) {
189
+ console.error(chalk.red(`Error: ${error.message}`));
190
+ if (opts.verbose) {
191
+ console.error(error.stack);
192
+ }
193
+ process.exit(1);
194
+ }
195
+ });
196
+
197
+ // Stats command
198
+ program
199
+ .command('stats [action]')
200
+ .description('Enable/disable statistics or show statistics (default: get)')
201
+ .action(async () => {
202
+ console.log(chalk.yellow('Note: Statistics require an active connection. Connect first or use menu mode.'));
203
+ process.exit(0);
204
+ });
205
+
206
+ // MQTT connections command
207
+ program
208
+ .command('mqtt-connections')
209
+ .description('View all active MQTT connections')
210
+ .option('--json', 'Output in JSON format')
211
+ .action(async () => {
212
+ const opts = program.opts();
213
+ try {
214
+ const { manager, config } = await connectMerossInstance(opts);
215
+ await listMqttConnections(manager, {
216
+ verbose: opts.verbose || false,
217
+ json: opts.json || false
218
+ });
219
+ const logoutResponse = await manager.logout();
220
+ if (config.verbose && logoutResponse) {
221
+ console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
222
+ }
223
+ } catch (error) {
224
+ console.error(chalk.red(`Error: ${error.message}`));
225
+ if (opts.verbose) {
226
+ console.error(error.stack);
227
+ }
228
+ process.exit(1);
229
+ }
230
+ });
231
+
232
+ // Dump command
233
+ program
234
+ .command('dump [file]')
235
+ .description('Dump device registry to JSON file (default: device-registry.json)')
236
+ .action(async (file) => {
237
+ const opts = program.opts();
238
+ try {
239
+ const filename = file || 'device-registry.json';
240
+ // Validate filename to prevent path injection
241
+ if (filename.includes('..') || path.isAbsolute(filename) && !filename.startsWith(process.cwd())) {
242
+ throw new Error('Invalid file path');
243
+ }
244
+ const { manager, config } = await connectMerossInstance(opts);
245
+ await dumpRegistry(manager, filename);
246
+ const logoutResponse = await manager.logout();
247
+ if (config.verbose && logoutResponse) {
248
+ console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
249
+ }
250
+ } catch (error) {
251
+ console.error(chalk.red(`Error: ${error.message}`));
252
+ if (opts.verbose) {
253
+ console.error(error.stack);
254
+ }
255
+ process.exit(1);
256
+ }
257
+ });
258
+
259
+ // Control command helper functions
260
+ function _validateControlUuid(uuid) {
261
+ if (!uuid || uuid.trim() === '') {
262
+ console.error(chalk.red('Error: UUID required for control command'));
263
+ console.error('Usage: meross-cli control <uuid> [method] [options]');
264
+ process.exit(1);
265
+ }
266
+ }
267
+
268
+ async function _waitForDeviceConnection(device) {
269
+ if (!device.deviceConnected) {
270
+ console.log(chalk.yellow('Waiting for device to connect...'));
271
+ for (let i = 0; i < 30; i++) {
272
+ await new Promise(resolve => setTimeout(resolve, 500));
273
+ if (device.deviceConnected) {break;}
274
+ }
275
+ if (!device.deviceConnected) {
276
+ console.error(chalk.red('Error: Device did not connect in time'));
277
+ process.exit(1);
278
+ }
279
+ }
280
+ }
281
+
282
+ function _resolveDevice(manager, uuid, subdeviceId) {
283
+ let device = manager.getDevice(uuid.trim());
284
+ if (!device) {
285
+ console.error(chalk.red(`Error: Device not found: ${uuid}`));
286
+ process.exit(1);
287
+ }
288
+
289
+ if (subdeviceId && device instanceof MerossHubDevice) {
290
+ const subdevices = device.getSubdevices();
291
+ const subdevice = subdevices.find(s => s.subdeviceId === subdeviceId);
292
+ if (!subdevice) {
293
+ console.error(chalk.red(`Error: Subdevice not found: ${subdeviceId}`));
294
+ process.exit(1);
295
+ }
296
+ device = subdevice;
297
+ }
298
+
299
+ return device;
300
+ }
301
+
302
+ function _listAvailableMethods(availableMethods, uuid) {
303
+ console.log(chalk.bold(`\nAvailable control methods for device ${uuid}:\n`));
304
+ const methodsByCategory = {};
305
+ for (const m of availableMethods) {
306
+ const category = m.category || 'Other';
307
+ if (!methodsByCategory[category]) {
308
+ methodsByCategory[category] = [];
309
+ }
310
+ methodsByCategory[category].push(m);
311
+ }
312
+
313
+ for (const [category, methods] of Object.entries(methodsByCategory)) {
314
+ console.log(chalk.bold(`${category }:`));
315
+ for (const m of methods) {
316
+ console.log(` ${chalk.cyan(m.methodName)} - ${m.name} - ${m.description}`);
317
+ }
318
+ console.log();
319
+ }
320
+ console.log('Usage: meross-cli control <uuid> <method> [options]');
321
+ console.log('Example: meross-cli control <uuid> setToggleX --channel 0 --on');
322
+ }
323
+
324
+ function _findMethodInfo(availableMethods, methodName) {
325
+ const methodInfo = availableMethods.find(m => m.methodName === methodName);
326
+ if (!methodInfo) {
327
+ console.error(chalk.red(`Error: Control method not found: ${methodName}`));
328
+ console.error('\nAvailable methods:');
329
+ availableMethods.forEach(m => {
330
+ console.error(` - ${m.methodName}`);
331
+ });
332
+ process.exit(1);
333
+ }
334
+ return methodInfo;
335
+ }
336
+
337
+ function _buildControlParams(options) {
338
+ const params = {};
339
+ if (options.channel !== undefined) {
340
+ params.channel = parseInt(options.channel, 10);
341
+ }
342
+ if (options.subdeviceId) {
343
+ params.subId = options.subdeviceId;
344
+ }
345
+ if (options.on !== undefined) {
346
+ params.onoff = true;
347
+ } else if (options.off !== undefined) {
348
+ params.onoff = false;
349
+ }
350
+ if (options.rgb) {
351
+ const parts = options.rgb.split(',').map(p => parseInt(p.trim(), 10));
352
+ if (parts.length === 3) {
353
+ params.rgb = parts;
354
+ }
355
+ }
356
+ if (options.brightness !== undefined) {
357
+ params.luminance = parseInt(options.brightness, 10);
358
+ }
359
+ if (options.temperature !== undefined) {
360
+ params.temperature = parseFloat(options.temperature);
361
+ }
362
+ if (options.position !== undefined) {
363
+ params.position = parseInt(options.position, 10);
364
+ }
365
+ if (options.mode !== undefined) {
366
+ params.mode = parseInt(options.mode, 10);
367
+ }
368
+ return params;
369
+ }
370
+
371
+ function _checkRequiredParams(params, methodInfo) {
372
+ const requiredParams = methodInfo.params ? methodInfo.params.filter(p => p.required) : [];
373
+ return requiredParams.every(p => {
374
+ if (p.name === 'channel' && params.channel !== undefined) {return true;}
375
+ if (p.name === 'onoff' && params.onoff !== undefined) {return true;}
376
+ if (p.name === 'subId' && params.subId !== undefined) {return true;}
377
+ if (p.name === 'mode' && params.mode !== undefined) {return true;}
378
+ if (p.name === 'position' && params.position !== undefined) {return true;}
379
+ if (p.name === 'rgb' && params.rgb !== undefined) {return true;}
380
+ if (p.name === 'luminance' && params.luminance !== undefined) {return true;}
381
+ if (p.name === 'temperature' && params.temperature !== undefined) {return true;}
382
+ return params[p.name] !== undefined;
383
+ });
384
+ }
385
+
386
+ async function _executeControlMethod(manager, uuid, subdeviceId, methodName, methodInfo, params, device) {
387
+ const hasAllRequired = _checkRequiredParams(params, methodInfo);
388
+ if (!hasAllRequired) {
389
+ const collectedParams = await collectControlParameters(methodName, methodInfo, device);
390
+ Object.assign(params, collectedParams);
391
+ }
392
+
393
+ console.log(chalk.cyan(`\nExecuting ${methodInfo.name}...`));
394
+ const result = await executeControlCommand(manager, uuid.trim(), subdeviceId, methodName, params);
395
+ console.log(chalk.green('\n✓ Command executed successfully!'));
396
+ if (result) {
397
+ console.log(chalk.dim('\nResponse:'));
398
+ console.log(JSON.stringify(result, null, 2));
399
+ }
400
+ }
401
+
402
+ // Control command
403
+ program
404
+ .command('control <uuid> [method]')
405
+ .description('Control a device (list available methods if method not specified)')
406
+ .option('--channel <n>', 'Channel number (default: 0)')
407
+ .option('--subdevice-id <id>', 'Subdevice ID (for hub devices)')
408
+ .option('--on', 'Turn on (for toggle methods)')
409
+ .option('--off', 'Turn off (for toggle methods)')
410
+ .option('--rgb <r,g,b>', 'RGB color (e.g., 255,0,0)')
411
+ .option('--brightness <n>', 'Brightness (0-100)')
412
+ .option('--temperature <n>', 'Temperature value')
413
+ .option('--position <n>', 'Position value (0-100, -1 to stop)')
414
+ .option('--mode <n>', 'Mode value')
415
+ .action(async (uuid, method, options) => {
416
+ const opts = program.opts();
417
+ try {
418
+ _validateControlUuid(uuid);
419
+ const { manager, config } = await connectMerossInstance(opts);
420
+
421
+ // Wait for devices to connect
422
+ await new Promise(resolve => setTimeout(resolve, 2000));
423
+
424
+ const subdeviceId = options.subdeviceId || null;
425
+ const device = _resolveDevice(manager, uuid, subdeviceId);
426
+ await _waitForDeviceConnection(device);
427
+
428
+ // Abilities are already loaded at device creation (single-phase initialization)
429
+ // Detect available methods (filtered by device capabilities)
430
+ const availableMethods = detectControlMethods(device);
431
+
432
+ if (!method || method.trim() === '') {
433
+ _listAvailableMethods(availableMethods, uuid);
434
+ } else {
435
+ const methodName = method.trim();
436
+ const methodInfo = _findMethodInfo(availableMethods, methodName);
437
+ const params = _buildControlParams(options);
438
+ await _executeControlMethod(manager, uuid, subdeviceId, methodName, methodInfo, params, device);
439
+ }
440
+
441
+ const logoutResponse = await manager.logout();
442
+ if (config.verbose && logoutResponse) {
443
+ console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
444
+ }
445
+ } catch (error) {
446
+ console.error(chalk.red(`Error: ${error.message}`));
447
+ if (opts.verbose) {
448
+ console.error(error.stack);
449
+ }
450
+ process.exit(1);
451
+ }
452
+ });
453
+
454
+ // Test command
455
+ program
456
+ .command('test <device-type>')
457
+ .description('Run tests for a specific device type (e.g., light, thermostat)')
458
+ .action(async (deviceType) => {
459
+ const opts = program.opts();
460
+ try {
461
+ if (!deviceType || deviceType.trim() === '') {
462
+ console.error(chalk.red('Error: Device type required for test command'));
463
+ console.error('Usage: meross-cli test <device-type> [options]');
464
+ console.error('\nAvailable test types:');
465
+ const availableTypes = testRunner.getAvailableTestTypes();
466
+ availableTypes.forEach(type => {
467
+ const description = testRunner.getTestDescription(type);
468
+ console.error(` - ${type} (${description})`);
469
+ });
470
+ process.exit(1);
471
+ }
472
+ const { manager, config } = await connectMerossInstance(opts);
473
+ await runTestCommand(manager, deviceType.trim());
474
+ const logoutResponse = await manager.logout();
475
+ if (config.verbose && logoutResponse) {
476
+ console.log('\nLogout response:', JSON.stringify(logoutResponse, null, 2));
477
+ }
478
+ } catch (error) {
479
+ console.error(chalk.red(`Error: ${error.message}`));
480
+ if (opts.verbose) {
481
+ console.error(error.stack);
482
+ }
483
+ process.exit(1);
484
+ }
485
+ });
486
+
487
+ // Help command
488
+ program
489
+ .command('help')
490
+ .description('Display help for command')
491
+ .action(() => {
492
+ printLogo(true, 'help');
493
+ program.outputHelp();
494
+ });
495
+
496
+ // Hook into help display to show logo
497
+ const originalHelp = program.helpInformation;
498
+ program.helpInformation = function () {
499
+ printLogo(true, 'help');
500
+ return originalHelp.call(this);
501
+ };
502
+
503
+ // Handle version command before parsing
504
+ if (process.argv.includes('--version') || process.argv.includes('-V')) {
505
+ printLogo(true, 'version');
506
+ printVersion();
507
+ process.exit(0);
508
+ }
509
+
510
+ // Check if no command provided - start menu mode before parsing
511
+ const args = process.argv.slice(2);
512
+ // Check if help/version flags are present
513
+ const isHelpOrVersion = args.includes('--help') || args.includes('-h') || args.includes('--version') || args.includes('-V');
514
+ // Check if there's an actual command (not just options)
515
+ const hasCommand = args.length > 0 && !args[0].startsWith('-') && !isHelpOrVersion;
516
+
517
+ // If no command and not help/version, start menu mode
518
+ if (!hasCommand && !isHelpOrVersion) {
519
+ try {
520
+ await menuMode();
521
+ return;
522
+ } catch (error) {
523
+ console.error(chalk.red(`Error: ${error.message}`));
524
+ if (error.stack) {
525
+ console.error(error.stack);
526
+ }
527
+ process.exit(1);
528
+ }
529
+ }
530
+
531
+ // Parse arguments
532
+ program.parse(process.argv);
533
+ }
534
+
535
+ // Run if called directly
536
+ if (require.main === module) {
537
+ main().catch(error => {
538
+ console.error(chalk.red(`Fatal error: ${error.message}`));
539
+ if (error.stack) {
540
+ console.error(error.stack);
541
+ }
542
+ process.exit(1);
543
+ });
544
+ }
545
+
546
+ module.exports = { main };
547
+