@lanonasis/cli 3.9.0 → 3.9.2
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 +38 -0
- package/README.md +3 -1
- package/dist/commands/auth.js +4 -0
- package/dist/commands/mcp.js +35 -5
- 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,28 @@
|
|
|
1
1
|
# Changelog - @lanonasis/cli
|
|
2
2
|
|
|
3
|
+
## [3.9.2] - 2026-02-02
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **Auth Method Override**: Fixed vendor key authentication not overriding previous OAuth `authMethod`
|
|
8
|
+
- When users explicitly authenticate with vendor key after OAuth, the `authMethod` is now correctly set to `vendor_key`
|
|
9
|
+
- This fixes "Authenticated: No" status after successful vendor key authentication
|
|
10
|
+
- Reverted changes from 3.9.1 that incorrectly removed vendor key storage from OAuth flow
|
|
11
|
+
|
|
12
|
+
## [3.9.1] - 2026-02-01
|
|
13
|
+
|
|
14
|
+
### 🔐 Authentication Fixes
|
|
15
|
+
|
|
16
|
+
- **OAuth Scope Clarification**: OAuth login now clearly states it enables MCP integration only
|
|
17
|
+
- **Improved Error Messages**: 401 errors for OAuth users include specific guidance for direct API access
|
|
18
|
+
- **Removed Misleading Storage**: OAuth tokens are no longer incorrectly stored as vendor keys
|
|
19
|
+
- **Documentation Updates**: README and in-CLI help clarify authentication method differences
|
|
20
|
+
|
|
21
|
+
### 🐛 Bug Fixes
|
|
22
|
+
|
|
23
|
+
- Fixed confusing error message when OAuth users try to use direct CLI commands
|
|
24
|
+
- Removed `.lanonasis/mcp-config.json` from version control
|
|
25
|
+
|
|
3
26
|
## [3.9.0] - 2026-02-01
|
|
4
27
|
|
|
5
28
|
### 🎨 CLI UX Revolution
|
|
@@ -26,6 +49,21 @@
|
|
|
26
49
|
- **User Preferences**: Captures and persists input mode, editor choice, and behavior preferences
|
|
27
50
|
- **Troubleshooting Guidance**: Context-aware help when issues are detected
|
|
28
51
|
|
|
52
|
+
### 🔐 Authentication Clarifications
|
|
53
|
+
|
|
54
|
+
#### OAuth vs Direct API Access
|
|
55
|
+
- **Clear Scope Documentation**: OAuth2 login now explicitly states it enables MCP integration
|
|
56
|
+
- **Improved Error Messages**: 401 errors for OAuth users include specific guidance for direct API access
|
|
57
|
+
- **Authentication Method Guidance**: CLI provides clear instructions for:
|
|
58
|
+
- **OAuth**: Use for MCP integration and real-time features
|
|
59
|
+
- **Vendor Key**: Obtain from dashboard for direct API access (`lanonasis auth login --vendor`)
|
|
60
|
+
- **Credentials**: Use username/password for direct API access (`lanonasis auth login --credentials`)
|
|
61
|
+
|
|
62
|
+
#### Secure Storage Fallback
|
|
63
|
+
- **Keytar Optional**: When keytar (native secure storage) is unavailable, CLI gracefully falls back to encrypted file storage
|
|
64
|
+
- **Cross-Platform**: Encrypted storage works consistently across all platforms
|
|
65
|
+
- **No Data Loss**: Credentials are preserved in `~/.lanonasis/api-key.enc` with AES-256-GCM encryption
|
|
66
|
+
|
|
29
67
|
### 🐛 Critical Bug Fixes (PR #93)
|
|
30
68
|
|
|
31
69
|
#### 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
|
@@ -606,6 +606,10 @@ async function handleVendorKeyAuth(vendorKey, config) {
|
|
|
606
606
|
const spinner = ora('Validating vendor key...').start();
|
|
607
607
|
try {
|
|
608
608
|
await config.setVendorKey(vendorKey);
|
|
609
|
+
// Explicitly set authMethod to vendor_key when user does explicit vendor key auth
|
|
610
|
+
// This overrides any previous OAuth authMethod
|
|
611
|
+
await config.set('authMethod', 'vendor_key');
|
|
612
|
+
await config.save();
|
|
609
613
|
// Test the vendor key with a health check
|
|
610
614
|
await apiClient.get('/health');
|
|
611
615
|
spinner.succeed('Vendor key authentication successful');
|
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') {
|
|
@@ -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