@lanonasis/cli 3.1.13 → 3.3.15

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.
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
+ import * as path from 'path';
3
4
  import { CLIConfig } from '../utils/config.js';
4
5
  import { apiClient } from '../utils/api.js';
5
6
  export function configCommands(program) {
@@ -178,6 +179,515 @@ export function configCommands(program) {
178
179
  process.exit(1);
179
180
  }
180
181
  });
182
+ // Service discovery and endpoint management
183
+ program
184
+ .command('discover')
185
+ .description('Discover service endpoints')
186
+ .option('-v, --verbose', 'show detailed discovery information')
187
+ .action(async (options) => {
188
+ const config = new CLIConfig();
189
+ await config.init();
190
+ console.log(chalk.blue('šŸ” Discovering service endpoints...'));
191
+ console.log();
192
+ try {
193
+ await config.discoverServices(options.verbose);
194
+ const services = config.get('discoveredServices');
195
+ if (services) {
196
+ console.log(chalk.green('āœ“ Service discovery completed'));
197
+ console.log();
198
+ console.log(chalk.yellow('Discovered endpoints:'));
199
+ console.log(` Auth: ${services.auth_base}`);
200
+ console.log(` Memory: ${services.memory_base}`);
201
+ console.log(` MCP HTTP: ${services.mcp_base}`);
202
+ console.log(` MCP WebSocket: ${services.mcp_ws_base}`);
203
+ console.log(` MCP SSE: ${services.mcp_sse_base}`);
204
+ console.log(` Project: ${services.project_scope}`);
205
+ }
206
+ }
207
+ catch (error) {
208
+ console.log(chalk.red('āœ– Service discovery failed'));
209
+ console.log(chalk.gray('Using fallback endpoints'));
210
+ }
211
+ });
212
+ program
213
+ .command('endpoints')
214
+ .description('Show current service endpoints')
215
+ .action(async () => {
216
+ const config = new CLIConfig();
217
+ await config.init();
218
+ const services = config.get('discoveredServices');
219
+ const hasManualOverrides = config.hasManualEndpointOverrides();
220
+ const lastDiscovery = config.get('lastServiceDiscovery');
221
+ const lastManualUpdate = config.get('lastManualEndpointUpdate');
222
+ console.log(chalk.blue.bold('🌐 Service Endpoints'));
223
+ console.log();
224
+ if (services) {
225
+ console.log(chalk.yellow('Current endpoints:'));
226
+ console.log(` Auth Base: ${chalk.white(services.auth_base)}`);
227
+ console.log(` Memory Base: ${chalk.white(services.memory_base)}`);
228
+ console.log(` MCP HTTP: ${chalk.white(services.mcp_base)}`);
229
+ console.log(` MCP WebSocket: ${chalk.white(services.mcp_ws_base)}`);
230
+ console.log(` MCP SSE: ${chalk.white(services.mcp_sse_base)}`);
231
+ console.log(` Project Scope: ${chalk.white(services.project_scope)}`);
232
+ console.log();
233
+ if (hasManualOverrides) {
234
+ console.log(chalk.cyan('šŸ“ Manual overrides are active'));
235
+ if (lastManualUpdate) {
236
+ const updateDate = new Date(lastManualUpdate);
237
+ console.log(` Last updated: ${updateDate.toLocaleString()}`);
238
+ }
239
+ }
240
+ else if (lastDiscovery) {
241
+ const discoveryDate = new Date(lastDiscovery);
242
+ console.log(chalk.green('šŸ” Endpoints from service discovery'));
243
+ console.log(` Last discovered: ${discoveryDate.toLocaleString()}`);
244
+ }
245
+ else {
246
+ console.log(chalk.gray('šŸ“‹ Using fallback endpoints'));
247
+ }
248
+ }
249
+ else {
250
+ console.log(chalk.yellow('āš ļø No endpoints configured'));
251
+ console.log(chalk.gray('Run: lanonasis config discover'));
252
+ }
253
+ });
254
+ program
255
+ .command('set-endpoint <type> <url>')
256
+ .description('Set manual endpoint override (auth|memory|mcp-http|mcp-ws|mcp-sse)')
257
+ .action(async (type, url) => {
258
+ const config = new CLIConfig();
259
+ await config.init();
260
+ // Validate URL format
261
+ try {
262
+ new URL(url);
263
+ }
264
+ catch {
265
+ console.log(chalk.red('āœ– Invalid URL format'));
266
+ process.exit(1);
267
+ }
268
+ // Map type to config key
269
+ const endpointMap = {
270
+ 'auth': 'auth_base',
271
+ 'memory': 'memory_base',
272
+ 'mcp-http': 'mcp_base',
273
+ 'mcp-ws': 'mcp_ws_base',
274
+ 'mcp-sse': 'mcp_sse_base'
275
+ };
276
+ const configKey = endpointMap[type];
277
+ if (!configKey) {
278
+ console.log(chalk.red('āœ– Invalid endpoint type'));
279
+ console.log(chalk.gray('Valid types: auth, memory, mcp-http, mcp-ws, mcp-sse'));
280
+ process.exit(1);
281
+ }
282
+ // Set the manual override
283
+ const overrides = { [configKey]: url };
284
+ await config.setManualEndpoints(overrides);
285
+ console.log(chalk.green(`āœ“ ${type} endpoint set to:`), url);
286
+ console.log(chalk.cyan('šŸ’” Use "lanonasis config clear-overrides" to remove manual overrides'));
287
+ });
288
+ program
289
+ .command('clear-overrides')
290
+ .description('Clear manual endpoint overrides and rediscover services')
291
+ .action(async () => {
292
+ const config = new CLIConfig();
293
+ await config.init();
294
+ if (!config.hasManualEndpointOverrides()) {
295
+ console.log(chalk.yellow('āš ļø No manual overrides to clear'));
296
+ return;
297
+ }
298
+ await config.clearManualEndpointOverrides();
299
+ console.log(chalk.green('āœ“ Manual endpoint overrides cleared'));
300
+ console.log(chalk.cyan('āœ“ Service endpoints rediscovered'));
301
+ });
302
+ // Validate configuration
303
+ program
304
+ .command('validate')
305
+ .description('Validate configuration and check for issues')
306
+ .option('-v, --verbose', 'show detailed validation information')
307
+ .option('--repair', 'automatically repair common issues')
308
+ .action(async (options) => {
309
+ const config = new CLIConfig();
310
+ await config.init();
311
+ console.log(chalk.blue.bold('šŸ” Configuration Validation'));
312
+ console.log(chalk.cyan('━'.repeat(50)));
313
+ console.log();
314
+ const validation = {
315
+ configExists: false,
316
+ configReadable: false,
317
+ configVersion: null,
318
+ configFormat: false,
319
+ authenticationValid: false,
320
+ endpointsValid: false,
321
+ mcpConfigValid: false,
322
+ backupExists: false,
323
+ issues: [],
324
+ repairs: []
325
+ };
326
+ // Step 1: Check config file existence and readability
327
+ console.log(chalk.cyan('1. Configuration File'));
328
+ try {
329
+ validation.configExists = await config.exists();
330
+ if (validation.configExists) {
331
+ console.log(chalk.green(' āœ“ Config file exists at'), config.getConfigPath());
332
+ // Try to read the config
333
+ await config.load();
334
+ validation.configReadable = true;
335
+ console.log(chalk.green(' āœ“ Config file is readable'));
336
+ // Check config version
337
+ validation.configVersion = config.get('version');
338
+ if (validation.configVersion) {
339
+ console.log(chalk.green(' āœ“ Config version:'), validation.configVersion);
340
+ }
341
+ else {
342
+ console.log(chalk.yellow(' ⚠ Config version missing (legacy config)'));
343
+ validation.issues.push('Config version missing');
344
+ if (options.repair) {
345
+ await config.save(); // This will add the version
346
+ validation.repairs.push('Added config version');
347
+ console.log(chalk.cyan(' → Repaired: Added config version'));
348
+ }
349
+ }
350
+ validation.configFormat = true;
351
+ }
352
+ else {
353
+ console.log(chalk.red(' āœ– Config file not found'));
354
+ validation.issues.push('Config file does not exist');
355
+ if (options.repair) {
356
+ await config.save(); // Create empty config
357
+ validation.repairs.push('Created config file');
358
+ console.log(chalk.cyan(' → Repaired: Created config file'));
359
+ }
360
+ }
361
+ }
362
+ catch (error) {
363
+ console.log(chalk.red(' āœ– Config file is corrupted or unreadable'));
364
+ console.log(chalk.gray(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
365
+ validation.issues.push('Config file is corrupted');
366
+ if (options.repair) {
367
+ try {
368
+ // Try to backup the corrupted config
369
+ const backupPath = await config.backupConfig();
370
+ console.log(chalk.cyan(` → Backed up corrupted config to: ${backupPath}`));
371
+ // Create new config
372
+ await config.clear();
373
+ await config.save();
374
+ validation.repairs.push('Recreated corrupted config file');
375
+ console.log(chalk.cyan(' → Repaired: Recreated config file'));
376
+ }
377
+ catch (repairError) {
378
+ console.log(chalk.red(' āœ– Could not repair corrupted config'));
379
+ }
380
+ }
381
+ }
382
+ // Step 2: Validate authentication configuration
383
+ console.log(chalk.cyan('\n2. Authentication Configuration'));
384
+ const token = config.getToken();
385
+ const vendorKey = config.getVendorKey();
386
+ const authMethod = config.get('authMethod');
387
+ if (!token && !vendorKey) {
388
+ console.log(chalk.yellow(' ⚠ No authentication credentials configured'));
389
+ validation.issues.push('No authentication credentials');
390
+ }
391
+ else {
392
+ console.log(chalk.green(' āœ“ Authentication credentials found'));
393
+ // Validate auth method consistency
394
+ if (vendorKey && authMethod !== 'vendor_key') {
395
+ console.log(chalk.yellow(' ⚠ Auth method mismatch (has vendor key but method is not vendor_key)'));
396
+ validation.issues.push('Auth method mismatch');
397
+ if (options.repair) {
398
+ config.set('authMethod', 'vendor_key');
399
+ await config.save();
400
+ validation.repairs.push('Fixed auth method for vendor key');
401
+ console.log(chalk.cyan(' → Repaired: Set auth method to vendor_key'));
402
+ }
403
+ }
404
+ else if (token && !vendorKey && authMethod !== 'jwt' && authMethod !== 'oauth') {
405
+ console.log(chalk.yellow(' ⚠ Auth method mismatch (has token but method is not jwt/oauth)'));
406
+ validation.issues.push('Auth method mismatch');
407
+ if (options.repair) {
408
+ config.set('authMethod', 'jwt');
409
+ await config.save();
410
+ validation.repairs.push('Fixed auth method for token');
411
+ console.log(chalk.cyan(' → Repaired: Set auth method to jwt'));
412
+ }
413
+ }
414
+ // Validate vendor key format if present
415
+ if (vendorKey) {
416
+ const formatValidation = config.validateVendorKeyFormat(vendorKey);
417
+ if (formatValidation === true) {
418
+ console.log(chalk.green(' āœ“ Vendor key format is valid'));
419
+ }
420
+ else {
421
+ console.log(chalk.red(' āœ– Vendor key format is invalid'));
422
+ validation.issues.push('Invalid vendor key format');
423
+ }
424
+ }
425
+ // Test authentication validity
426
+ try {
427
+ const isValid = await config.validateStoredCredentials();
428
+ validation.authenticationValid = isValid;
429
+ if (isValid) {
430
+ console.log(chalk.green(' āœ“ Authentication credentials are valid'));
431
+ }
432
+ else {
433
+ console.log(chalk.red(' āœ– Authentication credentials are invalid'));
434
+ validation.issues.push('Invalid authentication credentials');
435
+ }
436
+ }
437
+ catch (error) {
438
+ console.log(chalk.yellow(' ⚠ Could not validate authentication credentials'));
439
+ if (options.verbose) {
440
+ console.log(chalk.gray(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
441
+ }
442
+ }
443
+ }
444
+ // Step 3: Validate service endpoints
445
+ console.log(chalk.cyan('\n3. Service Endpoints'));
446
+ try {
447
+ await config.discoverServices(options.verbose);
448
+ const services = config.get('discoveredServices');
449
+ if (services) {
450
+ validation.endpointsValid = true;
451
+ console.log(chalk.green(' āœ“ Service endpoints are configured'));
452
+ // Validate endpoint URLs
453
+ const endpoints = [
454
+ { name: 'Auth', url: services.auth_base },
455
+ { name: 'Memory', url: services.memory_base },
456
+ { name: 'MCP HTTP', url: services.mcp_base },
457
+ { name: 'MCP WebSocket', url: services.mcp_ws_base },
458
+ { name: 'MCP SSE', url: services.mcp_sse_base }
459
+ ];
460
+ for (const endpoint of endpoints) {
461
+ if (endpoint.url) {
462
+ try {
463
+ new URL(endpoint.url);
464
+ if (options.verbose) {
465
+ console.log(chalk.green(` āœ“ ${endpoint.name}: ${endpoint.url}`));
466
+ }
467
+ }
468
+ catch {
469
+ console.log(chalk.red(` āœ– Invalid ${endpoint.name} URL: ${endpoint.url}`));
470
+ validation.issues.push(`Invalid ${endpoint.name} URL`);
471
+ }
472
+ }
473
+ }
474
+ // Check for manual overrides
475
+ if (config.hasManualEndpointOverrides()) {
476
+ console.log(chalk.cyan(' ℹ Manual endpoint overrides are active'));
477
+ const lastUpdate = config.get('lastManualEndpointUpdate');
478
+ if (lastUpdate && options.verbose) {
479
+ const updateDate = new Date(lastUpdate);
480
+ console.log(chalk.gray(` Last updated: ${updateDate.toLocaleString()}`));
481
+ }
482
+ }
483
+ }
484
+ else {
485
+ console.log(chalk.red(' āœ– Service endpoints are not configured'));
486
+ validation.issues.push('Service endpoints not configured');
487
+ }
488
+ }
489
+ catch (error) {
490
+ console.log(chalk.red(' āœ– Service endpoint discovery failed'));
491
+ validation.issues.push('Service endpoint discovery failed');
492
+ if (options.repair) {
493
+ // Set fallback endpoints
494
+ await config.setManualEndpoints({
495
+ auth_base: 'https://api.lanonasis.com',
496
+ memory_base: 'https://api.lanonasis.com/api/v1',
497
+ mcp_base: 'https://mcp.lanonasis.com/api/v1',
498
+ mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
499
+ mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events'
500
+ });
501
+ validation.repairs.push('Set fallback service endpoints');
502
+ console.log(chalk.cyan(' → Repaired: Set fallback service endpoints'));
503
+ }
504
+ }
505
+ // Step 4: Validate MCP configuration
506
+ console.log(chalk.cyan('\n4. MCP Configuration'));
507
+ const mcpPreference = config.get('mcpPreference');
508
+ const mcpServerPath = config.get('mcpServerPath');
509
+ const mcpServerUrl = config.get('mcpServerUrl');
510
+ if (mcpPreference) {
511
+ if (['local', 'remote', 'auto'].includes(mcpPreference)) {
512
+ console.log(chalk.green(` āœ“ MCP preference: ${mcpPreference}`));
513
+ validation.mcpConfigValid = true;
514
+ }
515
+ else {
516
+ console.log(chalk.red(` āœ– Invalid MCP preference: ${mcpPreference}`));
517
+ validation.issues.push('Invalid MCP preference');
518
+ if (options.repair) {
519
+ config.set('mcpPreference', 'auto');
520
+ await config.save();
521
+ validation.repairs.push('Reset MCP preference to auto');
522
+ console.log(chalk.cyan(' → Repaired: Reset MCP preference to auto'));
523
+ }
524
+ }
525
+ }
526
+ else {
527
+ console.log(chalk.yellow(' ⚠ MCP preference not set (using default: auto)'));
528
+ if (options.repair) {
529
+ config.set('mcpPreference', 'auto');
530
+ await config.save();
531
+ validation.repairs.push('Set default MCP preference');
532
+ console.log(chalk.cyan(' → Repaired: Set MCP preference to auto'));
533
+ }
534
+ }
535
+ // Validate MCP URLs if present
536
+ if (mcpServerUrl) {
537
+ try {
538
+ new URL(mcpServerUrl);
539
+ console.log(chalk.green(' āœ“ MCP server URL is valid'));
540
+ }
541
+ catch {
542
+ console.log(chalk.red(` āœ– Invalid MCP server URL: ${mcpServerUrl}`));
543
+ validation.issues.push('Invalid MCP server URL');
544
+ }
545
+ }
546
+ // Step 5: Check for configuration backup
547
+ console.log(chalk.cyan('\n5. Configuration Backup'));
548
+ try {
549
+ const configDir = path.dirname(config.getConfigPath());
550
+ const fs = await import('fs/promises');
551
+ const files = await fs.readdir(configDir);
552
+ const backupFiles = files.filter(f => f.startsWith('config.backup.'));
553
+ if (backupFiles.length > 0) {
554
+ validation.backupExists = true;
555
+ console.log(chalk.green(` āœ“ Found ${backupFiles.length} configuration backup(s)`));
556
+ if (options.verbose) {
557
+ const latestBackup = backupFiles.sort().reverse()[0];
558
+ console.log(chalk.gray(` Latest backup: ${latestBackup}`));
559
+ }
560
+ }
561
+ else {
562
+ console.log(chalk.yellow(' ⚠ No configuration backups found'));
563
+ if (options.repair) {
564
+ const backupPath = await config.backupConfig();
565
+ validation.repairs.push('Created configuration backup');
566
+ console.log(chalk.cyan(` → Repaired: Created backup at ${path.basename(backupPath)}`));
567
+ }
568
+ }
569
+ }
570
+ catch (error) {
571
+ console.log(chalk.yellow(' ⚠ Could not check for backups'));
572
+ if (options.verbose) {
573
+ console.log(chalk.gray(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
574
+ }
575
+ }
576
+ // Summary and recommendations
577
+ console.log(chalk.blue.bold('\nšŸ“‹ Configuration Validation Summary'));
578
+ console.log(chalk.cyan('━'.repeat(50)));
579
+ if (validation.issues.length === 0) {
580
+ console.log(chalk.green('āœ… Configuration validation passed!'));
581
+ console.log(chalk.cyan(' Your configuration is valid and healthy.'));
582
+ }
583
+ else {
584
+ console.log(chalk.red(`āŒ Found ${validation.issues.length} issue(s):`));
585
+ validation.issues.forEach(issue => {
586
+ console.log(chalk.red(` • ${issue}`));
587
+ });
588
+ }
589
+ if (validation.repairs.length > 0) {
590
+ console.log(chalk.yellow(`\nšŸ”§ Applied ${validation.repairs.length} repair(s):`));
591
+ validation.repairs.forEach(repair => {
592
+ console.log(chalk.cyan(` • ${repair}`));
593
+ });
594
+ }
595
+ // Recommendations
596
+ const recommendations = [];
597
+ if (!validation.configExists && !options.repair) {
598
+ recommendations.push('Run: lanonasis config validate --repair');
599
+ }
600
+ if (!validation.authenticationValid) {
601
+ recommendations.push('Run: lanonasis auth login');
602
+ }
603
+ if (!validation.endpointsValid && !options.repair) {
604
+ recommendations.push('Run: lanonasis config discover');
605
+ }
606
+ if (!validation.backupExists && !options.repair) {
607
+ recommendations.push('Run: lanonasis config validate --repair (to create backup)');
608
+ }
609
+ if (recommendations.length > 0) {
610
+ console.log(chalk.yellow('\nšŸ’” Recommended actions:'));
611
+ recommendations.forEach(rec => {
612
+ console.log(chalk.cyan(` • ${rec}`));
613
+ });
614
+ }
615
+ });
616
+ // Backup configuration
617
+ program
618
+ .command('backup')
619
+ .description('Create a backup of current configuration')
620
+ .action(async () => {
621
+ const config = new CLIConfig();
622
+ await config.init();
623
+ try {
624
+ const backupPath = await config.backupConfig();
625
+ console.log(chalk.green('āœ“ Configuration backed up to:'));
626
+ console.log(chalk.cyan(` ${backupPath}`));
627
+ }
628
+ catch (error) {
629
+ console.log(chalk.red('āœ– Failed to create backup:'));
630
+ console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
631
+ process.exit(1);
632
+ }
633
+ });
634
+ // Restore configuration from backup
635
+ program
636
+ .command('restore')
637
+ .description('Restore configuration from backup')
638
+ .argument('[backup-file]', 'specific backup file to restore from')
639
+ .action(async (backupFile) => {
640
+ const config = new CLIConfig();
641
+ await config.init();
642
+ try {
643
+ const configDir = path.dirname(config.getConfigPath());
644
+ const fs = await import('fs/promises');
645
+ let backupPath;
646
+ if (backupFile) {
647
+ // Use specified backup file
648
+ backupPath = path.isAbsolute(backupFile) ? backupFile : path.join(configDir, backupFile);
649
+ }
650
+ else {
651
+ // Find latest backup
652
+ const files = await fs.readdir(configDir);
653
+ const backupFiles = files.filter(f => f.startsWith('config.backup.')).sort().reverse();
654
+ if (backupFiles.length === 0) {
655
+ console.log(chalk.red('āœ– No backup files found'));
656
+ console.log(chalk.gray(' Run: lanonasis config backup'));
657
+ process.exit(1);
658
+ }
659
+ backupPath = path.join(configDir, backupFiles[0]);
660
+ console.log(chalk.cyan(`Using latest backup: ${backupFiles[0]}`));
661
+ }
662
+ // Verify backup file exists
663
+ await fs.access(backupPath);
664
+ // Confirm restoration
665
+ const answer = await inquirer.prompt([
666
+ {
667
+ type: 'confirm',
668
+ name: 'confirm',
669
+ message: `Restore configuration from backup? This will overwrite current config.`,
670
+ default: false
671
+ }
672
+ ]);
673
+ if (!answer.confirm) {
674
+ console.log(chalk.yellow('Restore cancelled'));
675
+ return;
676
+ }
677
+ // Create backup of current config before restoring
678
+ const currentBackupPath = await config.backupConfig();
679
+ console.log(chalk.cyan(`Current config backed up to: ${path.basename(currentBackupPath)}`));
680
+ // Restore from backup
681
+ await fs.copyFile(backupPath, config.getConfigPath());
682
+ console.log(chalk.green('āœ“ Configuration restored from backup'));
683
+ console.log(chalk.cyan(' You may need to re-authenticate if credentials were changed'));
684
+ }
685
+ catch (error) {
686
+ console.log(chalk.red('āœ– Failed to restore from backup:'));
687
+ console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
688
+ process.exit(1);
689
+ }
690
+ });
181
691
  // Reset configuration
182
692
  program
183
693
  .command('reset')
@@ -199,8 +709,16 @@ export function configCommands(program) {
199
709
  }
200
710
  }
201
711
  const config = new CLIConfig();
712
+ // Create backup before reset
713
+ try {
714
+ const backupPath = await config.backupConfig();
715
+ console.log(chalk.cyan(`Configuration backed up to: ${path.basename(backupPath)}`));
716
+ }
717
+ catch {
718
+ // Ignore backup errors during reset
719
+ }
202
720
  await config.clear();
203
721
  console.log(chalk.green('āœ“ Configuration reset'));
204
- console.log(chalk.yellow('Run'), chalk.white('memory init'), chalk.yellow('to reconfigure'));
722
+ console.log(chalk.yellow('Run'), chalk.white('lanonasis auth login'), chalk.yellow('to reconfigure'));
205
723
  });
206
724
  }