@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.
- package/dist/__tests__/auth-persistence.test.d.ts +1 -0
- package/dist/__tests__/auth-persistence.test.js +243 -0
- package/dist/__tests__/cross-device-integration.test.d.ts +1 -0
- package/dist/__tests__/cross-device-integration.test.js +305 -0
- package/dist/__tests__/mcp-connection-reliability.test.d.ts +1 -0
- package/dist/__tests__/mcp-connection-reliability.test.js +489 -0
- package/dist/__tests__/setup.d.ts +1 -0
- package/dist/__tests__/setup.js +26 -0
- package/dist/commands/api-keys.js +12 -6
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +420 -50
- package/dist/commands/config.js +519 -1
- package/dist/commands/mcp.js +299 -0
- package/dist/index.js +5 -1
- package/dist/mcp/server/lanonasis-server.d.ts +161 -6
- package/dist/mcp/server/lanonasis-server.js +813 -17
- package/dist/mcp/server/mcp/server/lanonasis-server.js +911 -0
- package/dist/mcp/server/utils/api.js +431 -0
- package/dist/mcp/server/utils/config.js +855 -0
- package/dist/utils/config.d.ts +40 -1
- package/dist/utils/config.js +273 -36
- package/dist/utils/mcp-client.d.ts +83 -2
- package/dist/utils/mcp-client.js +414 -15
- package/package.json +8 -4
package/dist/commands/config.js
CHANGED
|
@@ -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('
|
|
722
|
+
console.log(chalk.yellow('Run'), chalk.white('lanonasis auth login'), chalk.yellow('to reconfigure'));
|
|
205
723
|
});
|
|
206
724
|
}
|