@lanonasis/cli 3.4.15 → 3.5.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.
@@ -231,8 +231,11 @@ export async function diagnoseCommand() {
231
231
  console.log(chalk.green(' ✓ Token is not expired'));
232
232
  }
233
233
  }
234
- catch {
234
+ catch (error) {
235
235
  console.log(chalk.yellow(' ⚠ Could not validate token expiry'));
236
+ if (process.env.CLI_VERBOSE === 'true' && error instanceof Error) {
237
+ console.log(chalk.gray(` ${error.message}`));
238
+ }
236
239
  }
237
240
  }
238
241
  else {
@@ -315,8 +318,11 @@ export async function diagnoseCommand() {
315
318
  diagnostics.deviceId = deviceId;
316
319
  console.log(chalk.green(' ✓ Device ID:'), chalk.gray(deviceId));
317
320
  }
318
- catch {
321
+ catch (error) {
319
322
  console.log(chalk.yellow(' ⚠ Could not get device ID'));
323
+ if (process.env.CLI_VERBOSE === 'true' && error instanceof Error) {
324
+ console.log(chalk.gray(` ${error.message}`));
325
+ }
320
326
  }
321
327
  // Summary and recommendations
322
328
  console.log(chalk.blue.bold('\n📋 Diagnostic Summary'));
@@ -393,7 +393,6 @@ export class InteractiveSetup {
393
393
  mask: '*'
394
394
  }
395
395
  ]);
396
- void auth; // suppress unused until real auth wired
397
396
  const spinner = ora('Signing in...').start();
398
397
  await this.simulateDelay(1000);
399
398
  spinner.succeed('Sign in successful!');
