@lanonasis/cli 3.7.1 → 3.7.3

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
@@ -34,6 +34,17 @@ onasis health # Verify system health
34
34
  onasis memory create --title "Welcome" --content "My first memory"
35
35
  ```
36
36
 
37
+ ## 🤖 Claude Desktop Integration
38
+
39
+ For instant Claude Desktop MCP integration with OAuth2 authentication, see our [Claude Desktop Setup Guide](./CLAUDE_DESKTOP_SETUP.md).
40
+
41
+ **Quick Links**:
42
+ - **Authorization Endpoint**: `https://auth.lanonasis.com/oauth/authorize`
43
+ - **Client ID**: `claude-desktop`
44
+ - **Scopes**: `mcp:full memories:read memories:write`
45
+
46
+ The guide includes complete OAuth2 configuration, available MCP tools, and troubleshooting steps.
47
+
37
48
  ## 🎯 Command Aliases
38
49
 
39
50
  The CLI supports multiple command aliases for different use cases:
@@ -305,6 +305,40 @@ async function refreshOAuth2Token(config) {
305
305
  return false;
306
306
  }
307
307
  }
308
+ /**
309
+ * Exchange Supabase JWT token for auth-gateway API key
310
+ * This enables CLI to work with MCP WebSocket and all services seamlessly
311
+ */
312
+ async function exchangeSupabaseTokenForApiKey(supabaseToken, config) {
313
+ try {
314
+ const discoveredServices = config.get('discoveredServices');
315
+ const authBase = discoveredServices?.auth_base || 'https://auth.lanonasis.com';
316
+ if (process.env.CLI_VERBOSE === 'true') {
317
+ console.log(chalk.dim(` Exchanging token at: ${authBase}/v1/auth/token/exchange`));
318
+ }
319
+ const response = await axios.post(`${authBase}/v1/auth/token/exchange`, {
320
+ project_scope: 'lanonasis-maas',
321
+ platform: 'cli'
322
+ }, {
323
+ headers: {
324
+ 'Authorization': `Bearer ${supabaseToken}`,
325
+ 'Content-Type': 'application/json',
326
+ 'X-Project-Scope': 'lanonasis-maas'
327
+ }
328
+ });
329
+ return {
330
+ access_token: response.data.access_token,
331
+ user: response.data.user
332
+ };
333
+ }
334
+ catch (error) {
335
+ console.error(chalk.yellow('⚠️ Token exchange failed:', error.message));
336
+ if (process.env.CLI_VERBOSE === 'true' && error.response) {
337
+ console.error(chalk.dim(' Response:', JSON.stringify(error.response.data, null, 2)));
338
+ }
339
+ return null;
340
+ }
341
+ }
308
342
  export async function diagnoseCommand() {
309
343
  const config = new CLIConfig();
310
344
  await config.init();
@@ -674,14 +708,33 @@ async function handleOAuthFlow(config) {
674
708
  }
675
709
  const tokens = await exchangeCodeForTokens(code, pkce.verifier, authBase, redirectUri);
676
710
  spinner.succeed('Access tokens received');
677
- // Store tokens
711
+ // Store OAuth tokens
678
712
  await config.setToken(tokens.access_token);
679
713
  await config.set('refresh_token', tokens.refresh_token);
680
714
  await config.set('token_expires_at', Date.now() + (tokens.expires_in * 1000));
681
- await config.set('authMethod', 'oauth2');
682
- console.log();
683
- console.log(chalk.green('✓ OAuth2 authentication successful'));
684
- console.log(colors.info('You can now use Lanonasis services'));
715
+ // Exchange for unified API key
716
+ spinner.text = 'Configuring unified access...';
717
+ spinner.start();
718
+ const exchangeResult = await exchangeSupabaseTokenForApiKey(tokens.access_token, config);
719
+ if (exchangeResult) {
720
+ // Store the auth-gateway API key for MCP and other services
721
+ await config.setVendorKey(exchangeResult.access_token);
722
+ await config.set('authMethod', 'oauth2');
723
+ spinner.succeed('Unified authentication configured');
724
+ console.log();
725
+ console.log(chalk.green('✓ OAuth2 authentication successful'));
726
+ console.log(colors.info('You can now use all Lanonasis services'));
727
+ console.log(chalk.gray('✓ MCP, API, and CLI access configured'));
728
+ }
729
+ else {
730
+ // Fallback
731
+ await config.set('authMethod', 'oauth2');
732
+ spinner.warn('Token exchange failed, OAuth token stored');
733
+ console.log();
734
+ console.log(chalk.green('✓ OAuth2 authentication successful'));
735
+ console.log(colors.info('You can now use Lanonasis services'));
736
+ console.log(chalk.yellow('⚠️ Some services may require re-authentication'));
737
+ }
685
738
  process.exit(0);
686
739
  }
