@lanonasis/cli 3.7.8 → 3.8.1

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.
@@ -370,7 +370,7 @@ export async function diagnoseCommand() {
370
370
  // Step 2: Check stored credentials
371
371
  console.log(chalk.cyan('\n2. Stored Credentials'));
372
372
  const token = config.getToken();
373
- const vendorKey = config.getVendorKey();
373
+ const vendorKey = await config.getVendorKeyAsync();
374
374
  const authMethod = config.get('authMethod');
375
375
  if (vendorKey) {
376
376
  diagnostics.hasCredentials = true;
@@ -707,7 +707,7 @@ async function handleOAuthFlow(config) {
707
707
  await config.setToken(tokens.access_token);
708
708
  await config.set('refresh_token', tokens.refresh_token);
709
709
  await config.set('token_expires_at', Date.now() + (tokens.expires_in * 1000));
710
- await config.set('authMethod', 'oauth2');
710
+ await config.set('authMethod', 'oauth');
711
711
  // The OAuth access token from auth-gateway works as the API token for all services
712
712
  // Store it as the vendor key equivalent for MCP and API access
713
713
  spinner.text = 'Configuring unified access...';
@@ -825,10 +825,8 @@ async function handleCredentialsFlow(options, config) {
825
825
  if (response.user.role) {
826
826
  console.log(`Role: ${response.user.role}`);
827
827
  }
828
- console.log(chalk.gray('✓ API access configured'));
829
- console.log();
830
- console.log(chalk.dim('Note: MCP WebSocket commands require a vendor key.'));
831
- console.log(chalk.dim('Run'), chalk.white('onasis auth vendor-key <key>'), chalk.dim('to configure MCP access.'));
828
+ console.log(chalk.gray('✓ API and MCP access configured'));
829
+ console.log(chalk.dim('Your JWT token works with all services: API, MCP, CLI.'));
832
830
  }
833
831
  catch (error) {
834
832
  spinner.fail('Login failed');
@@ -382,7 +382,7 @@ export function configCommands(program) {
382
382
  // Step 2: Validate authentication configuration
383
383
  console.log(chalk.cyan('\n2. Authentication Configuration'));
384
384
  const token = config.getToken();
385
- const vendorKey = config.getVendorKey();
385
+ const vendorKey = await config.getVendorKeyAsync();
386
386
  const authMethod = config.get('authMethod');
387
387
  if (!token && !vendorKey) {
388
388
  console.log(chalk.yellow(' ⚠ No authentication credentials configured'));
@@ -391,8 +391,9 @@ export function configCommands(program) {
391
391
  else {
392
392
  console.log(chalk.green(' ✓ Authentication credentials found'));
393
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)'));
394
+ // Note: OAuth users can have vendorKey stored (for MCP access) while authMethod is 'oauth'
395
+ if (vendorKey && authMethod !== 'vendor_key' && authMethod !== 'oauth') {
396
+ console.log(chalk.yellow(' ⚠ Auth method mismatch (has vendor key but method is not vendor_key or oauth)'));
396
397
  validation.issues.push('Auth method mismatch');
397
398
  if (options.repair) {
398
399
  config.set('authMethod', 'vendor_key');
@@ -169,6 +169,7 @@ export function mcpCommands(program) {
169
169
  const client = getMCPClient();
170
170
  await client.disconnect();
171
171
  console.log(chalk.green('✓ Disconnected from MCP server'));
172
+ process.exit(0);
172
173
  });
173
174
  // Status command
174
175
  mcp.command('status')
@@ -183,6 +184,7 @@ export function mcpCommands(program) {
183
184
  await config.init();
184
185
  let healthLabel = chalk.gray('Unknown');
185
186
  let healthDetails;
187
+ let isServiceReachable = false;
186
188
  try {
187
189
  const axios = (await import('axios')).default;
188
190
  // Derive MCP health URL from discovered REST base (e.g. https://mcp.lanonasis.com/api/v1 -> https://mcp.lanonasis.com/health)
@@ -190,7 +192,7 @@ export function mcpCommands(program) {
190
192
  const rootBase = restUrl.replace(/\/api\/v1$/, '');
191
193
  const healthUrl = `${rootBase}/health`;
192
194
  const token = config.getToken();
193
- const vendorKey = config.getVendorKey();
195
+ const vendorKey = await config.getVendorKeyAsync();
194
196
  const headers = {};
195
197
  if (vendorKey) {
196
198
  headers['X-API-Key'] = vendorKey;
@@ -207,14 +209,17 @@ export function mcpCommands(program) {
207
209
  const overallStatus = String(response.data?.status ?? '').toLowerCase();
208
210
  const ok = response.status === 200 && (!overallStatus || overallStatus === 'healthy');
209
211
  if (ok) {
210
- healthLabel = chalk.green('Reachable');
212
+ healthLabel = chalk.green('Healthy');
213
+ isServiceReachable = true;
211
214
  }
212
215
  else {
213
216
  healthLabel = chalk.yellow('Degraded');
217
+ isServiceReachable = true; // Service is reachable but degraded
214
218
  }
215
219
  }
216
220
  catch (error) {
217
221
  healthLabel = chalk.red('Unreachable');
222
+ isServiceReachable = false;
218
223
  if (error instanceof Error) {
219
224
  healthDetails = error.message;
220
225
  }
@@ -224,7 +229,14 @@ export function mcpCommands(program) {
224
229
  }
225
230
  console.log(chalk.cyan('\n📊 MCP Connection Status'));
226
231
  console.log(chalk.cyan('========================'));
227
- console.log(`Status: ${status.connected ? chalk.green('Connected') : chalk.red('Disconnected')}`);
232
+ // Show status based on service reachability, not in-memory connection state
233
+ // The CLI isn't a persistent daemon - "connected" means the service is available
234
+ if (isServiceReachable) {
235
+ console.log(`Status: ${chalk.green('Ready')} (service reachable)`);
236
+ }
237
+ else {
238
+ console.log(`Status: ${chalk.red('Unavailable')} (service unreachable)`);
239
+ }
228
240
  // Display mode with proper labels
229
241
  let modeDisplay;
230
242
  switch (status.mode) {
@@ -246,7 +258,8 @@ export function mcpCommands(program) {
246
258
  if (healthDetails && process.env.CLI_VERBOSE === 'true') {
247
259
  console.log(chalk.gray(`Health details: ${healthDetails}`));
248
260
  }
249
- if (status.connected) {
261
+ // Show features when service is reachable
262
+ if (isServiceReachable) {
250
263
  if (status.mode === 'remote') {
251
264
  console.log(`\n${chalk.cyan('Features:')}`);
252
265
  console.log('• Real-time updates via SSE');
@@ -259,7 +272,12 @@ export function mcpCommands(program) {
259
272
  console.log('• Authenticated WebSocket connection');
260
273
  console.log('• Production-ready MCP server');
261
274
  }
275
+ console.log(chalk.green('\n✓ MCP tools are available. Run "lanonasis mcp tools" to see them.'));
276
+ }
277
+ else {
278
+ console.log(chalk.yellow('\n⚠ MCP service is not reachable. Run "lanonasis mcp diagnose" for troubleshooting.'));
262
279
  }
280
+ process.exit(0);
263
281
  });
264
282
  // List tools command
265
283
  mcp.command('tools')
@@ -456,6 +474,42 @@ export function mcpCommands(program) {
456
474
  console.log(' --prefer-local : Use local stdio mode (development only)');
457
475
  console.log(' --auto : Auto-detect based on configuration (default)');
458
476
  }
477
+ process.exit(0);
478
+ });
479
+ // Start MCP server for external clients
480
+ mcp.command('start')
481
+ .description('Start MCP server for external clients (Claude Desktop, Cursor, etc.)')
482
+ .option('--transport <type>', 'Transport: stdio (default), ws, http, sse', 'stdio')
483
+ .option('--port <number>', 'Port for ws/http/sse', '3009')
484
+ .option('--host <address>', 'Host address', '127.0.0.1')
485
+ .action(async (options) => {
486
+ const apiKey = process.env.LANONASIS_API_KEY;
487
+ if (!apiKey) {
488
+ console.error('Error: LANONASIS_API_KEY environment variable required');
489
+ process.exit(1);
490
+ }
491
+ try {
492
+ const { LanonasisMCPServer } = await import('../mcp/server/lanonasis-server.js');
493
+ const server = new LanonasisMCPServer({
494
+ apiKey,
495
+ transport: options.transport,
496
+ port: parseInt(options.port, 10),
497
+ host: options.host
498
+ });
499
+ if (options.transport === 'stdio') {
500
+ // Log to stderr since stdout is for MCP protocol
501
+ console.error(`Starting MCP server in stdio mode...`);
502
+ await server.startStdio();
503
+ }
504
+ else {
505
+ console.error(`Starting MCP server on ${options.host}:${options.port} (${options.transport})...`);
506
+ await server.start();
507
+ }
508
+ }
509
+ catch (error) {
510
+ console.error(`Failed to start MCP server: ${error instanceof Error ? error.message : 'Unknown error'}`);
511
+ process.exit(1);
512
+ }
459
513
  });
460
514
  // Diagnose MCP connection issues
461
515
  mcp.command('diagnose')
@@ -483,7 +537,7 @@ export function mcpCommands(program) {
483
537
  // Step 1: Check authentication status
484
538
  console.log(chalk.cyan('1. Authentication Status'));
485
539
  const token = config.getToken();
486
- const vendorKey = config.getVendorKey();
540
+ const vendorKey = await config.getVendorKeyAsync();
487
541
  if (!token && !vendorKey) {
488
542
  console.log(chalk.red(' ✖ No authentication credentials found'));
489
543
  console.log(chalk.gray(' → Run: lanonasis auth login'));
@@ -755,5 +809,6 @@ export function mcpCommands(program) {
755
809
  console.log(chalk.gray(' • Verify your network allows outbound HTTPS connections'));
756
810
  console.log(chalk.gray(' • Contact support if issues persist'));
757
811
  }
812
+ process.exit(0);
758
813
  });
759
814
  }
@@ -31,9 +31,9 @@ export function memoryCommands(program) {
31
31
  validate: (input) => input.length > 0 || 'Title is required'
32
32
  },
33
33
  {
34
- type: 'editor',
34
+ type: 'input',
35
35
  name: 'content',
36
- message: 'Memory content:',
36
+ message: 'Memory content (or use -c flag for multi-line):',
37
37
  default: content,
38
38
  validate: (input) => input.length > 0 || 'Content is required'
39
39
  },
@@ -192,7 +192,7 @@ export class MemoryAccessControl {
192
192
  const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com';
193
193
  const token = this.config.get('token');
194
194
  const axios = (await import('axios')).default;
195
- const response = await axios.get(`${apiUrl}/api/v1/memory/${memoryId}`, {
195
+ const response = await axios.get(`${apiUrl}/api/v1/memories/${memoryId}`, {
196
196
  headers: {
197
197
  'Authorization': `Bearer ${token}`,
198
198
  'Content-Type': 'application/json'
@@ -210,7 +210,7 @@ export class MemoryAccessControl {
210
210
  const apiUrl = this.config.get('apiUrl') || 'https://api.lanonasis.com';
211
211
  const token = this.config.get('token');
212
212
  const axios = (await import('axios')).default;
213
- const response = await axios.get(`${apiUrl}/api/v1/memory?user_id=${userId}`, {
213
+ const response = await axios.get(`${apiUrl}/api/v1/memories?user_id=${userId}`, {
214
214
  headers: {
215
215
  'Authorization': `Bearer ${token}`,
216
216
  'Content-Type': 'application/json'
@@ -15,15 +15,15 @@ export declare const MemoryCreateSchema: z.ZodObject<{
15
15
  content?: string;
16
16
  tags?: string[];
17
17
  topic_id?: string;
18
- memory_type?: "context" | "reference" | "note";
19
18
  metadata?: Record<string, any>;
19
+ memory_type?: "context" | "reference" | "note";
20
20
  }, {
21
21
  title?: string;
22
22
  content?: string;
23
23
  tags?: string[];
24
24
  topic_id?: string;
25
- memory_type?: "context" | "reference" | "note";
26
25
  metadata?: Record<string, any>;
26
+ memory_type?: "context" | "reference" | "note";
27
27
  }>;
28
28
  export declare const MemorySearchSchema: z.ZodObject<{
29
29
  query: z.ZodString;
@@ -59,15 +59,15 @@ export declare const MemoryUpdateSchema: z.ZodObject<{
59
59
  content?: string;
60
60
  tags?: string[];
61
61
  memory_id?: string;
62
- memory_type?: "context" | "reference" | "note";
63
62
  metadata?: Record<string, any>;
63
+ memory_type?: "context" | "reference" | "note";
64
64
  }, {
65
65
  title?: string;
66
66
  content?: string;
67
67
  tags?: string[];
68
68
  memory_id?: string;
69
- memory_type?: "context" | "reference" | "note";
70
69
  metadata?: Record<string, any>;
70
+ memory_type?: "context" | "reference" | "note";
71
71
  }>;
72
72
  export declare const MemoryDeleteSchema: z.ZodObject<{
73
73
  memory_id: z.ZodString;
@@ -217,13 +217,13 @@ export declare const BulkOperationSchema: z.ZodObject<{
217
217
  transaction: z.ZodDefault<z.ZodBoolean>;
218
218
  }, "strip", z.ZodTypeAny, {
219
219
  operation?: "create" | "delete" | "update";
220
- entity_type?: "topic" | "memory" | "apikey";
221
220
  items?: Record<string, any>[];
221
+ entity_type?: "topic" | "memory" | "apikey";
222
222
  transaction?: boolean;
223
223
  }, {
224
224
  operation?: "create" | "delete" | "update";
225
- entity_type?: "topic" | "memory" | "apikey";
226
225
  items?: Record<string, any>[];
226
+ entity_type?: "topic" | "memory" | "apikey";
227
227
  transaction?: boolean;
228
228
  }>;
229
229
  export declare const ImportExportSchema: z.ZodObject<{
@@ -387,15 +387,15 @@ export declare const MCPSchemas: {
387
387
  content?: string;
388
388
  tags?: string[];
389
389
  topic_id?: string;
390
- memory_type?: "context" | "reference" | "note";
391
390
  metadata?: Record<string, any>;
391
+ memory_type?: "context" | "reference" | "note";
392
392
  }, {
393
393
  title?: string;
394
394
  content?: string;
395
395
  tags?: string[];
396
396
  topic_id?: string;
397
- memory_type?: "context" | "reference" | "note";
398
397
  metadata?: Record<string, any>;
398
+ memory_type?: "context" | "reference" | "note";
399
399
  }>;
400
400
  search: z.ZodObject<{
401
401
  query: z.ZodString;
@@ -431,15 +431,15 @@ export declare const MCPSchemas: {
431
431
  content?: string;
432
432
  tags?: string[];
433
433
  memory_id?: string;
434
- memory_type?: "context" | "reference" | "note";
435
434
  metadata?: Record<string, any>;
435
+ memory_type?: "context" | "reference" | "note";
436
436
  }, {
437
437
  title?: string;
438
438
  content?: string;
439
439
  tags?: string[];
440
440
  memory_id?: string;
441
- memory_type?: "context" | "reference" | "note";
442
441
  metadata?: Record<string, any>;
442
+ memory_type?: "context" | "reference" | "note";
443
443
  }>;
444
444
  delete: z.ZodObject<{
445
445
  memory_id: z.ZodString;
@@ -597,13 +597,13 @@ export declare const MCPSchemas: {
597
597
  transaction: z.ZodDefault<z.ZodBoolean>;
598
598
  }, "strip", z.ZodTypeAny, {
599
599
  operation?: "create" | "delete" | "update";
600
- entity_type?: "topic" | "memory" | "apikey";
601
600
  items?: Record<string, any>[];
601
+ entity_type?: "topic" | "memory" | "apikey";
602
602
  transaction?: boolean;
603
603
  }, {
604
604
  operation?: "create" | "delete" | "update";
605
- entity_type?: "topic" | "memory" | "apikey";
606
605
  items?: Record<string, any>[];
606
+ entity_type?: "topic" | "memory" | "apikey";
607
607
  transaction?: boolean;
608
608
  }>;
609
609
  importExport: z.ZodObject<{
@@ -9,6 +9,10 @@ export interface LanonasisServerOptions {
9
9
  verbose?: boolean;
10
10
  apiUrl?: string;
11
11
  token?: string;
12
+ apiKey?: string;
13
+ transport?: 'stdio' | 'ws' | 'http' | 'sse';
14
+ port?: number;
15
+ host?: string;
12
16
  preferredTransport?: 'stdio' | 'websocket' | 'http';
13
17
  enableTransportFallback?: boolean;
14
18
  }
@@ -209,6 +213,11 @@ export declare class LanonasisMCPServer {
209
213
  * Start the server
210
214
  */
211
215
  start(): Promise<void>;
216
+ /**
217
+ * Start the server in stdio mode for external MCP clients (Claude Desktop, Cursor, etc.)
218
+ * This is the primary entry point for `lanonasis mcp start`
219
+ */
220
+ startStdio(): Promise<void>;
212
221
  /**
213
222
  * Stop the server
214
223
  */
@@ -407,7 +407,15 @@ export class LanonasisMCPServer {
407
407
  text: `Authentication Error: ${error instanceof Error ? error.message : 'Authentication failed'}`
408
408
  }
409
409
  ],
410
- isError: true
410
+ isError: true,
411
+ task: {
412
+ taskId: `${clientId}-${Date.now()}`,
413
+ status: 'failed',
414
+ ttl: null,
415
+ createdAt: new Date().toISOString(),
416
+ lastUpdatedAt: new Date().toISOString(),
417
+ statusMessage: 'Authentication failed'
418
+ }
411
419
  };
412
420
  }
413
421
  this.updateConnectionActivity(clientId);
@@ -419,7 +427,14 @@ export class LanonasisMCPServer {
419
427
  type: 'text',
420
428
  text: JSON.stringify(result, null, 2)
421
429
  }
422
- ]
430
+ ],
431
+ task: {
432
+ taskId: `${clientId}-${Date.now()}`,
433
+ status: 'completed',
434
+ ttl: null,
435
+ createdAt: new Date().toISOString(),
436
+ lastUpdatedAt: new Date().toISOString()
437
+ }
423
438
  };
424
439
  }
425
440
  catch (error) {
@@ -430,7 +445,15 @@ export class LanonasisMCPServer {
430
445
  text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
431
446
  }
432
447
  ],
433
- isError: true
448
+ isError: true,
449
+ task: {
450
+ taskId: `${clientId}-${Date.now()}`,
451
+ status: 'failed',
452
+ ttl: null,
453
+ createdAt: new Date().toISOString(),
454
+ lastUpdatedAt: new Date().toISOString(),
455
+ statusMessage: error instanceof Error ? error.message : 'Unknown error'
456
+ }
434
457
  };
435
458
  }
436
459
  });
@@ -1473,6 +1496,35 @@ Please choose an option (1-4):`
1473
1496
  throw error;
1474
1497
  }
1475
1498
  }
1499
+ /**
1500
+ * Start the server in stdio mode for external MCP clients (Claude Desktop, Cursor, etc.)
1501
+ * This is the primary entry point for `lanonasis mcp start`
1502
+ */
1503
+ async startStdio() {
1504
+ await this.initialize();
1505
+ try {
1506
+ // Use API key from options if provided (from LANONASIS_API_KEY env var)
1507
+ if (this.options.apiKey) {
1508
+ // Set the API key for authentication
1509
+ await this.config.setVendorKey(this.options.apiKey);
1510
+ }
1511
+ // Create stdio transport
1512
+ this.transport = new StdioServerTransport();
1513
+ await this.server.connect(this.transport);
1514
+ // Log to stderr since stdout is for MCP protocol
1515
+ console.error(chalk.cyan('🚀 Lanonasis MCP Server started (stdio mode)'));
1516
+ console.error(chalk.gray(`Backend: ${this.config.getApiUrl()}`));
1517
+ console.error(chalk.gray(`Tools: 18+ available`));
1518
+ console.error(chalk.gray('Ready for MCP client connections...'));
1519
+ // Keep the process alive
1520
+ process.stdin.resume();
1521
+ }
1522
+ catch (error) {
1523
+ console.error(chalk.red('❌ Failed to start MCP Server:'));
1524
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
1525
+ throw error;
1526
+ }
1527
+ }
1476
1528
  /**
1477
1529
  * Stop the server
1478
1530
  */
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Lanonasis MCP Server Entry Point
4
+ *
5
+ * Direct entry point for external MCP clients (Claude Desktop, Cursor, Windsurf, etc.)
6
+ * This allows simple configuration like:
7
+ *
8
+ * claude mcp add lanonasis -- lanonasis-mcp
9
+ *
10
+ * Or in claude_desktop_config.json:
11
+ * {
12
+ * "mcpServers": {
13
+ * "lanonasis": {
14
+ * "command": "lanonasis-mcp",
15
+ * "env": { "LANONASIS_API_KEY": "lano_xxx" }
16
+ * }
17
+ * }
18
+ * }
19
+ */
20
+ export {};
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Lanonasis MCP Server Entry Point
4
+ *
5
+ * Direct entry point for external MCP clients (Claude Desktop, Cursor, Windsurf, etc.)
6
+ * This allows simple configuration like:
7
+ *
8
+ * claude mcp add lanonasis -- lanonasis-mcp
9
+ *
10
+ * Or in claude_desktop_config.json:
11
+ * {
12
+ * "mcpServers": {
13
+ * "lanonasis": {
14
+ * "command": "lanonasis-mcp",
15
+ * "env": { "LANONASIS_API_KEY": "lano_xxx" }
16
+ * }
17
+ * }
18
+ * }
19
+ */
20
+ import { LanonasisMCPServer } from './mcp/server/lanonasis-server.js';
21
+ async function main() {
22
+ // Get API key from environment
23
+ const apiKey = process.env.LANONASIS_API_KEY;
24
+ if (!apiKey) {
25
+ console.error('Error: LANONASIS_API_KEY environment variable is required');
26
+ console.error('');
27
+ console.error('Usage:');
28
+ console.error(' LANONASIS_API_KEY=lano_xxx lanonasis-mcp');
29
+ console.error('');
30
+ console.error('Or configure in Claude Desktop/Cursor:');
31
+ console.error(' {');
32
+ console.error(' "mcpServers": {');
33
+ console.error(' "lanonasis": {');
34
+ console.error(' "command": "lanonasis-mcp",');
35
+ console.error(' "env": { "LANONASIS_API_KEY": "your_api_key" }');
36
+ console.error(' }');
37
+ console.error(' }');
38
+ console.error(' }');
39
+ process.exit(1);
40
+ }
41
+ try {
42
+ const server = new LanonasisMCPServer({
43
+ apiKey,
44
+ verbose: process.env.LOG_LEVEL === 'debug'
45
+ });
46
+ // Start in stdio mode (standard for MCP clients)
47
+ await server.startStdio();
48
+ }
49
+ catch (error) {
50
+ console.error(`Failed to start MCP server: ${error instanceof Error ? error.message : 'Unknown error'}`);
51
+ process.exit(1);
52
+ }
53
+ }
54
+ main();
@@ -77,12 +77,13 @@ export class CLIMCPServer {
77
77
  console.error(`Auth: ${this.config.hasVendorKey() ? 'Vendor Key' : 'JWT Token'}`);
78
78
  }
79
79
  const resolvedPort = typeof port === 'number' && !Number.isNaN(port) ? port : 3001;
80
+ const vendorKey = await this.config.getVendorKeyAsync();
80
81
  // Set environment variables from CLI config
81
82
  const env = {
82
83
  ...process.env,
83
84
  PORT: resolvedPort.toString(),
84
85
  MEMORY_API_URL: this.config.getApiUrl(),
85
- LANONASIS_VENDOR_KEY: this.config.getVendorKey(),
86
+ LANONASIS_VENDOR_KEY: vendorKey,
86
87
  LANONASIS_TOKEN: this.config.getToken(),
87
88
  MCP_VERBOSE: verbose ? 'true' : 'false',
88
89
  CLI_ALIGNED: 'true'
package/dist/utils/api.js CHANGED
@@ -27,7 +27,7 @@ export class APIClient {
27
27
  }
28
28
  // Enhanced Authentication Support
29
29
  const token = this.config.getToken();
30
- const vendorKey = this.config.getVendorKey();
30
+ const vendorKey = await this.config.getVendorKeyAsync();
31
31
  if (vendorKey) {
32
32
  // Vendor key authentication (validated server-side)
33
33
  // Send raw key - server handles hashing for comparison
@@ -99,40 +99,40 @@ export class APIClient {
99
99
  });
100
100
  return response.data;
101
101
  }
102
- // Memory operations - aligned with existing schema
103
- // All memory endpoints use /api/v1/memory path
102
+ // Memory operations - aligned with REST API canonical endpoints
103
+ // All memory endpoints use /api/v1/memories path (plural, per REST conventions)
104
104
  async createMemory(data) {
105
- const response = await this.client.post('/api/v1/memory', data);
105
+ const response = await this.client.post('/api/v1/memories', data);
106
106
  return response.data;
107
107
  }
108
108
  async getMemories(params = {}) {
109
- const response = await this.client.get('/api/v1/memory', { params });
109
+ const response = await this.client.get('/api/v1/memories', { params });
110
110
  return response.data;
111
111
  }
112
112
  async getMemory(id) {
113
- const response = await this.client.get(`/api/v1/memory/${id}`);
113
+ const response = await this.client.get(`/api/v1/memories/${id}`);
114
114
  return response.data;
115
115
  }
116
116
  async updateMemory(id, data) {
117
- const response = await this.client.put(`/api/v1/memory/${id}`, data);
117
+ const response = await this.client.put(`/api/v1/memories/${id}`, data);
118
118
  return response.data;
119
119
  }
120
120
  async deleteMemory(id) {
121
- await this.client.delete(`/api/v1/memory/${id}`);
121
+ await this.client.delete(`/api/v1/memories/${id}`);
122
122
  }
123
123
  async searchMemories(query, options = {}) {
124
- const response = await this.client.post('/api/v1/memory/search', {
124
+ const response = await this.client.post('/api/v1/memories/search', {
125
125
  query,
126
126
  ...options
127
127
  });
128
128
  return response.data;
129
129
  }
130
130
  async getMemoryStats() {
131
- const response = await this.client.get('/api/v1/memory/stats');
131
+ const response = await this.client.get('/api/v1/memories/stats');
132
132
  return response.data;
133
133
  }
134
134
  async bulkDeleteMemories(memoryIds) {
135
- const response = await this.client.post('/api/v1/memory/bulk/delete', {
135
+ const response = await this.client.post('/api/v1/memories/bulk/delete', {
136
136
  memory_ids: memoryIds
137
137
  });
138
138
  return response.data;
@@ -43,6 +43,7 @@ export declare class CLIConfig {
43
43
  private authCheckCache;
44
44
  private readonly AUTH_CACHE_TTL;
45
45
  private apiKeyStorage;
46
+ private vendorKeyCache?;
46
47
  constructor();
47
48
  /**
48
49
  * Overrides the configuration storage directory. Primarily used for tests.
@@ -14,6 +14,7 @@ export class CLIConfig {
14
14
  authCheckCache = null;
15
15
  AUTH_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
16
16
  apiKeyStorage;
17
+ vendorKeyCache;
17
18
  constructor() {
18
19
  this.configDir = path.join(os.homedir(), '.maas');
19
20
  this.configPath = path.join(this.configDir, 'config.json');
@@ -28,6 +29,7 @@ export class CLIConfig {
28
29
  this.configDir = configDir;
29
30
  this.configPath = path.join(configDir, 'config.json');
30
31
  this.lockFile = path.join(configDir, 'config.lock');
32
+ this.vendorKeyCache = undefined;
31
33
  }
32
34
  /**
33
35
  * Exposes the current config path for tests and diagnostics.
@@ -45,6 +47,8 @@ export class CLIConfig {
45
47
  }
46
48
  }
47
49
  async load() {
50
+ // Reset in-memory cache; disk state may have changed
51
+ this.vendorKeyCache = undefined;
48
52
  try {
49
53
  const data = await fs.readFile(this.configPath, 'utf-8');
50
54
  this.config = JSON.parse(data);
@@ -170,7 +174,7 @@ export class CLIConfig {
170
174
  getApiUrl() {
171
175
  const baseUrl = process.env.MEMORY_API_URL ||
172
176
  this.config.apiUrl ||
173
- 'https://mcp.lanonasis.com';
177
+ 'https://api.lanonasis.com'; // Primary REST API endpoint
174
178
  // Ensure we don't double-append /api/v1 - strip it if present since APIClient adds it
175
179
  return baseUrl.replace(/\/api\/v1\/?$/, '');
176
180
  }
@@ -192,8 +196,8 @@ export class CLIConfig {
192
196
  if (!this.config.discoveredServices) {
193
197
  this.config.discoveredServices = {
194
198
  auth_base: 'https://auth.lanonasis.com',
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
199
+ memory_base: 'https://api.lanonasis.com', // Primary REST API (Supabase Edge Functions)
200
+ mcp_base: 'https://mcp.lanonasis.com/api/v1', // MCP protocol REST path
197
201
  mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
198
202
  mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events',
199
203
  project_scope: 'lanonasis-maas'
@@ -245,10 +249,10 @@ export class CLIConfig {
245
249
  if (authBase.includes('localhost') || authBase.includes('127.0.0.1')) {
246
250
  authBase = 'https://auth.lanonasis.com';
247
251
  }
248
- // Memory base should be the MCP base URL without /api/v1 suffix
252
+ // Memory base should be the REST API URL without /api/v1 suffix
249
253
  // 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';
254
+ const rawMemoryBase = discovered.endpoints?.http || 'https://api.lanonasis.com/api/v1';
255
+ const memoryBase = rawMemoryBase.replace(/\/api\/v1\/?$/, '') || 'https://api.lanonasis.com';
252
256
  this.config.discoveredServices = {
253
257
  auth_base: authBase || 'https://auth.lanonasis.com',
254
258
  memory_base: memoryBase,
@@ -371,8 +375,8 @@ export class CLIConfig {
371
375
  const nodeEnv = (process.env.NODE_ENV ?? '').toLowerCase();
372
376
  const isDevEnvironment = nodeEnv === 'development' || nodeEnv === 'test';
373
377
  const defaultAuthBase = isDevEnvironment ? 'http://localhost:4000' : 'https://auth.lanonasis.com';
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
378
+ const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000' : 'https://api.lanonasis.com'; // Primary REST API (Supabase Edge Functions)
379
+ const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1'; // MCP protocol REST path
376
380
  const defaultMcpWsBase = isDevEnvironment ? 'ws://localhost:4100/ws' : 'wss://mcp.lanonasis.com/ws';
377
381
  const defaultMcpSseBase = isDevEnvironment ? 'http://localhost:4100/api/v1/events' : 'https://mcp.lanonasis.com/api/v1/events';
378
382
  const endpoints = {
@@ -438,8 +442,8 @@ export class CLIConfig {
438
442
  }
439
443
  const currentServices = this.config.discoveredServices ?? {
440
444
  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
445
+ memory_base: 'https://api.lanonasis.com', // Primary REST API (Supabase Edge Functions)
446
+ mcp_base: 'https://mcp.lanonasis.com/api/v1', // MCP protocol REST path
443
447
  mcp_ws_base: 'wss://mcp.lanonasis.com/ws',
444
448
  mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events',
445
449
  project_scope: 'lanonasis-maas'
@@ -488,12 +492,18 @@ export class CLIConfig {
488
492
  environment: process.env.NODE_ENV || 'production',
489
493
  createdAt: new Date().toISOString()
490
494
  });
495
+ // Cache the vendor key for this process so synchronous callers can reuse it
496
+ this.vendorKeyCache = trimmedKey;
491
497
  if (process.env.CLI_VERBOSE === 'true') {
492
498
  console.log('🔐 Vendor key stored securely via @lanonasis/oauth-client');
493
499
  }
494
500
  // Store a reference marker in config (not the actual key)
495
501
  this.config.vendorKey = 'stored_in_api_key_storage';
496
- this.config.authMethod = 'vendor_key';
502
+ // Only set authMethod to 'vendor_key' if not already set to OAuth
503
+ // This prevents overwriting OAuth auth method when storing the token for MCP access
504
+ if (!this.config.authMethod || !['oauth', 'oauth2', 'jwt'].includes(this.config.authMethod)) {
505
+ this.config.authMethod = 'vendor_key';
506
+ }
497
507
  this.config.lastValidated = new Date().toISOString();
498
508
  await this.resetFailureCount(); // Reset failure count on successful auth
499
509
  await this.save();
@@ -566,10 +576,16 @@ export class CLIConfig {
566
576
  }
567
577
  }
568
578
  getVendorKey() {
579
+ if (this.vendorKeyCache) {
580
+ return this.vendorKeyCache;
581
+ }
569
582
  try {
570
583
  // Retrieve from secure storage using ApiKeyStorage (synchronous wrapper)
571
584
  const stored = this.getVendorKeySync();
572
- return stored;
585
+ if (stored) {
586
+ this.vendorKeyCache = stored;
587
+ }
588
+ return this.vendorKeyCache;
573
589
  }
574
590
  catch (error) {
575
591
  if (process.env.CLI_VERBOSE === 'true') {
@@ -600,7 +616,8 @@ export class CLIConfig {
600
616
  await this.apiKeyStorage.initialize();
601
617
  const stored = await this.apiKeyStorage.retrieve();
602
618
  if (stored) {
603
- return stored.apiKey;
619
+ this.vendorKeyCache = stored.apiKey;
620
+ return this.vendorKeyCache;
604
621
  }
605
622
  }
606
623
  catch (error) {
@@ -613,13 +630,14 @@ export class CLIConfig {
613
630
  if (process.env.CLI_VERBOSE === 'true') {
614
631
  console.log('ℹ️ Found legacy plaintext vendor key, will migrate on next auth');
615
632
  }
616
- return this.config.vendorKey;
633
+ this.vendorKeyCache = this.config.vendorKey;
634
+ return this.vendorKeyCache;
617
635
  }
618
636
  return undefined;
619
637
  }
620
638
  hasVendorKey() {
621
639
  // Check for marker or legacy storage
622
- return !!this.config.vendorKey;
640
+ return !!(this.vendorKeyCache || this.config.vendorKey);
623
641
  }
624
642
  async setApiUrl(url) {
625
643
  this.config.apiUrl = url;
@@ -678,11 +696,41 @@ export class CLIConfig {
678
696
  this.authCheckCache = { isValid: true, timestamp: Date.now() };
679
697
  return true;
680
698
  }
681
- // For vendor keys, we trust that they were validated during setVendorKey()
682
- // and rely on the lastValidated timestamp. For additional security,
683
- // the server should revoke keys that are invalid.
684
- this.authCheckCache = { isValid: true, timestamp: Date.now() };
685
- return true;
699
+ // Vendor key not recently validated - verify with server
700
+ try {
701
+ await this.discoverServices();
702
+ const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
703
+ // Ping auth health with vendor key to verify it's still valid
704
+ await this.pingAuthHealth(axios, authBase, {
705
+ 'X-API-Key': vendorKey,
706
+ 'X-Auth-Method': 'vendor_key',
707
+ 'X-Project-Scope': 'lanonasis-maas'
708
+ }, { timeout: 5000, proxy: false });
709
+ // Update last validated timestamp on success
710
+ this.config.lastValidated = new Date().toISOString();
711
+ await this.save().catch(() => { }); // Don't fail auth check if save fails
712
+ this.authCheckCache = { isValid: true, timestamp: Date.now() };
713
+ return true;
714
+ }
715
+ catch (error) {
716
+ // Server validation failed - check for grace period (7 days offline)
717
+ const gracePeriod = 7 * 24 * 60 * 60 * 1000;
718
+ const withinGracePeriod = lastValidated &&
719
+ (Date.now() - new Date(lastValidated).getTime()) < gracePeriod;
720
+ if (withinGracePeriod) {
721
+ if (process.env.CLI_VERBOSE === 'true') {
722
+ console.warn('⚠️ Unable to validate vendor key with server, using cached validation');
723
+ }
724
+ this.authCheckCache = { isValid: true, timestamp: Date.now() };
725
+ return true;
726
+ }
727
+ // Grace period expired - require server validation
728
+ if (process.env.CLI_VERBOSE === 'true') {
729
+ console.warn('⚠️ Vendor key validation failed and grace period expired');
730
+ }
731
+ this.authCheckCache = { isValid: false, timestamp: Date.now() };
732
+ return false;
733
+ }
686
734
  }
687
735
  // Handle token-based authentication
688
736
  const token = this.getToken();
@@ -848,10 +896,25 @@ export class CLIConfig {
848
896
  async logout() {
849
897
  this.config.token = undefined;
850
898
  this.config.user = undefined;
899
+ this.vendorKeyCache = undefined;
900
+ this.config.vendorKey = undefined;
901
+ this.config.authMethod = undefined;
902
+ try {
903
+ await this.apiKeyStorage.initialize();
904
+ // ApiKeyStorage may implement clear() to remove encrypted secrets
905
+ const storage = this.apiKeyStorage;
906
+ if (typeof storage.clear === 'function') {
907
+ await storage.clear();
908
+ }
909
+ }
910
+ catch {
911
+ // Ignore storage cleanup errors during logout
912
+ }
851
913
  await this.save();
852
914
  }
853
915
  async clear() {
854
916
  this.config = {};
917
+ this.vendorKeyCache = undefined;
855
918
  await this.save();
856
919
  }
857
920
  async exists() {
@@ -866,7 +929,7 @@ export class CLIConfig {
866
929
  // Enhanced credential validation methods
867
930
  async validateStoredCredentials() {
868
931
  try {
869
- const vendorKey = this.getVendorKey();
932
+ const vendorKey = await this.getVendorKeyAsync();
870
933
  const token = this.getToken();
871
934
  if (!vendorKey && !token) {
872
935
  return false;
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import crypto from 'crypto';
10
10
  import { homedir, platform, hostname } from 'os';
11
- import { hashApiKey, isSha256Hash } from './hash-utils.js';
11
+ import { hashApiKey, isSha256Hash } from '@lanonasis/security-sdk/hash-utils';
12
12
  // Encryption algorithm configuration
13
13
  const ALGORITHM = 'aes-256-gcm';
14
14
  const KEY_LENGTH = 32; // 256 bits
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "3.7.8",
3
+ "version": "3.8.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
8
8
  "onasis": "dist/index.js",
9
- "lanonasis": "dist/index.js"
9
+ "lanonasis": "dist/index.js",
10
+ "lanonasis-mcp": "dist/mcp-server-entry.js"
10
11
  },
11
12
  "exports": {
12
13
  ".": {
@@ -23,8 +24,8 @@
23
24
  ],
24
25
  "dependencies": {
25
26
  "@lanonasis/oauth-client": "1.2.5",
26
- "@lanonasis/security-sdk": "^1.0.1",
27
- "@modelcontextprotocol/sdk": "^1.1.1",
27
+ "@lanonasis/security-sdk": "1.0.5",
28
+ "@modelcontextprotocol/sdk": "^1.25.2",
28
29
  "axios": "^1.7.7",
29
30
  "chalk": "^5.3.0",
30
31
  "cli-progress": "^3.12.0",
@@ -61,4 +62,4 @@
61
62
  "test:watch": "npm test -- --watch",
62
63
  "test:coverage": "npm test -- --coverage"
63
64
  }
64
- }
65
+ }