@lanonasis/cli 3.9.13 → 3.9.15
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 +20 -0
- package/dist/commands/api-keys.js +68 -314
- package/dist/commands/auth.js +25 -2
- package/dist/mcp/server/lanonasis-server.d.ts +1 -0
- package/dist/mcp/server/lanonasis-server.js +41 -9
- package/dist/utils/api.d.ts +2 -0
- package/dist/utils/api.js +74 -8
- package/dist/utils/mcp-client.js +12 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog - @lanonasis/cli
|
|
2
2
|
|
|
3
|
+
## [3.9.15] - 2026-04-04
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **Displayed memory ID prefixes now work across CLI memory reads and deletes**: `lanonasis memory get`, `memory update`, and `memory delete` now resolve 8+ character prefixes from the list output before calling the live API, so operators no longer need to paste full UUIDs for routine follow-up actions.
|
|
8
|
+
- **Legacy memory route drift no longer breaks prefix fetches**: Memory lookups now retry against the compatibility `GET /api/v1/memory/get?id=...` and delete fallback routes only when the canonical plural endpoint returns the known `Memory ID is required` validation drift.
|
|
9
|
+
- **MCP memory tools now accept both `id` and `memory_id`**: The bundled LanOnasis MCP server and remote MCP bridge now normalize legacy `memory_id` callers onto the live `id` contract, which restores OpenClaw and similar clients when they pass the older field name.
|
|
10
|
+
|
|
11
|
+
## [3.9.14] - 2026-04-03
|
|
12
|
+
|
|
13
|
+
### 🐛 Bug Fixes
|
|
14
|
+
|
|
15
|
+
- **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.
|
|
16
|
+
- **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.
|
|
17
|
+
- **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.
|
|
18
|
+
|
|
19
|
+
### ✨ Improvements
|
|
20
|
+
|
|
21
|
+
- **`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.
|
|
22
|
+
|
|
3
23
|
## [3.9.13] - 2026-04-02
|
|
4
24
|
|
|
5
25
|
### 🐛 Bug Fixes
|
|
@@ -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
|
|
@@ -43,6 +43,15 @@ export class LanonasisMCPServer {
|
|
|
43
43
|
// Setup error handling
|
|
44
44
|
this.setupErrorHandling();
|
|
45
45
|
}
|
|
46
|
+
extractMemoryIdentifier(args) {
|
|
47
|
+
const id = typeof args.id === 'string' ? args.id.trim() : '';
|
|
48
|
+
const legacyId = typeof args.memory_id === 'string' ? args.memory_id.trim() : '';
|
|
49
|
+
const resolved = id || legacyId;
|
|
50
|
+
if (!resolved) {
|
|
51
|
+
throw new Error('Memory ID is required');
|
|
52
|
+
}
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
46
55
|
/**
|
|
47
56
|
* Initialize the server
|
|
48
57
|
*/
|
|
@@ -170,12 +179,19 @@ export class LanonasisMCPServer {
|
|
|
170
179
|
inputSchema: {
|
|
171
180
|
type: 'object',
|
|
172
181
|
properties: {
|
|
182
|
+
id: {
|
|
183
|
+
type: 'string',
|
|
184
|
+
description: 'Memory ID or displayed prefix'
|
|
185
|
+
},
|
|
173
186
|
memory_id: {
|
|
174
187
|
type: 'string',
|
|
175
|
-
description: '
|
|
188
|
+
description: 'Legacy alias for memory ID or displayed prefix'
|
|
176
189
|
}
|
|
177
190
|
},
|
|
178
|
-
|
|
191
|
+
anyOf: [
|
|
192
|
+
{ required: ['id'] },
|
|
193
|
+
{ required: ['memory_id'] }
|
|
194
|
+
]
|
|
179
195
|
}
|
|
180
196
|
},
|
|
181
197
|
{
|
|
@@ -184,9 +200,13 @@ export class LanonasisMCPServer {
|
|
|
184
200
|
inputSchema: {
|
|
185
201
|
type: 'object',
|
|
186
202
|
properties: {
|
|
203
|
+
id: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
description: 'Memory ID or displayed prefix'
|
|
206
|
+
},
|
|
187
207
|
memory_id: {
|
|
188
208
|
type: 'string',
|
|
189
|
-
description: '
|
|
209
|
+
description: 'Legacy alias for memory ID or displayed prefix'
|
|
190
210
|
},
|
|
191
211
|
title: {
|
|
192
212
|
type: 'string',
|
|
@@ -202,7 +222,10 @@ export class LanonasisMCPServer {
|
|
|
202
222
|
description: 'New tags (optional)'
|
|
203
223
|
}
|
|
204
224
|
},
|
|
205
|
-
|
|
225
|
+
anyOf: [
|
|
226
|
+
{ required: ['id'] },
|
|
227
|
+
{ required: ['memory_id'] }
|
|
228
|
+
]
|
|
206
229
|
}
|
|
207
230
|
},
|
|
208
231
|
{
|
|
@@ -211,12 +234,19 @@ export class LanonasisMCPServer {
|
|
|
211
234
|
inputSchema: {
|
|
212
235
|
type: 'object',
|
|
213
236
|
properties: {
|
|
237
|
+
id: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: 'Memory ID or displayed prefix'
|
|
240
|
+
},
|
|
214
241
|
memory_id: {
|
|
215
242
|
type: 'string',
|
|
216
|
-
description: '
|
|
243
|
+
description: 'Legacy alias for memory ID or displayed prefix'
|
|
217
244
|
}
|
|
218
245
|
},
|
|
219
|
-
|
|
246
|
+
anyOf: [
|
|
247
|
+
{ required: ['id'] },
|
|
248
|
+
{ required: ['memory_id'] }
|
|
249
|
+
]
|
|
220
250
|
}
|
|
221
251
|
},
|
|
222
252
|
// Topic tools
|
|
@@ -647,11 +677,13 @@ Please choose an option (1-4):`
|
|
|
647
677
|
topic_id: args.topic_id
|
|
648
678
|
});
|
|
649
679
|
case 'memory_get':
|
|
650
|
-
return await this.apiClient.getMemory(args
|
|
680
|
+
return await this.apiClient.getMemory(this.extractMemoryIdentifier(args));
|
|
651
681
|
case 'memory_update':
|
|
652
|
-
|
|
682
|
+
const memoryId = this.extractMemoryIdentifier(args);
|
|
683
|
+
const { id: _id, memory_id: _legacyId, ...updateArgs } = args;
|
|
684
|
+
return await this.apiClient.updateMemory(memoryId, updateArgs);
|
|
653
685
|
case 'memory_delete':
|
|
654
|
-
return await this.apiClient.deleteMemory(args
|
|
686
|
+
return await this.apiClient.deleteMemory(this.extractMemoryIdentifier(args));
|
|
655
687
|
// Topic operations
|
|
656
688
|
case 'topic_create':
|
|
657
689
|
return await this.apiClient.createTopic(args);
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -198,6 +198,8 @@ export declare class APIClient {
|
|
|
198
198
|
private shouldUseLegacyMemoryRpcFallback;
|
|
199
199
|
private shouldRetryViaApiGateway;
|
|
200
200
|
private shouldRetryViaSupabaseMemoryFunctions;
|
|
201
|
+
private isUuid;
|
|
202
|
+
resolveMemoryId(idOrPrefix: string): Promise<string>;
|
|
201
203
|
private shouldUsePostListFallback;
|
|
202
204
|
private getSupabaseFunctionsBaseUrl;
|
|
203
205
|
private mapMemoryApiRouteToSupabaseFunctions;
|
package/dist/utils/api.js
CHANGED
|
@@ -10,6 +10,7 @@ const MEMORY_TYPES = [
|
|
|
10
10
|
'personal',
|
|
11
11
|
'workflow'
|
|
12
12
|
];
|
|
13
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
13
14
|
export class APIClient {
|
|
14
15
|
client;
|
|
15
16
|
config;
|
|
@@ -166,6 +167,61 @@ export class APIClient {
|
|
|
166
167
|
const hasVendorKey = this.config.hasVendorKey();
|
|
167
168
|
return hasVendorKey || hasOpaqueToken || authMethod === 'oauth' || authMethod === 'oauth2';
|
|
168
169
|
}
|
|
170
|
+
isUuid(value) {
|
|
171
|
+
return UUID_PATTERN.test(value);
|
|
172
|
+
}
|
|
173
|
+
async resolveMemoryId(idOrPrefix) {
|
|
174
|
+
const candidate = idOrPrefix.trim();
|
|
175
|
+
if (!candidate) {
|
|
176
|
+
throw new Error('Memory ID is required.');
|
|
177
|
+
}
|
|
178
|
+
if (this.isUuid(candidate)) {
|
|
179
|
+
return candidate;
|
|
180
|
+
}
|
|
181
|
+
if (candidate.length < 8) {
|
|
182
|
+
throw new Error('Memory ID prefix must be at least 8 characters or a full UUID.');
|
|
183
|
+
}
|
|
184
|
+
const matches = new Set();
|
|
185
|
+
const normalizedCandidate = candidate.toLowerCase();
|
|
186
|
+
const limit = 100;
|
|
187
|
+
let page = 1;
|
|
188
|
+
while (true) {
|
|
189
|
+
const result = await this.getMemories({ page, limit });
|
|
190
|
+
const memories = result.memories || result.data || [];
|
|
191
|
+
if (memories.length === 0) {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
for (const memory of memories) {
|
|
195
|
+
if (memory.id.toLowerCase().startsWith(normalizedCandidate)) {
|
|
196
|
+
matches.add(memory.id);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const pagination = result.pagination || {
|
|
200
|
+
total: memories.length,
|
|
201
|
+
limit,
|
|
202
|
+
offset: 0,
|
|
203
|
+
has_more: false,
|
|
204
|
+
};
|
|
205
|
+
const totalPages = Number.isFinite(Number(pagination.pages))
|
|
206
|
+
? Number(pagination.pages)
|
|
207
|
+
: Math.max(1, Math.ceil(Number(pagination.total || memories.length) / limit));
|
|
208
|
+
const hasMore = typeof pagination.has_more === 'boolean'
|
|
209
|
+
? pagination.has_more
|
|
210
|
+
: page < totalPages;
|
|
211
|
+
if (!hasMore) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
page += 1;
|
|
215
|
+
}
|
|
216
|
+
const resolvedMatches = [...matches];
|
|
217
|
+
if (resolvedMatches.length === 0) {
|
|
218
|
+
throw new Error(`Memory not found for ID/prefix: ${candidate}`);
|
|
219
|
+
}
|
|
220
|
+
if (resolvedMatches.length > 1) {
|
|
221
|
+
throw new Error(`Memory ID prefix is ambiguous: ${candidate}. Matches: ${resolvedMatches.slice(0, 5).join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
return resolvedMatches[0];
|
|
224
|
+
}
|
|
169
225
|
shouldUsePostListFallback(error) {
|
|
170
226
|
const status = Number(error?.response?.status || 0);
|
|
171
227
|
if (status === 405)
|
|
@@ -280,6 +336,9 @@ export class APIClient {
|
|
|
280
336
|
await this.config.discoverServices();
|
|
281
337
|
// Use appropriate base URL based on endpoint and auth method
|
|
282
338
|
const isAuthEndpoint = config.url?.includes('/auth/') || config.url?.includes('/login') || config.url?.includes('/register') || config.url?.includes('/oauth/');
|
|
339
|
+
const isAuthGatewayManagementEndpoint = typeof config.url === 'string'
|
|
340
|
+
&& (config.url.startsWith('/api/v1/auth/api-keys')
|
|
341
|
+
|| config.url.startsWith('/api/v1/projects'));
|
|
283
342
|
const discoveredServices = this.config.get('discoveredServices');
|
|
284
343
|
const authMethod = this.config.get('authMethod');
|
|
285
344
|
const vendorKey = await this.config.getVendorKeyAsync();
|
|
@@ -322,7 +381,7 @@ export class APIClient {
|
|
|
322
381
|
// - Other direct API calls -> api.lanonasis.com (vendor AI proxy)
|
|
323
382
|
let apiBaseUrl;
|
|
324
383
|
const useMcpServer = !forceDirectApi && !isAuthEndpoint && (prefersTokenAuth || useVendorKeyAuth || isMemoryEndpoint);
|
|
325
|
-
if (isAuthEndpoint) {
|
|
384
|
+
if (isAuthEndpoint || isAuthGatewayManagementEndpoint) {
|
|
326
385
|
apiBaseUrl = discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
327
386
|
}
|
|
328
387
|
else if (forceDirectApi) {
|
|
@@ -343,7 +402,7 @@ export class APIClient {
|
|
|
343
402
|
config.url = config.url.replace(/\/api\/v1\/memories/g, '/memory');
|
|
344
403
|
}
|
|
345
404
|
// Add project scope header for auth endpoints
|
|
346
|
-
if (isAuthEndpoint) {
|
|
405
|
+
if (isAuthEndpoint || isAuthGatewayManagementEndpoint) {
|
|
347
406
|
config.headers['X-Project-Scope'] = 'lanonasis-maas';
|
|
348
407
|
}
|
|
349
408
|
// Enhanced Authentication Support
|
|
@@ -624,13 +683,16 @@ export class APIClient {
|
|
|
624
683
|
}
|
|
625
684
|
}
|
|
626
685
|
async getMemory(id) {
|
|
686
|
+
const resolvedId = await this.resolveMemoryId(id);
|
|
627
687
|
try {
|
|
628
|
-
const response = await this.client.get(`/api/v1/memories/${
|
|
688
|
+
const response = await this.client.get(`/api/v1/memories/${encodeURIComponent(resolvedId)}`);
|
|
629
689
|
return this.normalizeMemoryEntry(response.data);
|
|
630
690
|
}
|
|
631
691
|
catch (error) {
|
|
632
692
|
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
633
|
-
const fallback = await this.client.
|
|
693
|
+
const fallback = await this.client.get('/api/v1/memory/get', {
|
|
694
|
+
params: { id: resolvedId }
|
|
695
|
+
});
|
|
634
696
|
const payload = fallback.data && typeof fallback.data === 'object'
|
|
635
697
|
? fallback.data.data ?? fallback.data
|
|
636
698
|
: fallback.data;
|
|
@@ -640,14 +702,15 @@ export class APIClient {
|
|
|
640
702
|
}
|
|
641
703
|
}
|
|
642
704
|
async updateMemory(id, data) {
|
|
705
|
+
const resolvedId = await this.resolveMemoryId(id);
|
|
643
706
|
try {
|
|
644
|
-
const response = await this.client.put(`/api/v1/memories/${
|
|
707
|
+
const response = await this.client.put(`/api/v1/memories/${encodeURIComponent(resolvedId)}`, data);
|
|
645
708
|
return this.normalizeMemoryEntry(response.data);
|
|
646
709
|
}
|
|
647
710
|
catch (error) {
|
|
648
711
|
if (this.shouldUseLegacyMemoryRpcFallback(error) || error?.response?.status === 404) {
|
|
649
712
|
const fallback = await this.client.post('/api/v1/memory/update', {
|
|
650
|
-
id,
|
|
713
|
+
id: resolvedId,
|
|
651
714
|
...data
|
|
652
715
|
});
|
|
653
716
|
const payload = fallback.data && typeof fallback.data === 'object'
|
|
@@ -659,12 +722,15 @@ export class APIClient {
|
|
|
659
722
|
}
|
|
660
723
|
}
|
|
661
724
|
async deleteMemory(id) {
|
|
725
|
+
const resolvedId = await this.resolveMemoryId(id);
|
|
662
726
|
try {
|
|
663
|
-
await this.client.delete(`/api/v1/memories/${
|
|
727
|
+
await this.client.delete(`/api/v1/memories/${encodeURIComponent(resolvedId)}`);
|
|
664
728
|
}
|
|
665
729
|
catch (error) {
|
|
666
730
|
if (this.shouldUseLegacyMemoryRpcFallback(error) || error?.response?.status === 404) {
|
|
667
|
-
await this.client.
|
|
731
|
+
await this.client.delete('/api/v1/memory/delete', {
|
|
732
|
+
params: { id: resolvedId }
|
|
733
|
+
});
|
|
668
734
|
return;
|
|
669
735
|
}
|
|
670
736
|
throw error;
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -5,6 +5,13 @@ import { CLIConfig } from './config.js';
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import { EventSource } from 'eventsource';
|
|
7
7
|
import WebSocket from 'ws';
|
|
8
|
+
function getMemoryToolId(args) {
|
|
9
|
+
const id = typeof args.id === 'string' ? args.id.trim() : '';
|
|
10
|
+
if (id)
|
|
11
|
+
return id;
|
|
12
|
+
const legacyId = typeof args.memory_id === 'string' ? args.memory_id.trim() : '';
|
|
13
|
+
return legacyId || undefined;
|
|
14
|
+
}
|
|
8
15
|
export class MCPClient {
|
|
9
16
|
client = null;
|
|
10
17
|
config;
|
|
@@ -838,6 +845,7 @@ export class MCPClient {
|
|
|
838
845
|
endpoint: '/memory/{id}',
|
|
839
846
|
transform: (args) => {
|
|
840
847
|
const data = { ...args };
|
|
848
|
+
delete data.id;
|
|
841
849
|
delete data.memory_id;
|
|
842
850
|
return data;
|
|
843
851
|
}
|
|
@@ -861,9 +869,9 @@ export class MCPClient {
|
|
|
861
869
|
const axios = (await import('axios')).default;
|
|
862
870
|
// Handle dynamic endpoint for memory operations that need ID
|
|
863
871
|
let endpoint = mapping.endpoint;
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
endpoint = endpoint.replace('{id}',
|
|
872
|
+
const memoryId = getMemoryToolId(args);
|
|
873
|
+
if (endpoint.includes('{id}') && memoryId) {
|
|
874
|
+
endpoint = endpoint.replace('{id}', encodeURIComponent(memoryId));
|
|
867
875
|
}
|
|
868
876
|
const response = await axios({
|
|
869
877
|
method: mapping.method,
|
|
@@ -873,7 +881,7 @@ export class MCPClient {
|
|
|
873
881
|
'Content-Type': 'application/json'
|
|
874
882
|
},
|
|
875
883
|
data: mapping.transform ? mapping.transform(args) : undefined,
|
|
876
|
-
params: mapping.method === 'GET' ? args : undefined
|
|
884
|
+
params: mapping.method === 'GET' && !mapping.endpoint.includes('{id}') ? args : undefined
|
|
877
885
|
});
|
|
878
886
|
return response.data;
|
|
879
887
|
}
|
package/package.json
CHANGED