687
740
  catch (error) {
@@ -766,16 +819,32 @@ async function handleCredentialsFlow(options, config) {
766
819
  const spinner = ora('Authenticating...').start();
767
820
  try {
768
821
  const response = await apiClient.login(email, password);
769
- // Store token and user info
770
- await config.setToken(response.token);
822
+ if (process.env.CLI_VERBOSE === 'true') {
823
+ console.log(chalk.dim(' Login response:'), JSON.stringify(response, null, 2));
824
+ }
825
+ // The auth-gateway login endpoint already returns the correct token format
826
+ // No need to exchange - this token works with all services (MCP, API, CLI)
827
+ const authToken = response.token || response.access_token;
828
+ if (!authToken) {
829
+ throw new Error('No token received from login response');
830
+ }
831
+ if (process.env.CLI_VERBOSE === 'true') {
832
+ console.log(chalk.dim(` JWT received (length: ${authToken.length})`));
833
+ }
834
+ // Store JWT token for API authentication
835
+ await config.setToken(authToken);
836
+ await config.set('authMethod', 'jwt');
771
837
  spinner.succeed('Login successful');
772
838
  console.log();
773
839
  console.log(chalk.green('✓ Authenticated successfully'));
774
840
  console.log(`Welcome, ${response.user.email}!`);
775
- if (response.user.organization_id) {
776
- console.log(`Organization: ${response.user.organization_id}`);
841
+ if (response.user.role) {
842
+ console.log(`Role: ${response.user.role}`);
777
843
  }
778
- console.log(`Plan: ${response.user.plan || 'free'}`);
844
+ console.log(chalk.gray('✓ API access configured'));
845
+ console.log();
846
+ console.log(chalk.dim('Note: MCP WebSocket commands require a vendor key.'));
847
+ console.log(chalk.dim('Run'), chalk.white('onasis auth vendor-key <key>'), chalk.dim('to configure MCP access.'));
779
848
  }
780
849
  catch (error) {
781
850
  spinner.fail('Login failed');
package/dist/utils/api.js CHANGED
@@ -101,64 +101,64 @@ export class APIClient {
101
101
  }
102
102
  // Memory operations - aligned with existing schema
103
103
  async createMemory(data) {
104
- const response = await this.client.post('/api/v1/memory', data);
104
+ const response = await this.client.post('/memory', data);
105
105
  return response.data;
106
106
  }
107
107
  async getMemories(params = {}) {
108
- const response = await this.client.get('/api/v1/memory', { params });
108
+ const response = await this.client.get('/memory', { params });
109
109
  return response.data;
110
110
  }
111
111
  async getMemory(id) {
112
- const response = await this.client.get(`/api/v1/memory/${id}`);
112
+ const response = await this.client.get(`/memory/${id}`);
113
113
  return response.data;
114
114
  }
115
115
  async updateMemory(id, data) {
116
- const response = await this.client.put(`/api/v1/memory/${id}`, data);
116
+ const response = await this.client.put(`/memory/${id}`, data);
117
117
  return response.data;
118
118
  }
119
119
  async deleteMemory(id) {
120
- await this.client.delete(`/api/v1/memory/${id}`);
120
+ await this.client.delete(`/memory/${id}`);
121
121
  }
122
122
  async searchMemories(query, options = {}) {
123
- const response = await this.client.post('/api/v1/memory/search', {
123
+ const response = await this.client.post('/memory/search', {
124
124
  query,
125
125
  ...options
126
126
  });
127
127
  return response.data;
128
128
  }
129
129
  async getMemoryStats() {
130
- const response = await this.client.get('/api/v1/memory/stats');
130
+ const response = await this.client.get('/memory/stats');
131
131
  return response.data;
132
132
  }
133
133
  async bulkDeleteMemories(memoryIds) {
134
- const response = await this.client.post('/api/v1/memory/bulk/delete', {
134
+ const response = await this.client.post('/memory/bulk/delete', {
135
135
  memory_ids: memoryIds
136
136
  });
137
137
  return response.data;
138
138
  }
139
139
  // Topic operations - working with existing memory_topics table
140
140
  async createTopic(data) {
141
- const response = await this.client.post('/api/v1/topics', data);
141
+ const response = await this.client.post('/topics', data);
142
142
  return response.data;
143
143
  }
144
144
  async getTopics() {
145
- const response = await this.client.get('/api/v1/topics');
145
+ const response = await this.client.get('/topics');
146
146
  return response.data;
147
147
  }
148
148
  async getTopic(id) {
149
- const response = await this.client.get(`/api/v1/topics/${id}`);
149
+ const response = await this.client.get(`/topics/${id}`);
150
150
  return response.data;
151
151
  }
152
152
  async updateTopic(id, data) {
153
- const response = await this.client.put(`/api/v1/topics/${id}`, data);
153
+ const response = await this.client.put(`/topics/${id}`, data);
154
154
  return response.data;
155
155
  }
156
156
  async deleteTopic(id) {
157
- await this.client.delete(`/api/v1/topics/${id}`);
157
+ await this.client.delete(`/topics/${id}`);
158
158
  }
159
159
  // Health check
160
160
  async getHealth() {
161
- const response = await this.client.get('/api/v1/health');
161
+ const response = await this.client.get('/health');
162
162
  return response.data;
163
163
  }
164
164
  // Generic HTTP methods
@@ -61,6 +61,7 @@ export declare class CLIConfig {
61
61
  private acquireLock;
62
62
  private releaseLock;
63
63
  getApiUrl(): string;
64
+ getApiUrlsWithFallbacks(): string[];
64
65
  discoverServices(verbose?: boolean): Promise<void>;
65
66
  private handleServiceDiscoveryFailure;
66
67
  private categorizeServiceDiscoveryError;
@@ -169,7 +169,17 @@ export class CLIConfig {
169
169
  getApiUrl() {
170
170
  return process.env.MEMORY_API_URL ||
171
171
  this.config.apiUrl ||
172
- 'https://mcp.lanonasis.com/api/v1';
172
+ 'https://api.lanonasis.com';
173
+ }
174
+ // Get API URLs with fallbacks - try multiple endpoints
175
+ getApiUrlsWithFallbacks() {
176
+ const primary = this.getApiUrl();
177
+ const fallbacks = [
178
+ 'https://api.lanonasis.com',
179
+ 'https://mcp.lanonasis.com'
180
+ ];
181
+ // Remove duplicates and return primary first
182
+ return [primary, ...fallbacks.filter(url => url !== primary)];
173
183
  }
174
184
  // Enhanced Service Discovery Integration
175
185
  async discoverServices(verbose = false) {
@@ -187,21 +197,45 @@ export class CLIConfig {
187
197
  }
188
198
  return;
189
199
  }
190
- const discoveryUrl = 'https://mcp.lanonasis.com/.well-known/onasis.json';
191
- try {
192
- // Use axios instead of fetch for consistency
193
- const axios = (await import('axios')).default;
194
- if (verbose) {
195
- console.log(`🔍 Discovering services from ${discoveryUrl}...`);
196
- }
197
- const response = await axios.get(discoveryUrl, {
198
- timeout: 10000,
199
- maxRedirects: 5,
200
- proxy: false, // Bypass proxy to avoid redirect loops
201
- headers: {
202
- 'User-Agent': 'Lanonasis-CLI/3.0.13'
200
+ // Try multiple discovery URLs with fallbacks
201
+ const discoveryUrls = [
202
+ 'https://api.lanonasis.com/.well-known/onasis.json',
203
+ 'https://mcp.lanonasis.com/.well-known/onasis.json'
204
+ ];
205
+ let response = null;
206
+ let lastError = null;
207
+ // Use axios instead of fetch for consistency
208
+ const axios = (await import('axios')).default;
209
+ for (const discoveryUrl of discoveryUrls) {
210
+ try {
211
+ if (verbose) {
212
+ console.log(`🔍 Discovering services from ${discoveryUrl}...`);
203
213
  }
204
- });
214
+ response = await axios.get(discoveryUrl, {
215
+ timeout: 10000,
216
+ maxRedirects: 5,
217
+ proxy: false, // Bypass proxy to avoid redirect loops
218
+ headers: {
219
+ 'User-Agent': 'Lanonasis-CLI/3.0.13'
220
+ }
221
+ });
222
+ if (verbose) {
223
+ console.log(`✓ Successfully discovered services from ${discoveryUrl}`);
224
+ }
225
+ break; // Success, exit loop
226
+ }
227
+ catch (err) {
228
+ lastError = err;
229
+ if (verbose) {
230
+ console.log(`⚠️ Failed to discover from ${discoveryUrl}, trying next...`);
231
+ }
232
+ continue;
233
+ }
234
+ }
235
+ if (!response) {
236
+ throw lastError || new Error('All service discovery URLs failed');
237
+ }
238
+ try {
205
239
  // Map discovery response to our config format
206
240
  const discovered = response.data;
207
241
  // Extract auth base, but filter out localhost URLs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanonasis/cli",
3
- "version": "3.7.1",
3
+ "version": "3.7.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "LICENSE"
23
23
  ],
24
24
  "dependencies": {
25
- "@lanonasis/oauth-client": "^1.0.0",
25
+ "@lanonasis/oauth-client": "^1.2.1",
26
26
  "@lanonasis/security-sdk": "^1.0.1",
27
27
  "@modelcontextprotocol/sdk": "^1.1.1",
28
28
  "axios": "^1.7.7",