@lanonasis/cli 3.7.3 → 3.7.4

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/README.md CHANGED
@@ -26,9 +26,9 @@ lanonasis --version # or onasis --version
26
26
  onasis guide
27
27
 
28
28
  # Quick manual setup
29
- onasis init # Initialize configuration
30
- onasis login --vendor-key pk_xxx.sk_xxx # Authenticate with vendor key
31
- onasis health # Verify system health
29
+ onasis init # Initialize configuration
30
+ onasis login --vendor-key <your-vendor-key> # Authenticate with vendor key
31
+ onasis health # Verify system health
32
32
 
33
33
  # Create your first memory
34
34
  onasis memory create --title "Welcome" --content "My first memory"
@@ -85,10 +85,10 @@ onasis login --vendor-key pk_xxxxx.sk_xxxxx // ✅ Automatically hashed
85
85
 
86
86
  ### 1. Vendor Key Authentication (Recommended)
87
87
 
88
- Best for API integrations and automation:
88
+ Best for API integrations and automation. Copy the vendor key value exactly as shown in your LanOnasis dashboard (keys may vary in format):
89
89
 
90
90
  ```bash
91
- onasis login --vendor-key pk_xxxxx.sk_xxxxx
91
+ onasis login --vendor-key <your-vendor-key>
92
92
  ```
93
93
 
94
94
  ### 2. OAuth Browser Authentication
@@ -288,7 +288,7 @@ Location: `~/.maas/config.json`
288
288
  "apiUrl": "https://api.lanonasis.com/api/v1",
289
289
  "defaultOutputFormat": "table",
290
290
  "mcpPreference": "auto",
291
- "vendorKey": "pk_xxxxx.sk_xxxxx"
291
+ "vendorKey": "<your-vendor-key>"
292
292
  }
293
293
  ```
294
294
 
@@ -374,7 +374,7 @@ onasis auth status
374
374
 
375
375
  # Re-authenticate
376
376
  onasis auth logout
377
- onasis login --vendor-key pk_xxx.sk_xxx
377
+ onasis login --vendor-key <your-vendor-key>
378
378
  ```
379
379
 
380
380
  #### Connection Issues
@@ -64,8 +64,16 @@ export class MemoryAccessControl {
64
64
  // Get shared memories based on permissions
65
65
  const sharedMemories = await this.getSharedMemories(userId, appId);
66
66
  // Combine and deduplicate
67
- const allMemories = [...new Set([...ownMemories, ...sharedMemories])];
68
- return allMemories;
67
+ const combined = [...ownMemories, ...sharedMemories];
68
+ const deduped = [];
69
+ const seen = new Set();
70
+ combined.forEach(memoryId => {
71
+ if (!seen.has(memoryId)) {
72
+ seen.add(memoryId);
73
+ deduped.push(memoryId);
74
+ }
75
+ });
76
+ return deduped;
69
77
  }
70
78
  catch (error) {
71
79
  logger.error('Failed to get accessible memories', { error, userId, appId });
@@ -45,11 +45,11 @@ export class EnhancedMCPClient extends EventEmitter {
45
45
  }));
46
46
  await Promise.allSettled(connectionPromises);
47
47
  // Start health monitoring for connected servers
48
- for (const [name, success] of results) {
48
+ results.forEach((success, name) => {
49
49
  if (success) {
50
50
  this.startHealthMonitoring(name);
51
51
  }
52
- }
52
+ });
53
53
  return results;
54
54
  }
55
55
  /**
@@ -327,12 +327,12 @@ export class EnhancedMCPClient extends EventEmitter {
327
327
  */
