@lanonasis/cli 3.9.0 → 3.9.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.
- package/CHANGELOG.md +29 -0
- package/README.md +3 -1
- package/dist/commands/auth.js +10 -11
- package/dist/commands/mcp.js +35 -5
- package/dist/utils/api.js +11 -1
- package/dist/utils/mcp-client.d.ts +5 -0
- package/dist/utils/mcp-client.js +19 -3
- package/dist/ux/implementations/ConnectionManagerImpl.js +5 -1
- package/dist/ux/implementations/TextInputHandlerImpl.js +20 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog - @lanonasis/cli
|
|
2
2
|
|
|
3
|
+
## [3.9.1] - 2026-02-01
|
|
4
|
+
|
|
5
|
+
### 🔐 Authentication Fixes
|
|
6
|
+
|
|
7
|
+
- **OAuth Scope Clarification**: OAuth login now clearly states it enables MCP integration only
|
|
8
|
+
- **Improved Error Messages**: 401 errors for OAuth users include specific guidance for direct API access
|
|
9
|
+
- **Removed Misleading Storage**: OAuth tokens are no longer incorrectly stored as vendor keys
|
|
10
|
+
- **Documentation Updates**: README and in-CLI help clarify authentication method differences
|
|
11
|
+
|
|
12
|
+
### 🐛 Bug Fixes
|
|
13
|
+
|
|
14
|
+
- Fixed confusing error message when OAuth users try to use direct CLI commands
|
|
15
|
+
- Removed `.lanonasis/mcp-config.json` from version control
|
|
16
|
+
|
|
3
17
|
## [3.9.0] - 2026-02-01
|
|
4
18
|
|
|
5
19
|
### 🎨 CLI UX Revolution
|
|
@@ -26,6 +40,21 @@
|
|
|
26
40
|
- **User Preferences**: Captures and persists input mode, editor choice, and behavior preferences
|
|
27
41
|
- **Troubleshooting Guidance**: Context-aware help when issues are detected
|
|
28
42
|
|
|
43
|
+
### 🔐 Authentication Clarifications
|
|
44
|
+
|
|
45
|
+
#### OAuth vs Direct API Access
|
|
46
|
+
- **Clear Scope Documentation**: OAuth2 login now explicitly states it enables MCP integration
|
|
47
|
+
- **Improved Error Messages**: 401 errors for OAuth users include specific guidance for direct API access
|
|
48
|
+
- **Authentication Method Guidance**: CLI provides clear instructions for:
|
|
49
|
+
- **OAuth**: Use for MCP integration and real-time features
|
|
50
|
+
- **Vendor Key**: Obtain from dashboard for direct API access (`lanonasis auth login --vendor`)
|
|
51
|
+
- **Credentials**: Use username/password for direct API access (`lanonasis auth login --credentials`)
|
|
52
|
+
|
|
53
|
+
#### Secure Storage Fallback
|
|
54
|
+
- **Keytar Optional**: When keytar (native secure storage) is unavailable, CLI gracefully falls back to encrypted file storage
|
|
55
|
+
- **Cross-Platform**: Encrypted storage works consistently across all platforms
|
|
56
|
+
- **No Data Loss**: Credentials are preserved in `~/.lanonasis/api-key.enc` with AES-256-GCM encryption
|
|
57
|
+
|
|
29
58
|
### 🐛 Critical Bug Fixes (PR #93)
|
|
30
59
|
|
|
31
60
|
#### P1: Connection Verification False Positive
|
package/README.md
CHANGED
|
@@ -156,12 +156,14 @@ onasis login --vendor-key <your-vendor-key>
|
|
|
156
156
|
|
|
157
157
|
### 2. OAuth Browser Authentication
|
|
158
158
|
|
|
159
|
-
Secure browser-based authentication:
|
|
159
|
+
Secure browser-based authentication for MCP integration:
|
|
160
160
|
|
|
161
161
|
```bash
|
|
162
162
|
onasis login --oauth
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
> **Note**: OAuth authentication enables MCP integration features (real-time updates, WebSocket connections). For direct CLI memory commands (`memory list`, `memory create`, etc.), use vendor key or credentials authentication.
|
|
166
|
+
|
|
165
167
|
### 3. Interactive Credentials
|
|
166
168
|
|
|
167
169
|
Traditional username/password authentication:
|
package/dist/commands/auth.js
CHANGED
|
@@ -702,23 +702,22 @@ async function handleOAuthFlow(config) {
|
|
|
702
702
|
}
|
|
703
703
|
const tokens = await exchangeCodeForTokens(code, pkce.verifier, authBase, redirectUri);
|
|
704
704
|
spinner.succeed('Access tokens received');
|
|
705
|
-
// Store OAuth tokens - these are
|
|
706
|
-
//
|
|
705
|
+
// Store OAuth tokens - these are auth-gateway tokens from /oauth/token
|
|
706
|
+
// Note: OAuth tokens are valid for MCP services but not for direct API access
|
|
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
710
|
await config.set('authMethod', 'oauth');
|
|
711
|
-
|
|
712
|
-
// Store it as the vendor key equivalent for MCP and API access
|
|
713
|
-
spinner.text = 'Configuring unified access...';
|
|
714
|
-
spinner.start();
|
|
715
|
-
// Use the OAuth access token directly - it's already an auth-gateway token
|
|
716
|
-
await config.setVendorKey(tokens.access_token);
|
|
717
|
-
spinner.succeed('Unified authentication configured');
|
|
711
|
+
spinner.succeed('OAuth tokens stored');
|
|
718
712
|
console.log();
|
|
719
713
|
console.log(chalk.green('✓ OAuth2 authentication successful'));
|
|
720
|
-
console.log(colors.info('You can now use
|
|
721
|
-
console.log(
|
|
714
|
+
console.log(colors.info('You can now use MCP integration features'));
|
|
715
|
+
console.log();
|
|
716
|
+
console.log(chalk.yellow('Note: ') + chalk.gray('OAuth login enables MCP integration.'));
|
|
717
|
+
console.log(chalk.gray('For direct CLI memory commands, use:'));
|
|
718
|
+
console.log(chalk.cyan(' lanonasis auth login --vendor') + chalk.gray(' (get a vendor key from dashboard)'));
|
|
719
|
+
console.log(chalk.gray(' OR'));
|
|
720
|
+
console.log(chalk.cyan(' lanonasis auth login --credentials') + chalk.gray(' (use username/password)'));
|
|
722
721
|
process.exit(0);
|
|
723
722
|
}
|
|
724
723
|
catch (error) {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -212,12 +212,38 @@ export function mcpCommands(program) {
|
|
|
212
212
|
let healthLabel = chalk.gray('Unknown');
|
|
213
213
|
let healthDetails;
|
|
214
214
|
let isServiceReachable = false;
|
|
215
|
+
let resolvedHealthUrl;
|
|
215
216
|
try {
|
|
216
217
|
const axios = (await import('axios')).default;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
const normalizeMcpHealthUrl = (inputUrl) => {
|
|
219
|
+
const parsed = new URL(inputUrl);
|
|
220
|
+
if (parsed.protocol === 'wss:') {
|
|
221
|
+
parsed.protocol = 'https:';
|
|
222
|
+
}
|
|
223
|
+
else if (parsed.protocol === 'ws:') {
|
|
224
|
+
parsed.protocol = 'http:';
|
|
225
|
+
}
|
|
226
|
+
parsed.pathname = '/health';
|
|
227
|
+
parsed.search = '';
|
|
228
|
+
parsed.hash = '';
|
|
229
|
+
return parsed.toString();
|
|
230
|
+
};
|
|
231
|
+
// Prefer MCP host health based on active mode:
|
|
232
|
+
// - websocket: use configured websocket host (wss -> https)
|
|
233
|
+
// - remote: use configured MCP REST host
|
|
234
|
+
// - local/default: fall back to discovered MCP REST host
|
|
235
|
+
let healthProbeBase;
|
|
236
|
+
if (status.mode === 'websocket') {
|
|
237
|
+
healthProbeBase = config.get('mcpWebSocketUrl') ?? config.getMCPServerUrl();
|
|
238
|
+
}
|
|
239
|
+
else if (status.mode === 'remote') {
|
|
240
|
+
healthProbeBase = config.get('mcpServerUrl') ?? config.getMCPRestUrl();
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
healthProbeBase = config.getMCPRestUrl();
|
|
244
|
+
}
|
|
245
|
+
const healthUrl = normalizeMcpHealthUrl(healthProbeBase);
|
|
246
|
+
resolvedHealthUrl = healthUrl;
|
|
221
247
|
const token = config.getToken();
|
|
222
248
|
const vendorKey = await config.getVendorKeyAsync();
|
|
223
249
|
const headers = {};
|
|
@@ -234,7 +260,8 @@ export function mcpCommands(program) {
|
|
|
234
260
|
timeout: 5000
|
|
235
261
|
});
|
|
236
262
|
const overallStatus = String(response.data?.status ?? '').toLowerCase();
|
|
237
|
-
const
|
|
263
|
+
const okStatuses = new Set(['healthy', 'ok', 'up']);
|
|
264
|
+
const ok = response.status === 200 && (!overallStatus || okStatuses.has(overallStatus));
|
|
238
265
|
if (ok) {
|
|
239
266
|
healthLabel = chalk.green('Healthy');
|
|
240
267
|
isServiceReachable = true;
|
|
@@ -285,6 +312,9 @@ export function mcpCommands(program) {
|
|
|
285
312
|
if (healthDetails && process.env.CLI_VERBOSE === 'true') {
|
|
286
313
|
console.log(chalk.gray(`Health details: ${healthDetails}`));
|
|
287
314
|
}
|
|
315
|
+
if (resolvedHealthUrl && process.env.CLI_VERBOSE === 'true') {
|
|
316
|
+
console.log(chalk.gray(`Health probe URL: ${resolvedHealthUrl}`));
|
|
317
|
+
}
|
|
288
318
|
// Show features when service is reachable
|
|
289
319
|
if (isServiceReachable) {
|
|
290
320
|
if (status.mode === 'remote') {
|
package/dist/utils/api.js
CHANGED
|
@@ -60,7 +60,17 @@ export class APIClient {
|
|
|
60
60
|
const { status, data } = error.response;
|
|
61
61
|
if (status === 401) {
|
|
62
62
|
console.error(chalk.red('✖ Authentication failed'));
|
|
63
|
-
|
|
63
|
+
// Check if user is using OAuth - OAuth tokens only work with MCP, not direct API
|
|
64
|
+
const authMethod = this.config.get('authMethod');
|
|
65
|
+
if (authMethod === 'oauth') {
|
|
66
|
+
console.log(chalk.yellow('\nNote: OAuth tokens are for MCP integration only.'));
|
|
67
|
+
console.log(chalk.gray('For direct API access, you have two options:'));
|
|
68
|
+
console.log(chalk.gray(' 1. Get a vendor key from the dashboard: ') + chalk.cyan('lanonasis auth login --vendor'));
|
|
69
|
+
console.log(chalk.gray(' 2. Login with username/password: ') + chalk.cyan('lanonasis auth login --credentials'));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
|
|
73
|
+
}
|
|
64
74
|
process.exit(1);
|
|
65
75
|
}
|
|
66
76
|
if (status === 403) {
|
|
@@ -191,6 +191,11 @@ export declare class MCPClient {
|
|
|
191
191
|
* Check if connected to MCP server
|
|
192
192
|
*/
|
|
193
193
|
isConnectedToServer(): boolean;
|
|
194
|
+
/**
|
|
195
|
+
* Determine whether tool operations should use the remote REST bridge.
|
|
196
|
+
* WebSocket mode uses the same bridge for tool list/call operations.
|
|
197
|
+
*/
|
|
198
|
+
private shouldUseRemoteToolBridge;
|
|
194
199
|
/**
|
|
195
200
|
* Get connection status details with health information
|
|
196
201
|
*/
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -68,6 +68,7 @@ export class MCPClient {
|
|
|
68
68
|
// Save the successful connection mode as preference
|
|
69
69
|
this.config.set('mcpConnectionMode', mode);
|
|
70
70
|
this.config.set('mcpPreference', mode);
|
|
71
|
+
this.config.set('mcpUseRemote', mode === 'remote' || mode === 'websocket');
|
|
71
72
|
// Save the specific URL that worked
|
|
72
73
|
if (url) {
|
|
73
74
|
if (mode === 'websocket') {
|
|
@@ -525,7 +526,12 @@ export class MCPClient {
|
|
|
525
526
|
this.wsConnection.on('message', (data) => {
|
|
526
527
|
try {
|
|
527
528
|
const message = JSON.parse(data.toString());
|
|
528
|
-
|
|
529
|
+
const messageId = message.id ?? 'event';
|
|
530
|
+
const messageType = message.method
|
|
531
|
+
|| (message.error ? 'error' : undefined)
|
|
532
|
+
|| (message.result ? 'result' : undefined)
|
|
533
|
+
|| 'response';
|
|
534
|
+
console.log(chalk.blue('📡 MCP message:'), messageId, messageType);
|
|
529
535
|
}
|
|
530
536
|
catch (error) {
|
|
531
537
|
console.error('Failed to parse WebSocket message:', error);
|
|
@@ -740,7 +746,7 @@ export class MCPClient {
|
|
|
740
746
|
if (!this.isConnected) {
|
|
741
747
|
throw new Error('Not connected to MCP server. Run "lanonasis mcp connect" first.');
|
|
742
748
|
}
|
|
743
|
-
const useRemote = this.
|
|
749
|
+
const useRemote = this.shouldUseRemoteToolBridge();
|
|
744
750
|
if (useRemote) {
|
|
745
751
|
// Remote MCP calls are translated to REST API calls
|
|
746
752
|
return await this.callRemoteTool(toolName, args);
|
|
@@ -854,7 +860,7 @@ export class MCPClient {
|
|
|
854
860
|
if (!this.isConnected) {
|
|
855
861
|
throw new Error('Not connected to MCP server');
|
|
856
862
|
}
|
|
857
|
-
const useRemote = this.
|
|
863
|
+
const useRemote = this.shouldUseRemoteToolBridge();
|
|
858
864
|
if (useRemote) {
|
|
859
865
|
// Return hardcoded list for remote mode
|
|
860
866
|
return [
|
|
@@ -883,6 +889,16 @@ export class MCPClient {
|
|
|
883
889
|
isConnectedToServer() {
|
|
884
890
|
return this.isConnected;
|
|
885
891
|
}
|
|
892
|
+
/**
|
|
893
|
+
* Determine whether tool operations should use the remote REST bridge.
|
|
894
|
+
* WebSocket mode uses the same bridge for tool list/call operations.
|
|
895
|
+
*/
|
|
896
|
+
shouldUseRemoteToolBridge() {
|
|
897
|
+
if (this.activeConnectionMode === 'remote' || this.activeConnectionMode === 'websocket') {
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
900
|
+
return this.config.get('mcpUseRemote') ?? false;
|
|
901
|
+
}
|
|
886
902
|
/**
|
|
887
903
|
* Get connection status details with health information
|
|
888
904
|
*/
|
|
@@ -202,6 +202,10 @@ export class ConnectionManagerImpl {
|
|
|
202
202
|
LOG_LEVEL: this.config.logLevel,
|
|
203
203
|
},
|
|
204
204
|
});
|
|
205
|
+
if (serverProcess.pid === undefined) {
|
|
206
|
+
reject(new Error('Failed to start local MCP server: process ID was not assigned'));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
205
209
|
const serverInstance = {
|
|
206
210
|
pid: serverProcess.pid,
|
|
207
211
|
port: this.config.serverPort || 3000,
|
|
@@ -291,7 +295,7 @@ export class ConnectionManagerImpl {
|
|
|
291
295
|
this.serverProcess.kill('SIGKILL');
|
|
292
296
|
}
|
|
293
297
|
}, 5000);
|
|
294
|
-
this.serverProcess.
|
|
298
|
+
this.serverProcess.once('exit', () => {
|
|
295
299
|
clearTimeout(forceKillTimeout);
|
|
296
300
|
this.serverProcess = null;
|
|
297
301
|
if (this.connectionStatus.serverInstance) {
|
|
@@ -47,10 +47,17 @@ export class TextInputHandlerImpl {
|
|
|
47
47
|
status: 'active',
|
|
48
48
|
};
|
|
49
49
|
return new Promise((resolve, reject) => {
|
|
50
|
+
let handleKeypress = null;
|
|
51
|
+
const cleanup = () => {
|
|
52
|
+
if (handleKeypress) {
|
|
53
|
+
process.stdin.removeListener('data', handleKeypress);
|
|
54
|
+
}
|
|
55
|
+
this.disableRawMode();
|
|
56
|
+
};
|
|
50
57
|
try {
|
|
51
58
|
this.enableRawMode();
|
|
52
|
-
this.displayInputPrompt(
|
|
53
|
-
|
|
59
|
+
this.displayInputPrompt(this.getCurrentContent());
|
|
60
|
+
handleKeypress = (chunk) => {
|
|
54
61
|
const key = this.parseKeyEvent(chunk);
|
|
55
62
|
if (this.handleSpecialKeys(key)) {
|
|
56
63
|
return;
|
|
@@ -61,10 +68,6 @@ export class TextInputHandlerImpl {
|
|
|
61
68
|
this.displayInputPrompt(this.getCurrentContent());
|
|
62
69
|
}
|
|
63
70
|
};
|
|
64
|
-
const cleanup = () => {
|
|
65
|
-
process.stdin.removeListener('data', handleKeypress);
|
|
66
|
-
this.disableRawMode();
|
|
67
|
-
};
|
|
68
71
|
// Set up completion handlers
|
|
69
72
|
const complete = (result) => {
|
|
70
73
|
cleanup();
|
|
@@ -86,7 +89,7 @@ export class TextInputHandlerImpl {
|
|
|
86
89
|
process.stdin.on('data', handleKeypress);
|
|
87
90
|
}
|
|
88
91
|
catch (error) {
|
|
89
|
-
|
|
92
|
+
cleanup();
|
|
90
93
|
reject(error);
|
|
91
94
|
}
|
|
92
95
|
});
|
|
@@ -322,15 +325,17 @@ export class TextInputHandlerImpl {
|
|
|
322
325
|
}
|
|
323
326
|
break;
|
|
324
327
|
case 'right':
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
cursorPosition.column
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
cursorPosition.line
|
|
331
|
-
|
|
328
|
+
{
|
|
329
|
+
const currentLineLength = content[cursorPosition.line]?.length || 0;
|
|
330
|
+
if (cursorPosition.column < currentLineLength) {
|
|
331
|
+
cursorPosition.column++;
|
|
332
|
+
}
|
|
333
|
+
else if (cursorPosition.line < content.length - 1) {
|
|
334
|
+
cursorPosition.line++;
|
|
335
|
+
cursorPosition.column = 0;
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
332
338
|
}
|
|
333
|
-
break;
|
|
334
339
|
}
|
|
335
340
|
}
|
|
336
341
|
/**
|
package/package.json
CHANGED