@lanonasis/cli 3.8.0 → 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.
@@ -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...';
@@ -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)
@@ -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,7 @@ 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);
459
478
  });
460
479
  // Start MCP server for external clients
461
480
  mcp.command('start')
@@ -790,5 +809,6 @@ export function mcpCommands(program) {
790
809
  console.log(chalk.gray(' • Verify your network allows outbound HTTPS connections'));
791
810
  console.log(chalk.gray(' • Contact support if issues persist'));
792
811
  }
812
+ process.exit(0);
793
813
  });
794
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'
@@ -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
  });
package/dist/utils/api.js CHANGED
@@ -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;
@@ -499,7 +499,11 @@ export class CLIConfig {
499
499
  }
500
500
  // Store a reference marker in config (not the actual key)
501
501
  this.config.vendorKey = 'stored_in_api_key_storage';
502
- 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
+ }
503
507
  this.config.lastValidated = new Date().toISOString();
504
508
  await this.resetFailureCount(); // Reset failure count on successful auth
505
509
  await this.save();
@@ -692,11 +696,41 @@ export class CLIConfig {
692
696
  this.authCheckCache = { isValid: true, timestamp: Date.now() };
693
697
  return true;
694
698
  }
695
- // For vendor keys, we trust that they were validated during setVendorKey()
696
- // and rely on the lastValidated timestamp. For additional security,
697
- // the server should revoke keys that are invalid.
698
- this.authCheckCache = { isValid: true, timestamp: Date.now() };
699
- 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
+ }
700
734
  }
701
735
  // Handle token-based authentication
702
736
  const token = this.getToken();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",