@lanonasis/cli 1.5.0 → 1.5.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.
@@ -1,41 +1,13 @@
1
1
  import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
3
  import ora from 'ora';
4
- import { createServer } from 'http';
5
- import { URL } from 'url';
6
- import open from 'open';
7
4
  import { apiClient } from '../utils/api.js';
8
5
  import { CLIConfig } from '../utils/config.js';
9
6
  export async function loginCommand(options) {
10
7
  const config = new CLIConfig();
11
8
  await config.init();
12
- console.log(chalk.blue.bold('šŸ” Login to Lanonasis Core Gateway'));
9
+ console.log(chalk.blue.bold('šŸ” Login to MaaS (Supabase Auth)'));
13
10
  console.log();
14
- // Check if API key is provided via environment or option
15
- const apiKey = process.env.LANONASIS_API_KEY;
16
- if (apiKey) {
17
- console.log(chalk.green('āœ“ Using API key authentication'));
18
- await config.setApiKey(apiKey);
19
- console.log(chalk.green('āœ“ Authentication configured successfully'));
20
- return;
21
- }
22
- // Choose authentication method
23
- const authMethod = await inquirer.prompt([
24
- {
25
- type: 'list',
26
- name: 'method',
27
- message: 'Choose authentication method:',
28
- choices: [
29
- { name: 'šŸ”‘ Username/Password (direct login)', value: 'password' },
30
- { name: '🌐 Web Browser (OAuth providers)', value: 'oauth' }
31
- ]
32
- }
33
- ]);
34
- if (authMethod.method === 'oauth') {
35
- await oauthLoginFlow(config);
36
- return;
37
- }
38
- // Password-based login flow
39
11
  let { email, password } = options;
40
12
  // Get credentials if not provided
41
13
  if (!email || !password) {
@@ -159,125 +131,3 @@ async function registerFlow(defaultEmail) {
159
131
  process.exit(1);
160
132
  }
161
133
  }
162
- async function oauthLoginFlow(config) {
163
- console.log(chalk.blue('🌐 OAuth Authentication'));
164
- console.log(chalk.gray('This will open your browser for secure authentication'));
165
- console.log();
166
- const port = 3721; // CLI callback port
167
- const redirectUri = `http://localhost:${port}/callback`;
168
- const state = Math.random().toString(36).substring(2, 15);
169
- // Construct OAuth URL
170
- const authUrl = new URL('https://api.lanonasis.com/v1/auth/oauth');
171
- authUrl.searchParams.set('redirect_uri', redirectUri);
172
- authUrl.searchParams.set('state', state);
173
- authUrl.searchParams.set('project_scope', 'maas');
174
- authUrl.searchParams.set('response_type', 'code');
175
- const spinner = ora('Starting OAuth flow...').start();
176
- try {
177
- // Start local callback server
178
- const server = createServer();
179
- let authCode = null;
180
- let authError = null;
181
- server.on('request', (req, res) => {
182
- const url = new URL(req.url, `http://localhost:${port}`);
183
- if (url.pathname === '/callback') {
184
- const code = url.searchParams.get('code');
185
- const error = url.searchParams.get('error');
186
- const returnedState = url.searchParams.get('state');
187
- // Validate state parameter
188
- if (returnedState !== state) {
189
- authError = 'Invalid state parameter';
190
- res.writeHead(400, { 'Content-Type': 'text/html' });
191
- res.end('<h1>Authentication Failed</h1><p>Invalid state parameter</p>');
192
- return;
193
- }
194
- if (error) {
195
- authError = url.searchParams.get('error_description') || error;
196
- res.writeHead(400, { 'Content-Type': 'text/html' });
197
- res.end(`<h1>Authentication Failed</h1><p>${authError}</p>`);
198
- }
199
- else if (code) {
200
- authCode = code;
201
- res.writeHead(200, { 'Content-Type': 'text/html' });
202
- res.end('<h1>Authentication Successful</h1><p>You can close this window and return to the CLI.</p>');
203
- }
204
- else {
205
- authError = 'No authorization code received';
206
- res.writeHead(400, { 'Content-Type': 'text/html' });
207
- res.end('<h1>Authentication Failed</h1><p>No authorization code received</p>');
208
- }
209
- server.close();
210
- }
211
- });
212
- // Start server
213
- await new Promise((resolve, reject) => {
214
- server.listen(port, (err) => {
215
- if (err)
216
- reject(err);
217
- else
218
- resolve();
219
- });
220
- });
221
- spinner.text = 'Opening browser for authentication...';
222
- // Open browser
223
- await open(authUrl.toString());
224
- console.log();
225
- console.log(chalk.yellow('Browser opened for authentication'));
226
- console.log(chalk.gray(`If browser doesn't open, visit: ${authUrl.toString()}`));
227
- console.log();
228
- spinner.text = 'Waiting for authentication...';
229
- // Wait for callback
230
- await new Promise((resolve, reject) => {
231
- server.on('close', () => {
232
- if (authCode)
233
- resolve();
234
- else
235
- reject(new Error(authError || 'Authentication failed'));
236
- });
237
- // Timeout after 5 minutes
238
- setTimeout(() => {
239
- server.close();
240
- reject(new Error('Authentication timeout'));
241
- }, 300000);
242
- });
243
- if (!authCode) {
244
- throw new Error(authError || 'No authorization code received');
245
- }
246
- spinner.text = 'Exchanging authorization code for session...';
247
- // Exchange code for session
248
- const response = await fetch('https://api.lanonasis.com/v1/auth/callback', {
249
- method: 'POST',
250
- headers: {
251
- 'Content-Type': 'application/json',
252
- 'x-project-scope': 'maas'
253
- },
254
- body: JSON.stringify({
255
- code: authCode,
256
- state: state,
257
- project_scope: 'maas'
258
- }),
259
- credentials: 'include'
260
- });
261
- if (!response.ok) {
262
- const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
263
- throw new Error(errorData.error || `HTTP ${response.status}`);
264
- }
265
- const sessionData = await response.json();
266
- // Store session token
267
- await config.setToken(sessionData.access_token);
268
- spinner.succeed('OAuth authentication successful');
269
- console.log();
270
- console.log(chalk.green('āœ“ Authenticated successfully via OAuth'));
271
- console.log(`Welcome, ${sessionData.user.email}!`);
272
- if (sessionData.user.organization_id) {
273
- console.log(`Organization: ${sessionData.user.organization_id}`);
274
- }
275
- console.log(`Plan: ${sessionData.user.plan || 'free'}`);
276
- }
277
- catch (error) {
278
- spinner.fail('OAuth authentication failed');
279
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
280
- console.error(chalk.red('āœ– OAuth failed:'), errorMessage);
281
- process.exit(1);
282
- }
283
- }
@@ -19,10 +19,9 @@ export function mcpCommands(program) {
19
19
  const config = new CLIConfig();
20
20
  const isAuthenticated = !!config.get('token');
21
21
  if (isAuthenticated) {
22
- console.log(chalk.green('āœ“ Authenticated - Using enterprise MCP modes'));
23
- console.log(' SSE Mode: api.lanonasis.com (regular users)');
24
- console.log(' WebSocket Mode: mcp.lanonasis.com (enterprise users)');
25
- console.log(' with real-time updates enabled');
22
+ console.log(chalk.green('āœ“ Authenticated - Using remote MCP mode'));
23
+ console.log(' Your memory operations will use api.lanonasis.com');
24
+ console.log(' with real-time SSE updates enabled');
26
25
  }
27
26
  else {
28
27
  console.log(chalk.yellow('āš ļø Not authenticated - Using local MCP mode'));
@@ -31,8 +30,7 @@ export function mcpCommands(program) {
31
30
  console.log('');
32
31
  console.log(chalk.cyan('Available MCP Commands:'));
33
32
  console.log(' lanonasis mcp connect # Auto-connect to best mode');
34
- console.log(' lanonasis mcp connect -r # Force remote SSE mode');
35
- console.log(' lanonasis mcp connect -w # Force WebSocket mode (enterprise)');
33
+ console.log(' lanonasis mcp connect -r # Force remote mode');
36
34
  console.log(' lanonasis mcp connect -l # Force local mode');
37
35
  console.log(' lanonasis mcp status # Check connection status');
38
36
  console.log(' lanonasis mcp tools # List available tools');
@@ -50,79 +48,74 @@ export function mcpCommands(program) {
50
48
  spinner.fail('Failed to auto-connect to MCP');
51
49
  }
52
50
  }
53
- catch (_error) {
51
+ catch {
54
52
  spinner.fail('MCP auto-connect failed');
55
53
  }
56
54
  });
57
55
  // Connect command
58
56
  mcp.command('connect')
59
- .description('Connect to MCP server (local, remote SSE, or WebSocket)')
57
+ .description('Connect to MCP server (local, remote, or WebSocket)')
60
58
  .option('-l, --local', 'Connect to local MCP server')
61
- .option('-r, --remote', 'Connect to remote SSE server (api.lanonasis.com)')
62
- .option('-w, --websocket', 'Connect to WebSocket server (mcp.lanonasis.com) - Enterprise')
59
+ .option('-r, --remote', 'Connect to remote MCP server (api.lanonasis.com)')
60
+ .option('-w, --websocket', 'Connect using WebSocket mode for enterprise users')
63
61
  .option('-s, --server <path>', 'Local MCP server path')
64
- .option('-u, --url <url>', 'Remote MCP server URL')
62
+ .option('-u, --url <url>', 'Remote/WebSocket server URL')
65
63
  .action(async (options) => {
66
64
  const spinner = ora('Connecting to MCP server...').start();
67
65
  const config = new CLIConfig();
68
66
  try {
69
- // Determine connection mode
70
- let connectionMode = 'local';
67
+ let connectionMode;
68
+ // Determine connection mode - WebSocket takes precedence over remote and local
71
69
  if (options.websocket) {
72
70
  connectionMode = 'websocket';
73
71
  }
74
72
  else if (options.remote) {
75
73
  connectionMode = 'remote';
76
74
  }
77
- else if (!options.local && !options.remote && !options.websocket) {
78
- // Auto-detect: WebSocket for enterprise, SSE for regular, local if not authenticated
79
- const token = config.get('token');
80
- if (token) {
81
- // Check if enterprise user (simplified check)
82
- connectionMode = 'remote'; // Default to SSE for now
83
- }
75
+ else if (options.local) {
76
+ connectionMode = 'local';
77
+ }
78
+ else {
79
+ // Default to remote if authenticated, otherwise local
80
+ connectionMode = !!config.get('token') ? 'remote' : 'local';
84
81
  }
85
- // Save preference
82
+ // Save preferences
86
83
  config.set('mcpConnectionMode', connectionMode);
87
84
  if (options.server) {
88
85
  config.set('mcpServerPath', options.server);
89
86
  }
90
87
  if (options.url) {
91
- config.set('mcpServerUrl', options.url);
88
+ if (connectionMode === 'websocket') {
89
+ config.set('mcpWebSocketUrl', options.url);
90
+ }
91
+ else {
92
+ config.set('mcpServerUrl', options.url);
93
+ }
92
94
  }
93
95
  const client = getMCPClient();
94
96
  const connected = await client.connect({
95
- mode: connectionMode,
97
+ connectionMode,
96
98
  serverPath: options.server,
97
99
  serverUrl: options.url
98
100
  });
99
101
  if (connected) {
100
- const modeLabels = {
101
- local: 'local',
102
- remote: 'remote SSE',
103
- websocket: 'WebSocket (Enterprise)'
104
- };
105
- spinner.succeed(chalk.green(`Connected to ${modeLabels[connectionMode]} MCP server`));
102
+ spinner.succeed(chalk.green(`Connected to MCP server in ${connectionMode} mode`));
106
103
  if (connectionMode === 'remote') {
107
104
  console.log(chalk.cyan('ā„¹ļø Using remote MCP via api.lanonasis.com'));
108
105
  console.log(chalk.cyan('šŸ“” SSE endpoint active for real-time updates'));
109
106
  }
110
107
  else if (connectionMode === 'websocket') {
111
- console.log(chalk.cyan('šŸš€ Using enterprise WebSocket MCP via mcp.lanonasis.com'));
112
- console.log(chalk.cyan('⚔ Real-time WebSocket connection active'));
113
- console.log(chalk.cyan('šŸ¢ Enterprise features enabled'));
108
+ console.log(chalk.cyan('ā„¹ļø Using enterprise WebSocket MCP server'));
109
+ console.log(chalk.cyan('šŸ“” WebSocket connection active with auto-reconnect'));
114
110
  }
115
111
  }
116
112
  else {
117
113
  spinner.fail('Failed to connect to MCP server');
118
- if (connectionMode === 'websocket') {
119
- console.log(chalk.yellow('šŸ’” WebSocket mode requires enterprise access'));
120
- console.log(chalk.yellow(' Try: lanonasis mcp connect -r (for SSE mode)'));
121
- }
122
114
  }
123
115
  }
124
116
  catch (error) {
125
117
  spinner.fail(`Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
118
+ process.exit(1);
126
119
  }
127
120
  });
128
121
  // Disconnect command
@@ -222,7 +215,7 @@ export function mcpCommands(program) {
222
215
  try {
223
216
  args = JSON.parse(options.args);
224
217
  }
225
- catch (error) {
218
+ catch {
226
219
  spinner.fail('Invalid JSON arguments');
227
220
  process.exit(1);
228
221
  }
@@ -6,8 +6,6 @@ import wrap from 'word-wrap';
6
6
  import { format } from 'date-fns';
7
7
  import { apiClient } from '../utils/api.js';
8
8
  import { formatBytes, truncateText } from '../utils/formatting.js';
9
- import { output } from '../utils/output.js';
10
- // Using GetMemoriesParams from api.ts
11
9
  export function memoryCommands(program) {
12
10
  // Create memory
13
11
  program
@@ -23,11 +21,7 @@ export function memoryCommands(program) {
23
21
  .action(async (options) => {
24
22
  try {
25
23
  let { title, content, type, tags, topicId, interactive } = options;
26
- // Validate required fields in silent mode
27
- if (output.isSilent() && (!title || !content)) {
28
- throw new Error('Title and content are required when using --output json or --silent');
29
- }
30
- if ((interactive || (!title || !content)) && !output.isSilent()) {
24
+ if (interactive || (!title || !content)) {
31
25
  const answers = await inquirer.prompt([
32
26
  {
33
27
  type: 'input',
@@ -62,7 +56,7 @@ export function memoryCommands(program) {
62
56
  type = answers.type;
63
57
  tags = answers.tags;
64
58
  }
65
- const spinner = output.isSilent() ? null : ora('Creating memory...').start();
59
+ const spinner = ora('Creating memory...').start();
66
60
  const memoryData = {
67
61
  title,
68
62
  content,
@@ -75,25 +69,19 @@ export function memoryCommands(program) {
75
69
  memoryData.topic_id = topicId;
76
70
  }
77
71
  const memory = await apiClient.createMemory(memoryData);
78
- if (spinner)
79
- spinner.succeed('Memory created successfully');
80
- if (output.isJsonOutput()) {
81
- output.json(memory);
82
- }
83
- else {
84
- console.log();
85
- console.log(chalk.green('āœ“ Memory created:'));
86
- console.log(` ID: ${chalk.cyan(memory.id)}`);
87
- console.log(` Title: ${memory.title}`);
88
- console.log(` Type: ${memory.memory_type}`);
89
- if (memory.tags && memory.tags.length > 0) {
90
- console.log(` Tags: ${memory.tags.join(', ')}`);
91
- }
72
+ spinner.succeed('Memory created successfully');
73
+ console.log();
74
+ console.log(chalk.green('āœ“ Memory created:'));
75
+ console.log(` ID: ${chalk.cyan(memory.id)}`);
76
+ console.log(` Title: ${memory.title}`);
77
+ console.log(` Type: ${memory.memory_type}`);
78
+ if (memory.tags && memory.tags.length > 0) {
79
+ console.log(` Tags: ${memory.tags.join(', ')}`);
92
80
  }
93
81
  }
94
82
  catch (error) {
95
83
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
96
- output.error(chalk.red('āœ– Failed to create memory:'), errorMessage);
84
+ console.error(chalk.red('āœ– Failed to create memory:'), errorMessage);
97
85
  process.exit(1);
98
86
  }
99
87
  });
@@ -111,41 +99,36 @@ export function memoryCommands(program) {
111
99
  .option('--order <order>', 'sort order (asc, desc)', 'desc')
112
100
  .action(async (options) => {
113
101
  try {
114
- const spinner = output.isSilent() ? null : ora('Fetching memories...').start();
102
+ const spinner = ora('Fetching memories...').start();
115
103
  const params = {
116
- offset: (parseInt(options.page || '1') - 1) * parseInt(options.limit || '20'),
104
+ page: parseInt(options.page || '1'),
117
105
  limit: parseInt(options.limit || '20'),
118
- sort_by: (options.sort || 'created_at'),
119
- sort_order: (options.order || 'desc')
106
+ sort: options.sort || 'created_at',
107
+ order: options.order || 'desc'
120
108
  };
121
109
  if (options.type)
122
110
  params.memory_type = options.type;
123
111
  if (options.tags)
124
- params.tags = options.tags.split(',').map(t => t.trim());
125
- // Note: user_id filtering removed as it's handled by authentication
112
+ params.tags = options.tags;
113
+ if (options.userId)
114
+ params.user_id = options.userId;
126
115
  const result = await apiClient.getMemories(params);
127
- if (spinner)
128
- spinner.stop();
129
- if (result.data.length === 0) {
130
- if (output.isJsonOutput()) {
131
- output.json({ data: [], pagination: result.pagination });
132
- }
133
- else {
134
- console.log(chalk.yellow('No memories found'));
135
- }
116
+ spinner.stop();
117
+ const memories = result.memories || result.data || [];
118
+ if (memories.length === 0) {
119
+ console.log(chalk.yellow('No memories found'));
136
120
  return;
137
121
  }
138
- if (output.isJsonOutput()) {
139
- output.json(result);
122
+ console.log(chalk.blue.bold(`\nšŸ“š Memories (${result.pagination.total} total)`));
123
+ console.log(chalk.gray(`Page ${result.pagination.page || 1} of ${result.pagination.pages || Math.ceil(result.pagination.total / result.pagination.limit)}`));
124
+ console.log();
125
+ const outputFormat = process.env.CLI_OUTPUT_FORMAT || 'table';
126
+ if (outputFormat === 'json') {
127
+ console.log(JSON.stringify(result, null, 2));
140
128
  }
141
129
  else {
142
- console.log(chalk.blue.bold(`\nšŸ“š Memories (${result.pagination.total} total)`));
143
- const currentPage = Math.floor(result.pagination.offset / result.pagination.limit) + 1;
144
- const totalPages = Math.ceil(result.pagination.total / result.pagination.limit);
145
- console.log(chalk.gray(`Page ${currentPage} of ${totalPages}`));
146
- console.log();
147
130
  // Table format
148
- const tableData = result.data.map((memory) => [
131
+ const tableData = memories.map((memory) => [
149
132
  truncateText(memory.title, 30),
150
133
  memory.memory_type,
151
134
  memory.tags.slice(0, 3).join(', '),
@@ -153,6 +136,7 @@ export function memoryCommands(program) {
153
136
  memory.access_count
154
137
  ]);
155
138
  const tableConfig = {
139
+ header: ['Title', 'Type', 'Tags', 'Created', 'Access'],
156
140
  columnDefault: {
157
141
  width: 20,
158
142
  wrapWord: true
@@ -165,12 +149,13 @@ export function memoryCommands(program) {
165
149
  { width: 8 }
166
150
  ]
167
151
  };
168
- const tableHeaders = ['Title', 'Type', 'Tags', 'Created', 'Access'];
169
- console.log(table([tableHeaders, ...tableData], tableConfig));
152
+ console.log(table([tableConfig.header, ...tableData], {
153
+ columnDefault: tableConfig.columnDefault,
154
+ columns: tableConfig.columns
155
+ }));
170
156
  // Pagination info
171
- if (result.pagination.has_more) {
172
- const nextPage = currentPage + 1;
173
- console.log(chalk.gray(`\nUse --page ${nextPage} for next page`));
157
+ if (result.pagination.pages > 1) {
158
+ console.log(chalk.gray(`\nUse --page ${result.pagination.page + 1} for next page`));
174
159
  }
175
160
  }
176
161
  }
@@ -191,7 +176,7 @@ export function memoryCommands(program) {
191
176
  .option('--tags <tags>', 'filter by tags (comma-separated)')
192
177
  .action(async (query, options) => {
193
178
  try {
194
- const spinner = output.isSilent() ? null : ora(`Searching for "${query}"...`).start();
179
+ const spinner = ora(`Searching for "${query}"...`).start();
195
180
  const searchOptions = {
196
181
  limit: parseInt(options.limit || '20'),
197
182
  threshold: parseFloat(options.threshold || '0.7')
@@ -203,35 +188,25 @@ export function memoryCommands(program) {
203
188
  searchOptions.tags = options.tags.split(',').map((t) => t.trim());
204
189
  }
205
190
  const result = await apiClient.searchMemories(query, searchOptions);
206
- if (spinner)
207
- spinner.stop();
208
- if (result.data.length === 0) {
209
- if (output.isJsonOutput()) {
210
- output.json({ data: [], pagination: result.pagination, query });
211
- }
212
- else {
213
- console.log(chalk.yellow('No memories found matching your search'));
214
- }
191
+ spinner.stop();
192
+ const results = result.results || result.data || [];
193
+ if (results.length === 0) {
194
+ console.log(chalk.yellow('No memories found matching your search'));
215
195
  return;
216
196
  }
217
- if (output.isJsonOutput()) {
218
- output.json({ ...result, query });
219
- }
220
- else {
221
- console.log(chalk.blue.bold(`\nšŸ” Search Results (${result.pagination.total} found)`));
222
- console.log(chalk.gray(`Query: "${query}"`));
197
+ console.log(chalk.blue.bold(`\nšŸ” Search Results (${result.total_results || results.length} found)`));
198
+ console.log(chalk.gray(`Query: "${query}" | Search time: ${result.search_time_ms || 0}ms`));
199
+ console.log();
200
+ results.forEach((memory, index) => {
201
+ const score = (memory.relevance_score * 100).toFixed(1);
202
+ console.log(chalk.green(`${index + 1}. ${memory.title}`) + chalk.gray(` (${score}% match)`));
203
+ console.log(chalk.white(` ${truncateText(memory.content, 100)}`));
204
+ console.log(chalk.cyan(` ID: ${memory.id}`) + chalk.gray(` | Type: ${memory.memory_type}`));
205
+ if (memory.tags.length > 0) {
206
+ console.log(chalk.yellow(` Tags: ${memory.tags.join(', ')}`));
207
+ }
223
208
  console.log();
224
- result.data.forEach((memory, index) => {
225
- const score = (memory.relevance_score * 100).toFixed(1);
226
- console.log(chalk.green(`${index + 1}. ${memory.title}`) + chalk.gray(` (${score}% match)`));
227
- console.log(chalk.white(` ${truncateText(memory.content, 100)}`));
228
- console.log(chalk.cyan(` ID: ${memory.id}`) + chalk.gray(` | Type: ${memory.memory_type}`));
229
- if (memory.tags.length > 0) {
230
- console.log(chalk.yellow(` Tags: ${memory.tags.join(', ')}`));
231
- }
232
- console.log();
233
- });
234
- }
209
+ });
235
210
  }
236
211
  catch (error) {
237
212
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';