@lanonasis/cli 3.7.3 → 3.7.5

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
  }
package/dist/utils/api.js CHANGED
@@ -2,7 +2,6 @@ import axios from 'axios';
2
2
  import chalk from 'chalk';
3
3
  import { randomUUID } from 'crypto';
4
4
  import { CLIConfig } from './config.js';
5
- import { ensureApiKeyHash } from './hash-utils.js';
6
5
  export class APIClient {
7
6
  client;
8
7
  config;
@@ -31,7 +30,8 @@ export class APIClient {
31
30
  const vendorKey = this.config.getVendorKey();
32
31
  if (vendorKey) {
33
32
  // Vendor key authentication (validated server-side)
34
- config.headers['X-API-Key'] = ensureApiKeyHash(vendorKey);
33
+ // Send raw key - server handles hashing for comparison
34
+ config.headers['X-API-Key'] = vendorKey;
35
35
  config.headers['X-Auth-Method'] = 'vendor_key';
36
36
  }
37
37
  else if (token) {
@@ -100,61 +100,62 @@ export class APIClient {
100
100
  return response.data;
101
101
  }
102
102
  // Memory operations - aligned with existing schema
103
+ // All memory endpoints use /api/v1/memory path
103
104
  async createMemory(data) {
104
- const response = await this.client.post('/memory', data);
105
+ const response = await this.client.post('/api/v1/memory', data);
105
106
  return response.data;
106
107
  }
107
108
  async getMemories(params = {}) {
108
- const response = await this.client.get('/memory', { params });
109
+ const response = await this.client.get('/api/v1/memory', { params });
109
110
  return response.data;
110
111
  }
111
112
  async getMemory(id) {
112
- const response = await this.client.get(`/memory/${id}`);
113
+ const response = await this.client.get(`/api/v1/memory/${id}`);
113
114
  return response.data;
114
115
  }
115
116
  async updateMemory(id, data) {
116
- const response = await this.client.put(`/memory/${id}`, data);
117
+ const response = await this.client.put(`/api/v1/memory/${id}`, data);
117
118
  return response.data;
118
119
  }
119
120
  async deleteMemory(id) {
120
- await this.client.delete(`/memory/${id}`);
121
+ await this.client.delete(`/api/v1/memory/${id}`);
121
122
  }
122
123
  async searchMemories(query, options = {}) {
123
- const response = await this.client.post('/memory/search', {
124
+ const response = await this.client.post('/api/v1/memory/search', {
124
125
  query,
125
126
  ...options
126
127
  });
127
128
  return response.data;
128
129
  }
129
130
  async getMemoryStats() {
130
- const response = await this.client.get('/memory/stats');
131
+ const response = await this.client.get('/api/v1/memory/stats');
131
132
  return response.data;
132
133
  }
133
134
  async bulkDeleteMemories(memoryIds) {
134
- const response = await this.client.post('/memory/bulk/delete', {
135
+ const response = await this.client.post('/api/v1/memory/bulk/delete', {
135
136
  memory_ids: memoryIds
136
137
  });
137
138
  return response.data;
138
139
  }
139
140
  // Topic operations - working with existing memory_topics table
140
141
  async createTopic(data) {
141
- const response = await this.client.post('/topics', data);
142
+ const response = await this.client.post('/api/v1/topics', data);
142
143
  return response.data;
143
144
  }
144
145
  async getTopics() {
145
- const response = await this.client.get('/topics');
146
+ const response = await this.client.get('/api/v1/topics');
146
147
  return response.data;
147
148
  }
148
149
  async getTopic(id) {
149
- const response = await this.client.get(`/topics/${id}`);
150
+ const response = await this.client.get(`/api/v1/topics/${id}`);
150
151
  return response.data;
151
152
  }
152
153
  async updateTopic(id, data) {
153
- const response = await this.client.put(`/topics/${id}`, data);
154
+ const response = await this.client.put(`/api/v1/topics/${id}`, data);
154
155
  return response.data;
155
156
  }
156
157
  async deleteTopic(id) {
157
- await this.client.delete(`/topics/${id}`);
158
+ await this.client.delete(`/api/v1/topics/${id}`);
158
159
  }
159
160
  // Health check
160
161
  async getHealth() {
@@ -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;
@@ -167,9 +168,11 @@ export class CLIConfig {
167
168
  }
168
169
  }
169
170
  getApiUrl() {
170
- return process.env.MEMORY_API_URL ||
171
+ const baseUrl = process.env.MEMORY_API_URL ||
171
172
  this.config.apiUrl ||
172
- 'https://api.lanonasis.com';
173
+ 'https://mcp.lanonasis.com';
174
+ // Ensure we don't double-append /api/v1 - strip it if present since APIClient adds it
175
+ return baseUrl.replace(/\/api\/v1\/?$/, '');
173
176
  }
174
177
  // Get API URLs with fallbacks - try multiple endpoints
175
178
  getApiUrlsWithFallbacks() {
@@ -183,13 +186,14 @@ export class CLIConfig {
183
186
  }
184
187
  // Enhanced Service Discovery Integration
185
188
  async discoverServices(verbose = false) {
186
- // Skip service discovery in test environment
187
- if (process.env.NODE_ENV === 'test' || process.env.SKIP_SERVICE_DISCOVERY === 'true') {
189
+ const isTestEnvironment = process.env.NODE_ENV === 'test';
190
+ const forceDiscovery = process.env.FORCE_SERVICE_DISCOVERY === 'true';
191
+ if ((isTestEnvironment && !forceDiscovery) || process.env.SKIP_SERVICE_DISCOVERY === 'true') {
188
192
  if (!this.config.discoveredServices) {
189
193
  this.config.discoveredServices = {
190
194
  auth_base: 'https://auth.lanonasis.com',
191
- memory_base: 'https://mcp.lanonasis.com/api/v1',
192
- mcp_base: 'https://mcp.lanonasis.com/api/v1',
195
+ memory_base: 'https://mcp.lanonasis.com', // Base URL without /api/v1
196
+ mcp_base: 'https://mcp.lanonasis.com/api/v1', // Full MCP REST path
193
197
  mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
194
198
  mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events',
195
199
  project_scope: 'lanonasis-maas'
@@ -236,25 +240,24 @@ export class CLIConfig {
236
240
  throw lastError || new Error('All service discovery URLs failed');
237
241
  }
238
242
  try {
239
- // Map discovery response to our config format
240
243
  const discovered = response.data;
241
- // Extract auth base, but filter out localhost URLs
242
244
  let authBase = discovered.auth?.base || discovered.auth?.login?.replace('/auth/login', '') || '';
243
- // Override localhost with production auth endpoint
244
245
  if (authBase.includes('localhost') || authBase.includes('127.0.0.1')) {
245
246
  authBase = 'https://auth.lanonasis.com';
246
247
  }
247
- const memoryBase = discovered.endpoints?.http || 'https://mcp.lanonasis.com/api/v1';
248
+ // Memory base should be the MCP base URL without /api/v1 suffix
249
+ // The API client will append the path as needed
250
+ const rawMemoryBase = discovered.endpoints?.http || 'https://mcp.lanonasis.com/api/v1';
251
+ const memoryBase = rawMemoryBase.replace(/\/api\/v1\/?$/, '') || 'https://mcp.lanonasis.com';
248
252
  this.config.discoveredServices = {
249
253
  auth_base: authBase || 'https://auth.lanonasis.com',
250
254
  memory_base: memoryBase,
251
- mcp_base: memoryBase,
255
+ mcp_base: `${memoryBase}/api/v1`, // Full path for MCP REST calls
252
256
  mcp_ws_base: discovered.endpoints?.websocket || 'wss://mcp.lanonasis.com/ws',
253
- mcp_sse_base: discovered.endpoints?.sse || 'https://mcp.lanonasis.com/api/v1/events',
257
+ mcp_sse_base: discovered.endpoints?.sse || `${memoryBase}/api/v1/events`,
254
258
  project_scope: 'lanonasis-maas'
255
259
  };
256
260
  this.config.apiUrl = memoryBase;
257
- // Mark discovery as successful
258
261
  this.config.lastServiceDiscovery = new Date().toISOString();
259
262
  await this.save();
260
263
  if (verbose) {
@@ -265,10 +268,24 @@ export class CLIConfig {
265
268
  }
266
269
  }
267
270
  catch (error) {
268
- // Enhanced error handling with user-visible messages
269
- await this.handleServiceDiscoveryFailure(error, verbose);
271
+ const normalizedError = this.normalizeServiceError(error);
272
+ await this.handleServiceDiscoveryFailure(normalizedError, verbose);
270
273
  }
271
274
  }
275
+ normalizeServiceError(error) {
276
+ if (error && typeof error === 'object') {
277
+ if (error instanceof Error) {
278
+ return {
279
+ ...error,
280
+ message: error.message
281
+ };
282
+ }
283
+ return error;
284
+ }
285
+ return {
286
+ message: typeof error === 'string' ? error : JSON.stringify(error)
287
+ };
288
+ }
272
289
  async handleServiceDiscoveryFailure(error, verbose) {
273
290
  const errorType = this.categorizeServiceDiscoveryError(error);
274
291
  if (verbose || process.env.CLI_VERBOSE === 'true') {
@@ -294,7 +311,6 @@ export class CLIConfig {
294
311
  console.log(` Reason: ${error.message || 'Unknown error'}`);
295
312
  }
296
313
  }
297
- // Use cached endpoints if available and recent (within 24 hours)
298
314
  if (this.config.discoveredServices && this.config.lastServiceDiscovery) {
299
315
  const lastDiscovery = new Date(this.config.lastServiceDiscovery);
300
316
  const hoursSinceDiscovery = (Date.now() - lastDiscovery.getTime()) / (1000 * 60 * 60);
@@ -311,7 +327,6 @@ export class CLIConfig {
311
327
  project_scope: 'lanonasis-maas'
312
328
  };
313
329
  this.config.apiUrl = fallback.endpoints.memory_base;
314
- // Mark as fallback (don't set lastServiceDiscovery)
315
330
  await this.save();
316
331
  this.logFallbackUsage(fallback.source, this.config.discoveredServices);
317
332
  if (verbose) {
@@ -331,7 +346,7 @@ export class CLIConfig {
331
346
  return 'timeout';
332
347
  }
333
348
  }
334
- if (error.response?.status >= 500) {
349
+ if ((error.response?.status ?? 0) >= 500) {
335
350
  return 'server_error';
336
351
  }
337
352
  if (error.response?.status === 404) {
@@ -356,8 +371,8 @@ export class CLIConfig {
356
371
  const nodeEnv = (process.env.NODE_ENV ?? '').toLowerCase();
357
372
  const isDevEnvironment = nodeEnv === 'development' || nodeEnv === 'test';
358
373
  const defaultAuthBase = isDevEnvironment ? 'http://localhost:4000' : 'https://auth.lanonasis.com';
359
- const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000/api/v1' : 'https://mcp.lanonasis.com/api/v1';
360
- const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1';
374
+ const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000' : 'https://mcp.lanonasis.com'; // Base URL without /api/v1
375
+ const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1'; // Full MCP REST path
361
376
  const defaultMcpWsBase = isDevEnvironment ? 'ws://localhost:4100/ws' : 'wss://mcp.lanonasis.com/ws';
362
377
  const defaultMcpSseBase = isDevEnvironment ? 'http://localhost:4100/api/v1/events' : 'https://mcp.lanonasis.com/api/v1/events';
363
378
  const endpoints = {
@@ -421,9 +436,17 @@ export class CLIConfig {
421
436
  // Initialize with defaults first
422
437
  await this.discoverServices();
423
438
  }
439
+ const currentServices = this.config.discoveredServices ?? {
440
+ auth_base: 'https://auth.lanonasis.com',
441
+ memory_base: 'https://mcp.lanonasis.com', // Base URL without /api/v1
442
+ mcp_base: 'https://mcp.lanonasis.com/api/v1', // Full MCP REST path
443
+ mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
444
+ mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events',
445
+ project_scope: 'lanonasis-maas'
446
+ };
424
447
  // Merge manual overrides with existing endpoints
425
448
  this.config.discoveredServices = {
426
- ...this.config.discoveredServices,
449
+ ...currentServices,
427
450
  ...endpoints
428
451
  };
429
452
  // Mark as manually configured
@@ -483,56 +506,26 @@ export class CLIConfig {
483
506
  return true;
484
507
  }
485
508
  async validateVendorKeyWithServer(vendorKey) {
509
+ if (process.env.SKIP_SERVER_VALIDATION === 'true') {
510
+ return;
511
+ }
486
512
  try {
487
513
  // Import axios dynamically to avoid circular dependency
488
- const axios = (await import('axios')).default;
489
514
  // Ensure service discovery is done
490
515
  await this.discoverServices();
491
516
  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
- }
517
+ // Use pingAuthHealth for validation (simpler and more reliable)
518
+ await this.pingAuthHealth(axios, authBase, {
519
+ 'X-API-Key': vendorKey,
520
+ 'X-Auth-Method': 'vendor_key',
521
+ 'X-Project-Scope': 'lanonasis-maas'
522
+ }, { timeout: 10000, proxy: false });
531
523
  }
532
524
  catch (error) {
525
+ const normalizedError = this.normalizeServiceError(error);
533
526
  // Provide specific error messages based on response
534
- if (error.response?.status === 401) {
535
- const errorData = error.response.data;
527
+ if (normalizedError.response?.status === 401) {
528
+ const errorData = normalizedError.response.data;
536
529
  if (errorData?.error?.includes('expired') || errorData?.message?.includes('expired')) {
537
530
  throw new Error('Vendor key validation failed: Key has expired. Please generate a new key from your dashboard.');
538
531
  }
@@ -546,29 +539,29 @@ export class CLIConfig {
546
539
  throw new Error('Vendor key validation failed: Authentication failed. The key may be invalid, expired, or revoked.');
547
540
  }
548
541
  }
549
- else if (error.response?.status === 403) {
542
+ else if (normalizedError.response?.status === 403) {
550
543
  throw new Error('Vendor key access denied. The key may not have sufficient permissions for this operation.');
551
544
  }
552
- else if (error.response?.status === 429) {
545
+ else if (normalizedError.response?.status === 429) {
553
546
  throw new Error('Too many validation attempts. Please wait a moment before trying again.');
554
547
  }
555
- else if (error.response?.status >= 500) {
548
+ else if ((normalizedError.response?.status ?? 0) >= 500) {
556
549
  throw new Error('Server error during validation. Please try again in a few moments.');
557
550
  }
558
- else if (error.code === 'ECONNREFUSED') {
551
+ else if (normalizedError.code === 'ECONNREFUSED') {
559
552
  throw new Error('Cannot connect to authentication server. Please check your internet connection and try again.');
560
553
  }
561
- else if (error.code === 'ENOTFOUND') {
554
+ else if (normalizedError.code === 'ENOTFOUND') {
562
555
  throw new Error('Authentication server not found. Please check your internet connection.');
563
556
  }
564
- else if (error.code === 'ETIMEDOUT') {
557
+ else if (normalizedError.code === 'ETIMEDOUT') {
565
558
  throw new Error('Validation request timed out. Please check your internet connection and try again.');
566
559
  }
567
- else if (error.code === 'ECONNRESET') {
560
+ else if (normalizedError.code === 'ECONNRESET') {
568
561
  throw new Error('Connection was reset during validation. Please try again.');
569
562
  }
570
563
  else {
571
- throw new Error(`Vendor key validation failed: ${error.message || 'Unknown error'}`);
564
+ throw new Error(`Vendor key validation failed: ${normalizedError.message || 'Unknown error'}`);
572
565
  }
573
566
  }
574
567
  }
@@ -731,7 +724,6 @@ export class CLIConfig {
731
724
  // If not locally valid, attempt server verification before failing
732
725
  if (!locallyValid) {
733
726
  try {
734
- const axios = (await import('axios')).default;
735
727
  const endpoints = [
736
728
  'http://localhost:4000/v1/auth/verify-token',
737
729
  'https://auth.lanonasis.com/v1/auth/verify-token'
@@ -768,7 +760,6 @@ export class CLIConfig {
768
760
  }
769
761
  // Verify with server (security check) for tokens that haven't been validated recently
770
762
  try {
771
- const axios = (await import('axios')).default;
772
763
  // Try auth-gateway first (port 4000), then fall back to Netlify function
773
764
  const endpoints = [
774
765
  'http://localhost:4000/v1/auth/verify-token',
@@ -880,7 +871,6 @@ export class CLIConfig {
880
871
  return false;
881
872
  }
882
873
  // Import axios dynamically to avoid circular dependency
883
- const axios = (await import('axios')).default;
884
874
  // Ensure service discovery is done
885
875
  await this.discoverServices();
886
876
  const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
@@ -925,7 +915,6 @@ export class CLIConfig {
925
915
  // Refresh if token expires within 5 minutes
926
916
  if (exp > 0 && (exp - now) < 300) {
927
917
  // Import axios dynamically
928
- const axios = (await import('axios')).default;
929
918
  await this.discoverServices();
930
919
  const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
931
920
  // Attempt token refresh
@@ -456,9 +456,11 @@ export class MCPClient {
456
456
  // Use the proper SSE endpoint from config
457
457
  const sseUrl = this.config.getMCPSSEUrl() ?? `${serverUrl}/events`;
458
458
  const token = this.config.get('token');
459
- if (token) {
459
+ const vendorKey = this.config.get('vendorKey');
460
+ const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
461
+ if (authKey) {
460
462
  // EventSource doesn't support headers directly, append token to URL
461
- this.sseConnection = new EventSource(`${sseUrl}?token=${encodeURIComponent(token)}`);
463
+ this.sseConnection = new EventSource(`${sseUrl}?token=${encodeURIComponent(authKey)}`);
462
464
  this.sseConnection.onmessage = (event) => {
463
465
  try {
464
466
  const data = JSON.parse(event.data);
@@ -478,7 +480,9 @@ export class MCPClient {
478
480
  */
479
481
  async initializeWebSocket(wsUrl) {
480
482
  const token = this.config.get('token');
481
- if (!token) {
483
+ const vendorKey = this.config.get('vendorKey');
484
+ const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
485
+ if (!authKey) {
482
486
  throw new Error('API key required for WebSocket mode. Set LANONASIS_API_KEY or login first.');
483
487
  }
484
488
  return new Promise((resolve, reject) => {
@@ -491,8 +495,8 @@ export class MCPClient {
491
495
  // Create new WebSocket connection with authentication
492
496
  this.wsConnection = new WebSocket(wsUrl, [], {
493
497
  headers: {
494
- 'Authorization': `Bearer ${token}`,
495
- 'X-API-Key': token
498
+ 'Authorization': `Bearer ${authKey}`,
499
+ 'X-API-Key': authKey
496
500
  }
497
501
  });
498
502
  this.wsConnection.on('open', () => {
@@ -624,15 +628,17 @@ export class MCPClient {
624
628
  async checkRemoteHealth() {
625
629
  const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
626
630
  const token = this.config.get('token');
627
- if (!token) {
631
+ const vendorKey = this.config.get('vendorKey');
632
+ const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
633
+ if (!authKey) {
628
634
  throw new Error('No authentication token available');
629
635
  }
630
636
  try {
631
637
  const axios = (await import('axios')).default;
632
638
  await axios.get(`${apiUrl}/health`, {
633
639
  headers: {
634
- 'Authorization': `Bearer ${token}`,
635
- 'x-api-key': String(token)
640
+ 'Authorization': `Bearer ${authKey}`,
641
+ 'X-API-Key': authKey
636
642
  },
637
643
  timeout: 5000
638
644
  });
@@ -749,7 +755,9 @@ export class MCPClient {
749
755
  async callRemoteTool(toolName, args) {
750
756
  const apiUrl = this.config.getMCPRestUrl() ?? 'https://mcp.lanonasis.com/api/v1';
751
757
  const token = this.config.get('token');
752
- if (!token) {
758
+ const vendorKey = this.config.get('vendorKey');
759
+ const authKey = token || vendorKey || process.env.LANONASIS_API_KEY;
760
+ if (!authKey) {
753
761
  throw new Error('Authentication required. Run "lanonasis auth login" first.');
754
762
  }
755
763
  // Map MCP tool names to REST API endpoints
@@ -805,8 +813,8 @@ export class MCPClient {
805
813
  method: mapping.method,
806
814
  url: `${apiUrl}${endpoint}`,
807
815
  headers: {
808
- 'Authorization': `Bearer ${token}`,
809
- 'x-api-key': String(token),
816
+ 'Authorization': `Bearer ${authKey}`,
817
+ 'X-API-Key': authKey,
810
818
  'Content-Type': 'application/json'
811
819
  },
812
820
  data: mapping.transform ? mapping.transform(args) : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "3.7.3",
3
+ "version": "3.7.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "@jest/globals": "^29.7.0",
47
47
  "@types/cli-progress": "^3.11.6",
48
48
  "@types/inquirer": "^9.0.7",
49
- "@types/node": "^22.10.2",
49
+ "@types/node": "^22.19.3",
50
50
  "@types/ws": "^8.5.12",
51
51
  "jest": "^29.7.0",
52
52
  "rimraf": "^5.0.7",