328
328
  async disconnectAll() {
329
329
  // Stop all health monitoring
330
- for (const interval of this.healthCheckIntervals.values()) {
330
+ this.healthCheckIntervals.forEach(interval => {
331
331
  clearInterval(interval);
332
- }
332
+ });
333
333
  this.healthCheckIntervals.clear();
334
334
  // Disconnect all clients
335
- for (const [name, client] of this.clients) {
335
+ await Promise.all(Array.from(this.clients.entries()).map(async ([name, client]) => {
336
336
  try {
337
337
  await client.close();
338
338
  console.log(chalk.gray(`Disconnected from ${name}`));
@@ -340,7 +340,7 @@ export class EnhancedMCPClient extends EventEmitter {
340
340
  catch {
341
341
  console.log(chalk.yellow(`Warning: Error disconnecting from ${name}`));
342
342
  }
343
- }
343
+ }));
344
344
  this.clients.clear();
345
345
  this.transports.clear();
346
346
  this.connectionStatus.clear();
@@ -202,13 +202,13 @@ export declare const SystemConfigSchema: z.ZodObject<{
202
202
  }, "strip", z.ZodTypeAny, {
203
203
  value?: any;
204
204
  action?: "get" | "set" | "reset";
205
- key?: string;
206
205
  scope?: "user" | "global";
206
+ key?: string;
207
207
  }, {
208
208
  value?: any;
209
209
  action?: "get" | "set" | "reset";
210
- key?: string;
211
210
  scope?: "user" | "global";
211
+ key?: string;
212
212
  }>;
213
213
  export declare const BulkOperationSchema: z.ZodObject<{
214
214
  operation: z.ZodEnum<["create", "update", "delete"]>;
@@ -580,13 +580,13 @@ export declare const MCPSchemas: {
580
580
  }, "strip", z.ZodTypeAny, {
581
581
  value?: any;
582
582
  action?: "get" | "set" | "reset";
583
- key?: string;
584
583
  scope?: "user" | "global";
584
+ key?: string;
585
585
  }, {
586
586
  value?: any;
587
587
  action?: "get" | "set" | "reset";
588
- key?: string;
589
588
  scope?: "user" | "global";
589
+ key?: string;
590
590
  }>;
591
591
  };
592
592
  operations: {
@@ -916,7 +916,7 @@ Please choose an option (1-4):`
916
916
  connectionsByTransport: {}
917
917
  };
918
918
  const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
919
- for (const connection of this.connectionPool.values()) {
919
+ this.connectionPool.forEach(connection => {
920
920
  // Count as active if there was activity in the last 5 minutes
921
921
  if (connection.lastActivity.getTime() > fiveMinutesAgo) {
922
922
  stats.activeConnections++;
@@ -926,7 +926,7 @@ Please choose an option (1-4):`
926
926
  }
927
927
  stats.connectionsByTransport[connection.transport] =
928
928
  (stats.connectionsByTransport[connection.transport] || 0) + 1;
929
- }
929
+ });
930
930
  return stats;
931
931
  }
932
932
  /**
@@ -953,12 +953,13 @@ Please choose an option (1-4):`
953
953
  cleanupStaleConnections() {
954
954
  const tenMinutesAgo = Date.now() - (10 * 60 * 1000);
955
955
  const staleConnections = [];
956
- for (const [clientId, connection] of this.connectionPool.entries()) {
956
+ this.connectionPool.forEach((connection, clientId) => {
957
957
  if (connection.lastActivity.getTime() < tenMinutesAgo) {
958
958
  staleConnections.push(clientId);
959
959
  }
960
- }
961
- for (const clientId of staleConnections) {
960
+ });
961
+ for (let i = 0; i < staleConnections.length; i++) {
962
+ const clientId = staleConnections[i];
962
963
  this.removeConnection(clientId);
963
964
  if (this.options.verbose) {
964
965
  console.log(chalk.yellow(`🧹 Cleaned up stale connection: ${clientId}`));
@@ -1385,12 +1386,12 @@ Please choose an option (1-4):`
1385
1386
  */
