@lanonasis/cli 3.9.12 → 3.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +2 -2
- package/dist/commands/api-keys.js +68 -314
- package/dist/commands/auth.js +33 -2
- package/dist/commands/memory.js +81 -1
- package/dist/utils/api.d.ts +5 -2
- package/dist/utils/api.js +5 -2
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.js +37 -46
- package/dist/utils/mcp-client.js +4 -13
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog - @lanonasis/cli
|
|
2
2
|
|
|
3
|
+
## [3.9.14] - 2026-04-03
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **Platform key project commands now hit the live auth-gateway contract**: `api-keys projects create/list` now use `/api/v1/projects` and route to `auth.lanonasis.com`, matching the current auth-gateway surface instead of the stale `/api/v1/auth/api-keys/projects` path.
|
|
8
|
+
- **Auth-gateway management calls no longer drift to the MCP/API host**: The CLI transport now treats `/api/v1/auth/api-keys/*` and `/api/v1/projects/*` as auth-gateway management endpoints, so token-authenticated key management requests consistently reach the auth service with the expected project scope header.
|
|
9
|
+
- **Stale MCP and analytics subcommands now fail honestly**: Deprecated `api-keys mcp *` and `api-keys analytics *` commands no longer call nonexistent gateway routes. They now exit immediately with explicit guidance toward the supported dashboard and service-scoping paths.
|
|
10
|
+
|
|
11
|
+
### ✨ Improvements
|
|
12
|
+
|
|
13
|
+
- **`key_context` is now exposed in CLI key management**: `api-keys create` accepts `--key-context personal|team|enterprise`, and `api-keys list/get` now display the stored key context so context-bound platform keys are visible and intentional at the CLI layer.
|
|
14
|
+
|
|
15
|
+
## [3.9.13] - 2026-04-02
|
|
16
|
+
|
|
17
|
+
### 🐛 Bug Fixes
|
|
18
|
+
|
|
19
|
+
- **JWT/password sessions now refresh through the real auth-gateway contract**: Expiring CLI sessions no longer call the nonexistent `/v1/auth/refresh` route. Refreshable JWT and OAuth sessions now use `POST /oauth/token` with `grant_type=refresh_token`, matching the live auth-gateway implementation.
|
|
20
|
+
- **Password login now persists refresh metadata**: Username/password authentication now saves `refresh_token` and `token_expires_at` from the login response, so successful JWT sessions can actually refresh instead of silently falling back to re-login loops.
|
|
21
|
+
- **MCP client refresh flow no longer drifts from the main CLI auth flow**: Removed the stale `/auth/refresh` path and incorrect `refreshToken` config key lookup in favor of the shared `CLIConfig.refreshTokenIfNeeded()` implementation.
|
|
22
|
+
|
|
23
|
+
### 🔄 Dependency Updates
|
|
24
|
+
|
|
25
|
+
- **Bundled `@lanonasis/mem-intel-sdk` updated to `2.1.0`**: Aligns the CLI with the newly published scoped intelligence query contract and predictive route support.
|
|
26
|
+
|
|
3
27
|
## [3.9.11] - 2026-03-27
|
|
4
28
|
|
|
5
29
|
### 🐛 Bug Fixes
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# @lanonasis/cli v3.9.
|
|
1
|
+
# @lanonasis/cli v3.9.13 - Auth Refresh Reliability
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@lanonasis/cli)
|
|
4
4
|
[](https://www.npmjs.com/package/@lanonasis/cli)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://api.lanonasis.com/.well-known/onasis.json)
|
|
7
7
|
|
|
8
|
-
🎉 **NEW IN v3.9.
|
|
8
|
+
🎉 **NEW IN v3.9.13**: JWT/password CLI sessions now refresh through the real auth-gateway OAuth token contract, password login persists refresh metadata correctly, the MCP client no longer uses stale refresh routes, and the bundled `@lanonasis/mem-intel-sdk` is updated to `2.1.0` for scoped intelligence query support.
|
|
9
9
|
|
|
10
10
|
## 🚀 Quick Start
|
|
11
11
|
|
|
@@ -16,7 +16,9 @@ const colors = {
|
|
|
16
16
|
highlight: chalk.white.bold
|
|
17
17
|
};
|
|
18
18
|
const AUTH_API_KEYS_BASE = '/api/v1/auth/api-keys';
|
|
19
|
+
const PROJECTS_API_BASE = '/api/v1/projects';
|
|
19
20
|
const VALID_ACCESS_LEVELS = ['public', 'authenticated', 'team', 'admin', 'enterprise'];
|
|
21
|
+
const VALID_KEY_CONTEXTS = ['personal', 'team', 'enterprise'];
|
|
20
22
|
function unwrapApiResponse(response) {
|
|
21
23
|
if (response && typeof response === 'object' && 'data' in response) {
|
|
22
24
|
return response.data ?? response;
|
|
@@ -33,6 +35,26 @@ function parseScopes(scopes) {
|
|
|
33
35
|
.filter(Boolean);
|
|
34
36
|
return parsed.length > 0 ? parsed : undefined;
|
|
35
37
|
}
|
|
38
|
+
function parseKeyContext(keyContext) {
|
|
39
|
+
if (!keyContext) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const normalized = keyContext.trim().toLowerCase();
|
|
43
|
+
if (!normalized) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (VALID_KEY_CONTEXTS.includes(normalized)) {
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
throw new Error('Invalid key context. Allowed: personal, team, enterprise');
|
|
50
|
+
}
|
|
51
|
+
function exitUnsupported(feature, guidance) {
|
|
52
|
+
console.error(colors.error(`✖ ${feature} is not exposed by the current auth-gateway API.`));
|
|
53
|
+
for (const line of guidance) {
|
|
54
|
+
console.error(colors.muted(` • ${line}`));
|
|
55
|
+
}
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
36
58
|
const apiKeysCommand = new Command('api-keys')
|
|
37
59
|
.alias('keys')
|
|
38
60
|
.description(colors.info('🔐 Manage API keys securely with enterprise-grade encryption'));
|
|
@@ -83,7 +105,7 @@ projectsCommand
|
|
|
83
105
|
]);
|
|
84
106
|
projectData = { ...projectData, ...answers };
|
|
85
107
|
}
|
|
86
|
-
const projectRes = await apiClient.post(
|
|
108
|
+
const projectRes = await apiClient.post(PROJECTS_API_BASE, projectData);
|
|
87
109
|
const project = unwrapApiResponse(projectRes);
|
|
88
110
|
console.log(chalk.green('✅ Project created successfully!'));
|
|
89
111
|
console.log(chalk.blue(`Project ID: ${project.id}`));
|
|
@@ -104,7 +126,7 @@ projectsCommand
|
|
|
104
126
|
.option('--json', 'Output as JSON')
|
|
105
127
|
.action(async (options) => {
|
|
106
128
|
try {
|
|
107
|
-
const projects = unwrapApiResponse(await apiClient.get(
|
|
129
|
+
const projects = unwrapApiResponse(await apiClient.get(PROJECTS_API_BASE));
|
|
108
130
|
if (options.json) {
|
|
109
131
|
console.log(JSON.stringify(projects, null, 2));
|
|
110
132
|
return;
|
|
@@ -143,12 +165,14 @@ apiKeysCommand
|
|
|
143
165
|
.option('-n, --name <name>', 'API key name')
|
|
144
166
|
.option('-d, --description <description>', 'API key description (optional)')
|
|
145
167
|
.option('--access-level <level>', 'Access level (public, authenticated, team, admin, enterprise)', 'team')
|
|
168
|
+
.option('--key-context <context>', 'Optional memory context (personal, team, enterprise)')
|
|
146
169
|
.option('--expires-in-days <days>', 'Expiration in days (default: 365)', '365')
|
|
147
170
|
.option('--scopes <scopes>', 'Comma-separated scopes (optional)')
|
|
148
171
|
.option('--interactive', 'Interactive mode')
|
|
149
172
|
.action(async (options) => {
|
|
150
173
|
try {
|
|
151
174
|
const accessLevel = (options.accessLevel || 'team').toLowerCase();
|
|
175
|
+
const keyContext = parseKeyContext(options.keyContext);
|
|
152
176
|
const expiresInDays = parseInt(options.expiresInDays, 10);
|
|
153
177
|
if (!VALID_ACCESS_LEVELS.includes(accessLevel)) {
|
|
154
178
|
throw new Error('Invalid access level. Allowed: public, authenticated, team, admin, enterprise');
|
|
@@ -159,6 +183,7 @@ apiKeysCommand
|
|
|
159
183
|
let keyData = {
|
|
160
184
|
name: options.name,
|
|
161
185
|
access_level: accessLevel,
|
|
186
|
+
key_context: keyContext,
|
|
162
187
|
expires_in_days: expiresInDays,
|
|
163
188
|
description: options.description?.trim() || undefined,
|
|
164
189
|
scopes: parseScopes(options.scopes)
|
|
@@ -185,6 +210,16 @@ apiKeysCommand
|
|
|
185
210
|
choices: VALID_ACCESS_LEVELS,
|
|
186
211
|
default: keyData.access_level
|
|
187
212
|
},
|
|
213
|
+
{
|
|
214
|
+
type: 'select',
|
|
215
|
+
name: 'key_context',
|
|
216
|
+
message: 'Memory context:',
|
|
217
|
+
choices: [
|
|
218
|
+
{ name: 'legacy / unbounded (default)', value: '' },
|
|
219
|
+
...VALID_KEY_CONTEXTS.map((context) => ({ name: context, value: context })),
|
|
220
|
+
],
|
|
221
|
+
default: keyData.key_context || ''
|
|
222
|
+
},
|
|
188
223
|
{
|
|
189
224
|
type: 'number',
|
|
190
225
|
name: 'expires_in_days',
|
|
@@ -202,6 +237,7 @@ apiKeysCommand
|
|
|
202
237
|
keyData = {
|
|
203
238
|
...keyData,
|
|
204
239
|
...answers,
|
|
240
|
+
key_context: parseKeyContext(typeof answers.key_context === 'string' ? answers.key_context : undefined) ?? keyData.key_context,
|
|
205
241
|
description: typeof answers.description === 'string'
|
|
206
242
|
? answers.description.trim() || undefined
|
|
207
243
|
: keyData.description,
|
|
@@ -214,6 +250,7 @@ apiKeysCommand
|
|
|
214
250
|
console.log(`${colors.highlight('Key ID:')} ${colors.primary(apiKey.id)}`);
|
|
215
251
|
console.log(`${colors.highlight('Name:')} ${colors.accent(apiKey.name)}`);
|
|
216
252
|
console.log(`${colors.highlight('Access Level:')} ${colors.info(apiKey.access_level || keyData.access_level)}`);
|
|
253
|
+
console.log(`${colors.highlight('Key Context:')} ${colors.info(apiKey.key_context || keyData.key_context || 'legacy')}`);
|
|
217
254
|
console.log(`${colors.highlight('Permissions:')} ${colors.muted((apiKey.permissions || keyData.scopes || []).join(', ') || 'legacy:full_access')}`);
|
|
218
255
|
if (apiKey.expires_at) {
|
|
219
256
|
console.log(`${colors.highlight('Expires At:')} ${colors.warning(formatDate(apiKey.expires_at))}`);
|
|
@@ -258,7 +295,7 @@ apiKeysCommand
|
|
|
258
295
|
console.log(colors.primary('🔐 API Key Management'));
|
|
259
296
|
console.log(colors.info('═'.repeat(80)));
|
|
260
297
|
const table = new Table({
|
|
261
|
-
head: ['ID', 'Name', 'Access', 'Permissions', 'Service', 'Status', 'Expires'].map(h => colors.accent(h)),
|
|
298
|
+
head: ['ID', 'Name', 'Access', 'Context', 'Permissions', 'Service', 'Status', 'Expires'].map(h => colors.accent(h)),
|
|
262
299
|
style: { head: [], border: [] }
|
|
263
300
|
});
|
|
264
301
|
apiKeys.forEach((key) => {
|
|
@@ -267,6 +304,7 @@ apiKeysCommand
|
|
|
267
304
|
truncateText(key.id, 20),
|
|
268
305
|
key.name,
|
|
269
306
|
key.access_level,
|
|
307
|
+
key.key_context || 'legacy',
|
|
270
308
|
truncateText((key.permissions || []).join(', ') || 'legacy:full_access', 28),
|
|
271
309
|
key.service || 'all',
|
|
272
310
|
statusColor(key.is_active ? 'active' : 'inactive'),
|
|
@@ -303,6 +341,7 @@ apiKeysCommand
|
|
|
303
341
|
console.log(`${colors.highlight('Description:')} ${colors.muted(apiKey.description)}`);
|
|
304
342
|
}
|
|
305
343
|
console.log(`${colors.highlight('Access Level:')} ${colors.warning(apiKey.access_level)}`);
|
|
344
|
+
console.log(`${colors.highlight('Key Context:')} ${colors.info(apiKey.key_context || 'legacy')}`);
|
|
306
345
|
console.log(`${colors.highlight('Permissions:')} ${colors.muted((apiKey.permissions || []).join(', ') || 'legacy:full_access')}`);
|
|
307
346
|
console.log(`${colors.highlight('Service Scope:')} ${colors.info(apiKey.service || 'all')}`);
|
|
308
347
|
const statusColor = apiKey.is_active ? colors.success : colors.error;
|
|
@@ -496,164 +535,23 @@ mcpCommand
|
|
|
496
535
|
.option('--risk-level <level>', 'Risk level (low, medium, high, critical)', 'medium')
|
|
497
536
|
.option('--interactive', 'Interactive mode')
|
|
498
537
|
.action(async (options) => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
keys: options.keys ? options.keys.split(',').map((k) => k.trim()) : [],
|
|
506
|
-
environments: options.environments ? options.environments.split(',').map((e) => e.trim()) : ['development'],
|
|
507
|
-
maxConcurrentSessions: parseInt(options.maxSessions),
|
|
508
|
-
maxSessionDuration: parseInt(options.maxDuration)
|
|
509
|
-
},
|
|
510
|
-
webhookUrl: options.webhookUrl,
|
|
511
|
-
autoApprove: options.autoApprove || false,
|
|
512
|
-
riskLevel: options.riskLevel
|
|
513
|
-
};
|
|
514
|
-
if (options.interactive || !toolData.toolId || !toolData.toolName || !toolData.organizationId) {
|
|
515
|
-
const answers = await inquirer.prompt([
|
|
516
|
-
{
|
|
517
|
-
type: 'input',
|
|
518
|
-
name: 'toolId',
|
|
519
|
-
message: 'Tool ID:',
|
|
520
|
-
when: !toolData.toolId,
|
|
521
|
-
validate: (input) => input.length > 0 || 'Tool ID is required'
|
|
522
|
-
},
|
|
523
|
-
{
|
|
524
|
-
type: 'input',
|
|
525
|
-
name: 'toolName',
|
|
526
|
-
message: 'Tool name:',
|
|
527
|
-
when: !toolData.toolName,
|
|
528
|
-
validate: (input) => input.length > 0 || 'Tool name is required'
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
type: 'input',
|
|
532
|
-
name: 'organizationId',
|
|
533
|
-
message: 'Organization ID:',
|
|
534
|
-
when: !toolData.organizationId,
|
|
535
|
-
validate: (input) => {
|
|
536
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
537
|
-
return uuidRegex.test(input) || 'Please enter a valid UUID';
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
type: 'input',
|
|
542
|
-
name: 'keys',
|
|
543
|
-
message: 'Accessible key names (comma-separated):',
|
|
544
|
-
filter: (input) => input.split(',').map((k) => k.trim()),
|
|
545
|
-
when: toolData.permissions.keys.length === 0
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
type: 'checkbox',
|
|
549
|
-
name: 'environments',
|
|
550
|
-
message: 'Accessible environments:',
|
|
551
|
-
choices: ['development', 'staging', 'production'],
|
|
552
|
-
default: ['development']
|
|
553
|
-
},
|
|
554
|
-
{
|
|
555
|
-
type: 'number',
|
|
556
|
-
name: 'maxConcurrentSessions',
|
|
557
|
-
message: 'Maximum concurrent sessions:',
|
|
558
|
-
default: 3,
|
|
559
|
-
validate: (input) => input > 0 && input <= 10 || 'Must be between 1 and 10'
|
|
560
|
-
},
|
|
561
|
-
{
|
|
562
|
-
type: 'number',
|
|
563
|
-
name: 'maxSessionDuration',
|
|
564
|
-
message: 'Maximum session duration (seconds):',
|
|
565
|
-
default: 900,
|
|
566
|
-
validate: (input) => input >= 60 && input <= 3600 || 'Must be between 60 and 3600 seconds'
|
|
567
|
-
},
|
|
568
|
-
{
|
|
569
|
-
type: 'input',
|
|
570
|
-
name: 'webhookUrl',
|
|
571
|
-
message: 'Webhook URL (optional):'
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
type: 'confirm',
|
|
575
|
-
name: 'autoApprove',
|
|
576
|
-
message: 'Enable auto-approval?',
|
|
577
|
-
default: false
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
type: 'select',
|
|
581
|
-
name: 'riskLevel',
|
|
582
|
-
message: 'Risk level:',
|
|
583
|
-
choices: ['low', 'medium', 'high', 'critical'],
|
|
584
|
-
default: 'medium'
|
|
585
|
-
}
|
|
586
|
-
]);
|
|
587
|
-
if (answers.keys)
|
|
588
|
-
toolData.permissions.keys = answers.keys;
|
|
589
|
-
if (answers.environments)
|
|
590
|
-
toolData.permissions.environments = answers.environments;
|
|
591
|
-
if (answers.maxConcurrentSessions)
|
|
592
|
-
toolData.permissions.maxConcurrentSessions = answers.maxConcurrentSessions;
|
|
593
|
-
if (answers.maxSessionDuration)
|
|
594
|
-
toolData.permissions.maxSessionDuration = answers.maxSessionDuration;
|
|
595
|
-
toolData = { ...toolData, ...answers };
|
|
596
|
-
delete toolData.keys;
|
|
597
|
-
delete toolData.environments;
|
|
598
|
-
delete toolData.maxConcurrentSessions;
|
|
599
|
-
delete toolData.maxSessionDuration;
|
|
600
|
-
}
|
|
601
|
-
const tool = unwrapApiResponse(await apiClient.post(`${AUTH_API_KEYS_BASE}/mcp/tools`, toolData));
|
|
602
|
-
console.log(colors.success('🤖 MCP tool registered successfully!'));
|
|
603
|
-
console.log(colors.info('━'.repeat(50)));
|
|
604
|
-
console.log(`${colors.highlight('Tool ID:')} ${colors.primary(tool.toolId)}`);
|
|
605
|
-
console.log(`${colors.highlight('Name:')} ${colors.accent(tool.toolName)}`);
|
|
606
|
-
console.log(`${colors.highlight('Risk Level:')} ${colors.warning(tool.riskLevel)}`);
|
|
607
|
-
console.log(`${colors.highlight('Auto Approve:')} ${tool.autoApprove ? colors.success('Yes') : colors.error('No')}`);
|
|
608
|
-
console.log(colors.info('━'.repeat(50)));
|
|
609
|
-
}
|
|
610
|
-
catch (error) {
|
|
611
|
-
console.error(colors.error('✖ Failed to register MCP tool:'), colors.muted(error.message));
|
|
612
|
-
process.exit(1);
|
|
613
|
-
}
|
|
538
|
+
void options;
|
|
539
|
+
exitUnsupported('MCP tool registration', [
|
|
540
|
+
'No /api/v1/auth/api-keys/mcp/* routes exist on the current auth-gateway.',
|
|
541
|
+
'Use API key service scoping instead: lanonasis api-keys create or update plus /service-scopes support on the gateway.',
|
|
542
|
+
'Manage MCP-specific workflows from the dashboard until dedicated routes are exposed.',
|
|
543
|
+
]);
|
|
614
544
|
});
|
|
615
545
|
mcpCommand
|
|
616
546
|
.command('list-tools')
|
|
617
547
|
.description('List registered MCP tools')
|
|
618
548
|
.option('--json', 'Output as JSON')
|
|
619
549
|
.action(async (options) => {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
626
|
-
if (!Array.isArray(tools) || tools.length === 0) {
|
|
627
|
-
console.log(colors.warning('⚠️ No MCP tools found'));
|
|
628
|
-
console.log(colors.muted('Run: lanonasis api-keys mcp register-tool'));
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
console.log(colors.primary('🤖 Registered MCP Tools'));
|
|
632
|
-
console.log(colors.info('═'.repeat(80)));
|
|
633
|
-
const table = new Table({
|
|
634
|
-
head: ['Tool ID', 'Name', 'Risk Level', 'Status', 'Auto Approve', 'Created'].map(h => colors.accent(h)),
|
|
635
|
-
style: { head: [], border: [] }
|
|
636
|
-
});
|
|
637
|
-
tools.forEach((tool) => {
|
|
638
|
-
const statusColor = tool.status === 'active' ? colors.success :
|
|
639
|
-
tool.status === 'suspended' ? colors.error : colors.warning;
|
|
640
|
-
table.push([
|
|
641
|
-
tool.toolId,
|
|
642
|
-
tool.toolName,
|
|
643
|
-
tool.riskLevel,
|
|
644
|
-
statusColor(tool.status),
|
|
645
|
-
tool.autoApprove ? colors.success('Yes') : colors.error('No'),
|
|
646
|
-
formatDate(tool.createdAt)
|
|
647
|
-
]);
|
|
648
|
-
});
|
|
649
|
-
console.log(table.toString());
|
|
650
|
-
console.log(colors.info('═'.repeat(80)));
|
|
651
|
-
console.log(colors.muted(`🤖 Total: ${colors.highlight(tools.length)} MCP tools`));
|
|
652
|
-
}
|
|
653
|
-
catch (error) {
|
|
654
|
-
console.error(colors.error('✖ Failed to list MCP tools:'), colors.muted(error.message));
|
|
655
|
-
process.exit(1);
|
|
656
|
-
}
|
|
550
|
+
void options;
|
|
551
|
+
exitUnsupported('MCP tool listing', [
|
|
552
|
+
'No /api/v1/auth/api-keys/mcp/* routes exist on the current auth-gateway.',
|
|
553
|
+
'The current gateway exposes configured external services at /api/v1/auth/api-keys/services/configured.',
|
|
554
|
+
]);
|
|
657
555
|
});
|
|
658
556
|
mcpCommand
|
|
659
557
|
.command('request-access')
|
|
@@ -666,82 +564,11 @@ mcpCommand
|
|
|
666
564
|
.option('--duration <seconds>', 'Estimated duration in seconds', '900')
|
|
667
565
|
.option('--interactive', 'Interactive mode')
|
|
668
566
|
.action(async (options) => {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
environment: options.environment,
|
|
675
|
-
justification: options.justification,
|
|
676
|
-
estimatedDuration: parseInt(options.duration),
|
|
677
|
-
context: {}
|
|
678
|
-
};
|
|
679
|
-
if (options.interactive || !requestData.toolId || !requestData.organizationId ||
|
|
680
|
-
requestData.keyNames.length === 0 || !requestData.environment || !requestData.justification) {
|
|
681
|
-
const mcpTools = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}/mcp/tools`));
|
|
682
|
-
const tools = Array.isArray(mcpTools) ? mcpTools : [];
|
|
683
|
-
const answers = await inquirer.prompt([
|
|
684
|
-
{
|
|
685
|
-
type: 'select',
|
|
686
|
-
name: 'toolId',
|
|
687
|
-
message: 'Select MCP tool:',
|
|
688
|
-
when: !requestData.toolId && tools.length > 0,
|
|
689
|
-
choices: tools.map((t) => ({ name: `${t.toolName} (${t.toolId})`, value: t.toolId }))
|
|
690
|
-
},
|
|
691
|
-
{
|
|
692
|
-
type: 'input',
|
|
693
|
-
name: 'organizationId',
|
|
694
|
-
message: 'Organization ID:',
|
|
695
|
-
when: !requestData.organizationId,
|
|
696
|
-
validate: (input) => {
|
|
697
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
698
|
-
return uuidRegex.test(input) || 'Please enter a valid UUID';
|
|
699
|
-
}
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
type: 'input',
|
|
703
|
-
name: 'keyNames',
|
|
704
|
-
message: 'Key names to access (comma-separated):',
|
|
705
|
-
when: requestData.keyNames.length === 0,
|
|
706
|
-
filter: (input) => input.split(',').map((k) => k.trim()),
|
|
707
|
-
validate: (input) => input.length > 0 || 'At least one key name is required'
|
|
708
|
-
},
|
|
709
|
-
{
|
|
710
|
-
type: 'select',
|
|
711
|
-
name: 'environment',
|
|
712
|
-
message: 'Environment:',
|
|
713
|
-
when: !requestData.environment,
|
|
714
|
-
choices: ['development', 'staging', 'production']
|
|
715
|
-
},
|
|
716
|
-
{
|
|
717
|
-
type: 'input',
|
|
718
|
-
name: 'justification',
|
|
719
|
-
message: 'Justification for access:',
|
|
720
|
-
when: !requestData.justification,
|
|
721
|
-
validate: (input) => input.length > 0 || 'Justification is required'
|
|
722
|
-
},
|
|
723
|
-
{
|
|
724
|
-
type: 'number',
|
|
725
|
-
name: 'estimatedDuration',
|
|
726
|
-
message: 'Estimated duration (seconds):',
|
|
727
|
-
default: 900,
|
|
728
|
-
validate: (input) => input >= 60 && input <= 3600 || 'Must be between 60 and 3600 seconds'
|
|
729
|
-
}
|
|
730
|
-
]);
|
|
731
|
-
requestData = { ...requestData, ...answers };
|
|
732
|
-
}
|
|
733
|
-
const response = unwrapApiResponse(await apiClient.post(`${AUTH_API_KEYS_BASE}/mcp/request-access`, requestData));
|
|
734
|
-
console.log(colors.success('🔐 Access request created successfully!'));
|
|
735
|
-
console.log(colors.info('━'.repeat(50)));
|
|
736
|
-
console.log(`${colors.highlight('Request ID:')} ${colors.primary(response.requestId)}`);
|
|
737
|
-
console.log(`${colors.highlight('Status:')} ${colors.accent(response.status)}`);
|
|
738
|
-
console.log(colors.info('━'.repeat(50)));
|
|
739
|
-
console.log(colors.warning('💡 Check the status with: lanonasis api-keys analytics usage'));
|
|
740
|
-
}
|
|
741
|
-
catch (error) {
|
|
742
|
-
console.error(colors.error('✖ Failed to create access request:'), colors.muted(error.message));
|
|
743
|
-
process.exit(1);
|
|
744
|
-
}
|
|
567
|
+
void options;
|
|
568
|
+
exitUnsupported('MCP access requests', [
|
|
569
|
+
'No /api/v1/auth/api-keys/mcp/* routes exist on the current auth-gateway.',
|
|
570
|
+
'Use platform API keys plus service scoping for current gateway-backed access control.',
|
|
571
|
+
]);
|
|
745
572
|
});
|
|
746
573
|
// ============================================================================
|
|
747
574
|
// ANALYTICS COMMANDS
|
|
@@ -755,48 +582,11 @@ analyticsCommand
|
|
|
755
582
|
.option('--days <days>', 'Number of days to look back', '30')
|
|
756
583
|
.option('--json', 'Output as JSON')
|
|
757
584
|
.action(async (options) => {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
if (options.days)
|
|
764
|
-
params.append('days', options.days);
|
|
765
|
-
if (params.toString()) {
|
|
766
|
-
url += `?${params.toString()}`;
|
|
767
|
-
}
|
|
768
|
-
const analytics = unwrapApiResponse(await apiClient.get(url));
|
|
769
|
-
if (options.json) {
|
|
770
|
-
console.log(JSON.stringify(analytics, null, 2));
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
if (!Array.isArray(analytics) || analytics.length === 0) {
|
|
774
|
-
console.log(chalk.yellow('No usage data found'));
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
const table = new Table({
|
|
778
|
-
head: ['Key ID', 'Operation', 'Tool ID', 'Success', 'Timestamp'].map(h => chalk.cyan(h)),
|
|
779
|
-
style: { head: [], border: [] }
|
|
780
|
-
});
|
|
781
|
-
analytics.forEach((entry) => {
|
|
782
|
-
const successColor = entry.success ? colors.success('✓') : colors.error('✗');
|
|
783
|
-
table.push([
|
|
784
|
-
truncateText(entry.keyId || '-', 20),
|
|
785
|
-
entry.operation,
|
|
786
|
-
truncateText(entry.toolId || '-', 15),
|
|
787
|
-
successColor,
|
|
788
|
-
formatDate(entry.timestamp)
|
|
789
|
-
]);
|
|
790
|
-
});
|
|
791
|
-
console.log(table.toString());
|
|
792
|
-
console.log(colors.info('═'.repeat(80)));
|
|
793
|
-
console.log(colors.muted(`📈 Total: ${colors.highlight(analytics.length)} events`));
|
|
794
|
-
}
|
|
795
|
-
catch (error) {
|
|
796
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
797
|
-
console.error(colors.error('✖ Failed to get usage analytics:'), colors.muted(errorMessage));
|
|
798
|
-
process.exit(1);
|
|
799
|
-
}
|
|
585
|
+
void options;
|
|
586
|
+
exitUnsupported('API key usage analytics', [
|
|
587
|
+
'No /api/v1/auth/api-keys/analytics/* routes exist on the current auth-gateway.',
|
|
588
|
+
'Use dashboard reporting or direct platform logs until analytics endpoints are exposed.',
|
|
589
|
+
]);
|
|
800
590
|
});
|
|
801
591
|
analyticsCommand
|
|
802
592
|
.command('security-events')
|
|
@@ -804,47 +594,11 @@ analyticsCommand
|
|
|
804
594
|
.option('--severity <level>', 'Filter by severity (low, medium, high, critical)')
|
|
805
595
|
.option('--json', 'Output as JSON')
|
|
806
596
|
.action(async (options) => {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
const events = unwrapApiResponse(await apiClient.get(url));
|
|
813
|
-
if (options.json) {
|
|
814
|
-
console.log(JSON.stringify(events, null, 2));
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
if (!Array.isArray(events) || events.length === 0) {
|
|
818
|
-
console.log(colors.success('✅ No security events found'));
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
console.log(colors.primary('🛡️ Security Events Monitor'));
|
|
822
|
-
console.log(colors.info('═'.repeat(80)));
|
|
823
|
-
const table = new Table({
|
|
824
|
-
head: ['Event Type', 'Severity', 'Description', 'Resolved', 'Timestamp'].map(h => colors.accent(h)),
|
|
825
|
-
style: { head: [], border: [] }
|
|
826
|
-
});
|
|
827
|
-
events.forEach((event) => {
|
|
828
|
-
const severityColor = event.severity === 'critical' ? colors.error :
|
|
829
|
-
event.severity === 'high' ? colors.accent :
|
|
830
|
-
event.severity === 'medium' ? colors.warning : colors.success;
|
|
831
|
-
table.push([
|
|
832
|
-
event.eventType,
|
|
833
|
-
severityColor(event.severity.toUpperCase()),
|
|
834
|
-
truncateText(event.description, 40),
|
|
835
|
-
event.resolved ? colors.success('✓') : colors.warning('Pending'),
|
|
836
|
-
formatDate(event.timestamp)
|
|
837
|
-
]);
|
|
838
|
-
});
|
|
839
|
-
console.log(table.toString());
|
|
840
|
-
console.log(colors.info('═'.repeat(80)));
|
|
841
|
-
console.log(colors.muted(`🛡️ Total: ${colors.highlight(events.length)} security events`));
|
|
842
|
-
}
|
|
843
|
-
catch (error) {
|
|
844
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
845
|
-
console.error(colors.error('✖ Failed to get security events:'), colors.muted(errorMessage));
|
|
846
|
-
process.exit(1);
|
|
847
|
-
}
|
|
597
|
+
void options;
|
|
598
|
+
exitUnsupported('API key security event analytics', [
|
|
599
|
+
'No /api/v1/auth/api-keys/analytics/* routes exist on the current auth-gateway.',
|
|
600
|
+
'Use dashboard security reporting until gateway analytics endpoints are exposed.',
|
|
601
|
+
]);
|
|
848
602
|
});
|
|
849
603
|
// Add subcommands
|
|
850
604
|
apiKeysCommand.addCommand(projectsCommand);
|
package/dist/commands/auth.js
CHANGED
|
@@ -662,8 +662,31 @@ async function handleOAuthFlow(config) {
|
|
|
662
662
|
}
|
|
663
663
|
]);
|
|
664
664
|
if (!openBrowser) {
|
|
665
|
-
|
|
666
|
-
|
|
665
|
+
// Fallback: manual token paste for headless/remote environments
|
|
666
|
+
console.log();
|
|
667
|
+
console.log(chalk.cyan('📋 Manual Token Authentication'));
|
|
668
|
+
console.log(chalk.gray('Open this URL in any browser to authenticate:'));
|
|
669
|
+
const authBase = config.getDiscoveredApiUrl();
|
|
670
|
+
console.log(chalk.white(` ${authBase}/auth/cli-login`));
|
|
671
|
+
console.log();
|
|
672
|
+
console.log(chalk.gray('After logging in, expand "Headless/Remote?" to copy the token, then paste it below.'));
|
|
673
|
+
console.log();
|
|
674
|
+
const { token } = await inquirer.prompt([
|
|
675
|
+
{
|
|
676
|
+
type: 'password',
|
|
677
|
+
name: 'token',
|
|
678
|
+
message: 'Paste token:',
|
|
679
|
+
mask: '*',
|
|
680
|
+
validate: (input) => input.trim().length > 0 || 'Token is required',
|
|
681
|
+
}
|
|
682
|
+
]);
|
|
683
|
+
const trimmed = token.trim();
|
|
684
|
+
await config.setToken(trimmed);
|
|
685
|
+
await config.set('authMethod', 'jwt');
|
|
686
|
+
console.log();
|
|
687
|
+
console.log(chalk.green('✓ Token saved successfully'));
|
|
688
|
+
console.log(colors.info('You can now use all Lanonasis services'));
|
|
689
|
+
process.exit(0);
|
|
667
690
|
}
|
|
668
691
|
try {
|
|
669
692
|
// Generate PKCE challenge
|
|
@@ -815,8 +838,16 @@ async function handleCredentialsFlow(options, config) {
|
|
|
815
838
|
if (process.env.CLI_VERBOSE === 'true') {
|
|
816
839
|
console.log(chalk.dim(` JWT received (length: ${authToken.length})`));
|
|
817
840
|
}
|
|
841
|
+
const refreshToken = response.refresh_token;
|
|
842
|
+
const expiresIn = response.expires_in;
|
|
818
843
|
// Store JWT token for API authentication
|
|
819
844
|
await config.setToken(authToken);
|
|
845
|
+
if (typeof refreshToken === 'string' && refreshToken.length > 0) {
|
|
846
|
+
config.set('refresh_token', refreshToken);
|
|
847
|
+
}
|
|
848
|
+
if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) {
|
|
849
|
+
config.set('token_expires_at', Date.now() + (expiresIn * 1000));
|
|
850
|
+
}
|
|
820
851
|
await config.setAndSave('authMethod', 'jwt');
|
|
821
852
|
spinner.succeed('Login successful');
|
|
822
853
|
console.log();
|
package/dist/commands/memory.js
CHANGED
|
@@ -22,6 +22,12 @@ const MEMORY_TYPE_CHOICES = [
|
|
|
22
22
|
'personal',
|
|
23
23
|
'workflow',
|
|
24
24
|
];
|
|
25
|
+
const QUERY_SCOPE_CHOICES = [
|
|
26
|
+
'personal',
|
|
27
|
+
'team',
|
|
28
|
+
'organization',
|
|
29
|
+
'hybrid',
|
|
30
|
+
];
|
|
25
31
|
const coerceMemoryType = (value) => {
|
|
26
32
|
if (typeof value !== 'string')
|
|
27
33
|
return undefined;
|
|
@@ -34,6 +40,48 @@ const coerceMemoryType = (value) => {
|
|
|
34
40
|
}
|
|
35
41
|
return undefined;
|
|
36
42
|
};
|
|
43
|
+
const coerceQueryScope = (value) => {
|
|
44
|
+
if (typeof value !== 'string')
|
|
45
|
+
return undefined;
|
|
46
|
+
const normalized = value.trim().toLowerCase();
|
|
47
|
+
if (QUERY_SCOPE_CHOICES.includes(normalized)) {
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
const parseMemoryTypesOption = (value) => {
|
|
53
|
+
if (!value?.trim())
|
|
54
|
+
return undefined;
|
|
55
|
+
const parsed = value
|
|
56
|
+
.split(',')
|
|
57
|
+
.map((entry) => coerceMemoryType(entry))
|
|
58
|
+
.filter((entry) => Boolean(entry));
|
|
59
|
+
if (parsed.length === 0) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return [...new Set(parsed)];
|
|
63
|
+
};
|
|
64
|
+
const buildIntelligenceContextPayload = (options) => {
|
|
65
|
+
const payload = {};
|
|
66
|
+
if (options.organizationId?.trim()) {
|
|
67
|
+
payload.organization_id = options.organizationId.trim();
|
|
68
|
+
}
|
|
69
|
+
if (options.topicId?.trim()) {
|
|
70
|
+
payload.topic_id = options.topicId.trim();
|
|
71
|
+
}
|
|
72
|
+
if (options.scope) {
|
|
73
|
+
const scope = coerceQueryScope(options.scope);
|
|
74
|
+
if (!scope) {
|
|
75
|
+
throw new Error(`Invalid scope \"${options.scope}\". Expected one of: ${QUERY_SCOPE_CHOICES.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
payload.query_scope = scope;
|
|
78
|
+
}
|
|
79
|
+
const memoryTypes = parseMemoryTypesOption(options.memoryTypes);
|
|
80
|
+
if (memoryTypes?.length) {
|
|
81
|
+
payload.memory_types = memoryTypes;
|
|
82
|
+
}
|
|
83
|
+
return payload;
|
|
84
|
+
};
|
|
37
85
|
const resolveInputMode = async () => {
|
|
38
86
|
const config = new CLIConfig();
|
|
39
87
|
await config.init();
|
|
@@ -1015,13 +1063,21 @@ export function memoryCommands(program) {
|
|
|
1015
1063
|
intelligence
|
|
1016
1064
|
.command('health-check')
|
|
1017
1065
|
.description('Run memory intelligence health check')
|
|
1066
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1067
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1068
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1069
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1018
1070
|
.option('--json', 'Output raw JSON payload')
|
|
1019
1071
|
.action(async (options) => {
|
|
1020
1072
|
try {
|
|
1021
1073
|
const spinner = ora('Running intelligence health check...').start();
|
|
1022
1074
|
const transport = await createIntelligenceTransport();
|
|
1023
1075
|
const userId = await resolveCurrentUserId();
|
|
1024
|
-
const result = await postIntelligenceEndpoint(transport, '/intelligence/health-check', {
|
|
1076
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/health-check', {
|
|
1077
|
+
user_id: userId,
|
|
1078
|
+
response_format: 'json',
|
|
1079
|
+
...buildIntelligenceContextPayload(options),
|
|
1080
|
+
});
|
|
1025
1081
|
spinner.stop();
|
|
1026
1082
|
printIntelligenceResult('🩺 Intelligence Health Check', result, options);
|
|
1027
1083
|
}
|
|
@@ -1036,6 +1092,9 @@ export function memoryCommands(program) {
|
|
|
1036
1092
|
.description('Suggest tags for a memory')
|
|
1037
1093
|
.argument('<memory-id>', 'Memory ID')
|
|
1038
1094
|
.option('--max <number>', 'Maximum suggestions', '8')
|
|
1095
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1096
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1097
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1039
1098
|
.option('--json', 'Output raw JSON payload')
|
|
1040
1099
|
.action(async (memoryId, options) => {
|
|
1041
1100
|
try {
|
|
@@ -1049,6 +1108,7 @@ export function memoryCommands(program) {
|
|
|
1049
1108
|
max_suggestions: maxSuggestions,
|
|
1050
1109
|
include_existing_tags: true,
|
|
1051
1110
|
response_format: 'json',
|
|
1111
|
+
...buildIntelligenceContextPayload(options),
|
|
1052
1112
|
});
|
|
1053
1113
|
spinner.stop();
|
|
1054
1114
|
printIntelligenceResult('🏷️ Tag Suggestions', result, options);
|
|
@@ -1065,6 +1125,10 @@ export function memoryCommands(program) {
|
|
|
1065
1125
|
.argument('<memory-id>', 'Source memory ID')
|
|
1066
1126
|
.option('--limit <number>', 'Maximum related memories', '5')
|
|
1067
1127
|
.option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
|
|
1128
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1129
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1130
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1131
|
+
.option('--memory-types <types>', `Optional comma-separated candidate memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1068
1132
|
.option('--json', 'Output raw JSON payload')
|
|
1069
1133
|
.action(async (memoryId, options) => {
|
|
1070
1134
|
try {
|
|
@@ -1077,6 +1141,7 @@ export function memoryCommands(program) {
|
|
|
1077
1141
|
limit: Math.max(1, Math.min(20, parseInt(options.limit || '5', 10))),
|
|
1078
1142
|
similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.7'))),
|
|
1079
1143
|
response_format: 'json',
|
|
1144
|
+
...buildIntelligenceContextPayload(options),
|
|
1080
1145
|
});
|
|
1081
1146
|
spinner.stop();
|
|
1082
1147
|
printIntelligenceResult('🔗 Related Memories', result, options);
|
|
@@ -1092,6 +1157,10 @@ export function memoryCommands(program) {
|
|
|
1092
1157
|
.description('Detect duplicate memory entries')
|
|
1093
1158
|
.option('--threshold <number>', 'Similarity threshold (0-1)', '0.88')
|
|
1094
1159
|
.option('--max-pairs <number>', 'Maximum duplicate pairs to inspect', '100')
|
|
1160
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1161
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1162
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1163
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1095
1164
|
.option('--json', 'Output raw JSON payload')
|
|
1096
1165
|
.action(async (options) => {
|
|
1097
1166
|
try {
|
|
@@ -1103,6 +1172,7 @@ export function memoryCommands(program) {
|
|
|
1103
1172
|
similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.88'))),
|
|
1104
1173
|
max_pairs: Math.max(10, Math.min(500, parseInt(options.maxPairs || '100', 10))),
|
|
1105
1174
|
response_format: 'json',
|
|
1175
|
+
...buildIntelligenceContextPayload(options),
|
|
1106
1176
|
});
|
|
1107
1177
|
spinner.stop();
|
|
1108
1178
|
printIntelligenceResult('🧬 Duplicate Detection', result, options);
|
|
@@ -1119,6 +1189,10 @@ export function memoryCommands(program) {
|
|
|
1119
1189
|
.option('--topic <topic>', 'Optional topic filter')
|
|
1120
1190
|
.option('--type <type>', `Optional memory type filter (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1121
1191
|
.option('--max-memories <number>', 'Maximum memories to analyze', '50')
|
|
1192
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1193
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1194
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1195
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1122
1196
|
.option('--json', 'Output raw JSON payload')
|
|
1123
1197
|
.action(async (options) => {
|
|
1124
1198
|
try {
|
|
@@ -1135,6 +1209,7 @@ export function memoryCommands(program) {
|
|
|
1135
1209
|
memory_type: memoryType,
|
|
1136
1210
|
max_memories: Math.max(5, Math.min(200, parseInt(options.maxMemories || '50', 10))),
|
|
1137
1211
|
response_format: 'json',
|
|
1212
|
+
...buildIntelligenceContextPayload(options),
|
|
1138
1213
|
});
|
|
1139
1214
|
spinner.stop();
|
|
1140
1215
|
printIntelligenceResult('💡 Memory Insights', result, options);
|
|
@@ -1149,6 +1224,10 @@ export function memoryCommands(program) {
|
|
|
1149
1224
|
.command('analyze-patterns')
|
|
1150
1225
|
.description('Analyze memory usage patterns')
|
|
1151
1226
|
.option('--days <number>', 'Days to include in analysis', '30')
|
|
1227
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1228
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1229
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1230
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1152
1231
|
.option('--json', 'Output raw JSON payload')
|
|
1153
1232
|
.action(async (options) => {
|
|
1154
1233
|
try {
|
|
@@ -1159,6 +1238,7 @@ export function memoryCommands(program) {
|
|
|
1159
1238
|
user_id: userId,
|
|
1160
1239
|
time_range_days: Math.max(1, Math.min(365, parseInt(options.days || '30', 10))),
|
|
1161
1240
|
response_format: 'json',
|
|
1241
|
+
...buildIntelligenceContextPayload(options),
|
|
1162
1242
|
});
|
|
1163
1243
|
spinner.stop();
|
|
1164
1244
|
printIntelligenceResult('📈 Pattern Analysis', result, options);
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -9,8 +9,11 @@ export interface AuthResponse {
|
|
|
9
9
|
created_at: string;
|
|
10
10
|
updated_at: string;
|
|
11
11
|
};
|
|
12
|
-
token
|
|
13
|
-
|
|
12
|
+
token?: string;
|
|
13
|
+
access_token?: string;
|
|
14
|
+
refresh_token?: string;
|
|
15
|
+
expires_in?: number;
|
|
16
|
+
expires_at?: string;
|
|
14
17
|
}
|
|
15
18
|
export interface RegisterRequest {
|
|
16
19
|
email: string;
|
package/dist/utils/api.js
CHANGED
|
@@ -280,6 +280,9 @@ export class APIClient {
|
|
|
280
280
|
await this.config.discoverServices();
|
|
281
281
|
// Use appropriate base URL based on endpoint and auth method
|
|
282
282
|
const isAuthEndpoint = config.url?.includes('/auth/') || config.url?.includes('/login') || config.url?.includes('/register') || config.url?.includes('/oauth/');
|
|
283
|
+
const isAuthGatewayManagementEndpoint = typeof config.url === 'string'
|
|
284
|
+
&& (config.url.startsWith('/api/v1/auth/api-keys')
|
|
285
|
+
|| config.url.startsWith('/api/v1/projects'));
|
|
283
286
|
const discoveredServices = this.config.get('discoveredServices');
|
|
284
287
|
const authMethod = this.config.get('authMethod');
|
|
285
288
|
const vendorKey = await this.config.getVendorKeyAsync();
|
|
@@ -322,7 +325,7 @@ export class APIClient {
|
|
|
322
325
|
// - Other direct API calls -> api.lanonasis.com (vendor AI proxy)
|
|
323
326
|
let apiBaseUrl;
|
|
324
327
|
const useMcpServer = !forceDirectApi && !isAuthEndpoint && (prefersTokenAuth || useVendorKeyAuth || isMemoryEndpoint);
|
|
325
|
-
if (isAuthEndpoint) {
|
|
328
|
+
if (isAuthEndpoint || isAuthGatewayManagementEndpoint) {
|
|
326
329
|
apiBaseUrl = discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
327
330
|
}
|
|
328
331
|
else if (forceDirectApi) {
|
|
@@ -343,7 +346,7 @@ export class APIClient {
|
|
|
343
346
|
config.url = config.url.replace(/\/api\/v1\/memories/g, '/memory');
|
|
344
347
|
}
|
|
345
348
|
// Add project scope header for auth endpoints
|
|
346
|
-
if (isAuthEndpoint) {
|
|
349
|
+
if (isAuthEndpoint || isAuthGatewayManagementEndpoint) {
|
|
347
350
|
config.headers['X-Project-Scope'] = 'lanonasis-maas';
|
|
348
351
|
}
|
|
349
352
|
// Enhanced Authentication Support
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ interface CLIConfigData {
|
|
|
27
27
|
lastManualEndpointUpdate?: string;
|
|
28
28
|
vendorKey?: string | undefined;
|
|
29
29
|
authMethod?: 'jwt' | 'vendor_key' | 'oauth' | 'oauth2' | undefined;
|
|
30
|
+
refresh_token?: string | undefined;
|
|
31
|
+
token_expires_at?: number | string | undefined;
|
|
30
32
|
tokenExpiry?: number | undefined;
|
|
31
33
|
lastValidated?: string | undefined;
|
|
32
34
|
deviceId?: string;
|
|
@@ -130,6 +132,7 @@ export declare class CLIConfig {
|
|
|
130
132
|
exists(): Promise<boolean>;
|
|
131
133
|
validateStoredCredentials(): Promise<boolean>;
|
|
132
134
|
refreshTokenIfNeeded(): Promise<void>;
|
|
135
|
+
private refreshViaOAuthTokenEndpoint;
|
|
133
136
|
clearInvalidCredentials(): Promise<void>;
|
|
134
137
|
incrementFailureCount(): Promise<void>;
|
|
135
138
|
resetFailureCount(): Promise<void>;
|
package/dist/utils/config.js
CHANGED
|
@@ -1283,9 +1283,9 @@ export class CLIConfig {
|
|
|
1283
1283
|
if (String(this.config.authMethod || '').toLowerCase() === 'vendor_key') {
|
|
1284
1284
|
return;
|
|
1285
1285
|
}
|
|
1286
|
+
const refreshToken = this.get('refresh_token');
|
|
1286
1287
|
// OAuth token refresh (opaque tokens + refresh_token + token_expires_at)
|
|
1287
|
-
if (this.config.authMethod === 'oauth') {
|
|
1288
|
-
const refreshToken = this.get('refresh_token');
|
|
1288
|
+
if (this.config.authMethod === 'oauth' || this.config.authMethod === 'oauth2') {
|
|
1289
1289
|
if (!refreshToken) {
|
|
1290
1290
|
return;
|
|
1291
1291
|
}
|
|
@@ -1310,37 +1310,7 @@ export class CLIConfig {
|
|
|
1310
1310
|
return;
|
|
1311
1311
|
}
|
|
1312
1312
|
await this.discoverServices();
|
|
1313
|
-
|
|
1314
|
-
const resp = await axios.post(`${authBase}/oauth/token`, {
|
|
1315
|
-
grant_type: 'refresh_token',
|
|
1316
|
-
refresh_token: refreshToken,
|
|
1317
|
-
client_id: 'lanonasis-cli'
|
|
1318
|
-
}, {
|
|
1319
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1320
|
-
timeout: 10000,
|
|
1321
|
-
proxy: false
|
|
1322
|
-
});
|
|
1323
|
-
// Some gateways wrap responses as `{ data: { ... } }`.
|
|
1324
|
-
const raw = resp?.data;
|
|
1325
|
-
const payload = raw && typeof raw === 'object' && raw.data && typeof raw.data === 'object'
|
|
1326
|
-
? raw.data
|
|
1327
|
-
: raw;
|
|
1328
|
-
const accessToken = payload?.access_token ?? payload?.token;
|
|
1329
|
-
const refreshedRefreshToken = payload?.refresh_token;
|
|
1330
|
-
const expiresIn = payload?.expires_in;
|
|
1331
|
-
if (typeof accessToken !== 'string' || accessToken.length === 0) {
|
|
1332
|
-
throw new Error('Token refresh response missing access_token');
|
|
1333
|
-
}
|
|
1334
|
-
// setToken() assumes JWT by default; ensure authMethod stays oauth after storing.
|
|
1335
|
-
await this.setToken(accessToken);
|
|
1336
|
-
this.config.authMethod = 'oauth';
|
|
1337
|
-
if (typeof refreshedRefreshToken === 'string' && refreshedRefreshToken.length > 0) {
|
|
1338
|
-
this.config.refresh_token = refreshedRefreshToken;
|
|
1339
|
-
}
|
|
1340
|
-
if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) {
|
|
1341
|
-
this.config.token_expires_at = Date.now() + (expiresIn * 1000);
|
|
1342
|
-
}
|
|
1343
|
-
await this.save().catch(() => { });
|
|
1313
|
+
await this.refreshViaOAuthTokenEndpoint(refreshToken, this.getDiscoveredApiUrl(), 'oauth');
|
|
1344
1314
|
return;
|
|
1345
1315
|
}
|
|
1346
1316
|
// Check if token is JWT and if it's close to expiry
|
|
@@ -1359,20 +1329,11 @@ export class CLIConfig {
|
|
|
1359
1329
|
const exp = typeof decoded.exp === 'number' ? decoded.exp : 0;
|
|
1360
1330
|
// Refresh if token expires within 5 minutes
|
|
1361
1331
|
if (exp > 0 && (exp - now) < 300) {
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
1365
|
-
// Attempt token refresh
|
|
1366
|
-
const response = await axios.post(`${authBase}/v1/auth/refresh`, {}, {
|
|
1367
|
-
headers: {
|
|
1368
|
-
'Authorization': `Bearer ${token}`,
|
|
1369
|
-
'X-Project-Scope': 'lanonasis-maas'
|
|
1370
|
-
},
|
|
1371
|
-
timeout: 10000
|
|
1372
|
-
});
|
|
1373
|
-
if (response.data.token) {
|
|
1374
|
-
await this.setToken(response.data.token);
|
|
1332
|
+
if (!refreshToken) {
|
|
1333
|
+
return;
|
|
1375
1334
|
}
|
|
1335
|
+
await this.discoverServices();
|
|
1336
|
+
await this.refreshViaOAuthTokenEndpoint(refreshToken, this.getDiscoveredApiUrl(), 'jwt');
|
|
1376
1337
|
}
|
|
1377
1338
|
}
|
|
1378
1339
|
catch (err) {
|
|
@@ -1383,6 +1344,36 @@ export class CLIConfig {
|
|
|
1383
1344
|
}
|
|
1384
1345
|
}
|
|
1385
1346
|
}
|
|
1347
|
+
async refreshViaOAuthTokenEndpoint(refreshToken, authBase, authMethod) {
|
|
1348
|
+
const resp = await axios.post(`${authBase}/oauth/token`, {
|
|
1349
|
+
grant_type: 'refresh_token',
|
|
1350
|
+
refresh_token: refreshToken,
|
|
1351
|
+
client_id: 'lanonasis-cli'
|
|
1352
|
+
}, {
|
|
1353
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1354
|
+
timeout: 10000,
|
|
1355
|
+
proxy: false
|
|
1356
|
+
});
|
|
1357
|
+
const raw = resp?.data;
|
|
1358
|
+
const payload = raw && typeof raw === 'object' && raw.data && typeof raw.data === 'object'
|
|
1359
|
+
? raw.data
|
|
1360
|
+
: raw;
|
|
1361
|
+
const accessToken = payload?.access_token ?? payload?.token;
|
|
1362
|
+
const refreshedRefreshToken = payload?.refresh_token;
|
|
1363
|
+
const expiresIn = payload?.expires_in;
|
|
1364
|
+
if (typeof accessToken !== 'string' || accessToken.length === 0) {
|
|
1365
|
+
throw new Error('Token refresh response missing access_token');
|
|
1366
|
+
}
|
|
1367
|
+
await this.setToken(accessToken);
|
|
1368
|
+
this.config.authMethod = authMethod;
|
|
1369
|
+
if (typeof refreshedRefreshToken === 'string' && refreshedRefreshToken.length > 0) {
|
|
1370
|
+
this.config.refresh_token = refreshedRefreshToken;
|
|
1371
|
+
}
|
|
1372
|
+
if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) {
|
|
1373
|
+
this.config.token_expires_at = Date.now() + (expiresIn * 1000);
|
|
1374
|
+
}
|
|
1375
|
+
await this.save().catch(() => { });
|
|
1376
|
+
}
|
|
1386
1377
|
async clearInvalidCredentials() {
|
|
1387
1378
|
this.config.token = undefined;
|
|
1388
1379
|
this.config.vendorKey = undefined;
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -451,20 +451,11 @@ export class MCPClient {
|
|
|
451
451
|
* Refresh token if needed
|
|
452
452
|
*/
|
|
453
453
|
async refreshTokenIfNeeded() {
|
|
454
|
-
const refreshToken = this.config.get('refreshToken');
|
|
455
|
-
if (!refreshToken) {
|
|
456
|
-
throw new Error('No refresh token available. Please re-authenticate.');
|
|
457
|
-
}
|
|
458
454
|
try {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
}, {
|
|
464
|
-
timeout: 10000
|
|
465
|
-
});
|
|
466
|
-
if (response.data.access_token) {
|
|
467
|
-
await this.config.setAndSave('token', response.data.access_token);
|
|
455
|
+
const previousToken = this.config.getToken();
|
|
456
|
+
await this.config.refreshTokenIfNeeded();
|
|
457
|
+
const currentToken = this.config.getToken();
|
|
458
|
+
if (currentToken && currentToken !== previousToken) {
|
|
468
459
|
console.log(chalk.green('✓ Token refreshed successfully'));
|
|
469
460
|
}
|
|
470
461
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/cli",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.14",
|
|
4
4
|
"description": "Professional CLI for LanOnasis Memory as a Service (MaaS) with MCP support, seamless inline editing, and enterprise-grade security",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lanonasis",
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
"CHANGELOG.md"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@lanonasis/mem-intel-sdk": "2.0
|
|
55
|
+
"@lanonasis/mem-intel-sdk": "2.1.0",
|
|
56
56
|
"@lanonasis/oauth-client": "2.0.4",
|
|
57
57
|
"@lanonasis/security-sdk": "1.0.5",
|
|
58
58
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
59
|
-
"axios": "^1.
|
|
59
|
+
"axios": "^1.14.0",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-progress": "^3.12.0",
|
|
62
62
|
"cli-table3": "^0.6.5",
|