@@ -59,7 +59,7 @@ export class EnhancedMCPClient extends EventEmitter {
59
59
  const maxRetries = config.maxRetries || 3;
60
60
  const timeout = config.timeout || 30000;
61
61
  let attempts = 0;
62
- while (attempts < maxRetries) {
62
+ while (true) {
63
63
  try {
64
64
  this.updateConnectionStatus(config.name, 'connecting');
65
65
  const client = await this.createClientWithTimeout(config, timeout);
@@ -82,7 +82,6 @@ export class EnhancedMCPClient extends EventEmitter {
82
82
  await this.delay(delay);
83
83
  }
84
84
  }
85
- return false;
86
85
  }
87
86
  /**
88
87
  * Create client with timeout
@@ -17,6 +17,7 @@ export declare class CLIMCPServer {
17
17
  * Start MCP server using CLI configuration
18
18
  */
19
19
  start(options?: MCPServerOptions): Promise<void>;
20
+ private resolveMCPServerPath;
20
21
  /**
21
22
  * Start local MCP server using CLI auth config
22
23
  */
@@ -6,10 +6,13 @@
6
6
  */
7
7
  import { fileURLToPath } from 'url';
8
8
  import { dirname, join } from 'path';
9
+ import { existsSync } from 'fs';
10
+ import { createRequire } from 'module';
9
11
  import { spawn } from 'child_process';
10
12
  import { CLIConfig } from './utils/config.js';
11
13
  const __filename = fileURLToPath(import.meta.url);
12
14
  const __dirname = dirname(__filename);
15
+ const nodeRequire = createRequire(import.meta.url);
13
16
  export class CLIMCPServer {
14
17
  config;
15
18
  constructor() {
@@ -28,6 +31,33 @@ export class CLIMCPServer {
28
31
  await this.startLocalMCP(options);
29
32
  }
30
33
  }
34
+ resolveMCPServerPath() {
35
+ const candidates = new Set();
36
+ if (process.env.MCP_SERVER_PATH) {
37
+ candidates.add(process.env.MCP_SERVER_PATH);
38
+ }
39
+ const packageRequests = [
40
+ '@lanonasis/mcp-server/dist/cli-aligned-mcp-server.js',
41
+ 'lanonasis-mcp-server/dist/cli-aligned-mcp-server.js'
42
+ ];
43
+ for (const request of packageRequests) {
44
+ try {
45
+ const resolved = nodeRequire.resolve(request);
46
+ candidates.add(resolved);
47
+ }
48
+ catch {
49
+ // Ignore resolution failures and continue through fallbacks
50
+ }
51
+ }
52
+ candidates.add(join(process.cwd(), 'mcp-server/dist/cli-aligned-mcp-server.js'));
53
+ candidates.add(join(__dirname, '../../../mcp-server/dist/cli-aligned-mcp-server.js'));
54
+ for (const candidate of candidates) {
55
+ if (candidate && existsSync(candidate)) {
56
+ return candidate;
57
+ }
58
+ }
59
+ throw new Error('Unable to locate the CLI-aligned MCP server. Set MCP_SERVER_PATH or install @lanonasis/mcp-server.');
60
+ }
31
61
  /**
32
62
  * Start local MCP server using CLI auth config
33
63
  */
@@ -42,10 +72,11 @@ export class CLIMCPServer {
42
72
  console.error(`Config: ~/.maas/config.json`);
43
73
  console.error(`Auth: ${this.config.hasVendorKey() ? 'Vendor Key' : 'JWT Token'}`);
44
74
  }
75
+ const resolvedPort = typeof port === 'number' && !Number.isNaN(port) ? port : 3001;
45
76
  // Set environment variables from CLI config
46
77
  const env = {
47
78
  ...process.env,
48
- PORT: port?.toString(),
79
+ PORT: resolvedPort.toString(),
49
80
  MEMORY_API_URL: this.config.getApiUrl(),
50
81
  LANONASIS_VENDOR_KEY: this.config.getVendorKey(),
51
82
  LANONASIS_TOKEN: this.config.getToken(),
@@ -84,14 +115,13 @@ export class CLIMCPServer {
84
115
  */
85
116
  async startRemoteMCP(options) {
86
117
  const { verbose } = options;
118
+ const message = 'Remote MCP not implemented; remove --remote or use local mode.';
87
119
  if (verbose) {
88
120
  console.error('🌐 Connecting to remote MCP server...');
89
121
  console.error(`URL: ${this.config.getMCPServerUrl()}`);
90
122
  }
91
- // For remote MCP, we'd need to implement a proxy or client
92
- // For now, fall back to local mode
93
- console.error('⚠️ Remote MCP not yet implemented, falling back to local mode');
94
- await this.startLocalMCP({ ...options, useRemote: false });
123
+ console.error(`❌ ${message}`);
124
+ throw new Error(message);
95
125
  }
96
126
  /**
97
127
  * Check if MCP server is available and configured
@@ -149,6 +179,10 @@ Examples:
149
179
  await server.start(options);
150
180
  }
151
181
  if (import.meta.url === `file://${process.argv[1]}`) {
152
- main().catch(console.error);
182
+ main().catch(error => {
183
+ const message = error instanceof Error ? error.message : String(error);
184
+ console.error(message);
185
+ process.exit(1);
186
+ });
153
187
  }
154
188
  export default CLIMCPServer;
@@ -43,6 +43,14 @@ export declare class CLIConfig {
43
43
  private authCheckCache;
44
44
  private readonly AUTH_CACHE_TTL;
45
45
  constructor();
46
+ /**
47
+ * Overrides the configuration storage directory. Primarily used for tests.
48
+ */
49
+ setConfigDirectory(configDir: string): void;
50
+ /**
51
+ * Exposes the current config path for tests and diagnostics.
52
+ */
53
+ getConfigPath(): string;
46
54
  init(): Promise<void>;
47
55
  load(): Promise<void>;
48
56
  private migrateConfigIfNeeded;
@@ -55,6 +63,8 @@ export declare class CLIConfig {
55
63
  discoverServices(verbose?: boolean): Promise<void>;
56
64
  private handleServiceDiscoveryFailure;
57
65
  private categorizeServiceDiscoveryError;
66
+ private resolveFallbackEndpoints;
67
+ private logFallbackUsage;
58
68
  setManualEndpoints(endpoints: Partial<CLIConfigData['discoveredServices']>): Promise<void>;
59
69
  hasManualEndpointOverrides(): boolean;
60
70
  clearManualEndpointOverrides(): Promise<void>;
@@ -71,7 +81,6 @@ export declare class CLIConfig {
71
81
  isAuthenticated(): Promise<boolean>;
72
82
  logout(): Promise<void>;
73
83
  clear(): Promise<void>;
74
- getConfigPath(): string;
75
84
  exists(): Promise<boolean>;
76
85
  validateStoredCredentials(): Promise<boolean>;
77
86
  refreshTokenIfNeeded(): Promise<void>;
@@ -16,6 +16,20 @@ export class CLIConfig {
16
16
  this.configPath = path.join(this.configDir, 'config.json');
17
17
  this.lockFile = path.join(this.configDir, 'config.lock');
18
18
  }
19
+ /**
20
+ * Overrides the configuration storage directory. Primarily used for tests.
21
+ */
22
+ setConfigDirectory(configDir) {
23
+ this.configDir = configDir;
24
+ this.configPath = path.join(configDir, 'config.json');
25
+ this.lockFile = path.join(configDir, 'config.lock');
26
+ }
27
+ /**
28
+ * Exposes the current config path for tests and diagnostics.
29
+ */
30
+ getConfigPath() {
31
+ return this.configPath;
32
+ }
19
33
  async init() {
20
34
  try {
21
35
  await fs.mkdir(this.configDir, { recursive: true });
@@ -229,20 +243,17 @@ export class CLIConfig {
229
243
  return;
230
244
  }
231
245
  }
232
- // Set fallback service endpoints
246
+ const fallback = this.resolveFallbackEndpoints();
233
247
  this.config.discoveredServices = {
234
- auth_base: 'https://api.lanonasis.com', // CLI auth goes to central auth system
235
- memory_base: 'https://api.lanonasis.com/api/v1', // Memory via onasis-core
236
- mcp_base: 'https://mcp.lanonasis.com/api/v1', // MCP HTTP/REST
237
- mcp_ws_base: 'wss://mcp.lanonasis.com/ws', // MCP WebSocket
238
- mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events', // MCP SSE
239
- project_scope: 'lanonasis-maas' // Correct project scope
248
+ ...fallback.endpoints,
249
+ project_scope: 'lanonasis-maas'
240
250
  };
241
251
  // Mark as fallback (don't set lastServiceDiscovery)
242
252
  await this.save();
253
+ this.logFallbackUsage(fallback.source, this.config.discoveredServices);
243
254
  if (verbose) {
244
255
  console.log('✓ Using fallback service endpoints');
245
- console.log(' These are the standard production endpoints');
256
+ console.log(` Source: ${fallback.source === 'environment' ? 'environment overrides' : 'built-in defaults'}`);
246
257
  }
247
258
  }
248
259
  categorizeServiceDiscoveryError(error) {
@@ -272,6 +283,47 @@ export class CLIConfig {
272
283
  }
273
284
  return 'unknown';
274
285
  }
286
+ resolveFallbackEndpoints() {
287
+ const envAuthBase = process.env.LANONASIS_FALLBACK_AUTH_BASE ?? process.env.AUTH_BASE;
288
+ const envMemoryBase = process.env.LANONASIS_FALLBACK_MEMORY_BASE ?? process.env.MEMORY_BASE;
289
+ const envMcpBase = process.env.LANONASIS_FALLBACK_MCP_BASE ?? process.env.MCP_BASE;
290
+ const envMcpWsBase = process.env.LANONASIS_FALLBACK_MCP_WS_BASE ?? process.env.MCP_WS_BASE;
291
+ const envMcpSseBase = process.env.LANONASIS_FALLBACK_MCP_SSE_BASE ?? process.env.MCP_SSE_BASE;
292
+ const hasEnvOverrides = Boolean(envAuthBase || envMemoryBase || envMcpBase || envMcpWsBase || envMcpSseBase);
293
+ const nodeEnv = (process.env.NODE_ENV ?? '').toLowerCase();
294
+ const isDevEnvironment = nodeEnv === 'development' || nodeEnv === 'test';
295
+ const defaultAuthBase = isDevEnvironment ? 'http://localhost:4000' : 'https://api.lanonasis.com';
296
+ const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000/api/v1' : 'https://api.lanonasis.com/api/v1';
297
+ const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1';
298
+ const defaultMcpWsBase = isDevEnvironment ? 'ws://localhost:4100/ws' : 'wss://mcp.lanonasis.com/ws';
299
+ const defaultMcpSseBase = isDevEnvironment ? 'http://localhost:4100/api/v1/events' : 'https://mcp.lanonasis.com/api/v1/events';
300
+ const endpoints = {
301
+ auth_base: envAuthBase ?? defaultAuthBase,
302
+ memory_base: envMemoryBase ?? defaultMemoryBase,
303
+ mcp_base: envMcpBase ?? defaultMcpBase,
304
+ mcp_ws_base: envMcpWsBase ?? defaultMcpWsBase,
305
+ mcp_sse_base: envMcpSseBase ?? defaultMcpSseBase
306
+ };
307
+ return {
308
+ endpoints,
309
+ source: hasEnvOverrides ? 'environment' : 'default'
310
+ };
311
+ }
312
+ logFallbackUsage(source, endpoints) {
313
+ const summary = {
314
+ auth: endpoints.auth_base,
315
+ mcp: endpoints.mcp_base,
316
+ websocket: endpoints.mcp_ws_base,
317
+ sse: endpoints.mcp_sse_base,
318
+ source
319
+ };
320
+ const message = `Service discovery fallback activated using ${source === 'environment' ? 'environment overrides' : 'built-in defaults'}`;
321
+ console.warn(`⚠️ ${message}`);
322
+ console.info('📊 service_discovery_fallback', summary);
323
+ if (typeof process.emitWarning === 'function') {
324
+ process.emitWarning(message, 'ServiceDiscoveryFallback');
325
+ }
326
+ }
275
327
  // Manual endpoint override functionality
276
328
  async setManualEndpoints(endpoints) {
277
329
  if (!this.config.discoveredServices) {
@@ -570,9 +622,6 @@ export class CLIConfig {
570
622
  this.config = {};
571
623
  await this.save();
572
624
  }
573
- getConfigPath() {
574
- return this.configPath;
575
- }
576
625
  async exists() {
577
626
  try {
578
627
  await fs.access(this.configPath);
@@ -656,9 +705,12 @@ export class CLIConfig {
656
705
  }
657
706
  }
658
707
  }
659
- catch {
708
+ catch (err) {
660
709
  // If refresh fails, mark credentials as potentially invalid
661
710
  await this.incrementFailureCount();
711
+ if (process.env.CLI_VERBOSE === 'true' || process.env.NODE_ENV !== 'production') {
712
+ console.debug('Token refresh failed:', err.message);
713
+ }
662
714
  }
663
715
  }
664
716
  async clearInvalidCredentials() {
@@ -57,6 +57,23 @@ export declare class MCPClient {
57
57
  private lastHealthCheck;
58
58
  private activeConnectionMode;
59
59
  constructor();
60
+ /**
61
+ * Overrides the configuration directory used by the underlying CLI config.
62
+ * Useful for tests that need isolated config state.
63
+ */
64
+ setConfigDirectory(configDir: string): void;
65
+ /**
66
+ * Returns the current config file path. Primarily used for test introspection.
67
+ */
68
+ getConfigPath(): string;
69
+ /**
70
+ * Helper for tests to seed authentication tokens without accessing internals.
71
+ */
72
+ setTokenForTesting(token: string): Promise<void>;
73
+ /**
74
+ * Helper for tests to seed vendor keys without accessing internals.
75
+ */
76
+ setVendorKeyForTesting(vendorKey: string): Promise<void>;
60
77
  /**
61
78
  * Initialize the MCP client configuration
62
79
  */
@@ -20,6 +20,31 @@ export class MCPClient {
20
20
  constructor() {
21
21
  this.config = new CLIConfig();
22
22
  }
23
+ /**
24
+ * Overrides the configuration directory used by the underlying CLI config.
25
+ * Useful for tests that need isolated config state.
26
+ */
27
+ setConfigDirectory(configDir) {
28
+ this.config.setConfigDirectory(configDir);
29
+ }
30
+ /**
31
+ * Returns the current config file path. Primarily used for test introspection.
32
+ */
33
+ getConfigPath() {
34
+ return this.config.getConfigPath();
35
+ }
36
+ /**
37
+ * Helper for tests to seed authentication tokens without accessing internals.
38
+ */
39
+ async setTokenForTesting(token) {
40
+ await this.config.setToken(token);
41
+ }
42
+ /**
43
+ * Helper for tests to seed vendor keys without accessing internals.
44
+ */
45
+ async setVendorKeyForTesting(vendorKey) {
46
+ await this.config.setVendorKey(vendorKey);
47
+ }
23
48
  /**
24
49
  * Initialize the MCP client configuration
25
50
  */
@@ -528,7 +553,7 @@ export class MCPClient {
528
553
  }
529
554
  try {
530
555
  this.lastHealthCheck = new Date();
531
- const connectionMode = this.config.get('mcpConnectionMode') ?? 'remote';
556
+ const connectionMode = this.activeConnectionMode || 'remote';
532
557
  switch (connectionMode) {
533
558
  case 'websocket':
534
559
  await this.checkWebSocketHealth();
@@ -542,7 +567,8 @@ export class MCPClient {
542
567
  }
543
568
  }
544
569
  catch {
545
- console.log(chalk.yellow('⚠️ Health check failed, attempting reconnection...'));
570
+ const connectionMode = this.activeConnectionMode || 'remote';
571
+ console.log(chalk.yellow(`⚠️ ${connectionMode} connection health check failed, attempting reconnection...`));
546
572
  await this.handleHealthCheckFailure();
547
573
  }
548
574
  }
@@ -607,10 +633,11 @@ export class MCPClient {
607
633
  this.isConnected = false;
608
634
  this.stopHealthMonitoring();
609
635
  // Attempt to reconnect with current configuration
610
- const connectionMode = this.config.get('mcpConnectionMode') ?? 'remote';
636
+ const connectionMode = (this.activeConnectionMode || 'remote');
611
637
  const options = {
612
- connectionMode: connectionMode
638
+ connectionMode
613
639
  };
640
+ console.log(chalk.yellow(`↻ Attempting reconnection using ${connectionMode} mode...`));
614
641
  // Add specific URLs if available
615
642
  if (connectionMode === 'websocket') {
616
643
  options.serverUrl = this.config.get('mcpWebSocketUrl');
@@ -648,7 +675,7 @@ export class MCPClient {
648
675
  this.wsConnection = null;
649
676
  }
650
677
  this.isConnected = false;
651
- this.activeConnectionMode = 'local'; // Reset to default
678
+ this.activeConnectionMode = 'websocket'; // Reset to default
652
679
  }
653
680
  /**
654
681
  * Call an MCP tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "3.4.15",
3
+ "version": "3.5.15",
4
4
  "description": "LanOnasis Enterprise CLI - Memory as a Service, API Key Management, and Infrastructure Orchestration",
5
5
  "main": "dist/index-simple.js",
6
6
  "bin": {