1386
1387
  getTransportStatus() {
1387
1388
  const failures = {};
1388
- for (const [transport, failure] of this.transportFailures.entries()) {
1389
+ this.transportFailures.forEach((failure, transport) => {
1389
1390
  failures[transport] = {
1390
1391
  count: failure.count,
1391
1392
  lastFailure: failure.lastFailure.toISOString()
1392
1393
  };
1393
- }
1394
+ });
1394
1395
  return {
1395
1396
  supportedTransports: this.supportedTransports,
1396
1397
  preferredTransport: this.options.preferredTransport || 'stdio',
@@ -411,9 +411,9 @@ export class MCPTransportManager {
411
411
  */
412
412
  getStatuses() {
413
413
  const statuses = {};
414
- for (const [name, transport] of this.transports) {
414
+ this.transports.forEach((transport, name) => {
415
415
  statuses[name] = transport.isConnected();
416
- }
416
+ });
417
417
  return statuses;
418
418
  }
419
419
  /**
@@ -51,10 +51,14 @@ export class CLIMCPServer {
51
51
  }
52
52
  candidates.add(join(process.cwd(), 'mcp-server/dist/cli-aligned-mcp-server.js'));
53
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;
54
+ let resolvedPath = null;
55
+ candidates.forEach(candidate => {
56
+ if (!resolvedPath && candidate && existsSync(candidate)) {
57
+ resolvedPath = candidate;
57
58
  }
59
+ });
60
+ if (resolvedPath) {
61
+ return resolvedPath;
58
62
  }
59
63
  throw new Error('Unable to locate the CLI-aligned MCP server. Set MCP_SERVER_PATH or install @lanonasis/mcp-server.');
60
64
  }
@@ -63,6 +63,7 @@ export declare class CLIConfig {
63
63
  getApiUrl(): string;
64
64
  getApiUrlsWithFallbacks(): string[];
65
65
  discoverServices(verbose?: boolean): Promise<void>;
66
+ private normalizeServiceError;
66
67
  private handleServiceDiscoveryFailure;
67
68
  private categorizeServiceDiscoveryError;
68
69
  private resolveFallbackEndpoints;
@@ -4,6 +4,7 @@ import * as os from 'os';
4
4
  import { jwtDecode } from 'jwt-decode';
5
5
  import { randomUUID } from 'crypto';
6
6
  import { ApiKeyStorage } from '@lanonasis/oauth-client';
7
+ import axios from 'axios';
7
8
  export class CLIConfig {
8
9
  configDir;
9
10
  configPath;
@@ -183,8 +184,9 @@ export class CLIConfig {
183
184
  }
184
185
  // Enhanced Service Discovery Integration
185
186
  async discoverServices(verbose = false) {
186
- // Skip service discovery in test environment
187
- if (process.env.NODE_ENV === 'test' || process.env.SKIP_SERVICE_DISCOVERY === 'true') {
187
+ const isTestEnvironment = process.env.NODE_ENV === 'test';
188
+ const forceDiscovery = process.env.FORCE_SERVICE_DISCOVERY === 'true';
189
+ if ((isTestEnvironment && !forceDiscovery) || process.env.SKIP_SERVICE_DISCOVERY === 'true') {
188
190
  if (!this.config.discoveredServices) {
189
191
  this.config.discoveredServices = {
190
192
  auth_base: 'https://auth.lanonasis.com',
@@ -236,11 +238,8 @@ export class CLIConfig {
236
238
  throw lastError || new Error('All service discovery URLs failed');
237
239
  }
238
240
  try {
239
- // Map discovery response to our config format
240
241
  const discovered = response.data;
241
- // Extract auth base, but filter out localhost URLs
242
242
  let authBase = discovered.auth?.base || discovered.auth?.login?.replace('/auth/login', '') || '';
243
- // Override localhost with production auth endpoint
244
243
  if (authBase.includes('localhost') || authBase.includes('127.0.0.1')) {
245
244
  authBase = 'https://auth.lanonasis.com';
246
245
  }
@@ -254,7 +253,6 @@ export class CLIConfig {
254
253
  project_scope: 'lanonasis-maas'
255
254
  };
256
255
  this.config.apiUrl = memoryBase;
257
- // Mark discovery as successful
258
256
  this.config.lastServiceDiscovery = new Date().toISOString();
259
257
  await this.save();
260
258
  if (verbose) {
@@ -265,10 +263,24 @@ export class CLIConfig {
265
263
  }
266
264
  }
267
265
  catch (error) {
268
- // Enhanced error handling with user-visible messages
269
- await this.handleServiceDiscoveryFailure(error, verbose);
266
+ const normalizedError = this.normalizeServiceError(error);
267
+ await this.handleServiceDiscoveryFailure(normalizedError, verbose);
270
268
  }
271
269
  }
270
+ normalizeServiceError(error) {
271
+ if (error && typeof error === 'object') {
272
+ if (error instanceof Error) {
273
+ return {
274
+ ...error,
275
+ message: error.message
276
+ };
277
+ }
278
+ return error;
279
+ }
280
+ return {
281
+ message: typeof error === 'string' ? error : JSON.stringify(error)
282
+ };
283
+ }
272
284
  async handleServiceDiscoveryFailure(error, verbose) {
273
285
  const errorType = this.categorizeServiceDiscoveryError(error);
274
286
  if (verbose || process.env.CLI_VERBOSE === 'true') {
@@ -294,7 +306,6 @@ export class CLIConfig {
294
306
  console.log(` Reason: ${error.message || 'Unknown error'}`);
295
307
  }
296
308
  }
297
- // Use cached endpoints if available and recent (within 24 hours)
298
309
  if (this.config.discoveredServices && this.config.lastServiceDiscovery) {
299
310
  const lastDiscovery = new Date(this.config.lastServiceDiscovery);
300
311
  const hoursSinceDiscovery = (Date.now() - lastDiscovery.getTime()) / (1000 * 60 * 60);
@@ -311,7 +322,6 @@ export class CLIConfig {
311
322
  project_scope: 'lanonasis-maas'
312
323
  };
313
324
  this.config.apiUrl = fallback.endpoints.memory_base;
314
- // Mark as fallback (don't set lastServiceDiscovery)
315
325
  await this.save();
316
326
  this.logFallbackUsage(fallback.source, this.config.discoveredServices);
317
327
  if (verbose) {
@@ -331,7 +341,7 @@ export class CLIConfig {
331
341
  return 'timeout';
332
342
  }
333
343
  }
334
- if (error.response?.status >= 500) {
344
+ if ((error.response?.status ?? 0) >= 500) {
335
345
  return 'server_error';
336
346
  }
337
347
  if (error.response?.status === 404) {
@@ -421,9 +431,17 @@ export class CLIConfig {
421
431
  // Initialize with defaults first
422
432
  await this.discoverServices();
423
433
  }
434
+ const currentServices = this.config.discoveredServices ?? {
435
+ auth_base: 'https://auth.lanonasis.com',
436
+ memory_base: 'https://mcp.lanonasis.com/api/v1',
437
+ mcp_base: 'https://mcp.lanonasis.com/api/v1',
438
+ mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
439
+ mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events',
440
+ project_scope: 'lanonasis-maas'
441
+ };
424
442
  // Merge manual overrides with existing endpoints
425
443
  this.config.discoveredServices = {
426
- ...this.config.discoveredServices,
444
+ ...currentServices,
427
445
  ...endpoints
428
446
  };
429
447
  // Mark as manually configured
@@ -483,56 +501,26 @@ export class CLIConfig {
483
501
  return true;
484
502
  }
485
503
  async validateVendorKeyWithServer(vendorKey) {
504
+ if (process.env.SKIP_SERVER_VALIDATION === 'true') {
505
+ return;
506
+ }
486
507
  try {
487
508
  // Import axios dynamically to avoid circular dependency
488
- const axios = (await import('axios')).default;
489
509
  // Ensure service discovery is done
490
510
  await this.discoverServices();
491
511
  const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
492
- const normalizedBase = authBase.replace(/\/$/, '');
493
- // Try multiple validation endpoints
494
- const validationEndpoints = [
495
- `${normalizedBase}/api/v1/auth/validate`,
496
- `${normalizedBase}/api/v1/auth/validate-vendor-key`,
497
- `${normalizedBase}/v1/auth/validate`
498
- ];
499
- let lastError;
500
- let validated = false;
501
- for (const endpoint of validationEndpoints) {
502
- try {
503
- const response = await axios.post(endpoint, { key: vendorKey }, {
504
- headers: {
505
- 'X-API-Key': vendorKey,
506
- 'X-Auth-Method': 'vendor_key',
507
- 'X-Project-Scope': 'lanonasis-maas',
508
- 'Content-Type': 'application/json'
509
- },
510
- timeout: 10000,
511
- proxy: false
512
- });
513
- // Check if response indicates validation success
514
- if (response.data && (response.data.valid === true || response.data.success === true)) {
515
- validated = true;
516
- break;
517
- }
518
- }
519
- catch (error) {
520
- lastError = error;
521
- // Continue to next endpoint if this one fails
522
- continue;
523
- }
524
- }
525
- if (!validated && lastError) {
526
- throw lastError;
527
- }
528
- else if (!validated) {
529
- throw new Error('Vendor key validation failed: Unable to validate key with server');
530
- }
512
+ // Use pingAuthHealth for validation (simpler and more reliable)
513
+ await this.pingAuthHealth(axios, authBase, {
514
+ 'X-API-Key': vendorKey,
515
+ 'X-Auth-Method': 'vendor_key',
516
+ 'X-Project-Scope': 'lanonasis-maas'
517
+ }, { timeout: 10000, proxy: false });
531
518
  }
532
519
  catch (error) {
520
+ const normalizedError = this.normalizeServiceError(error);
533
521
  // Provide specific error messages based on response
534
- if (error.response?.status === 401) {
535
- const errorData = error.response.data;
522
+ if (normalizedError.response?.status === 401) {
523
+ const errorData = normalizedError.response.data;
536
524
  if (errorData?.error?.includes('expired') || errorData?.message?.includes('expired')) {
537
525
  throw new Error('Vendor key validation failed: Key has expired. Please generate a new key from your dashboard.');
538
526
  }
@@ -546,29 +534,29 @@ export class CLIConfig {
546
534
  throw new Error('Vendor key validation failed: Authentication failed. The key may be invalid, expired, or revoked.');
547
535
  }
548
536
  }
549
- else if (error.response?.status === 403) {
537
+ else if (normalizedError.response?.status === 403) {
550
538
  throw new Error('Vendor key access denied. The key may not have sufficient permissions for this operation.');
551
539
  }
552
- else if (error.response?.status === 429) {
540
+ else if (normalizedError.response?.status === 429) {
553
541
  throw new Error('Too many validation attempts. Please wait a moment before trying again.');
554
542
  }
555
- else if (error.response?.status >= 500) {
543
+ else if ((normalizedError.response?.status ?? 0) >= 500) {
556
544
  throw new Error('Server error during validation. Please try again in a few moments.');
557
545
  }
558
- else if (error.code === 'ECONNREFUSED') {
546
+ else if (normalizedError.code === 'ECONNREFUSED') {
559
547
  throw new Error('Cannot connect to authentication server. Please check your internet connection and try again.');
560
548
  }
561
- else if (error.code === 'ENOTFOUND') {
549
+ else if (normalizedError.code === 'ENOTFOUND') {
562
550
  throw new Error('Authentication server not found. Please check your internet connection.');
563
551
  }
564
- else if (error.code === 'ETIMEDOUT') {
552
+ else if (normalizedError.code === 'ETIMEDOUT') {
565
553
  throw new Error('Validation request timed out. Please check your internet connection and try again.');
566
554
  }
567
- else if (error.code === 'ECONNRESET') {
555
+ else if (normalizedError.code === 'ECONNRESET') {
568
556
  throw new Error('Connection was reset during validation. Please try again.');
569
557
  }
570
558
  else {
571
- throw new Error(`Vendor key validation failed: ${error.message || 'Unknown error'}`);
559
+ throw new Error(`Vendor key validation failed: ${normalizedError.message || 'Unknown error'}`);
572
560
  }
573
561
  }
574
562
  }
@@ -731,7 +719,6 @@ export class CLIConfig {
731
719
  // If not locally valid, attempt server verification before failing
732
720
  if (!locallyValid) {
733
721
  try {
734
- const axios = (await import('axios')).default;
735
722
  const endpoints = [
736
723
  'http://localhost:4000/v1/auth/verify-token',
737
724
  'https://auth.lanonasis.com/v1/auth/verify-token'
@@ -768,7 +755,6 @@ export class CLIConfig {
768
755
  }
769
756
  // Verify with server (security check) for tokens that haven't been validated recently
770
757
  try {
771
- const axios = (await import('axios')).default;
772
758
  // Try auth-gateway first (port 4000), then fall back to Netlify function
773
759
  const endpoints = [
774
760
  'http://localhost:4000/v1/auth/verify-token',
@@ -880,7 +866,6 @@ export class CLIConfig {
880
866
  return false;
881
867
  }
882
868
  // Import axios dynamically to avoid circular dependency
883
- const axios = (await import('axios')).default;
884
869
  // Ensure service discovery is done
885
870
  await this.discoverServices();
886
871
  const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
@@ -925,7 +910,6 @@ export class CLIConfig {
925
910
  // Refresh if token expires within 5 minutes
926
911
  if (exp > 0 && (exp - now) < 300) {
927
912
  // Import axios dynamically
928
- const axios = (await import('axios')).default;
929
913
  await this.discoverServices();
930
914
  const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
931
915
  // Attempt token refresh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "3.7.3",
3
+ "version": "3.7.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",