@lanonasis/cli 3.5.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 +175 -119
- 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 -18
- package/dist/index-simple.js +19 -2
- package/dist/index.js +41 -0
- package/dist/mcp/schemas/tool-schemas.d.ts +4 -4
- package/dist/utils/api.js +1 -1
- package/dist/utils/config.js +33 -40
- package/dist/utils/mcp-client.d.ts +0 -4
- package/dist/utils/mcp-client.js +7 -15
- 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) {
|
|
@@ -335,7 +448,7 @@ export async function diagnoseCommand() {
|
|
|
335
448
|
}
|
|
336
449
|
if (!diagnostics.hasCredentials) {
|
|
337
450
|
issues.push('No authentication credentials stored');
|
|
338
|
-
recommendations.push('Run: lanonasis auth login --vendor-key
|
|
451
|
+
recommendations.push('Run: lanonasis auth login --vendor-key <your-key>');
|
|
339
452
|
}
|
|
340
453
|
if (diagnostics.hasCredentials && !diagnostics.credentialsValid) {
|
|
341
454
|
issues.push('Stored credentials are invalid');
|
|
@@ -370,7 +483,7 @@ export async function diagnoseCommand() {
|
|
|
370
483
|
// Additional troubleshooting info
|
|
371
484
|
if (diagnostics.authFailures > 0 || !diagnostics.credentialsValid) {
|
|
372
485
|
console.log(chalk.gray('\n🔧 Additional troubleshooting:'));
|
|
373
|
-
console.log(chalk.gray(' • Verify
|
|
486
|
+
console.log(chalk.gray(' • Verify the vendor key matches the value shown in your dashboard'));
|
|
374
487
|
console.log(chalk.gray(' • Check if your key is active in the dashboard'));
|
|
375
488
|
console.log(chalk.gray(' • Try browser authentication: lanonasis auth login --use-web-auth'));
|
|
376
489
|
console.log(chalk.gray(' • Contact support if issues persist'));
|
|
@@ -457,84 +570,37 @@ async function handleVendorKeyAuth(vendorKey, config) {
|
|
|
457
570
|
async function handleVendorKeyFlow(config) {
|
|
458
571
|
console.log();
|
|
459
572
|
console.log(chalk.yellow('🔑 Vendor Key Authentication'));
|
|
460
|
-
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.'));
|
|
461
574
|
console.log();
|
|
462
575
|
// Enhanced guidance for obtaining vendor keys
|
|
463
576
|
console.log(chalk.cyan('📋 How to get your vendor key:'));
|
|
464
577
|
console.log(chalk.gray('1. Visit your Lanonasis dashboard at https://app.lanonasis.com'));
|
|
465
578
|
console.log(chalk.gray('2. Navigate to Settings → API Keys'));
|
|
466
|
-
console.log(chalk.gray('3. Click "Generate New Key" and copy the full key'));
|
|
467
|
-
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'));
|
|
468
580
|
console.log();
|
|
469
581
|
const { vendorKey } = await inquirer.prompt([
|
|
470
582
|
{
|
|
471
583
|
type: 'password',
|
|
472
584
|
name: 'vendorKey',
|
|
473
|
-
message: 'Enter your vendor key
|
|
585
|
+
message: 'Enter your vendor key:',
|
|
474
586
|
mask: '*',
|
|
475
587
|
validate: (input) => {
|
|
476
|
-
return validateVendorKeyFormat(input);
|
|
588
|
+
return config.validateVendorKeyFormat(input);
|
|
477
589
|
}
|
|
478
590
|
}
|
|
479
591
|
]);
|
|
480
592
|
await handleVendorKeyAuth(vendorKey, config);
|
|
481
593
|
}
|
|
482
|
-
// Enhanced vendor key format validation with detailed error messages
|
|
483
|
-
function validateVendorKeyFormat(input) {
|
|
484
|
-
if (!input || input.trim().length === 0) {
|
|
485
|
-
return 'Vendor key is required';
|
|
486
|
-
}
|
|
487
|
-
const trimmed = input.trim();
|
|
488
|
-
// Check basic format
|
|
489
|
-
if (!trimmed.includes('.')) {
|
|
490
|
-
return 'Invalid format: Vendor key must contain a dot (.) separator\nExpected format: pk_xxx.sk_xxx';
|
|
491
|
-
}
|
|
492
|
-
const parts = trimmed.split('.');
|
|
493
|
-
if (parts.length !== 2) {
|
|
494
|
-
return 'Invalid format: Vendor key must have exactly two parts separated by a dot\nExpected format: pk_xxx.sk_xxx';
|
|
495
|
-
}
|
|
496
|
-
const [publicPart, secretPart] = parts;
|
|
497
|
-
// Validate public key part
|
|
498
|
-
if (!publicPart.startsWith('pk_')) {
|
|
499
|
-
return 'Invalid format: First part must start with "pk_"\nExpected format: pk_xxx.sk_xxx';
|
|
500
|
-
}
|
|
501
|
-
if (publicPart.length < 4) {
|
|
502
|
-
return 'Invalid format: Public key part is too short\nExpected format: pk_xxx.sk_xxx (where xxx is alphanumeric)';
|
|
503
|
-
}
|
|
504
|
-
const publicKeyContent = publicPart.substring(3); // Remove 'pk_'
|
|
505
|
-
if (!/^[a-zA-Z0-9]+$/.test(publicKeyContent)) {
|
|
506
|
-
return 'Invalid format: Public key part contains invalid characters\nOnly letters and numbers are allowed after "pk_"';
|
|
507
|
-
}
|
|
508
|
-
// Validate secret key part
|
|
509
|
-
if (!secretPart.startsWith('sk_')) {
|
|
510
|
-
return 'Invalid format: Second part must start with "sk_"\nExpected format: pk_xxx.sk_xxx';
|
|
511
|
-
}
|
|
512
|
-
if (secretPart.length < 4) {
|
|
513
|
-
return 'Invalid format: Secret key part is too short\nExpected format: pk_xxx.sk_xxx (where xxx is alphanumeric)';
|
|
514
|
-
}
|
|
515
|
-
const secretKeyContent = secretPart.substring(3); // Remove 'sk_'
|
|
516
|
-
if (!/^[a-zA-Z0-9]+$/.test(secretKeyContent)) {
|
|
517
|
-
return 'Invalid format: Secret key part contains invalid characters\nOnly letters and numbers are allowed after "sk_"';
|
|
518
|
-
}
|
|
519
|
-
// Check minimum length requirements
|
|
520
|
-
if (publicKeyContent.length < 8) {
|
|
521
|
-
return 'Invalid format: Public key part is too short (minimum 8 characters after "pk_")';
|
|
522
|
-
}
|
|
523
|
-
if (secretKeyContent.length < 16) {
|
|
524
|
-
return 'Invalid format: Secret key part is too short (minimum 16 characters after "sk_")';
|
|
525
|
-
}
|
|
526
|
-
return true;
|
|
527
|
-
}
|
|
528
594
|
async function handleOAuthFlow(config) {
|
|
529
595
|
console.log();
|
|
530
|
-
console.log(chalk.yellow('🌐 Browser-Based Authentication'));
|
|
531
|
-
console.log(chalk.gray('
|
|
596
|
+
console.log(chalk.yellow('🌐 Browser-Based OAuth2 Authentication'));
|
|
597
|
+
console.log(chalk.gray('Secure authentication using OAuth2 with PKCE'));
|
|
532
598
|
console.log();
|
|
533
599
|
const { openBrowser } = await inquirer.prompt([
|
|
534
600
|
{
|
|
535
601
|
type: 'confirm',
|
|
536
602
|
name: 'openBrowser',
|
|
537
|
-
message: 'Open browser for authentication?',
|
|
603
|
+
message: 'Open browser for OAuth2 authentication?',
|
|
538
604
|
default: true
|
|
539
605
|
}
|
|
540
606
|
]);
|
|
@@ -542,63 +608,53 @@ async function handleOAuthFlow(config) {
|
|
|
542
608
|
console.log(chalk.yellow('⚠️ Authentication cancelled'));
|
|
543
609
|
return;
|
|
544
610
|
}
|
|
545
|
-
// Use the browser-based CLI login endpoint discovered from auth_base
|
|
546
|
-
const authBase = config.getDiscoveredApiUrl();
|
|
547
|
-
const authUrl = `${authBase.replace(/\/$/, '')}/auth/cli-login`;
|
|
548
611
|
try {
|
|
549
|
-
|
|
550
|
-
|
|
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'));
|
|
551
629
|
console.log();
|
|
552
|
-
console.log(colors.info('
|
|
553
|
-
|
|
554
|
-
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()}`));
|
|
555
634
|
console.log();
|
|
556
|
-
//
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return 'Invalid token format. Expected format: cli_xxx or JWT token';
|
|
574
|
-
}
|
|
575
|
-
// Verify token with server
|
|
576
|
-
try {
|
|
577
|
-
const response = await apiClient.post('/auth/verify', { token: trimmed });
|
|
578
|
-
if (!response.valid) {
|
|
579
|
-
return 'Token verification failed. Please try again.';
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
catch (error) {
|
|
583
|
-
const errorMessage = error instanceof Error ? error.message : 'Server verification failed';
|
|
584
|
-
return `Token verification error: ${errorMessage}`;
|
|
585
|
-
}
|
|
586
|
-
return true;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
]);
|
|
590
|
-
if (token && token.trim()) {
|
|
591
|
-
await config.setToken(token.trim());
|
|
592
|
-
console.log(chalk.green('✓ Browser authentication successful'));
|
|
593
|
-
console.log(colors.info('You can now use Lanonasis services'));
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
console.log(chalk.yellow('⚠️ No token provided'));
|
|
597
|
-
}
|
|
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'));
|
|
598
652
|
}
|
|
599
|
-
catch {
|
|
600
|
-
console.error(chalk.red('✖
|
|
601
|
-
|
|
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);
|
|
602
658
|
}
|
|
603
659
|
}
|
|
604
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!');
|
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')
|
|
@@ -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/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.js
CHANGED
|
@@ -354,55 +354,25 @@ export class CLIConfig {
|
|
|
354
354
|
}
|
|
355
355
|
// Enhanced authentication support
|
|
356
356
|
async setVendorKey(vendorKey) {
|
|
357
|
-
|
|
358
|
-
|
|
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);
|
|
359
360
|
if (formatValidation !== true) {
|
|
360
|
-
throw new Error(typeof formatValidation === 'string' ? formatValidation : '
|
|
361
|
+
throw new Error(typeof formatValidation === 'string' ? formatValidation : 'Vendor key is invalid');
|
|
361
362
|
}
|
|
362
363
|
// Server-side validation
|
|
363
|
-
await this.validateVendorKeyWithServer(
|
|
364
|
-
this.config.vendorKey =
|
|
364
|
+
await this.validateVendorKeyWithServer(trimmedKey);
|
|
365
|
+
this.config.vendorKey = trimmedKey;
|
|
365
366
|
this.config.authMethod = 'vendor_key';
|
|
366
367
|
this.config.lastValidated = new Date().toISOString();
|
|
367
368
|
await this.resetFailureCount(); // Reset failure count on successful auth
|
|
368
369
|
await this.save();
|
|
369
370
|
}
|
|
370
371
|
validateVendorKeyFormat(vendorKey) {
|
|
371
|
-
|
|
372
|
+
const trimmed = typeof vendorKey === 'string' ? vendorKey.trim() : '';
|
|
373
|
+
if (!trimmed) {
|
|
372
374
|
return 'Vendor key is required';
|
|
373
375
|
}
|
|
374
|
-
const trimmed = vendorKey.trim();
|
|
375
|
-
// Check basic format
|
|
376
|
-
if (!trimmed.includes('.')) {
|
|
377
|
-
return 'Invalid vendor key format: Must contain a dot (.) separator. Expected format: pk_xxx.sk_xxx';
|
|
378
|
-
}
|
|
379
|
-
const parts = trimmed.split('.');
|
|
380
|
-
if (parts.length !== 2) {
|
|
381
|
-
return 'Invalid vendor key format: Must have exactly two parts separated by a dot. Expected format: pk_xxx.sk_xxx';
|
|
382
|
-
}
|
|
383
|
-
const [publicPart, secretPart] = parts;
|
|
384
|
-
// Validate public key part
|
|
385
|
-
if (!publicPart.startsWith('pk_')) {
|
|
386
|
-
return 'Invalid vendor key format: First part must start with "pk_". Expected format: pk_xxx.sk_xxx';
|
|
387
|
-
}
|
|
388
|
-
if (publicPart.length < 11) { // pk_ + minimum 8 chars
|
|
389
|
-
return 'Invalid vendor key format: Public key part is too short. Expected format: pk_xxx.sk_xxx (minimum 8 characters after "pk_")';
|
|
390
|
-
}
|
|
391
|
-
const publicKeyContent = publicPart.substring(3); // Remove 'pk_'
|
|
392
|
-
if (!/^[a-zA-Z0-9]+$/.test(publicKeyContent)) {
|
|
393
|
-
return 'Invalid vendor key format: Public key part contains invalid characters. Only letters and numbers are allowed after "pk_"';
|
|
394
|
-
}
|
|
395
|
-
// Validate secret key part
|
|
396
|
-
if (!secretPart.startsWith('sk_')) {
|
|
397
|
-
return 'Invalid vendor key format: Second part must start with "sk_". Expected format: pk_xxx.sk_xxx';
|
|
398
|
-
}
|
|
399
|
-
if (secretPart.length < 19) { // sk_ + minimum 16 chars
|
|
400
|
-
return 'Invalid vendor key format: Secret key part is too short. Expected format: pk_xxx.sk_xxx (minimum 16 characters after "sk_")';
|
|
401
|
-
}
|
|
402
|
-
const secretKeyContent = secretPart.substring(3); // Remove 'sk_'
|
|
403
|
-
if (!/^[a-zA-Z0-9]+$/.test(secretKeyContent)) {
|
|
404
|
-
return 'Invalid vendor key format: Secret key part contains invalid characters. Only letters and numbers are allowed after "sk_"';
|
|
405
|
-
}
|
|
406
376
|
return true;
|
|
407
377
|
}
|
|
408
378
|
async validateVendorKeyWithServer(vendorKey) {
|
|
@@ -576,7 +546,17 @@ export class CLIConfig {
|
|
|
576
546
|
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
577
547
|
return false;
|
|
578
548
|
}
|
|
579
|
-
//
|
|
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
|
|
580
560
|
try {
|
|
581
561
|
const axios = (await import('axios')).default;
|
|
582
562
|
// Try auth-gateway first (port 4000), then fall back to Netlify function
|
|
@@ -599,16 +579,29 @@ export class CLIConfig {
|
|
|
599
579
|
}
|
|
600
580
|
}
|
|
601
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
|
+
}
|
|
602
590
|
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
603
591
|
return false;
|
|
604
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
|
|
605
596
|
this.authCheckCache = { isValid: true, timestamp: Date.now() };
|
|
606
597
|
return true;
|
|
607
598
|
}
|
|
608
599
|
catch {
|
|
609
600
|
// If all server checks fail, fall back to local validation
|
|
610
601
|
// This allows offline usage but is less secure
|
|
611
|
-
|
|
602
|
+
if (process.env.CLI_VERBOSE === 'true') {
|
|
603
|
+
console.warn('⚠️ Unable to verify token with server, using local validation');
|
|
604
|
+
}
|
|
612
605
|
this.authCheckCache = { isValid: locallyValid, timestamp: Date.now() };
|
|
613
606
|
return locallyValid;
|
|
614
607
|
}
|
|
@@ -110,10 +110,6 @@ export declare class MCPClient {
|
|
|
110
110
|
* Validate authentication credentials before attempting MCP connection
|
|
111
111
|
*/
|
|
112
112
|
private validateAuthBeforeConnect;
|
|
113
|
-
/**
|
|
114
|
-
* Validate vendor key format
|
|
115
|
-
*/
|
|
116
|
-
private validateVendorKeyFormat;
|
|
117
113
|
/**
|
|
118
114
|
* Validate and refresh token if needed
|
|
119
115
|
*/
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -245,11 +245,10 @@ export class MCPClient {
|
|
|
245
245
|
const msg = error?.message ?? '';
|
|
246
246
|
if (msg.includes('AUTHENTICATION_REQUIRED')) {
|
|
247
247
|
console.log(chalk.cyan('• No credentials found. Run: lanonasis auth login'));
|
|
248
|
-
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>'));
|
|
249
249
|
}
|
|
250
250
|
else if (msg.includes('AUTHENTICATION_INVALID')) {
|
|
251
|
-
console.log(chalk.cyan('• Invalid credentials.
|
|
252
|
-
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'));
|
|
253
252
|
console.log(chalk.cyan('• Try: lanonasis auth logout && lanonasis auth login'));
|
|
254
253
|
}
|
|
255
254
|
else if (msg.includes('expired')) {
|
|
@@ -259,7 +258,7 @@ export class MCPClient {
|
|
|
259
258
|
else {
|
|
260
259
|
console.log(chalk.cyan('• Check authentication status: lanonasis auth status'));
|
|
261
260
|
console.log(chalk.cyan('• Re-authenticate: lanonasis auth login'));
|
|
262
|
-
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>'));
|
|
263
262
|
}
|
|
264
263
|
}
|
|
265
264
|
/**
|
|
@@ -331,21 +330,14 @@ export class MCPClient {
|
|
|
331
330
|
throw new Error(`AUTHENTICATION_INVALID: ${error instanceof Error ? error.message : 'Token validation failed'}`);
|
|
332
331
|
}
|
|
333
332
|
}
|
|
334
|
-
// If we have a vendor key,
|
|
333
|
+
// If we have a vendor key, ensure it is valid (non-empty)
|
|
335
334
|
if (vendorKey && !token) {
|
|
336
|
-
|
|
337
|
-
|
|
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'}`);
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Validate vendor key format
|
|
343
|
-
*/
|
|
344
|
-
validateVendorKeyFormat(vendorKey) {
|
|
345
|
-
// Vendor key should be in format: pk_xxx.sk_xxx
|
|
346
|
-
const vendorKeyPattern = /^pk_[a-zA-Z0-9]+\.sk_[a-zA-Z0-9]+$/;
|
|
347
|
-
return vendorKeyPattern.test(vendorKey);
|
|
348
|
-
}
|
|
349
341
|
/**
|
|
350
342
|
* Validate and refresh token if needed
|
|
351
343
|
*/
|
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
|
+
}
|