@lanonasis/cli 3.4.15 → 3.6.0
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 +68 -0
- package/dist/commands/auth.js +183 -121
- package/dist/commands/completion.js +1 -1
- package/dist/commands/config.js +4 -4
- package/dist/commands/guide.js +2 -2
- package/dist/core/power-mode.js +1 -1
- package/dist/core/welcome.js +7 -19
- package/dist/index-simple.js +19 -2
- package/dist/index.js +41 -0
- package/dist/mcp/client/enhanced-client.js +1 -2
- package/dist/mcp/schemas/tool-schemas.d.ts +4 -4
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +40 -6
- package/dist/utils/api.js +1 -1
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.js +97 -52
- package/dist/utils/mcp-client.d.ts +17 -4
- package/dist/utils/mcp-client.js +39 -20
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -452,3 +452,71 @@ MIT License - see [LICENSE](../LICENSE) for details.
|
|
|
452
452
|
---
|
|
453
453
|
|
|
454
454
|
_Professional CLI for Enterprise Memory as a Service - Golden Contract Compliant_
|
|
455
|
+
|
|
456
|
+
## OAuth2 Authentication (v3.5.0+)
|
|
457
|
+
|
|
458
|
+
### Browser Login with OAuth2 PKCE
|
|
459
|
+
|
|
460
|
+
The CLI now supports secure OAuth2 authentication with PKCE (Proof Key for Code Exchange) for browser-based login:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
onasis auth login
|
|
464
|
+
# Choose: 🌐 Browser Login (Get token from web page)
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**How it works:**
|
|
468
|
+
1. CLI starts a local callback server on port 8888
|
|
469
|
+
2. Opens your browser to the OAuth2 authorization page
|
|
470
|
+
3. You authenticate in the browser
|
|
471
|
+
4. Authorization code is sent back to the CLI
|
|
472
|
+
5. CLI exchanges code for access and refresh tokens
|
|
473
|
+
6. Tokens are securely stored locally
|
|
474
|
+
|
|
475
|
+
**Benefits:**
|
|
476
|
+
- ✅ More secure (PKCE prevents code interception)
|
|
477
|
+
- ✅ Automatic token refresh
|
|
478
|
+
- ✅ Revocable access
|
|
479
|
+
- ✅ Industry-standard OAuth2 flow
|
|
480
|
+
|
|
481
|
+
### Authentication Methods
|
|
482
|
+
|
|
483
|
+
The CLI supports three authentication methods:
|
|
484
|
+
|
|
485
|
+
1. **🔑 Vendor Key** (Recommended for API access)
|
|
486
|
+
- Long-lived API key from dashboard
|
|
487
|
+
- Best for automation and CI/CD
|
|
488
|
+
|
|
489
|
+
2. **🌐 Browser Login** (OAuth2 PKCE)
|
|
490
|
+
- Secure browser-based authentication
|
|
491
|
+
- Automatic token refresh
|
|
492
|
+
- Best for interactive use
|
|
493
|
+
|
|
494
|
+
3. **⚙️ Username/Password** (Direct credentials)
|
|
495
|
+
- Traditional email/password login
|
|
496
|
+
- Returns JWT token
|
|
497
|
+
- Legacy method
|
|
498
|
+
|
|
499
|
+
### Token Management
|
|
500
|
+
|
|
501
|
+
OAuth2 tokens are automatically refreshed when expired:
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
# Check authentication status
|
|
505
|
+
onasis auth status
|
|
506
|
+
|
|
507
|
+
# Force re-authentication
|
|
508
|
+
onasis auth logout
|
|
509
|
+
onasis auth login
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Troubleshooting
|
|
513
|
+
|
|
514
|
+
**Port 8888 already in use:**
|
|
515
|
+
The CLI needs port 8888 for the OAuth callback. If it's in use, close the application using it or use the Vendor Key method instead.
|
|
516
|
+
|
|
517
|
+
**Browser doesn't open:**
|
|
518
|
+
The CLI will show the authorization URL - copy and paste it into your browser manually.
|
|
519
|
+
|
|
520
|
+
**Token refresh failed:**
|
|
521
|
+
Run `onasis auth login` to re-authenticate.
|
|
522
|
+
|
package/dist/commands/auth.js
CHANGED
|
@@ -2,6 +2,9 @@ import chalk from 'chalk';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import open from 'open';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import http from 'http';
|
|
7
|
+
import url from 'url';
|
|
5
8
|
import { apiClient } from '../utils/api.js';
|
|
6
9
|
import { CLIConfig } from '../utils/config.js';
|
|
7
10
|
// Color scheme
|
|
@@ -49,9 +52,9 @@ async function handleAuthenticationFailure(error, config, authMethod = 'jwt') {
|
|
|
49
52
|
case 'invalid_credentials':
|
|
50
53
|
console.log(chalk.red('Invalid credentials provided'));
|
|
51
54
|
if (authMethod === 'vendor_key') {
|
|
52
|
-
console.log(chalk.gray('•
|
|
53
|
-
console.log(chalk.gray('•
|
|
54
|
-
console.log(chalk.gray('• Ensure you copied the
|
|
55
|
+
console.log(chalk.gray('• Verify the vendor key matches the value shown in your dashboard'));
|
|
56
|
+
console.log(chalk.gray('• Confirm the key is active and has not been revoked'));
|
|
57
|
+
console.log(chalk.gray('• Ensure you copied the entire key without extra spaces'));
|
|
55
58
|
}
|
|
56
59
|
else {
|
|
57
60
|
console.log(chalk.gray('• Double-check your email and password'));
|
|
@@ -163,6 +166,120 @@ function categorizeAuthError(error) {
|
|
|
163
166
|
}
|
|
164
167
|
return 'unknown';
|
|
165
168
|
}
|
|
169
|
+
// ============================================
|
|
170
|
+
// OAuth2 PKCE Helper Functions
|
|
171
|
+
// ============================================
|
|
172
|
+
/**
|
|
173
|
+
* Generate PKCE code verifier and challenge for OAuth2
|
|
174
|
+
*/
|
|
175
|
+
function generatePKCE() {
|
|
176
|
+
// Generate random verifier (43-128 chars, base64url)
|
|
177
|
+
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
178
|
+
// Generate challenge: base64url(sha256(verifier))
|
|
179
|
+
const challenge = crypto
|
|
180
|
+
.createHash('sha256')
|
|
181
|
+
.update(verifier)
|
|
182
|
+
.digest('base64url');
|
|
183
|
+
return { verifier, challenge };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Start local HTTP server to catch OAuth2 callback
|
|
187
|
+
*/
|
|
188
|
+
function createCallbackServer(port = 8888) {
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
const server = http.createServer((req, res) => {
|
|
191
|
+
const parsedUrl = url.parse(req.url, true);
|
|
192
|
+
if (parsedUrl.pathname === '/callback') {
|
|
193
|
+
const { code, state, error, error_description } = parsedUrl.query;
|
|
194
|
+
// Send response to browser
|
|
195
|
+
if (error) {
|
|
196
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
197
|
+
res.end(`
|
|
198
|
+
<html>
|
|
199
|
+
<head><title>Authentication Failed</title></head>
|
|
200
|
+
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
201
|
+
<h1>❌ Authentication Failed</h1>
|
|
202
|
+
<p>${error_description || error}</p>
|
|
203
|
+
<p style="color: gray;">You can close this window.</p>
|
|
204
|
+
</body>
|
|
205
|
+
</html>
|
|
206
|
+
`);
|
|
207
|
+
reject(new Error(`OAuth error: ${error_description || error}`));
|
|
208
|
+
}
|
|
209
|
+
else if (code) {
|
|
210
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
211
|
+
res.end(`
|
|
212
|
+
<html>
|
|
213
|
+
<head><title>Authentication Successful</title></head>
|
|
214
|
+
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
215
|
+
<h1>✅ Authentication Successful</h1>
|
|
216
|
+
<p>You can close this window and return to the CLI.</p>
|
|
217
|
+
<script>setTimeout(() => window.close(), 2000);</script>
|
|
218
|
+
</body>
|
|
219
|
+
</html>
|
|
220
|
+
`);
|
|
221
|
+
resolve({ code: code, state: state });
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
225
|
+
res.end('Invalid callback');
|
|
226
|
+
reject(new Error('No authorization code received'));
|
|
227
|
+
}
|
|
228
|
+
// Close server after handling request
|
|
229
|
+
server.close();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
server.listen(port, () => {
|
|
233
|
+
console.log(chalk.gray(` Local callback server listening on port ${port}`));
|
|
234
|
+
});
|
|
235
|
+
// Timeout after 5 minutes
|
|
236
|
+
setTimeout(() => {
|
|
237
|
+
server.close();
|
|
238
|
+
reject(new Error('Authentication timeout - please try again'));
|
|
239
|
+
}, 300000);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Exchange authorization code for OAuth2 tokens
|
|
244
|
+
*/
|
|
245
|
+
async function exchangeCodeForTokens(code, verifier, authBase) {
|
|
246
|
+
const tokenEndpoint = `${authBase}/oauth/token`;
|
|
247
|
+
const response = await apiClient.post(tokenEndpoint, {
|
|
248
|
+
grant_type: 'authorization_code',
|
|
249
|
+
code,
|
|
250
|
+
code_verifier: verifier,
|
|
251
|
+
client_id: 'lanonasis-cli',
|
|
252
|
+
redirect_uri: 'http://localhost:8888/callback'
|
|
253
|
+
});
|
|
254
|
+
return response;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Refresh OAuth2 access token using refresh token
|
|
258
|
+
*/
|
|
259
|
+
async function refreshOAuth2Token(config) {
|
|
260
|
+
const refreshToken = config.get('refresh_token');
|
|
261
|
+
if (!refreshToken) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const authBase = config.getDiscoveredApiUrl();
|
|
266
|
+
const response = await apiClient.post(`${authBase}/oauth/token`, {
|
|
267
|
+
grant_type: 'refresh_token',
|
|
268
|
+
refresh_token: refreshToken,
|
|
269
|
+
client_id: 'lanonasis-cli'
|
|
270
|
+
});
|
|
271
|
+
await config.setToken(response.access_token);
|
|
272
|
+
if (response.refresh_token) {
|
|
273
|
+
await config.set('refresh_token', response.refresh_token);
|
|
274
|
+
}
|
|
275
|
+
await config.set('token_expires_at', Date.now() + (response.expires_in * 1000));
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.error(chalk.yellow('⚠️ Token refresh failed, please re-authenticate'));
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
166
283
|
export async function diagnoseCommand() {
|
|
167
284
|
const config = new CLIConfig();
|
|
168
285
|
await config.init();
|
|
@@ -206,14 +323,10 @@ export async function diagnoseCommand() {
|
|
|
206
323
|
diagnostics.hasCredentials = true;
|
|
207
324
|
diagnostics.credentialType = 'vendor_key';
|
|
208
325
|
console.log(chalk.green(' ✓ Vendor key found'));
|
|
209
|
-
// Validate vendor key
|
|
326
|
+
// Validate vendor key presence
|
|
210
327
|
const formatValidation = config.validateVendorKeyFormat(vendorKey);
|
|
211
|
-
if (formatValidation
|
|
212
|
-
console.log(chalk.
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
console.log(chalk.red(' ✖ Vendor key format is invalid:'));
|
|
216
|
-
console.log(chalk.gray(` ${formatValidation}`));
|
|
328
|
+
if (formatValidation !== true) {
|
|
329
|
+
console.log(chalk.red(` ✖ Vendor key issue: ${formatValidation}`));
|
|
217
330
|
}
|
|
218
331
|
}
|
|
219
332
|
else if (token) {
|
|
@@ -231,8 +344,11 @@ export async function diagnoseCommand() {
|
|
|
231
344
|
console.log(chalk.green(' ✓ Token is not expired'));
|
|
232
345
|
}
|
|
233
346
|
}
|
|
234
|
-
catch {
|
|
347
|
+
catch (error) {
|
|
235
348
|
console.log(chalk.yellow(' ⚠ Could not validate token expiry'));
|
|
349
|
+
if (process.env.CLI_VERBOSE === 'true' && error instanceof Error) {
|
|
350
|
+
console.log(chalk.gray(` ${error.message}`));
|
|
351
|
+
}
|
|
236
352
|
}
|
|
237
353
|
}
|
|
238
354
|
else {
|
|
@@ -315,8 +431,11 @@ export async function diagnoseCommand() {
|
|
|
315
431
|
diagnostics.deviceId = deviceId;
|
|
316
432
|
console.log(chalk.green(' ✓ Device ID:'), chalk.gray(deviceId));
|
|
317
433
|
}
|
|
318
|
-
catch {
|
|
434
|
+
catch (error) {
|
|
319
435
|
console.log(chalk.yellow(' ⚠ Could not get device ID'));
|
|
436
|
+
if (process.env.CLI_VERBOSE === 'true' && error instanceof Error) {
|
|
437
|
+
console.log(chalk.gray(` ${error.message}`));
|
|
438
|
+
}
|
|
320
439
|
}
|
|
321
440
|
// Summary and recommendations
|
|
322
441
|
console.log(chalk.blue.bold('\n📋 Diagnostic Summary'));
|
|
@@ -329,7 +448,7 @@ export async function diagnoseCommand() {
|
|
|
329
448
|
}
|
|
330
449
|
if (!diagnostics.hasCredentials) {
|
|
331
450
|
issues.push('No authentication credentials stored');
|
|
332
|
-
recommendations.push('Run: lanonasis auth login --vendor-key
|
|
451
|
+
recommendations.push('Run: lanonasis auth login --vendor-key <your-key>');
|
|
333
452
|
}
|
|
334
453
|
if (diagnostics.hasCredentials && !diagnostics.credentialsValid) {
|
|
335
454
|
issues.push('Stored credentials are invalid');
|
|
@@ -364,7 +483,7 @@ export async function diagnoseCommand() {
|
|
|
364
483
|
// Additional troubleshooting info
|
|
365
484
|
if (diagnostics.authFailures > 0 || !diagnostics.credentialsValid) {
|
|
366
485
|
console.log(chalk.gray('\n🔧 Additional troubleshooting:'));
|
|
367
|
-
console.log(chalk.gray(' • Verify
|
|
486
|
+
console.log(chalk.gray(' • Verify the vendor key matches the value shown in your dashboard'));
|
|
368
487
|
console.log(chalk.gray(' • Check if your key is active in the dashboard'));
|
|
369
488
|
console.log(chalk.gray(' • Try browser authentication: lanonasis auth login --use-web-auth'));
|
|
370
489
|
console.log(chalk.gray(' • Contact support if issues persist'));
|
|
@@ -451,84 +570,37 @@ async function handleVendorKeyAuth(vendorKey, config) {
|
|
|
451
570
|
async function handleVendorKeyFlow(config) {
|
|
452
571
|
console.log();
|
|
453
572
|
console.log(chalk.yellow('🔑 Vendor Key Authentication'));
|
|
454
|
-
console.log(chalk.gray('Vendor keys provide secure API access
|
|
573
|
+
console.log(chalk.gray('Vendor keys provide secure API access for automation and integrations.'));
|
|
455
574
|
console.log();
|
|
456
575
|
// Enhanced guidance for obtaining vendor keys
|
|
457
576
|
console.log(chalk.cyan('📋 How to get your vendor key:'));
|
|
458
577
|
console.log(chalk.gray('1. Visit your Lanonasis dashboard at https://app.lanonasis.com'));
|
|
459
578
|
console.log(chalk.gray('2. Navigate to Settings → API Keys'));
|
|
460
|
-
console.log(chalk.gray('3. Click "Generate New Key" and copy the full key'));
|
|
461
|
-
console.log(chalk.gray('4. The key format should be: pk_[letters/numbers].sk_[letters/numbers]'));
|
|
579
|
+
console.log(chalk.gray('3. Click "Generate New Key" and copy the full key value'));
|
|
462
580
|
console.log();
|
|
463
581
|
const { vendorKey } = await inquirer.prompt([
|
|
464
582
|
{
|
|
465
583
|
type: 'password',
|
|
466
584
|
name: 'vendorKey',
|
|
467
|
-
message: 'Enter your vendor key
|
|
585
|
+
message: 'Enter your vendor key:',
|
|
468
586
|
mask: '*',
|
|
469
587
|
validate: (input) => {
|
|
470
|
-
return validateVendorKeyFormat(input);
|
|
588
|
+
return config.validateVendorKeyFormat(input);
|
|
471
589
|
}
|
|
472
590
|
}
|
|
473
591
|
]);
|
|
474
592
|
await handleVendorKeyAuth(vendorKey, config);
|
|
475
593
|
}
|
|
476
|
-
// Enhanced vendor key format validation with detailed error messages
|
|
477
|
-
function validateVendorKeyFormat(input) {
|
|
478
|
-
if (!input || input.trim().length === 0) {
|
|
479
|
-
return 'Vendor key is required';
|
|
480
|
-
}
|
|
481
|
-
const trimmed = input.trim();
|
|
482
|
-
// Check basic format
|
|
483
|
-
if (!trimmed.includes('.')) {
|
|
484
|
-
return 'Invalid format: Vendor key must contain a dot (.) separator\nExpected format: pk_xxx.sk_xxx';
|
|
485
|
-
}
|
|
486
|
-
const parts = trimmed.split('.');
|
|
487
|
-
if (parts.length !== 2) {
|
|
488
|
-
return 'Invalid format: Vendor key must have exactly two parts separated by a dot\nExpected format: pk_xxx.sk_xxx';
|
|
489
|
-
}
|
|
490
|
-
const [publicPart, secretPart] = parts;
|
|
491
|
-
// Validate public key part
|
|
492
|
-
if (!publicPart.startsWith('pk_')) {
|
|
493
|
-
return 'Invalid format: First part must start with "pk_"\nExpected format: pk_xxx.sk_xxx';
|
|
494
|
-
}
|
|
495
|
-
if (publicPart.length < 4) {
|
|
496
|
-
return 'Invalid format: Public key part is too short\nExpected format: pk_xxx.sk_xxx (where xxx is alphanumeric)';
|
|
497
|
-
}
|
|
498
|
-
const publicKeyContent = publicPart.substring(3); // Remove 'pk_'
|
|
499
|
-
if (!/^[a-zA-Z0-9]+$/.test(publicKeyContent)) {
|
|
500
|
-
return 'Invalid format: Public key part contains invalid characters\nOnly letters and numbers are allowed after "pk_"';
|
|
501
|
-
}
|
|
502
|
-
// Validate secret key part
|
|
503
|
-
if (!secretPart.startsWith('sk_')) {
|
|
504
|
-
return 'Invalid format: Second part must start with "sk_"\nExpected format: pk_xxx.sk_xxx';
|
|
505
|
-
}
|
|
506
|
-
if (secretPart.length < 4) {
|
|
507
|
-
return 'Invalid format: Secret key part is too short\nExpected format: pk_xxx.sk_xxx (where xxx is alphanumeric)';
|
|
508
|
-
}
|
|
509
|
-
const secretKeyContent = secretPart.substring(3); // Remove 'sk_'
|
|
510
|
-
if (!/^[a-zA-Z0-9]+$/.test(secretKeyContent)) {
|
|
511
|
-
return 'Invalid format: Secret key part contains invalid characters\nOnly letters and numbers are allowed after "sk_"';
|
|
512
|
-
}
|
|
513
|
-
// Check minimum length requirements
|
|
514
|
-
if (publicKeyContent.length < 8) {
|
|
515
|
-
return 'Invalid format: Public key part is too short (minimum 8 characters after "pk_")';
|
|
516
|
-
}
|
|
517
|
-
if (secretKeyContent.length < 16) {
|
|
518
|
-
return 'Invalid format: Secret key part is too short (minimum 16 characters after "sk_")';
|
|
519
|
-
}
|
|
520
|
-
return true;
|
|
521
|
-
}
|
|
522
594
|
async function handleOAuthFlow(config) {
|
|
523
595
|
console.log();
|
|
524
|
-
console.log(chalk.yellow('🌐 Browser-Based Authentication'));
|
|
525
|
-
console.log(chalk.gray('
|
|
596
|
+
console.log(chalk.yellow('🌐 Browser-Based OAuth2 Authentication'));
|
|
597
|
+
console.log(chalk.gray('Secure authentication using OAuth2 with PKCE'));
|
|
526
598
|
console.log();
|
|
527
599
|
const { openBrowser } = await inquirer.prompt([
|
|
528
600
|
{
|
|
529
601
|
type: 'confirm',
|
|
530
602
|
name: 'openBrowser',
|
|
531
|
-
message: 'Open browser for authentication?',
|
|
603
|
+
message: 'Open browser for OAuth2 authentication?',
|
|
532
604
|
default: true
|
|
533
605
|
}
|
|
534
606
|
]);
|
|
@@ -536,63 +608,53 @@ async function handleOAuthFlow(config) {
|
|
|
536
608
|
console.log(chalk.yellow('⚠️ Authentication cancelled'));
|
|
537
609
|
return;
|
|
538
610
|
}
|
|
539
|
-
// Use the browser-based CLI login endpoint discovered from auth_base
|
|
540
|
-
const authBase = config.getDiscoveredApiUrl();
|
|
541
|
-
const authUrl = `${authBase.replace(/\/$/, '')}/auth/cli-login`;
|
|
542
611
|
try {
|
|
543
|
-
|
|
544
|
-
|
|
612
|
+
// Generate PKCE challenge
|
|
613
|
+
const pkce = generatePKCE();
|
|
614
|
+
console.log(chalk.gray(' ✓ Generated PKCE challenge'));
|
|
615
|
+
// Start local callback server
|
|
616
|
+
const callbackPort = 8888;
|
|
617
|
+
const callbackPromise = createCallbackServer(callbackPort);
|
|
618
|
+
console.log(chalk.gray(` ✓ Started local callback server on port ${callbackPort}`));
|
|
619
|
+
// Build OAuth2 authorization URL
|
|
620
|
+
const authBase = config.getDiscoveredApiUrl();
|
|
621
|
+
const authUrl = new URL(`${authBase}/oauth/authorize`);
|
|
622
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
623
|
+
authUrl.searchParams.set('client_id', 'lanonasis-cli');
|
|
624
|
+
authUrl.searchParams.set('redirect_uri', `http://localhost:${callbackPort}/callback`);
|
|
625
|
+
authUrl.searchParams.set('scope', 'read write offline_access');
|
|
626
|
+
authUrl.searchParams.set('code_challenge', pkce.challenge);
|
|
627
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
628
|
+
authUrl.searchParams.set('state', crypto.randomBytes(16).toString('hex'));
|
|
545
629
|
console.log();
|
|
546
|
-
console.log(colors.info('
|
|
547
|
-
|
|
548
|
-
console.log(colors.
|
|
630
|
+
console.log(colors.info('Opening browser for authentication...'));
|
|
631
|
+
await open(authUrl.toString());
|
|
632
|
+
console.log(colors.info('Waiting for authentication in browser...'));
|
|
633
|
+
console.log(colors.muted(`If browser doesn't open, visit: ${authUrl.toString()}`));
|
|
549
634
|
console.log();
|
|
550
|
-
//
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
return 'Invalid token format. Expected format: cli_xxx or JWT token';
|
|
568
|
-
}
|
|
569
|
-
// Verify token with server
|
|
570
|
-
try {
|
|
571
|
-
const response = await apiClient.post('/auth/verify', { token: trimmed });
|
|
572
|
-
if (!response.valid) {
|
|
573
|
-
return 'Token verification failed. Please try again.';
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
catch (error) {
|
|
577
|
-
const errorMessage = error instanceof Error ? error.message : 'Server verification failed';
|
|
578
|
-
return `Token verification error: ${errorMessage}`;
|
|
579
|
-
}
|
|
580
|
-
return true;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
]);
|
|
584
|
-
if (token && token.trim()) {
|
|
585
|
-
await config.setToken(token.trim());
|
|
586
|
-
console.log(chalk.green('✓ Browser authentication successful'));
|
|
587
|
-
console.log(colors.info('You can now use Lanonasis services'));
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
590
|
-
console.log(chalk.yellow('⚠️ No token provided'));
|
|
591
|
-
}
|
|
635
|
+
// Wait for callback
|
|
636
|
+
const spinner = ora('Waiting for authorization...').start();
|
|
637
|
+
const { code } = await callbackPromise;
|
|
638
|
+
spinner.succeed('Authorization code received');
|
|
639
|
+
// Exchange code for tokens
|
|
640
|
+
spinner.text = 'Exchanging code for access tokens...';
|
|
641
|
+
spinner.start();
|
|
642
|
+
const tokens = await exchangeCodeForTokens(code, pkce.verifier, authBase);
|
|
643
|
+
spinner.succeed('Access tokens received');
|
|
644
|
+
// Store tokens
|
|
645
|
+
await config.setToken(tokens.access_token);
|
|
646
|
+
await config.set('refresh_token', tokens.refresh_token);
|
|
647
|
+
await config.set('token_expires_at', Date.now() + (tokens.expires_in * 1000));
|
|
648
|
+
await config.set('authMethod', 'oauth2');
|
|
649
|
+
console.log();
|
|
650
|
+
console.log(chalk.green('✓ OAuth2 authentication successful'));
|
|
651
|
+
console.log(colors.info('You can now use Lanonasis services'));
|
|
592
652
|
}
|
|
593
|
-
catch {
|
|
594
|
-
console.error(chalk.red('✖
|
|
595
|
-
|
|
653
|
+
catch (error) {
|
|
654
|
+
console.error(chalk.red('✖ OAuth2 authentication failed'));
|
|
655
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
656
|
+
console.error(chalk.gray(` ${errorMessage}`));
|
|
657
|
+
process.exit(1);
|
|
596
658
|
}
|
|
597
659
|
}
|
|
598
660
|
async function handleCredentialsFlow(options, config) {
|
|
@@ -48,7 +48,7 @@ export async function generateCompletionData() {
|
|
|
48
48
|
options: [
|
|
49
49
|
{ name: '--email', description: 'Email address', type: 'string' },
|
|
50
50
|
{ name: '--password', description: 'Password', type: 'string' },
|
|
51
|
-
{ name: '--vendor-key', description: 'Vendor key
|
|
51
|
+
{ name: '--vendor-key', description: 'Vendor key value', type: 'string' },
|
|
52
52
|
{ name: '--oauth', description: 'Use OAuth flow', type: 'boolean' }
|
|
53
53
|
]
|
|
54
54
|
},
|
package/dist/commands/config.js
CHANGED
|
@@ -411,15 +411,15 @@ export function configCommands(program) {
|
|
|
411
411
|
console.log(chalk.cyan(' → Repaired: Set auth method to jwt'));
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
|
-
// Validate vendor key
|
|
414
|
+
// Validate vendor key presence if present
|
|
415
415
|
if (vendorKey) {
|
|
416
416
|
const formatValidation = config.validateVendorKeyFormat(vendorKey);
|
|
417
417
|
if (formatValidation === true) {
|
|
418
|
-
console.log(chalk.green(' ✓ Vendor key
|
|
418
|
+
console.log(chalk.green(' ✓ Vendor key is set'));
|
|
419
419
|
}
|
|
420
420
|
else {
|
|
421
|
-
console.log(chalk.red(
|
|
422
|
-
validation.issues.push('
|
|
421
|
+
console.log(chalk.red(` ✖ Vendor key issue: ${formatValidation}`));
|
|
422
|
+
validation.issues.push('Vendor key missing or invalid');
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
// Test authentication validity
|
package/dist/commands/guide.js
CHANGED
|
@@ -233,7 +233,7 @@ export class UserGuidanceSystem {
|
|
|
233
233
|
switch (authMethod) {
|
|
234
234
|
case 'vendor_key':
|
|
235
235
|
console.log(chalk.yellow('📝 Vendor keys provide secure, programmatic access'));
|
|
236
|
-
console.log(chalk.gray('
|
|
236
|
+
console.log(chalk.gray('Find it in your dashboard under API Keys.'));
|
|
237
237
|
console.log();
|
|
238
238
|
break;
|
|
239
239
|
case 'oauth':
|
|
@@ -409,7 +409,7 @@ export async function quickStartCommand() {
|
|
|
409
409
|
category: 'Setup',
|
|
410
410
|
commands: [
|
|
411
411
|
{ cmd: 'lanonasis init', desc: 'Initialize configuration' },
|
|
412
|
-
{ cmd: 'lanonasis login --vendor-key
|
|
412
|
+
{ cmd: 'lanonasis login --vendor-key <your-key>', desc: 'Authenticate with vendor key' },
|
|
413
413
|
{ cmd: 'lanonasis health', desc: 'Verify system health' }
|
|
414
414
|
]
|
|
415
415
|
},
|
package/dist/core/power-mode.js
CHANGED
|
@@ -293,7 +293,7 @@ export class PowerUserMode {
|
|
|
293
293
|
const subCommand = args[0];
|
|
294
294
|
switch (subCommand) {
|
|
295
295
|
case 'keys':
|
|
296
|
-
console.log('API Keys:
|
|
296
|
+
console.log('API Keys: vendor-key-1... (active), vendor-key-2... (revoked)');
|
|
297
297
|
break;
|
|
298
298
|
case 'limits':
|
|
299
299
|
console.log('Rate Limits: 1000/hour (432 used)');
|
package/dist/core/welcome.js
CHANGED
|
@@ -296,7 +296,7 @@ export class InteractiveSetup {
|
|
|
296
296
|
message: 'Select authentication method:',
|
|
297
297
|
choices: [
|
|
298
298
|
{
|
|
299
|
-
name: authBox('🔑 Vendor Key', 'Secure API access with
|
|
299
|
+
name: authBox('🔑 Vendor Key', 'Secure API access with long-lived credentials', ['No expiration', 'Ideal for CI/CD', 'Full API access']),
|
|
300
300
|
value: 'vendor',
|
|
301
301
|
short: 'Vendor Key'
|
|
302
302
|
},
|
|
@@ -327,32 +327,21 @@ export class InteractiveSetup {
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
async authenticateWithVendorKey() {
|
|
330
|
-
const
|
|
331
|
-
{
|
|
332
|
-
type: 'input',
|
|
333
|
-
name: 'publicKey',
|
|
334
|
-
message: 'Enter your Public Key (pk_xxx):',
|
|
335
|
-
validate: (input) => {
|
|
336
|
-
if (!input.startsWith('pk_')) {
|
|
337
|
-
return 'Public key must start with pk_';
|
|
338
|
-
}
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
},
|
|
330
|
+
const { vendorKey } = await inquirer.prompt([
|
|
342
331
|
{
|
|
343
332
|
type: 'password',
|
|
344
|
-
name: '
|
|
345
|
-
message: 'Enter your
|
|
333
|
+
name: 'vendorKey',
|
|
334
|
+
message: 'Enter your vendor key:',
|
|
346
335
|
mask: '*',
|
|
347
336
|
validate: (input) => {
|
|
348
|
-
if (!input.
|
|
349
|
-
return '
|
|
337
|
+
if (!input || input.trim().length === 0) {
|
|
338
|
+
return 'Vendor key is required';
|
|
350
339
|
}
|
|
351
340
|
return true;
|
|
352
341
|
}
|
|
353
342
|
}
|
|
354
343
|
]);
|
|
355
|
-
void
|
|
344
|
+
void vendorKey; // collected for future use when hooking real auth
|
|
356
345
|
const spinner = ora('Authenticating...').start();
|
|
357
346
|
await this.simulateDelay(1000);
|
|
358
347
|
spinner.succeed('Authentication successful!');
|
|
@@ -393,7 +382,6 @@ export class InteractiveSetup {
|
|
|
393
382
|
mask: '*'
|
|
394
383
|
}
|
|
395
384
|
]);
|
|
396
|
-
void auth; // suppress unused until real auth wired
|
|
397
385
|
const spinner = ora('Signing in...').start();
|
|
398
386
|
await this.simulateDelay(1000);
|
|
399
387
|
spinner.succeed('Sign in successful!');
|
package/dist/index-simple.js
CHANGED
|
@@ -123,7 +123,7 @@ const showWelcome = () => {
|
|
|
123
123
|
console.log();
|
|
124
124
|
if (isOnasisInvocation) {
|
|
125
125
|
console.log(colors.info('🔑 Golden Contract Authentication:'));
|
|
126
|
-
console.log(` ${colors.success(`${cmdName} login --vendor-key
|
|
126
|
+
console.log(` ${colors.success(`${cmdName} login --vendor-key <your-key>`)} ${colors.muted('# Vendor key auth')}`);
|
|
127
127
|
console.log(` ${colors.success(`${cmdName} login --oauth`)} ${colors.muted('# Browser OAuth')}`);
|
|
128
128
|
console.log();
|
|
129
129
|
}
|
|
@@ -175,11 +175,24 @@ const healthCheck = async () => {
|
|
|
175
175
|
process.stdout.write('MCP Server status: ');
|
|
176
176
|
try {
|
|
177
177
|
const client = getMCPClient();
|
|
178
|
+
const status = client.getConnectionStatus();
|
|
179
|
+
const mcpPreference = await cliConfig.get('mcpPreference') || 'auto';
|
|
178
180
|
if (client.isConnectedToServer()) {
|
|
179
181
|
console.log(colors.success('✅ Connected'));
|
|
182
|
+
console.log(` Mode: ${colors.highlight(status.mode)}`);
|
|
183
|
+
console.log(` Server: ${colors.highlight(status.server || 'N/A')}`);
|
|
180
184
|
}
|
|
181
185
|
else {
|
|
182
186
|
console.log(colors.warning('⚠️ Disconnected'));
|
|
187
|
+
console.log(` Configured preference: ${colors.highlight(mcpPreference)}`);
|
|
188
|
+
if (mcpPreference === 'remote') {
|
|
189
|
+
const mcpUrl = cliConfig.getMCPServerUrl();
|
|
190
|
+
console.log(` Remote server: ${colors.highlight(mcpUrl)}`);
|
|
191
|
+
}
|
|
192
|
+
else if (mcpPreference === 'local') {
|
|
193
|
+
const mcpPath = cliConfig.getMCPServerPath();
|
|
194
|
+
console.log(` Local server: ${colors.highlight(mcpPath || 'Not configured')}`);
|
|
195
|
+
}
|
|
183
196
|
}
|
|
184
197
|
}
|
|
185
198
|
catch (error) {
|
|
@@ -229,7 +242,7 @@ authCmd
|
|
|
229
242
|
.description('Login to your MaaS account')
|
|
230
243
|
.option('-e, --email <email>', 'email address')
|
|
231
244
|
.option('-p, --password <password>', 'password')
|
|
232
|
-
.option('--vendor-key <key>', 'vendor key (
|
|
245
|
+
.option('--vendor-key <key>', 'vendor key (as provided in your dashboard)')
|
|
233
246
|
.option('--oauth', 'use OAuth browser flow')
|
|
234
247
|
.action(async (options) => {
|
|
235
248
|
// Handle oauth flag
|
|
@@ -249,6 +262,8 @@ authCmd
|
|
|
249
262
|
.command('status')
|
|
250
263
|
.description('Show authentication status')
|
|
251
264
|
.action(async () => {
|
|
265
|
+
// Initialize config first
|
|
266
|
+
await cliConfig.init();
|
|
252
267
|
const isAuth = await cliConfig.isAuthenticated();
|
|
253
268
|
const user = await cliConfig.getCurrentUser();
|
|
254
269
|
const failureCount = cliConfig.getFailureCount();
|
|
@@ -523,6 +538,8 @@ program
|
|
|
523
538
|
.command('status')
|
|
524
539
|
.description('Show overall system status')
|
|
525
540
|
.action(async () => {
|
|
541
|
+
// Initialize config first
|
|
542
|
+
await cliConfig.init();
|
|
526
543
|
const isAuth = await cliConfig.isAuthenticated();
|
|
527
544
|
const apiUrl = cliConfig.getApiUrl();
|
|
528
545
|
console.log(chalk.blue.bold('MaaS CLI Status'));
|
package/dist/index.js
CHANGED
|
@@ -270,6 +270,47 @@ const memoryCmd = program
|
|
|
270
270
|
requireAuth(memoryCmd);
|
|
271
271
|
memoryCommands(memoryCmd);
|
|
272
272
|
// Note: Memory commands are now MCP-powered when available
|
|
273
|
+
// REPL command (lightweight REPL for memory operations)
|
|
274
|
+
program
|
|
275
|
+
.command('repl')
|
|
276
|
+
.description('Start lightweight REPL session for memory operations')
|
|
277
|
+
.option('--mcp', 'Use MCP mode')
|
|
278
|
+
.option('--api <url>', 'Override API URL')
|
|
279
|
+
.option('--token <token>', 'Authentication token')
|
|
280
|
+
.action(async (options) => {
|
|
281
|
+
try {
|
|
282
|
+
// Try to use the REPL package if available
|
|
283
|
+
const { spawn } = await import('child_process');
|
|
284
|
+
const { fileURLToPath } = await import('url');
|
|
285
|
+
const { dirname, join } = await import('path');
|
|
286
|
+
// Try to find the REPL package
|
|
287
|
+
const replPath = join(process.cwd(), 'packages', 'repl-cli', 'dist', 'index.js');
|
|
288
|
+
const args = ['start'];
|
|
289
|
+
if (options.mcp)
|
|
290
|
+
args.push('--mcp');
|
|
291
|
+
if (options.api)
|
|
292
|
+
args.push('--api', options.api);
|
|
293
|
+
if (options.token)
|
|
294
|
+
args.push('--token', options.token);
|
|
295
|
+
const repl = spawn('node', [replPath, ...args], {
|
|
296
|
+
stdio: 'inherit',
|
|
297
|
+
cwd: process.cwd()
|
|
298
|
+
});
|
|
299
|
+
repl.on('error', (err) => {
|
|
300
|
+
console.error(colors.error('Failed to start REPL:'), err.message);
|
|
301
|
+
console.log(colors.muted('Make sure the REPL package is built: cd packages/repl-cli && bun run build'));
|
|
302
|
+
process.exit(1);
|
|
303
|
+
});
|
|
304
|
+
repl.on('exit', (code) => {
|
|
305
|
+
process.exit(code || 0);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
console.error(colors.error('Failed to start REPL:'), error instanceof Error ? error.message : String(error));
|
|
310
|
+
console.log(colors.muted('Install the REPL package: cd packages/repl-cli && bun install && bun run build'));
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
273
314
|
// Topic commands (require auth)
|
|
274
315
|
const topicCmd = program
|
|
275
316
|
.command('topic')
|
|
@@ -59,7 +59,7 @@ export class EnhancedMCPClient extends EventEmitter {
|
|
|
59
59
|
const maxRetries = config.maxRetries || 3;
|
|
60
60
|
const timeout = config.timeout || 30000;
|
|
61
61
|
let attempts = 0;
|
|
62
|
-
while (
|
|
62
|
+
while (true) {
|
|
63
63
|
try {
|
|
64
64
|
this.updateConnectionStatus(config.name, 'connecting');
|
|
65
65
|
const client = await this.createClientWithTimeout(config, timeout);
|
|
@@ -82,7 +82,6 @@ export class EnhancedMCPClient extends EventEmitter {
|
|
|
82
82
|
await this.delay(delay);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
return false;
|
|
86
85
|
}
|
|
87
86
|
/**
|
|
88
87
|
* Create client with timeout
|
|
@@ -202,13 +202,13 @@ export declare const SystemConfigSchema: z.ZodObject<{
|
|
|
202
202
|
}, "strip", z.ZodTypeAny, {
|
|
203
203
|
value?: any;
|
|
204
204
|
action?: "get" | "set" | "reset";
|
|
205
|
-
key?: string;
|
|
206
205
|
scope?: "user" | "global";
|
|
206
|
+
key?: string;
|
|
207
207
|
}, {
|
|
208
208
|
value?: any;
|
|
209
209
|
action?: "get" | "set" | "reset";
|
|
210
|
-
key?: string;
|
|
211
210
|
scope?: "user" | "global";
|
|
211
|
+
key?: string;
|
|
212
212
|
}>;
|
|
213
213
|
export declare const BulkOperationSchema: z.ZodObject<{
|
|
214
214
|
operation: z.ZodEnum<["create", "update", "delete"]>;
|
|
@@ -580,13 +580,13 @@ export declare const MCPSchemas: {
|
|
|
580
580
|
}, "strip", z.ZodTypeAny, {
|
|
581
581
|
value?: any;
|
|
582
582
|
action?: "get" | "set" | "reset";
|
|
583
|
-
key?: string;
|
|
584
583
|
scope?: "user" | "global";
|
|
584
|
+
key?: string;
|
|
585
585
|
}, {
|
|
586
586
|
value?: any;
|
|
587
587
|
action?: "get" | "set" | "reset";
|
|
588
|
-
key?: string;
|
|
589
588
|
scope?: "user" | "global";
|
|
589
|
+
key?: string;
|
|
590
590
|
}>;
|
|
591
591
|
};
|
|
592
592
|
operations: {
|
package/dist/mcp-server.d.ts
CHANGED
package/dist/mcp-server.js
CHANGED
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { dirname, join } from 'path';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { createRequire } from 'module';
|
|
9
11
|
import { spawn } from 'child_process';
|
|
10
12
|
import { CLIConfig } from './utils/config.js';
|
|
11
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
14
|
const __dirname = dirname(__filename);
|
|
15
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
13
16
|
export class CLIMCPServer {
|
|
14
17
|
config;
|
|
15
18
|
constructor() {
|
|
@@ -28,6 +31,33 @@ export class CLIMCPServer {
|
|
|
28
31
|
await this.startLocalMCP(options);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
34
|
+
resolveMCPServerPath() {
|
|
35
|
+
const candidates = new Set();
|
|
36
|
+
if (process.env.MCP_SERVER_PATH) {
|
|
37
|
+
candidates.add(process.env.MCP_SERVER_PATH);
|
|
38
|
+
}
|
|
39
|
+
const packageRequests = [
|
|
40
|
+
'@lanonasis/mcp-server/dist/cli-aligned-mcp-server.js',
|
|
41
|
+
'lanonasis-mcp-server/dist/cli-aligned-mcp-server.js'
|
|
42
|
+
];
|
|
43
|
+
for (const request of packageRequests) {
|
|
44
|
+
try {
|
|
45
|
+
const resolved = nodeRequire.resolve(request);
|
|
46
|
+
candidates.add(resolved);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Ignore resolution failures and continue through fallbacks
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
candidates.add(join(process.cwd(), 'mcp-server/dist/cli-aligned-mcp-server.js'));
|
|
53
|
+
candidates.add(join(__dirname, '../../../mcp-server/dist/cli-aligned-mcp-server.js'));
|
|
54
|
+
for (const candidate of candidates) {
|
|
55
|
+
if (candidate && existsSync(candidate)) {
|
|
56
|
+
return candidate;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw new Error('Unable to locate the CLI-aligned MCP server. Set MCP_SERVER_PATH or install @lanonasis/mcp-server.');
|
|
60
|
+
}
|
|
31
61
|
/**
|
|
32
62
|
* Start local MCP server using CLI auth config
|
|
33
63
|
*/
|
|
@@ -42,10 +72,11 @@ export class CLIMCPServer {
|
|
|
42
72
|
console.error(`Config: ~/.maas/config.json`);
|
|
43
73
|
console.error(`Auth: ${this.config.hasVendorKey() ? 'Vendor Key' : 'JWT Token'}`);
|
|
44
74
|
}
|
|
75
|
+
const resolvedPort = typeof port === 'number' && !Number.isNaN(port) ? port : 3001;
|
|
45
76
|
// Set environment variables from CLI config
|
|
46
77
|
const env = {
|
|
47
78
|
...process.env,
|
|
48
|
-
PORT:
|
|
79
|
+
PORT: resolvedPort.toString(),
|
|
49
80
|
MEMORY_API_URL: this.config.getApiUrl(),
|
|
50
81
|
LANONASIS_VENDOR_KEY: this.config.getVendorKey(),
|
|
51
82
|
LANONASIS_TOKEN: this.config.getToken(),
|
|
@@ -84,14 +115,13 @@ export class CLIMCPServer {
|
|
|
84
115
|
*/
|
|
85
116
|
async startRemoteMCP(options) {
|
|
86
117
|
const { verbose } = options;
|
|
118
|
+
const message = 'Remote MCP not implemented; remove --remote or use local mode.';
|
|
87
119
|
if (verbose) {
|
|
88
120
|
console.error('🌐 Connecting to remote MCP server...');
|
|
89
121
|
console.error(`URL: ${this.config.getMCPServerUrl()}`);
|
|
90
122
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
console.error('⚠️ Remote MCP not yet implemented, falling back to local mode');
|
|
94
|
-
await this.startLocalMCP({ ...options, useRemote: false });
|
|
123
|
+
console.error(`❌ ${message}`);
|
|
124
|
+
throw new Error(message);
|
|
95
125
|
}
|
|
96
126
|
/**
|
|
97
127
|
* Check if MCP server is available and configured
|
|
@@ -149,6 +179,10 @@ Examples:
|
|
|
149
179
|
await server.start(options);
|
|
150
180
|
}
|
|
151
181
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
152
|
-
main().catch(
|
|
182
|
+
main().catch(error => {
|
|
183
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
184
|
+
console.error(message);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
});
|
|
153
187
|
}
|
|
154
188
|
export default CLIMCPServer;
|
package/dist/utils/api.js
CHANGED
|
@@ -27,7 +27,7 @@ export class APIClient {
|
|
|
27
27
|
const token = this.config.getToken();
|
|
28
28
|
const vendorKey = this.config.getVendorKey();
|
|
29
29
|
if (vendorKey) {
|
|
30
|
-
// Vendor key authentication (
|
|
30
|
+
// Vendor key authentication (validated server-side)
|
|
31
31
|
config.headers['X-API-Key'] = vendorKey;
|
|
32
32
|
config.headers['X-Auth-Method'] = 'vendor_key';
|
|
33
33
|
}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -43,6 +43,14 @@ export declare class CLIConfig {
|
|
|
43
43
|
private authCheckCache;
|
|
44
44
|
private readonly AUTH_CACHE_TTL;
|
|
45
45
|
constructor();
|
|
46
|
+
/**
|
|
47
|
+
* Overrides the configuration storage directory. Primarily used for tests.
|
|
48
|
+
*/
|
|
49
|
+
setConfigDirectory(configDir: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Exposes the current config path for tests and diagnostics.
|
|
52
|
+
*/
|
|
53
|
+
getConfigPath(): string;
|
|
46
54
|
init(): Promise<void>;
|
|
47
55
|
load(): Promise<void>;
|
|
48
56
|
private migrateConfigIfNeeded;
|
|
@@ -55,6 +63,8 @@ export declare class CLIConfig {
|
|
|
55
63
|
discoverServices(verbose?: boolean): Promise<void>;
|
|
56
64
|
private handleServiceDiscoveryFailure;
|
|
57
65
|
private categorizeServiceDiscoveryError;
|
|
66
|
+
private resolveFallbackEndpoints;
|
|
67
|
+
private logFallbackUsage;
|
|
58
68
|
setManualEndpoints(endpoints: Partial<CLIConfigData['discoveredServices']>): Promise<void>;
|
|
59
69
|
hasManualEndpointOverrides(): boolean;
|
|
60
70
|
clearManualEndpointOverrides(): Promise<void>;
|
|
@@ -71,7 +81,6 @@ export declare class CLIConfig {
|
|
|
71
81
|
isAuthenticated(): Promise<boolean>;
|
|
72
82
|
logout(): Promise<void>;
|
|
73
83
|
clear(): Promise<void>;
|
|
74
|
-
getConfigPath(): string;
|
|
75
84
|
exists(): Promise<boolean>;
|
|
76
85
|
validateStoredCredentials(): Promise<boolean>;
|
|
77
86
|
refreshTokenIfNeeded(): Promise<void>;
|
package/dist/utils/config.js
CHANGED
|
@@ -16,6 +16,20 @@ export class CLIConfig {
|
|
|
16
16
|
this.configPath = path.join(this.configDir, 'config.json');
|
|
17
17
|
this.lockFile = path.join(this.configDir, 'config.lock');
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Overrides the configuration storage directory. Primarily used for tests.
|
|
21
|
+
*/
|
|
22
|
+
setConfigDirectory(configDir) {
|
|
23
|
+
this.configDir = configDir;
|
|
24
|
+
this.configPath = path.join(configDir, 'config.json');
|
|
25
|
+
this.lockFile = path.join(configDir, 'config.lock');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Exposes the current config path for tests and diagnostics.
|
|
29
|
+
*/
|
|
30
|
+
getConfigPath() {
|
|
31
|
+
return this.configPath;
|
|
32
|
+
}
|
|
19
33
|
async init() {
|
|
20
34
|
try {
|
|
21
35
|
await fs.mkdir(this.configDir, { recursive: true });
|
|
@@ -229,20 +243,17 @@ export class CLIConfig {
|
|
|
229
243
|
return;
|
|
230
244
|
}
|
|
231
245
|
}
|
|
232
|
-
|
|
246
|
+
const fallback = this.resolveFallbackEndpoints();
|
|
233
247
|
this.config.discoveredServices = {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
mcp_base: 'https://mcp.lanonasis.com/api/v1', // MCP HTTP/REST
|
|
237
|
-
mcp_ws_base: 'wss://mcp.lanonasis.com/ws', // MCP WebSocket
|
|
238
|
-
mcp_sse_base: 'https://mcp.lanonasis.com/api/v1/events', // MCP SSE
|
|
239
|
-
project_scope: 'lanonasis-maas' // Correct project scope
|
|
248
|
+
...fallback.endpoints,
|
|
249
|
+
project_scope: 'lanonasis-maas'
|
|
240
250
|
};
|
|
241
251
|
// Mark as fallback (don't set lastServiceDiscovery)
|
|
242
252
|
await this.save();
|
|
253
|
+
this.logFallbackUsage(fallback.source, this.config.discoveredServices);
|
|
243
254
|
if (verbose) {
|
|
244
255
|
console.log('✓ Using fallback service endpoints');
|
|
245
|
-
console.log(
|
|
256
|
+
console.log(` Source: ${fallback.source === 'environment' ? 'environment overrides' : 'built-in defaults'}`);
|
|
246
257
|
}
|
|
247
258
|
}
|
|
248
259
|
categorizeServiceDiscoveryError(error) {
|
|
@@ -272,6 +283,47 @@ export class CLIConfig {
|
|
|
272
283
|
}
|
|
273
284
|
return 'unknown';
|
|
274
285
|
}
|
|
286
|
+
resolveFallbackEndpoints() {
|
|
287
|
+
const envAuthBase = process.env.LANONASIS_FALLBACK_AUTH_BASE ?? process.env.AUTH_BASE;
|
|
288
|
+
const envMemoryBase = process.env.LANONASIS_FALLBACK_MEMORY_BASE ?? process.env.MEMORY_BASE;
|
|
289
|
+
const envMcpBase = process.env.LANONASIS_FALLBACK_MCP_BASE ?? process.env.MCP_BASE;
|
|
290
|
+
const envMcpWsBase = process.env.LANONASIS_FALLBACK_MCP_WS_BASE ?? process.env.MCP_WS_BASE;
|
|
291
|
+
const envMcpSseBase = process.env.LANONASIS_FALLBACK_MCP_SSE_BASE ?? process.env.MCP_SSE_BASE;
|
|
292
|
+
const hasEnvOverrides = Boolean(envAuthBase || envMemoryBase || envMcpBase || envMcpWsBase || envMcpSseBase);
|
|
293
|
+
const nodeEnv = (process.env.NODE_ENV ?? '').toLowerCase();
|
|
294
|
+
const isDevEnvironment = nodeEnv === 'development' || nodeEnv === 'test';
|
|
295
|
+
const defaultAuthBase = isDevEnvironment ? 'http://localhost:4000' : 'https://api.lanonasis.com';
|
|
296
|
+
const defaultMemoryBase = isDevEnvironment ? 'http://localhost:4000/api/v1' : 'https://api.lanonasis.com/api/v1';
|
|
297
|
+
const defaultMcpBase = isDevEnvironment ? 'http://localhost:4100/api/v1' : 'https://mcp.lanonasis.com/api/v1';
|
|
298
|
+
const defaultMcpWsBase = isDevEnvironment ? 'ws://localhost:4100/ws' : 'wss://mcp.lanonasis.com/ws';
|
|
299
|
+
const defaultMcpSseBase = isDevEnvironment ? 'http://localhost:4100/api/v1/events' : 'https://mcp.lanonasis.com/api/v1/events';
|
|
300
|
+
const endpoints = {
|
|
301
|
+
auth_base: envAuthBase ?? defaultAuthBase,
|
|
302
|
+
memory_base: envMemoryBase ?? defaultMemoryBase,
|
|
303
|
+
mcp_base: envMcpBase ?? defaultMcpBase,
|
|
304
|
+
mcp_ws_base: envMcpWsBase ?? defaultMcpWsBase,
|
|
305
|
+
mcp_sse_base: envMcpSseBase ?? defaultMcpSseBase
|
|
306
|
+
};
|
|
307
|
+
return {
|
|
308
|
+
endpoints,
|
|
309
|
+
source: hasEnvOverrides ? 'environment' : 'default'
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
logFallbackUsage(source, endpoints) {
|
|
313
|
+
const summary = {
|
|
314
|
+
auth: endpoints.auth_base,
|
|
315
|
+
mcp: endpoints.mcp_base,
|
|
316
|
+
websocket: endpoints.mcp_ws_base,
|
|
317
|
+
sse: endpoints.mcp_sse_base,
|
|
318
|
+
source
|
|
319
|
+
};
|
|
320
|
+
const message = `Service discovery fallback activated using ${source === 'environment' ? 'environment overrides' : 'built-in defaults'}`;
|
|
321
|
+
console.warn(`⚠️ ${message}`);
|
|
322
|
+
console.info('📊 service_discovery_fallback', summary);
|
|
323
|
+
if (typeof process.emitWarning === 'function') {
|
|
324
|
+
process.emitWarning(message, 'ServiceDiscoveryFallback');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
275
327
|
// Manual endpoint override functionality
|
|
276
328
|
async setManualEndpoints(endpoints) {
|
|
277
329
|
if (!this.config.discoveredServices) {
|
|
@@ -302,55 +354,25 @@ export class CLIConfig {
|
|
|
302
354
|
}
|
|
303
355
|
// Enhanced authentication support
|
|
304
356
|
async setVendorKey(vendorKey) {
|
|
305
|
-
|
|
306
|
-
|
|
357
|
+
const trimmedKey = typeof vendorKey === 'string' ? vendorKey.trim() : '';
|
|
358
|
+
// Minimal format validation (non-empty); rely on server-side checks for everything else
|
|
359
|
+
const formatValidation = this.validateVendorKeyFormat(trimmedKey);
|
|
307
360
|
if (formatValidation !== true) {
|
|
308
|
-
throw new Error(typeof formatValidation === 'string' ? formatValidation : '
|
|
361
|
+
throw new Error(typeof formatValidation === 'string' ? formatValidation : 'Vendor key is invalid');
|
|
309
362
|
}
|
|
310
363
|
// Server-side validation
|
|
311
|
-
await this.validateVendorKeyWithServer(
|
|
312
|
-
this.config.vendorKey =
|
|
364
|
+
await this.validateVendorKeyWithServer(trimmedKey);
|
|
365
|
+
this.config.vendorKey = trimmedKey;
|
|
313
366
|
this.config.authMethod = 'vendor_key';
|
|
314
367
|
this.config.lastValidated = new Date().toISOString();
|
|
315
368
|
await this.resetFailureCount(); // Reset failure count on successful auth
|
|
316
369
|
await this.save();
|
|
317
370
|
}
|
|
318
371
|
validateVendorKeyFormat(vendorKey) {
|
|
319
|
-
|
|
372
|
+
const trimmed = typeof vendorKey === 'string' ? vendorKey.trim() : '';
|
|
373
|
+
if (!trimmed) {
|
|
320
374
|
return 'Vendor key is required';
|
|
321
375
|
}
|
|
322
|
-
const trimmed = vendorKey.trim();
|
|
323
|
-
// Check basic format
|
|
324
|
-
if (!trimmed.includes('.')) {
|
|
325
|
-
return 'Invalid vendor key format: Must contain a dot (.) separator. Expected format: pk_xxx.sk_xxx';
|
|
326
|
-
}
|
|
327
|
-
const parts = trimmed.split('.');
|
|
328
|
-
if (parts.length !== 2) {
|
|
329
|
-
return 'Invalid vendor key format: Must have exactly two parts separated by a dot. Expected format: pk_xxx.sk_xxx';
|
|
330
|
-
}
|
|
331
|
-
const [publicPart, secretPart] = parts;
|
|
332
|
-
// Validate public key part
|
|
333
|
-
if (!publicPart.startsWith('pk_')) {
|
|
334
|
-
return 'Invalid vendor key format: First part must start with "pk_". Expected format: pk_xxx.sk_xxx';
|
|
335
|
-
}
|
|
336
|
-
if (publicPart.length < 11) { // pk_ + minimum 8 chars
|
|
337
|
-
return 'Invalid vendor key format: Public key part is too short. Expected format: pk_xxx.sk_xxx (minimum 8 characters after "pk_")';
|
|
338
|
-
}
|
|
339
|
-
const publicKeyContent = publicPart.substring(3); // Remove 'pk_'
|
|
340
|
-
if (!/^[a-zA-Z0-9]+$/.test(publicKeyContent)) {
|
|
341
|
-
return 'Invalid vendor key format: Public key part contains invalid characters. Only letters and numbers are allowed after "pk_"';
|
|
342
|
-
}
|
|
343
|
-
// Validate secret key part
|
|
344
|
-
if (!secretPart.startsWith('sk_')) {
|
|
345
|
-
return 'Invalid vendor key format: Second part must start with "sk_". Expected format: pk_xxx.sk_xxx';
|
|
346
|
-
}
|
|
347
|
-
if (secretPart.length < 19) { // sk_ + minimum 16 chars
|
|
348
|
-
return 'Invalid vendor key format: Secret key part is too short. Expected format: pk_xxx.sk_xxx (minimum 16 characters after "sk_")';
|
|
349
|
-
}
|
|
350
|
-
const secretKeyContent = secretPart.substring(3); // Remove 'sk_'
|
|
351
|
-
if (!/^[a-zA-Z0-9]+$/.test(secretKeyContent)) {
|
|
352
|
-
return 'Invalid vendor key format: Secret key part contains invalid characters. Only letters and numbers are allowed after "sk_"';
|
|
353
|
-
}
|
|
354
376
|
return true;
|
|
355
377
|
}
|
|
356
378
|
async validateVendorKeyWithServer(vendorKey) {
|
|
@@ -524,7 +546,17 @@ export class CLIConfig {
|
|
|
524
546
|
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
525
547
|
return false;
|
|
526
548
|
}
|
|
527
|
-
//
|
|
549
|
+
// Token is locally valid - check if we need server validation
|
|
550
|
+
// Skip server validation if we have a recent lastValidated timestamp (within 24 hours)
|
|
551
|
+
const lastValidated = this.config.lastValidated;
|
|
552
|
+
const skipServerValidation = lastValidated &&
|
|
553
|
+
(Date.now() - new Date(lastValidated).getTime()) < (24 * 60 * 60 * 1000); // 24 hours
|
|
554
|
+
if (skipServerValidation) {
|
|
555
|
+
// Trust the local validation if it was recently validated
|
|
556
|
+
this.authCheckCache = { isValid: locallyValid, timestamp: Date.now() };
|
|
557
|
+
return locallyValid;
|
|
558
|
+
}
|
|
559
|
+
// Verify with server (security check) for tokens that haven't been validated recently
|
|
528
560
|
try {
|
|
529
561
|
const axios = (await import('axios')).default;
|
|
530
562
|
// Try auth-gateway first (port 4000), then fall back to Netlify function
|
|
@@ -547,16 +579,29 @@ export class CLIConfig {
|
|
|
547
579
|
}
|
|
548
580
|
}
|
|
549
581
|
if (!response || response.data.valid !== true) {
|
|
582
|
+
// Server says invalid - but if locally valid and recent, trust local
|
|
583
|
+
if (locallyValid) {
|
|
584
|
+
if (process.env.CLI_VERBOSE === 'true') {
|
|
585
|
+
console.warn('⚠️ Server validation failed, but token is locally valid - using local validation');
|
|
586
|
+
}
|
|
587
|
+
this.authCheckCache = { isValid: locallyValid, timestamp: Date.now() };
|
|
588
|
+
return locallyValid;
|
|
589
|
+
}
|
|
550
590
|
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
551
591
|
return false;
|
|
552
592
|
}
|
|
593
|
+
// Update lastValidated on successful server validation
|
|
594
|
+
this.config.lastValidated = new Date().toISOString();
|
|
595
|
+
await this.save().catch(() => { }); // Don't fail auth check if save fails
|
|
553
596
|
this.authCheckCache = { isValid: true, timestamp: Date.now() };
|
|
554
597
|
return true;
|
|
555
598
|
}
|
|
556
599
|
catch {
|
|
557
600
|
// If all server checks fail, fall back to local validation
|
|
558
601
|
// This allows offline usage but is less secure
|
|
559
|
-
|
|
602
|
+
if (process.env.CLI_VERBOSE === 'true') {
|
|
603
|
+
console.warn('⚠️ Unable to verify token with server, using local validation');
|
|
604
|
+
}
|
|
560
605
|
this.authCheckCache = { isValid: locallyValid, timestamp: Date.now() };
|
|
561
606
|
return locallyValid;
|
|
562
607
|
}
|
|
@@ -570,9 +615,6 @@ export class CLIConfig {
|
|
|
570
615
|
this.config = {};
|
|
571
616
|
await this.save();
|
|
572
617
|
}
|
|
573
|
-
getConfigPath() {
|
|
574
|
-
return this.configPath;
|
|
575
|
-
}
|
|
576
618
|
async exists() {
|
|
577
619
|
try {
|
|
578
620
|
await fs.access(this.configPath);
|
|
@@ -656,9 +698,12 @@ export class CLIConfig {
|
|
|
656
698
|
}
|
|
657
699
|
}
|
|
658
700
|
}
|
|
659
|
-
catch {
|
|
701
|
+
catch (err) {
|
|
660
702
|
// If refresh fails, mark credentials as potentially invalid
|
|
661
703
|
await this.incrementFailureCount();
|
|
704
|
+
if (process.env.CLI_VERBOSE === 'true' || process.env.NODE_ENV !== 'production') {
|
|
705
|
+
console.debug('Token refresh failed:', err.message);
|
|
706
|
+
}
|
|
662
707
|
}
|
|
663
708
|
}
|
|
664
709
|
async clearInvalidCredentials() {
|
|
@@ -57,6 +57,23 @@ export declare class MCPClient {
|
|
|
57
57
|
private lastHealthCheck;
|
|
58
58
|
private activeConnectionMode;
|
|
59
59
|
constructor();
|
|
60
|
+
/**
|
|
61
|
+
* Overrides the configuration directory used by the underlying CLI config.
|
|
62
|
+
* Useful for tests that need isolated config state.
|
|
63
|
+
*/
|
|
64
|
+
setConfigDirectory(configDir: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Returns the current config file path. Primarily used for test introspection.
|
|
67
|
+
*/
|
|
68
|
+
getConfigPath(): string;
|
|
69
|
+
/**
|
|
70
|
+
* Helper for tests to seed authentication tokens without accessing internals.
|
|
71
|
+
*/
|
|
72
|
+
setTokenForTesting(token: string): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Helper for tests to seed vendor keys without accessing internals.
|
|
75
|
+
*/
|
|
76
|
+
setVendorKeyForTesting(vendorKey: string): Promise<void>;
|
|
60
77
|
/**
|
|
61
78
|
* Initialize the MCP client configuration
|
|
62
79
|
*/
|
|
@@ -93,10 +110,6 @@ export declare class MCPClient {
|
|
|
93
110
|
* Validate authentication credentials before attempting MCP connection
|
|
94
111
|
*/
|
|
95
112
|
private validateAuthBeforeConnect;
|
|
96
|
-
/**
|
|
97
|
-
* Validate vendor key format
|
|
98
|
-
*/
|
|
99
|
-
private validateVendorKeyFormat;
|
|
100
113
|
/**
|
|
101
114
|
* Validate and refresh token if needed
|
|
102
115
|
*/
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -20,6 +20,31 @@ export class MCPClient {
|
|
|
20
20
|
constructor() {
|
|
21
21
|
this.config = new CLIConfig();
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Overrides the configuration directory used by the underlying CLI config.
|
|
25
|
+
* Useful for tests that need isolated config state.
|
|
26
|
+
*/
|
|
27
|
+
setConfigDirectory(configDir) {
|
|
28
|
+
this.config.setConfigDirectory(configDir);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns the current config file path. Primarily used for test introspection.
|
|
32
|
+
*/
|
|
33
|
+
getConfigPath() {
|
|
34
|
+
return this.config.getConfigPath();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Helper for tests to seed authentication tokens without accessing internals.
|
|
38
|
+
*/
|
|
39
|
+
async setTokenForTesting(token) {
|
|
40
|
+
await this.config.setToken(token);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Helper for tests to seed vendor keys without accessing internals.
|
|
44
|
+
*/
|
|
45
|
+
async setVendorKeyForTesting(vendorKey) {
|
|
46
|
+
await this.config.setVendorKey(vendorKey);
|
|
47
|
+
}
|
|
23
48
|
/**
|
|
24
49
|
* Initialize the MCP client configuration
|
|
25
50
|
*/
|
|
@@ -220,11 +245,10 @@ export class MCPClient {
|
|
|
220
245
|
const msg = error?.message ?? '';
|
|
221
246
|
if (msg.includes('AUTHENTICATION_REQUIRED')) {
|
|
222
247
|
console.log(chalk.cyan('• No credentials found. Run: lanonasis auth login'));
|
|
223
|
-
console.log(chalk.cyan('• Or set vendor key: lanonasis auth login --vendor-key
|
|
248
|
+
console.log(chalk.cyan('• Or set a vendor key: lanonasis auth login --vendor-key <your-key>'));
|
|
224
249
|
}
|
|
225
250
|
else if (msg.includes('AUTHENTICATION_INVALID')) {
|
|
226
|
-
console.log(chalk.cyan('• Invalid credentials.
|
|
227
|
-
console.log(chalk.cyan('• Expected format: pk_xxx.sk_xxx'));
|
|
251
|
+
console.log(chalk.cyan('• Invalid credentials. Confirm the vendor key matches your dashboard value'));
|
|
228
252
|
console.log(chalk.cyan('• Try: lanonasis auth logout && lanonasis auth login'));
|
|
229
253
|
}
|
|
230
254
|
else if (msg.includes('expired')) {
|
|
@@ -234,7 +258,7 @@ export class MCPClient {
|
|
|
234
258
|
else {
|
|
235
259
|
console.log(chalk.cyan('• Check authentication status: lanonasis auth status'));
|
|
236
260
|
console.log(chalk.cyan('• Re-authenticate: lanonasis auth login'));
|
|
237
|
-
console.log(chalk.cyan('• Verify vendor key: lanonasis auth login --vendor-key
|
|
261
|
+
console.log(chalk.cyan('• Verify vendor key: lanonasis auth login --vendor-key <your-key>'));
|
|
238
262
|
}
|
|
239
263
|
}
|
|
240
264
|
/**
|
|
@@ -306,21 +330,14 @@ export class MCPClient {
|
|
|
306
330
|
throw new Error(`AUTHENTICATION_INVALID: ${error instanceof Error ? error.message : 'Token validation failed'}`);
|
|
307
331
|
}
|
|
308
332
|
}
|
|
309
|
-
// If we have a vendor key,
|
|
333
|
+
// If we have a vendor key, ensure it is valid (non-empty)
|
|
310
334
|
if (vendorKey && !token) {
|
|
311
|
-
|
|
312
|
-
|
|
335
|
+
const validationResult = this.config.validateVendorKeyFormat(vendorKey);
|
|
336
|
+
if (validationResult !== true) {
|
|
337
|
+
throw new Error(`AUTHENTICATION_INVALID: ${typeof validationResult === 'string' ? validationResult : 'Vendor key is invalid'}`);
|
|
313
338
|
}
|
|
314
339
|
}
|
|
315
340
|
}
|
|
316
|
-
/**
|
|
317
|
-
* Validate vendor key format
|
|
318
|
-
*/
|
|
319
|
-
validateVendorKeyFormat(vendorKey) {
|
|
320
|
-
// Vendor key should be in format: pk_xxx.sk_xxx
|
|
321
|
-
const vendorKeyPattern = /^pk_[a-zA-Z0-9]+\.sk_[a-zA-Z0-9]+$/;
|
|
322
|
-
return vendorKeyPattern.test(vendorKey);
|
|
323
|
-
}
|
|
324
341
|
/**
|
|
325
342
|
* Validate and refresh token if needed
|
|
326
343
|
*/
|
|
@@ -528,7 +545,7 @@ export class MCPClient {
|
|
|
528
545
|
}
|
|
529
546
|
try {
|
|
530
547
|
this.lastHealthCheck = new Date();
|
|
531
|
-
const connectionMode = this.
|
|
548
|
+
const connectionMode = this.activeConnectionMode || 'remote';
|
|
532
549
|
switch (connectionMode) {
|
|
533
550
|
case 'websocket':
|
|
534
551
|
await this.checkWebSocketHealth();
|
|
@@ -542,7 +559,8 @@ export class MCPClient {
|
|
|
542
559
|
}
|
|
543
560
|
}
|
|
544
561
|
catch {
|
|
545
|
-
|
|
562
|
+
const connectionMode = this.activeConnectionMode || 'remote';
|
|
563
|
+
console.log(chalk.yellow(`⚠️ ${connectionMode} connection health check failed, attempting reconnection...`));
|
|
546
564
|
await this.handleHealthCheckFailure();
|
|
547
565
|
}
|
|
548
566
|
}
|
|
@@ -607,10 +625,11 @@ export class MCPClient {
|
|
|
607
625
|
this.isConnected = false;
|
|
608
626
|
this.stopHealthMonitoring();
|
|
609
627
|
// Attempt to reconnect with current configuration
|
|
610
|
-
const connectionMode = this.
|
|
628
|
+
const connectionMode = (this.activeConnectionMode || 'remote');
|
|
611
629
|
const options = {
|
|
612
|
-
connectionMode
|
|
630
|
+
connectionMode
|
|
613
631
|
};
|
|
632
|
+
console.log(chalk.yellow(`↻ Attempting reconnection using ${connectionMode} mode...`));
|
|
614
633
|
// Add specific URLs if available
|
|
615
634
|
if (connectionMode === 'websocket') {
|
|
616
635
|
options.serverUrl = this.config.get('mcpWebSocketUrl');
|
|
@@ -648,7 +667,7 @@ export class MCPClient {
|
|
|
648
667
|
this.wsConnection = null;
|
|
649
668
|
}
|
|
650
669
|
this.isConnected = false;
|
|
651
|
-
this.activeConnectionMode = '
|
|
670
|
+
this.activeConnectionMode = 'websocket'; // Reset to default
|
|
652
671
|
}
|
|
653
672
|
/**
|
|
654
673
|
* Call an MCP tool
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "LanOnasis Enterprise CLI - Memory as a Service, API Key Management, and Infrastructure Orchestration",
|
|
5
5
|
"main": "dist/index-simple.js",
|
|
6
6
|
"bin": {
|
|
@@ -99,4 +99,4 @@
|
|
|
99
99
|
"engines": {
|
|
100
100
|
"node": ">=18.0.0"
|
|
101
101
|
}
|
|
102
|
-
}
|
|
102
|
+
}
|