@lanonasis/cli 3.9.10 → 3.9.12
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 +8 -0
- package/README.md +2 -2
- package/dist/commands/api-keys.js +196 -161
- package/dist/commands/auth.js +0 -4
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/memory.js +52 -11
- package/dist/index.js +14 -0
- package/dist/utils/api.d.ts +6 -0
- package/dist/utils/api.js +81 -3
- package/dist/utils/config.d.ts +8 -1
- package/dist/utils/config.js +98 -22
- package/dist/utils/mcp-client.js +4 -0
- package/dist/ux/implementations/ConnectionManagerImpl.js +14 -2
- package/package.json +26 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog - @lanonasis/cli
|
|
2
2
|
|
|
3
|
+
## [3.9.11] - 2026-03-27
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **`memory stats` no longer crashes on wrapped responses**: The CLI now normalizes both flat and `{ data: ... }` stats payloads and safely defaults optional fields like `total_size_bytes` and `avg_access_count` when the backend omits them.
|
|
8
|
+
- **Secure storage no longer initializes on every command startup**: Vendor key storage is now lazy-loaded, which prevents unnecessary keychain fallback warnings on CLI commands that do not access vendor key storage.
|
|
9
|
+
- **Bundled auth client updated to `@lanonasis/oauth-client@2.0.4`**: Pulls in the ESM-safe native keychain loader fix and semver-compliant optional React peer metadata.
|
|
10
|
+
|
|
3
11
|
## [3.9.8] - 2026-02-25
|
|
4
12
|
|
|
5
13
|
### ✨ New Features
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# @lanonasis/cli v3.9.
|
|
1
|
+
# @lanonasis/cli v3.9.11 - Stable Stats & Cleaner Startup
|
|
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.11**: `onasis memory stats` now handles wrapped/partial backend responses without crashing, vendor key secure storage is lazy-loaded so unrelated commands no longer trigger noisy fallback warnings, and the bundled `@lanonasis/oauth-client` is updated to `2.0.4` for the ESM-safe keychain loader fix.
|
|
9
9
|
|
|
10
10
|
## 🚀 Quick Start
|
|
11
11
|
|
|
@@ -15,6 +15,24 @@ const colors = {
|
|
|
15
15
|
muted: chalk.gray,
|
|
16
16
|
highlight: chalk.white.bold
|
|
17
17
|
};
|
|
18
|
+
const AUTH_API_KEYS_BASE = '/api/v1/auth/api-keys';
|
|
19
|
+
const VALID_ACCESS_LEVELS = ['public', 'authenticated', 'team', 'admin', 'enterprise'];
|
|
20
|
+
function unwrapApiResponse(response) {
|
|
21
|
+
if (response && typeof response === 'object' && 'data' in response) {
|
|
22
|
+
return response.data ?? response;
|
|
23
|
+
}
|
|
24
|
+
return response;
|
|
25
|
+
}
|
|
26
|
+
function parseScopes(scopes) {
|
|
27
|
+
if (!scopes) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const parsed = scopes
|
|
31
|
+
.split(',')
|
|
32
|
+
.map((scope) => scope.trim())
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
return parsed.length > 0 ? parsed : undefined;
|
|
35
|
+
}
|
|
18
36
|
const apiKeysCommand = new Command('api-keys')
|
|
19
37
|
.alias('keys')
|
|
20
38
|
.description(colors.info('🔐 Manage API keys securely with enterprise-grade encryption'));
|
|
@@ -65,7 +83,8 @@ projectsCommand
|
|
|
65
83
|
]);
|
|
66
84
|
projectData = { ...projectData, ...answers };
|
|
67
85
|
}
|
|
68
|
-
const
|
|
86
|
+
const projectRes = await apiClient.post(`${AUTH_API_KEYS_BASE}/projects`, projectData);
|
|
87
|
+
const project = unwrapApiResponse(projectRes);
|
|
69
88
|
console.log(chalk.green('✅ Project created successfully!'));
|
|
70
89
|
console.log(chalk.blue(`Project ID: ${project.id}`));
|
|
71
90
|
console.log(chalk.blue(`Name: ${project.name}`));
|
|
@@ -85,12 +104,12 @@ projectsCommand
|
|
|
85
104
|
.option('--json', 'Output as JSON')
|
|
86
105
|
.action(async (options) => {
|
|
87
106
|
try {
|
|
88
|
-
const projects = await apiClient.get(
|
|
107
|
+
const projects = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}/projects`));
|
|
89
108
|
if (options.json) {
|
|
90
109
|
console.log(JSON.stringify(projects, null, 2));
|
|
91
110
|
return;
|
|
92
111
|
}
|
|
93
|
-
if (projects.length === 0) {
|
|
112
|
+
if (!Array.isArray(projects) || projects.length === 0) {
|
|
94
113
|
console.log(chalk.yellow('No projects found'));
|
|
95
114
|
return;
|
|
96
115
|
}
|
|
@@ -122,30 +141,29 @@ apiKeysCommand
|
|
|
122
141
|
.command('create')
|
|
123
142
|
.description('Create a new API key')
|
|
124
143
|
.option('-n, --name <name>', 'API key name')
|
|
125
|
-
.option('-
|
|
126
|
-
.option('-
|
|
127
|
-
.option('-
|
|
128
|
-
.option('
|
|
129
|
-
.option('--access-level <level>', 'Access level (public, authenticated, team, admin, enterprise)')
|
|
130
|
-
.option('--tags <tags>', 'Comma-separated tags')
|
|
131
|
-
.option('--expires-at <date>', 'Expiration date (ISO format)')
|
|
132
|
-
.option('--rotation-frequency <days>', 'Rotation frequency in days', '90')
|
|
144
|
+
.option('-d, --description <description>', 'API key description (optional)')
|
|
145
|
+
.option('--access-level <level>', 'Access level (public, authenticated, team, admin, enterprise)', 'team')
|
|
146
|
+
.option('--expires-in-days <days>', 'Expiration in days (default: 365)', '365')
|
|
147
|
+
.option('--scopes <scopes>', 'Comma-separated scopes (optional)')
|
|
133
148
|
.option('--interactive', 'Interactive mode')
|
|
134
149
|
.action(async (options) => {
|
|
135
150
|
try {
|
|
151
|
+
const accessLevel = (options.accessLevel || 'team').toLowerCase();
|
|
152
|
+
const expiresInDays = parseInt(options.expiresInDays, 10);
|
|
153
|
+
if (!VALID_ACCESS_LEVELS.includes(accessLevel)) {
|
|
154
|
+
throw new Error('Invalid access level. Allowed: public, authenticated, team, admin, enterprise');
|
|
155
|
+
}
|
|
156
|
+
if (!Number.isInteger(expiresInDays) || expiresInDays <= 0 || expiresInDays > 3650) {
|
|
157
|
+
throw new Error('expires-in-days must be a positive integer up to 3650');
|
|
158
|
+
}
|
|
136
159
|
let keyData = {
|
|
137
160
|
name: options.name,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
accessLevel: options.accessLevel || 'team',
|
|
143
|
-
tags: options.tags ? options.tags.split(',').map((tag) => tag.trim()) : [],
|
|
144
|
-
expiresAt: options.expiresAt,
|
|
145
|
-
rotationFrequency: parseInt(options.rotationFrequency)
|
|
161
|
+
access_level: accessLevel,
|
|
162
|
+
expires_in_days: expiresInDays,
|
|
163
|
+
description: options.description?.trim() || undefined,
|
|
164
|
+
scopes: parseScopes(options.scopes)
|
|
146
165
|
};
|
|
147
|
-
if (options.interactive || !keyData.name
|
|
148
|
-
const projects = await apiClient.get('/api-keys/projects');
|
|
166
|
+
if (options.interactive || !keyData.name) {
|
|
149
167
|
const answers = await inquirer.prompt([
|
|
150
168
|
{
|
|
151
169
|
type: 'input',
|
|
@@ -155,85 +173,62 @@ apiKeysCommand
|
|
|
155
173
|
validate: (input) => input.length > 0 || 'Name is required'
|
|
156
174
|
},
|
|
157
175
|
{
|
|
158
|
-
type: '
|
|
159
|
-
name: '
|
|
160
|
-
message: '
|
|
161
|
-
|
|
162
|
-
validate: (input) => input.length > 0 || 'Value is required'
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
type: 'select',
|
|
166
|
-
name: 'keyType',
|
|
167
|
-
message: 'Key type:',
|
|
168
|
-
when: !keyData.keyType,
|
|
169
|
-
choices: [
|
|
170
|
-
'api_key',
|
|
171
|
-
'database_url',
|
|
172
|
-
'oauth_token',
|
|
173
|
-
'certificate',
|
|
174
|
-
'ssh_key',
|
|
175
|
-
'webhook_secret',
|
|
176
|
-
'encryption_key'
|
|
177
|
-
]
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
type: 'select',
|
|
181
|
-
name: 'environment',
|
|
182
|
-
message: 'Environment:',
|
|
183
|
-
choices: ['development', 'staging', 'production'],
|
|
184
|
-
default: 'development'
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
type: 'select',
|
|
188
|
-
name: 'projectId',
|
|
189
|
-
message: 'Select project:',
|
|
190
|
-
when: !keyData.projectId && projects.length > 0,
|
|
191
|
-
choices: projects.map((p) => ({ name: `${p.name} (${p.id})`, value: p.id }))
|
|
176
|
+
type: 'input',
|
|
177
|
+
name: 'description',
|
|
178
|
+
message: 'Description (optional):',
|
|
179
|
+
default: keyData.description || ''
|
|
192
180
|
},
|
|
193
181
|
{
|
|
194
182
|
type: 'select',
|
|
195
|
-
name: '
|
|
183
|
+
name: 'access_level',
|
|
196
184
|
message: 'Access level:',
|
|
197
|
-
choices:
|
|
198
|
-
default:
|
|
185
|
+
choices: VALID_ACCESS_LEVELS,
|
|
186
|
+
default: keyData.access_level
|
|
199
187
|
},
|
|
200
188
|
{
|
|
201
|
-
type: '
|
|
202
|
-
name: '
|
|
203
|
-
message: '
|
|
204
|
-
|
|
189
|
+
type: 'number',
|
|
190
|
+
name: 'expires_in_days',
|
|
191
|
+
message: 'Expires in days:',
|
|
192
|
+
default: keyData.expires_in_days,
|
|
193
|
+
validate: (input) => Number.isInteger(input) && input > 0 && input <= 3650 || 'Must be between 1 and 3650 days'
|
|
205
194
|
},
|
|
206
195
|
{
|
|
207
196
|
type: 'input',
|
|
208
|
-
name: '
|
|
209
|
-
message: '
|
|
210
|
-
|
|
211
|
-
if (!input)
|
|
212
|
-
return true;
|
|
213
|
-
const date = new Date(input);
|
|
214
|
-
return !isNaN(date.getTime()) || 'Please enter a valid date';
|
|
215
|
-
},
|
|
216
|
-
filter: (input) => input ? new Date(input).toISOString() : undefined
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
type: 'number',
|
|
220
|
-
name: 'rotationFrequency',
|
|
221
|
-
message: 'Rotation frequency (days):',
|
|
222
|
-
default: 90,
|
|
223
|
-
validate: (input) => input > 0 && input <= 365 || 'Must be between 1 and 365 days'
|
|
197
|
+
name: 'scopes',
|
|
198
|
+
message: 'Scopes (comma-separated, optional):',
|
|
199
|
+
default: (keyData.scopes || []).join(', ')
|
|
224
200
|
}
|
|
225
201
|
]);
|
|
226
|
-
keyData = {
|
|
202
|
+
keyData = {
|
|
203
|
+
...keyData,
|
|
204
|
+
...answers,
|
|
205
|
+
description: typeof answers.description === 'string'
|
|
206
|
+
? answers.description.trim() || undefined
|
|
207
|
+
: keyData.description,
|
|
208
|
+
scopes: parseScopes(typeof answers.scopes === 'string' ? answers.scopes : undefined) ?? keyData.scopes
|
|
209
|
+
};
|
|
227
210
|
}
|
|
228
|
-
const apiKey = await apiClient.post(
|
|
211
|
+
const apiKey = unwrapApiResponse(await apiClient.post(AUTH_API_KEYS_BASE, keyData));
|
|
229
212
|
console.log(colors.success('🔐 API key created successfully!'));
|
|
230
213
|
console.log(colors.info('━'.repeat(50)));
|
|
231
214
|
console.log(`${colors.highlight('Key ID:')} ${colors.primary(apiKey.id)}`);
|
|
232
215
|
console.log(`${colors.highlight('Name:')} ${colors.accent(apiKey.name)}`);
|
|
233
|
-
console.log(`${colors.highlight('
|
|
234
|
-
console.log(`${colors.highlight('
|
|
216
|
+
console.log(`${colors.highlight('Access Level:')} ${colors.info(apiKey.access_level || keyData.access_level)}`);
|
|
217
|
+
console.log(`${colors.highlight('Permissions:')} ${colors.muted((apiKey.permissions || keyData.scopes || []).join(', ') || 'legacy:full_access')}`);
|
|
218
|
+
if (apiKey.expires_at) {
|
|
219
|
+
console.log(`${colors.highlight('Expires At:')} ${colors.warning(formatDate(apiKey.expires_at))}`);
|
|
220
|
+
}
|
|
221
|
+
if (keyData.description) {
|
|
222
|
+
console.log(`${colors.highlight('Description:')} ${colors.muted(keyData.description)}`);
|
|
223
|
+
}
|
|
235
224
|
console.log(colors.info('━'.repeat(50)));
|
|
236
|
-
|
|
225
|
+
if (apiKey.key) {
|
|
226
|
+
console.log(`${colors.highlight('API Key:')} ${colors.primary(apiKey.key)}`);
|
|
227
|
+
console.log(colors.warning('⚠️ Save this key now. It will not be shown again.'));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.log(colors.warning('⚠️ Key value was not returned. If newly created, it cannot be retrieved later.'));
|
|
231
|
+
}
|
|
237
232
|
}
|
|
238
233
|
catch (error) {
|
|
239
234
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -245,15 +240,12 @@ apiKeysCommand
|
|
|
245
240
|
.command('list')
|
|
246
241
|
.alias('ls')
|
|
247
242
|
.description('List API keys')
|
|
248
|
-
.option('
|
|
243
|
+
.option('--all', 'Include inactive keys')
|
|
249
244
|
.option('--json', 'Output as JSON')
|
|
250
245
|
.action(async (options) => {
|
|
251
246
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
url += `?projectId=${options.projectId}`;
|
|
255
|
-
}
|
|
256
|
-
const apiKeys = await apiClient.get(url);
|
|
247
|
+
const query = options.all ? '?active_only=false' : '';
|
|
248
|
+
const apiKeys = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}${query}`));
|
|
257
249
|
if (options.json) {
|
|
258
250
|
console.log(JSON.stringify(apiKeys, null, 2));
|
|
259
251
|
return;
|
|
@@ -266,20 +258,19 @@ apiKeysCommand
|
|
|
266
258
|
console.log(colors.primary('🔐 API Key Management'));
|
|
267
259
|
console.log(colors.info('═'.repeat(80)));
|
|
268
260
|
const table = new Table({
|
|
269
|
-
head: ['ID', 'Name', '
|
|
261
|
+
head: ['ID', 'Name', 'Access', 'Permissions', 'Service', 'Status', 'Expires'].map(h => colors.accent(h)),
|
|
270
262
|
style: { head: [], border: [] }
|
|
271
263
|
});
|
|
272
264
|
apiKeys.forEach((key) => {
|
|
273
|
-
const statusColor = key.
|
|
274
|
-
key.status === 'rotating' ? colors.warning : colors.error;
|
|
265
|
+
const statusColor = key.is_active ? colors.success : colors.error;
|
|
275
266
|
table.push([
|
|
276
267
|
truncateText(key.id, 20),
|
|
277
268
|
key.name,
|
|
278
|
-
key.
|
|
279
|
-
key.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
formatDate(key.
|
|
269
|
+
key.access_level,
|
|
270
|
+
truncateText((key.permissions || []).join(', ') || 'legacy:full_access', 28),
|
|
271
|
+
key.service || 'all',
|
|
272
|
+
statusColor(key.is_active ? 'active' : 'inactive'),
|
|
273
|
+
key.expires_at ? formatDate(key.expires_at) : colors.muted('Never')
|
|
283
274
|
]);
|
|
284
275
|
});
|
|
285
276
|
console.log(table.toString());
|
|
@@ -299,7 +290,7 @@ apiKeysCommand
|
|
|
299
290
|
.option('--json', 'Output as JSON')
|
|
300
291
|
.action(async (keyId, options) => {
|
|
301
292
|
try {
|
|
302
|
-
const apiKey = await apiClient.get(
|
|
293
|
+
const apiKey = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}/${keyId}`));
|
|
303
294
|
if (options.json) {
|
|
304
295
|
console.log(JSON.stringify(apiKey, null, 2));
|
|
305
296
|
return;
|
|
@@ -308,21 +299,20 @@ apiKeysCommand
|
|
|
308
299
|
console.log(colors.info('═'.repeat(60)));
|
|
309
300
|
console.log(`${colors.highlight('ID:')} ${colors.primary(apiKey.id)}`);
|
|
310
301
|
console.log(`${colors.highlight('Name:')} ${colors.accent(apiKey.name)}`);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
console.log(`${colors.highlight('Access Level:')} ${colors.warning(apiKey.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
console.log(`${colors.highlight('
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
console.log(`${colors.highlight('Created:')} ${colors.muted(formatDate(apiKey.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
console.log(`${colors.highlight('Expires:')} ${colors.warning(formatDate(apiKey.expiresAt))}`);
|
|
302
|
+
if (apiKey.description) {
|
|
303
|
+
console.log(`${colors.highlight('Description:')} ${colors.muted(apiKey.description)}`);
|
|
304
|
+
}
|
|
305
|
+
console.log(`${colors.highlight('Access Level:')} ${colors.warning(apiKey.access_level)}`);
|
|
306
|
+
console.log(`${colors.highlight('Permissions:')} ${colors.muted((apiKey.permissions || []).join(', ') || 'legacy:full_access')}`);
|
|
307
|
+
console.log(`${colors.highlight('Service Scope:')} ${colors.info(apiKey.service || 'all')}`);
|
|
308
|
+
const statusColor = apiKey.is_active ? colors.success : colors.error;
|
|
309
|
+
console.log(`${colors.highlight('Status:')} ${statusColor(apiKey.is_active ? 'active' : 'inactive')}`);
|
|
310
|
+
if (apiKey.last_used_at) {
|
|
311
|
+
console.log(`${colors.highlight('Last Used:')} ${colors.muted(formatDate(apiKey.last_used_at))}`);
|
|
312
|
+
}
|
|
313
|
+
console.log(`${colors.highlight('Created:')} ${colors.muted(formatDate(apiKey.created_at))}`);
|
|
314
|
+
if (apiKey.expires_at) {
|
|
315
|
+
console.log(`${colors.highlight('Expires:')} ${colors.warning(formatDate(apiKey.expires_at))}`);
|
|
326
316
|
}
|
|
327
317
|
console.log(colors.info('═'.repeat(60)));
|
|
328
318
|
}
|
|
@@ -337,23 +327,39 @@ apiKeysCommand
|
|
|
337
327
|
.description('Update an API key')
|
|
338
328
|
.argument('<keyId>', 'API key ID')
|
|
339
329
|
.option('-n, --name <name>', 'New name')
|
|
340
|
-
.option('-
|
|
341
|
-
.option('--
|
|
342
|
-
.option('--
|
|
330
|
+
.option('-d, --description <description>', 'New description')
|
|
331
|
+
.option('--access-level <level>', 'New access level')
|
|
332
|
+
.option('--expires-in-days <days>', 'Set a new expiry in days')
|
|
333
|
+
.option('--clear-expiry', 'Remove the current expiry')
|
|
334
|
+
.option('--scopes <scopes>', 'Replace scopes with a comma-separated list')
|
|
343
335
|
.option('--interactive', 'Interactive mode')
|
|
344
336
|
.action(async (keyId, options) => {
|
|
345
337
|
try {
|
|
346
338
|
let updateData = {};
|
|
347
339
|
if (options.name)
|
|
348
340
|
updateData.name = options.name;
|
|
349
|
-
if (options.
|
|
350
|
-
updateData.
|
|
351
|
-
if (options.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
341
|
+
if (options.description !== undefined)
|
|
342
|
+
updateData.description = options.description.trim() || null;
|
|
343
|
+
if (options.accessLevel) {
|
|
344
|
+
const accessLevel = options.accessLevel.toLowerCase();
|
|
345
|
+
if (!VALID_ACCESS_LEVELS.includes(accessLevel)) {
|
|
346
|
+
throw new Error('Invalid access level. Allowed: public, authenticated, team, admin, enterprise');
|
|
347
|
+
}
|
|
348
|
+
updateData.access_level = accessLevel;
|
|
349
|
+
}
|
|
350
|
+
if (options.expiresInDays) {
|
|
351
|
+
const expiresInDays = parseInt(options.expiresInDays, 10);
|
|
352
|
+
if (!Number.isInteger(expiresInDays) || expiresInDays <= 0 || expiresInDays > 3650) {
|
|
353
|
+
throw new Error('expires-in-days must be a positive integer up to 3650');
|
|
354
|
+
}
|
|
355
|
+
updateData.expires_in_days = expiresInDays;
|
|
356
|
+
}
|
|
357
|
+
if (options.clearExpiry)
|
|
358
|
+
updateData.clear_expiry = true;
|
|
359
|
+
if (options.scopes)
|
|
360
|
+
updateData.scopes = parseScopes(options.scopes);
|
|
355
361
|
if (options.interactive || Object.keys(updateData).length === 0) {
|
|
356
|
-
const current = await apiClient.get(
|
|
362
|
+
const current = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}/${keyId}`));
|
|
357
363
|
const answers = await inquirer.prompt([
|
|
358
364
|
{
|
|
359
365
|
type: 'input',
|
|
@@ -362,47 +368,75 @@ apiKeysCommand
|
|
|
362
368
|
default: current.name,
|
|
363
369
|
when: !updateData.name
|
|
364
370
|
},
|
|
371
|
+
{
|
|
372
|
+
type: 'input',
|
|
373
|
+
name: 'description',
|
|
374
|
+
message: 'Description (optional):',
|
|
375
|
+
default: current.description || '',
|
|
376
|
+
when: !updateData.description
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
type: 'select',
|
|
380
|
+
name: 'access_level',
|
|
381
|
+
message: 'Access level:',
|
|
382
|
+
choices: VALID_ACCESS_LEVELS,
|
|
383
|
+
default: current.access_level,
|
|
384
|
+
when: !updateData.access_level
|
|
385
|
+
},
|
|
365
386
|
{
|
|
366
387
|
type: 'confirm',
|
|
367
|
-
name: '
|
|
368
|
-
message: '
|
|
388
|
+
name: 'changeExpiry',
|
|
389
|
+
message: 'Change expiry?',
|
|
369
390
|
default: false,
|
|
370
|
-
when: !updateData.
|
|
391
|
+
when: updateData.expires_in_days === undefined && !updateData.clear_expiry
|
|
371
392
|
},
|
|
372
393
|
{
|
|
373
|
-
type: '
|
|
374
|
-
name: '
|
|
375
|
-
message: '
|
|
376
|
-
|
|
394
|
+
type: 'number',
|
|
395
|
+
name: 'expires_in_days',
|
|
396
|
+
message: 'Expires in days:',
|
|
397
|
+
default: current.expires_at ? 365 : 365,
|
|
398
|
+
when: (answers) => answers.changeExpiry === true && updateData.expires_in_days === undefined && !updateData.clear_expiry,
|
|
399
|
+
validate: (input) => Number.isInteger(input) && input > 0 && input <= 3650 || 'Must be between 1 and 3650 days'
|
|
377
400
|
},
|
|
378
401
|
{
|
|
379
|
-
type: '
|
|
380
|
-
name: '
|
|
381
|
-
message: '
|
|
382
|
-
default:
|
|
383
|
-
|
|
384
|
-
when: !updateData.tags
|
|
402
|
+
type: 'confirm',
|
|
403
|
+
name: 'clear_expiry',
|
|
404
|
+
message: 'Clear expiry instead?',
|
|
405
|
+
default: false,
|
|
406
|
+
when: (answers) => answers.changeExpiry === true && updateData.expires_in_days === undefined && !updateData.clear_expiry && Boolean(current.expires_at)
|
|
385
407
|
},
|
|
386
408
|
{
|
|
387
|
-
type: '
|
|
388
|
-
name: '
|
|
389
|
-
message: '
|
|
390
|
-
default: current.
|
|
391
|
-
|
|
392
|
-
when: !updateData.rotationFrequency
|
|
409
|
+
type: 'input',
|
|
410
|
+
name: 'scopes',
|
|
411
|
+
message: 'Scopes (comma-separated, optional):',
|
|
412
|
+
default: (current.permissions || []).join(', '),
|
|
413
|
+
when: !updateData.scopes
|
|
393
414
|
}
|
|
394
415
|
]);
|
|
395
|
-
updateData = {
|
|
396
|
-
|
|
416
|
+
updateData = {
|
|
417
|
+
...updateData,
|
|
418
|
+
...answers,
|
|
419
|
+
description: typeof answers.description === 'string'
|
|
420
|
+
? answers.description.trim() || null
|
|
421
|
+
: updateData.description,
|
|
422
|
+
scopes: parseScopes(typeof answers.scopes === 'string' ? answers.scopes : undefined) ?? updateData.scopes
|
|
423
|
+
};
|
|
424
|
+
delete updateData.changeExpiry;
|
|
425
|
+
}
|
|
426
|
+
if (Object.keys(updateData).length === 0) {
|
|
427
|
+
console.log(colors.warning('🚫 Nothing to update'));
|
|
428
|
+
return;
|
|
397
429
|
}
|
|
398
|
-
const updatedKey = await apiClient.put(
|
|
430
|
+
const updatedKey = unwrapApiResponse(await apiClient.put(`${AUTH_API_KEYS_BASE}/${keyId}`, updateData));
|
|
399
431
|
console.log(colors.success('🔄 API key updated successfully!'));
|
|
400
432
|
console.log(colors.info('━'.repeat(40)));
|
|
401
433
|
console.log(`${colors.highlight('Name:')} ${colors.accent(updatedKey.name)}`);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
console.log(colors.warning('⚠️ The key value has been updated and re-encrypted.'));
|
|
434
|
+
if (updatedKey.description || updateData.description) {
|
|
435
|
+
console.log(`${colors.highlight('Description:')} ${colors.muted(updatedKey.description || updateData.description)}`);
|
|
405
436
|
}
|
|
437
|
+
console.log(`${colors.highlight('Access Level:')} ${colors.info(updatedKey.access_level)}`);
|
|
438
|
+
console.log(`${colors.highlight('Permissions:')} ${colors.muted((updatedKey.permissions || []).join(', ') || 'legacy:full_access')}`);
|
|
439
|
+
console.log(`${colors.highlight('Expires:')} ${updatedKey.expires_at ? colors.warning(formatDate(updatedKey.expires_at)) : colors.muted('Never')}`);
|
|
406
440
|
console.log(colors.info('━'.repeat(40)));
|
|
407
441
|
}
|
|
408
442
|
catch (error) {
|
|
@@ -420,7 +454,7 @@ apiKeysCommand
|
|
|
420
454
|
.action(async (keyId, options) => {
|
|
421
455
|
try {
|
|
422
456
|
if (!options.force) {
|
|
423
|
-
const apiKey = await apiClient.get(
|
|
457
|
+
const apiKey = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}/${keyId}`));
|
|
424
458
|
const { confirm } = await inquirer.prompt([
|
|
425
459
|
{
|
|
426
460
|
type: 'confirm',
|
|
@@ -434,7 +468,7 @@ apiKeysCommand
|
|
|
434
468
|
return;
|
|
435
469
|
}
|
|
436
470
|
}
|
|
437
|
-
await apiClient.delete(
|
|
471
|
+
await apiClient.delete(`${AUTH_API_KEYS_BASE}/${keyId}`);
|
|
438
472
|
console.log(colors.success('🗑️ API key deleted successfully!'));
|
|
439
473
|
}
|
|
440
474
|
catch (error) {
|
|
@@ -564,7 +598,7 @@ mcpCommand
|
|
|
564
598
|
delete toolData.maxConcurrentSessions;
|
|
565
599
|
delete toolData.maxSessionDuration;
|
|
566
600
|
}
|
|
567
|
-
const tool = await apiClient.post(
|
|
601
|
+
const tool = unwrapApiResponse(await apiClient.post(`${AUTH_API_KEYS_BASE}/mcp/tools`, toolData));
|
|
568
602
|
console.log(colors.success('🤖 MCP tool registered successfully!'));
|
|
569
603
|
console.log(colors.info('━'.repeat(50)));
|
|
570
604
|
console.log(`${colors.highlight('Tool ID:')} ${colors.primary(tool.toolId)}`);
|
|
@@ -584,12 +618,12 @@ mcpCommand
|
|
|
584
618
|
.option('--json', 'Output as JSON')
|
|
585
619
|
.action(async (options) => {
|
|
586
620
|
try {
|
|
587
|
-
const tools = await apiClient.get(
|
|
621
|
+
const tools = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}/mcp/tools`));
|
|
588
622
|
if (options.json) {
|
|
589
623
|
console.log(JSON.stringify(tools, null, 2));
|
|
590
624
|
return;
|
|
591
625
|
}
|
|
592
|
-
if (tools.length === 0) {
|
|
626
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
593
627
|
console.log(colors.warning('⚠️ No MCP tools found'));
|
|
594
628
|
console.log(colors.muted('Run: lanonasis api-keys mcp register-tool'));
|
|
595
629
|
return;
|
|
@@ -644,7 +678,8 @@ mcpCommand
|
|
|
644
678
|
};
|
|
645
679
|
if (options.interactive || !requestData.toolId || !requestData.organizationId ||
|
|
646
680
|
requestData.keyNames.length === 0 || !requestData.environment || !requestData.justification) {
|
|
647
|
-
const
|
|
681
|
+
const mcpTools = unwrapApiResponse(await apiClient.get(`${AUTH_API_KEYS_BASE}/mcp/tools`));
|
|
682
|
+
const tools = Array.isArray(mcpTools) ? mcpTools : [];
|
|
648
683
|
const answers = await inquirer.prompt([
|
|
649
684
|
{
|
|
650
685
|
type: 'select',
|
|
@@ -695,7 +730,7 @@ mcpCommand
|
|
|
695
730
|
]);
|
|
696
731
|
requestData = { ...requestData, ...answers };
|
|
697
732
|
}
|
|
698
|
-
const response = await apiClient.post(
|
|
733
|
+
const response = unwrapApiResponse(await apiClient.post(`${AUTH_API_KEYS_BASE}/mcp/request-access`, requestData));
|
|
699
734
|
console.log(colors.success('🔐 Access request created successfully!'));
|
|
700
735
|
console.log(colors.info('━'.repeat(50)));
|
|
701
736
|
console.log(`${colors.highlight('Request ID:')} ${colors.primary(response.requestId)}`);
|
|
@@ -721,7 +756,7 @@ analyticsCommand
|
|
|
721
756
|
.option('--json', 'Output as JSON')
|
|
722
757
|
.action(async (options) => {
|
|
723
758
|
try {
|
|
724
|
-
let url =
|
|
759
|
+
let url = `${AUTH_API_KEYS_BASE}/analytics/usage`;
|
|
725
760
|
const params = new URLSearchParams();
|
|
726
761
|
if (options.keyId)
|
|
727
762
|
params.append('keyId', options.keyId);
|
|
@@ -730,12 +765,12 @@ analyticsCommand
|
|
|
730
765
|
if (params.toString()) {
|
|
731
766
|
url += `?${params.toString()}`;
|
|
732
767
|
}
|
|
733
|
-
const analytics = await apiClient.get(url);
|
|
768
|
+
const analytics = unwrapApiResponse(await apiClient.get(url));
|
|
734
769
|
if (options.json) {
|
|
735
770
|
console.log(JSON.stringify(analytics, null, 2));
|
|
736
771
|
return;
|
|
737
772
|
}
|
|
738
|
-
if (analytics.length === 0) {
|
|
773
|
+
if (!Array.isArray(analytics) || analytics.length === 0) {
|
|
739
774
|
console.log(chalk.yellow('No usage data found'));
|
|
740
775
|
return;
|
|
741
776
|
}
|
|
@@ -770,16 +805,16 @@ analyticsCommand
|
|
|
770
805
|
.option('--json', 'Output as JSON')
|
|
771
806
|
.action(async (options) => {
|
|
772
807
|
try {
|
|
773
|
-
let url =
|
|
808
|
+
let url = `${AUTH_API_KEYS_BASE}/analytics/security-events`;
|
|
774
809
|
if (options.severity) {
|
|
775
810
|
url += `?severity=${options.severity}`;
|
|
776
811
|
}
|
|
777
|
-
const events = await apiClient.get(url);
|
|
812
|
+
const events = unwrapApiResponse(await apiClient.get(url));
|
|
778
813
|
if (options.json) {
|
|
779
814
|
console.log(JSON.stringify(events, null, 2));
|
|
780
815
|
return;
|
|
781
816
|
}
|
|
782
|
-
if (events.length === 0) {
|
|
817
|
+
if (!Array.isArray(events) || events.length === 0) {
|
|
783
818
|
console.log(colors.success('✅ No security events found'));
|
|
784
819
|
return;
|
|
785
820
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -712,12 +712,8 @@ async function handleOAuthFlow(config) {
|
|
|
712
712
|
await config.set('refresh_token', tokens.refresh_token);
|
|
713
713
|
await config.set('token_expires_at', Date.now() + (tokens.expires_in * 1000));
|
|
714
714
|
await config.set('authMethod', 'oauth');
|
|
715
|
-
// The OAuth access token from auth-gateway works as the API token for all services
|
|
716
|
-
// Store it as the vendor key equivalent for MCP and API access
|
|
717
715
|
spinner.text = 'Configuring unified access...';
|
|
718
716
|
spinner.start();
|
|
719
|
-
// Use the OAuth access token directly - it's already an auth-gateway token
|
|
720
|
-
await config.setVendorKey(tokens.access_token);
|
|
721
717
|
spinner.succeed('Unified authentication configured');
|
|
722
718
|
console.log();
|
|
723
719
|
console.log(chalk.green('✓ OAuth2 authentication successful'));
|
package/dist/commands/mcp.js
CHANGED
|
@@ -439,6 +439,12 @@ export function mcpCommands(program) {
|
|
|
439
439
|
if (options.args) {
|
|
440
440
|
try {
|
|
441
441
|
args = JSON.parse(options.args);
|
|
442
|
+
// Validate that args is a plain object (not array or primitive)
|
|
443
|
+
if (typeof args !== 'object' || args === null || Array.isArray(args)) {
|
|
444
|
+
spinner.fail('Arguments must be a JSON object, not an array or primitive value');
|
|
445
|
+
console.log(chalk.yellow('Example: --args \'{"key": "value"}\''));
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
442
448
|
}
|
|
443
449
|
catch {
|
|
444
450
|
spinner.fail('Invalid JSON arguments');
|
package/dist/commands/memory.js
CHANGED
|
@@ -103,12 +103,20 @@ const ensureBehaviorActions = (value, fieldName, options = {}) => {
|
|
|
103
103
|
if (!tool) {
|
|
104
104
|
throw new Error(`${fieldName}[${index}].tool is required`);
|
|
105
105
|
}
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
const outcome = typeof entry.outcome === 'string' ? entry.outcome.trim() : '';
|
|
107
|
+
if (!['success', 'partial', 'failed'].includes(outcome)) {
|
|
108
|
+
throw new Error(`${fieldName}[${index}].outcome must be success, partial, or failed`);
|
|
109
|
+
}
|
|
110
|
+
const parsed = {
|
|
111
|
+
tool,
|
|
112
|
+
outcome: outcome,
|
|
113
|
+
};
|
|
114
|
+
const rawParameters = entry.parameters ?? entry.params;
|
|
115
|
+
if (rawParameters !== undefined) {
|
|
116
|
+
if (!isPlainObject(rawParameters)) {
|
|
117
|
+
throw new Error(`${fieldName}[${index}].parameters must be a JSON object`);
|
|
110
118
|
}
|
|
111
|
-
parsed.
|
|
119
|
+
parsed.parameters = rawParameters;
|
|
112
120
|
}
|
|
113
121
|
if (entry.timestamp !== undefined) {
|
|
114
122
|
if (typeof entry.timestamp !== 'string' || !entry.timestamp.trim()) {
|
|
@@ -127,6 +135,33 @@ const ensureBehaviorActions = (value, fieldName, options = {}) => {
|
|
|
127
135
|
return parsed;
|
|
128
136
|
});
|
|
129
137
|
};
|
|
138
|
+
const ensureCompletedSteps = (value, fieldName) => {
|
|
139
|
+
if (value === undefined) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
if (!Array.isArray(value)) {
|
|
143
|
+
throw new Error(`${fieldName} must be a JSON array`);
|
|
144
|
+
}
|
|
145
|
+
return value.map((entry, index) => {
|
|
146
|
+
if (typeof entry === 'string') {
|
|
147
|
+
const normalized = entry.trim();
|
|
148
|
+
if (!normalized) {
|
|
149
|
+
throw new Error(`${fieldName}[${index}] must be a non-empty string`);
|
|
150
|
+
}
|
|
151
|
+
return normalized;
|
|
152
|
+
}
|
|
153
|
+
if (isPlainObject(entry)) {
|
|
154
|
+
const tool = typeof entry.tool === 'string'
|
|
155
|
+
? entry.tool?.trim()
|
|
156
|
+
: '';
|
|
157
|
+
if (!tool) {
|
|
158
|
+
throw new Error(`${fieldName}[${index}].tool must be a non-empty string`);
|
|
159
|
+
}
|
|
160
|
+
return tool;
|
|
161
|
+
}
|
|
162
|
+
throw new Error(`${fieldName}[${index}] must be a string or an object with a tool field`);
|
|
163
|
+
});
|
|
164
|
+
};
|
|
130
165
|
const clampThreshold = (value) => {
|
|
131
166
|
if (!Number.isFinite(value))
|
|
132
167
|
return 0.55;
|
|
@@ -235,8 +270,16 @@ const createIntelligenceTransport = async () => {
|
|
|
235
270
|
}),
|
|
236
271
|
};
|
|
237
272
|
}
|
|
238
|
-
//
|
|
239
|
-
|
|
273
|
+
// Pre-hashed key (64 hex chars from oauth-client normalizeApiKey): inject via header.
|
|
274
|
+
// The edge functions accept pre-hashed keys after the auth.ts shared utility update.
|
|
275
|
+
return {
|
|
276
|
+
mode: 'sdk',
|
|
277
|
+
client: new MemoryIntelligenceClient({
|
|
278
|
+
apiUrl,
|
|
279
|
+
allowMissingAuth: true,
|
|
280
|
+
headers: { 'X-API-Key': apiKey },
|
|
281
|
+
}),
|
|
282
|
+
};
|
|
240
283
|
}
|
|
241
284
|
throw new Error('Authentication required. Run "lanonasis auth login" first.');
|
|
242
285
|
};
|
|
@@ -1207,7 +1250,7 @@ export function memoryCommands(program) {
|
|
|
1207
1250
|
.description('Suggest next actions from learned behavior patterns')
|
|
1208
1251
|
.requiredOption('--task <text>', 'Current task description')
|
|
1209
1252
|
.option('--state <json>', 'Additional current state JSON object')
|
|
1210
|
-
.option('--completed-steps <json>', 'Completed steps JSON array')
|
|
1253
|
+
.option('--completed-steps <json>', 'Completed steps JSON array (strings preferred)')
|
|
1211
1254
|
.option('--max-suggestions <number>', 'Maximum suggestions', '3')
|
|
1212
1255
|
.option('--json', 'Output raw JSON payload')
|
|
1213
1256
|
.action(async (options) => {
|
|
@@ -1218,9 +1261,7 @@ export function memoryCommands(program) {
|
|
|
1218
1261
|
const parsedState = parseJsonOption(options.state, '--state');
|
|
1219
1262
|
const state = ensureJsonObject(parsedState, '--state') || {};
|
|
1220
1263
|
const parsedCompletedSteps = parseJsonOption(options.completedSteps, '--completed-steps');
|
|
1221
|
-
const completedSteps = parsedCompletedSteps
|
|
1222
|
-
? undefined
|
|
1223
|
-
: ensureBehaviorActions(parsedCompletedSteps, '--completed-steps', { allowEmpty: true });
|
|
1264
|
+
const completedSteps = ensureCompletedSteps(parsedCompletedSteps, '--completed-steps');
|
|
1224
1265
|
const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-suggest', {
|
|
1225
1266
|
user_id: userId,
|
|
1226
1267
|
current_state: {
|
package/dist/index.js
CHANGED
|
@@ -280,10 +280,16 @@ authCmd
|
|
|
280
280
|
const profileClient = new APIClient();
|
|
281
281
|
profileClient.noExit = true;
|
|
282
282
|
const profile = await profileClient.getUserProfile();
|
|
283
|
+
await cliConfig.updateCurrentUserProfile(profile);
|
|
284
|
+
const organizationId = profile.organization_id || profile.organizationId;
|
|
283
285
|
console.log(`Email: ${profile.email}`);
|
|
284
286
|
if (profile.name)
|
|
285
287
|
console.log(`Name: ${profile.name}`);
|
|
288
|
+
if (organizationId)
|
|
289
|
+
console.log(`Organization: ${organizationId}`);
|
|
286
290
|
console.log(`Role: ${profile.role}`);
|
|
291
|
+
if (profile.plan)
|
|
292
|
+
console.log(`Plan: ${profile.plan}`);
|
|
287
293
|
if (profile.provider)
|
|
288
294
|
console.log(`Provider: ${profile.provider}`);
|
|
289
295
|
if (profile.last_sign_in_at) {
|
|
@@ -736,13 +742,21 @@ program
|
|
|
736
742
|
const profileClient = new APIClient();
|
|
737
743
|
profileClient.noExit = true;
|
|
738
744
|
const profile = await profileClient.getUserProfile();
|
|
745
|
+
await cliConfig.updateCurrentUserProfile(profile);
|
|
746
|
+
const organizationId = profile.organization_id || profile.organizationId;
|
|
739
747
|
console.log(chalk.blue.bold('👤 Current User'));
|
|
740
748
|
console.log('━'.repeat(40));
|
|
741
749
|
console.log(`Email: ${chalk.white(profile.email)}`);
|
|
742
750
|
if (profile.name) {
|
|
743
751
|
console.log(`Name: ${chalk.white(profile.name)}`);
|
|
744
752
|
}
|
|
753
|
+
if (organizationId) {
|
|
754
|
+
console.log(`Organization: ${chalk.white(organizationId)}`);
|
|
755
|
+
}
|
|
745
756
|
console.log(`Role: ${chalk.white(profile.role)}`);
|
|
757
|
+
if (profile.plan) {
|
|
758
|
+
console.log(`Plan: ${chalk.white(profile.plan)}`);
|
|
759
|
+
}
|
|
746
760
|
if (profile.provider) {
|
|
747
761
|
console.log(`Provider: ${chalk.white(profile.provider)}`);
|
|
748
762
|
}
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -169,7 +169,10 @@ export interface UserProfile {
|
|
|
169
169
|
email: string;
|
|
170
170
|
name: string | null;
|
|
171
171
|
avatar_url: string | null;
|
|
172
|
+
organization_id?: string | null;
|
|
173
|
+
organizationId?: string | null;
|
|
172
174
|
role: string;
|
|
175
|
+
plan?: string | null;
|
|
173
176
|
provider: string | null;
|
|
174
177
|
project_scope: string | null;
|
|
175
178
|
platform: string | null;
|
|
@@ -185,7 +188,10 @@ export declare class APIClient {
|
|
|
185
188
|
private config;
|
|
186
189
|
/** When true, throw on 401/403 instead of printing+exiting (for callers that handle errors) */
|
|
187
190
|
noExit: boolean;
|
|
191
|
+
private isLikelyHashedCredential;
|
|
188
192
|
private normalizeMemoryEntry;
|
|
193
|
+
private tryNormalizeMemoryEntry;
|
|
194
|
+
private normalizeMemoryStats;
|
|
189
195
|
private shouldUseLegacyMemoryRpcFallback;
|
|
190
196
|
private shouldRetryViaApiGateway;
|
|
191
197
|
private shouldRetryViaSupabaseMemoryFunctions;
|
package/dist/utils/api.js
CHANGED
|
@@ -2,11 +2,22 @@ import axios from 'axios';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { CLIConfig } from './config.js';
|
|
5
|
+
const MEMORY_TYPES = [
|
|
6
|
+
'context',
|
|
7
|
+
'project',
|
|
8
|
+
'knowledge',
|
|
9
|
+
'reference',
|
|
10
|
+
'personal',
|
|
11
|
+
'workflow'
|
|
12
|
+
];
|
|
5
13
|
export class APIClient {
|
|
6
14
|
client;
|
|
7
15
|
config;
|
|
8
16
|
/** When true, throw on 401/403 instead of printing+exiting (for callers that handle errors) */
|
|
9
17
|
noExit = false;
|
|
18
|
+
isLikelyHashedCredential(value) {
|
|
19
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value.trim());
|
|
20
|
+
}
|
|
10
21
|
normalizeMemoryEntry(payload) {
|
|
11
22
|
// API responses are inconsistent across gateways:
|
|
12
23
|
// - Some return the memory entry directly
|
|
@@ -27,13 +38,77 @@ export class APIClient {
|
|
|
27
38
|
}
|
|
28
39
|
return payload;
|
|
29
40
|
}
|
|
41
|
+
tryNormalizeMemoryEntry(payload) {
|
|
42
|
+
if (!payload || typeof payload !== 'object') {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
const normalized = this.normalizeMemoryEntry(payload);
|
|
46
|
+
if (!normalized || typeof normalized !== 'object') {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const normalizedRecord = normalized;
|
|
50
|
+
return typeof normalizedRecord.id === 'string' ? normalized : undefined;
|
|
51
|
+
}
|
|
52
|
+
normalizeMemoryStats(payload) {
|
|
53
|
+
if (!payload || typeof payload !== 'object') {
|
|
54
|
+
throw new Error('Memory stats endpoint returned an invalid response.');
|
|
55
|
+
}
|
|
56
|
+
const envelope = payload;
|
|
57
|
+
const rawStats = envelope.data && typeof envelope.data === 'object' && !Array.isArray(envelope.data)
|
|
58
|
+
? envelope.data
|
|
59
|
+
: envelope;
|
|
60
|
+
const rawByType = rawStats.memories_by_type && typeof rawStats.memories_by_type === 'object' && !Array.isArray(rawStats.memories_by_type)
|
|
61
|
+
? rawStats.memories_by_type
|
|
62
|
+
: rawStats.by_type && typeof rawStats.by_type === 'object' && !Array.isArray(rawStats.by_type)
|
|
63
|
+
? rawStats.by_type
|
|
64
|
+
: {};
|
|
65
|
+
const memoriesByType = MEMORY_TYPES.reduce((accumulator, memoryType) => {
|
|
66
|
+
const value = rawByType[memoryType];
|
|
67
|
+
accumulator[memoryType] = typeof value === 'number' ? value : 0;
|
|
68
|
+
return accumulator;
|
|
69
|
+
}, {});
|
|
70
|
+
const recentMemories = Array.isArray(rawStats.recent_memories)
|
|
71
|
+
? rawStats.recent_memories
|
|
72
|
+
.map((entry) => this.tryNormalizeMemoryEntry(entry))
|
|
73
|
+
.filter((entry) => entry !== undefined)
|
|
74
|
+
: [];
|
|
75
|
+
const totalMemories = typeof rawStats.total_memories === 'number'
|
|
76
|
+
? rawStats.total_memories
|
|
77
|
+
: Object.values(memoriesByType).reduce((sum, count) => sum + count, 0);
|
|
78
|
+
return {
|
|
79
|
+
total_memories: totalMemories,
|
|
80
|
+
memories_by_type: memoriesByType,
|
|
81
|
+
total_size_bytes: typeof rawStats.total_size_bytes === 'number' ? rawStats.total_size_bytes : 0,
|
|
82
|
+
avg_access_count: typeof rawStats.avg_access_count === 'number' ? rawStats.avg_access_count : 0,
|
|
83
|
+
most_accessed_memory: this.tryNormalizeMemoryEntry(rawStats.most_accessed_memory),
|
|
84
|
+
recent_memories: recentMemories
|
|
85
|
+
};
|
|
86
|
+
}
|
|
30
87
|
shouldUseLegacyMemoryRpcFallback(error) {
|
|
31
88
|
const status = error?.response?.status;
|
|
32
89
|
const errorData = error?.response?.data;
|
|
33
90
|
const message = `${errorData?.error || ''} ${errorData?.message || ''}`.toLowerCase();
|
|
91
|
+
const rawRequestUrl = String(error?.config?.url || '');
|
|
92
|
+
const requestPath = (() => {
|
|
93
|
+
try {
|
|
94
|
+
if (/^https?:\/\//i.test(rawRequestUrl)) {
|
|
95
|
+
return new URL(rawRequestUrl).pathname;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Fall back to the raw URL if URL parsing fails.
|
|
100
|
+
}
|
|
101
|
+
return rawRequestUrl;
|
|
102
|
+
})();
|
|
103
|
+
const normalizedRequestUrl = requestPath.startsWith('/memory')
|
|
104
|
+
? this.normalizeMcpPathToApi(requestPath)
|
|
105
|
+
: requestPath;
|
|
34
106
|
if (status === 405) {
|
|
35
107
|
return true;
|
|
36
108
|
}
|
|
109
|
+
if (status === 404 && /^\/api\/v1\/memories\/[^/?#]+$/.test(normalizedRequestUrl)) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
37
112
|
if (status === 400 && message.includes('memory id is required')) {
|
|
38
113
|
return true;
|
|
39
114
|
}
|
|
@@ -238,6 +313,9 @@ export class APIClient {
|
|
|
238
313
|
const forceDirectApi = forceApiFromEnv || forceApiFromConfig || forceDirectApiRetry;
|
|
239
314
|
const prefersTokenAuth = Boolean(token) && (authMethod === 'jwt' || authMethod === 'oauth' || authMethod === 'oauth2');
|
|
240
315
|
const useVendorKeyAuth = Boolean(vendorKey) && !prefersTokenAuth;
|
|
316
|
+
if (authMethod === 'vendor_key' && this.isLikelyHashedCredential(vendorKey)) {
|
|
317
|
+
throw new Error('Stored vendor key is in legacy hashed format. Run "lanonasis auth login --vendor-key <your-key>" to refresh secure storage.');
|
|
318
|
+
}
|
|
241
319
|
// Determine the correct API base URL:
|
|
242
320
|
// - Auth endpoints -> auth.lanonasis.com
|
|
243
321
|
// - Memory/MCP operations (JWT or vendor key) -> mcp.lanonasis.com (the memory service)
|
|
@@ -567,7 +645,7 @@ export class APIClient {
|
|
|
567
645
|
return this.normalizeMemoryEntry(response.data);
|
|
568
646
|
}
|
|
569
647
|
catch (error) {
|
|
570
|
-
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
648
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error) || error?.response?.status === 404) {
|
|
571
649
|
const fallback = await this.client.post('/api/v1/memory/update', {
|
|
572
650
|
id,
|
|
573
651
|
...data
|
|
@@ -585,7 +663,7 @@ export class APIClient {
|
|
|
585
663
|
await this.client.delete(`/api/v1/memories/${id}`);
|
|
586
664
|
}
|
|
587
665
|
catch (error) {
|
|
588
|
-
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
666
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error) || error?.response?.status === 404) {
|
|
589
667
|
await this.client.post('/api/v1/memory/delete', { id });
|
|
590
668
|
return;
|
|
591
669
|
}
|
|
@@ -601,7 +679,7 @@ export class APIClient {
|
|
|
601
679
|
}
|
|
602
680
|
async getMemoryStats() {
|
|
603
681
|
const response = await this.client.get('/api/v1/memories/stats');
|
|
604
|
-
return response.data;
|
|
682
|
+
return this.normalizeMemoryStats(response.data);
|
|
605
683
|
}
|
|
606
684
|
async bulkDeleteMemories(memoryIds) {
|
|
607
685
|
const response = await this.client.post('/api/v1/memories/bulk/delete', {
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -48,9 +48,15 @@ export declare class CLIConfig {
|
|
|
48
48
|
private static readonly CONFIG_VERSION;
|
|
49
49
|
private authCheckCache;
|
|
50
50
|
private readonly AUTH_CACHE_TTL;
|
|
51
|
-
private apiKeyStorage
|
|
51
|
+
private apiKeyStorage?;
|
|
52
52
|
private vendorKeyCache?;
|
|
53
|
+
private isLegacyHashedCredential;
|
|
54
|
+
private getLegacyHashedVendorKeyReason;
|
|
55
|
+
private normalizeOptionalString;
|
|
56
|
+
private extractOrganizationId;
|
|
57
|
+
private buildUserProfile;
|
|
53
58
|
constructor();
|
|
59
|
+
private getApiKeyStorage;
|
|
54
60
|
/**
|
|
55
61
|
* Overrides the configuration storage directory. Primarily used for tests.
|
|
56
62
|
*/
|
|
@@ -117,6 +123,7 @@ export declare class CLIConfig {
|
|
|
117
123
|
getToken(): string | undefined;
|
|
118
124
|
getAuthMethod(): string | undefined;
|
|
119
125
|
getCurrentUser(): Promise<UserProfile | undefined>;
|
|
126
|
+
updateCurrentUserProfile(profile: Record<string, unknown>): Promise<void>;
|
|
120
127
|
isAuthenticated(): Promise<boolean>;
|
|
121
128
|
logout(): Promise<void>;
|
|
122
129
|
clear(): Promise<void>;
|
package/dist/utils/config.js
CHANGED
|
@@ -15,12 +15,51 @@ export class CLIConfig {
|
|
|
15
15
|
AUTH_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
16
16
|
apiKeyStorage;
|
|
17
17
|
vendorKeyCache;
|
|
18
|
+
isLegacyHashedCredential(value) {
|
|
19
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value.trim());
|
|
20
|
+
}
|
|
21
|
+
getLegacyHashedVendorKeyReason() {
|
|
22
|
+
return 'Stored vendor key is in legacy hashed format. Run "lanonasis auth login --vendor-key <your-key>" to refresh secure storage.';
|
|
23
|
+
}
|
|
24
|
+
normalizeOptionalString(value) {
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
28
|
+
}
|
|
29
|
+
if (value === null || value === undefined) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return String(value);
|
|
33
|
+
}
|
|
34
|
+
extractOrganizationId(source) {
|
|
35
|
+
return this.normalizeOptionalString(source.organization_id)
|
|
36
|
+
?? this.normalizeOptionalString(source.organizationId);
|
|
37
|
+
}
|
|
38
|
+
buildUserProfile(source, existing) {
|
|
39
|
+
const email = this.normalizeOptionalString(source.email) ?? existing?.email ?? '';
|
|
40
|
+
const organization_id = this.extractOrganizationId(source) ?? existing?.organization_id ?? '';
|
|
41
|
+
const role = this.normalizeOptionalString(source.role) ?? existing?.role ?? '';
|
|
42
|
+
const plan = this.normalizeOptionalString(source.plan) ?? existing?.plan ?? '';
|
|
43
|
+
if (!email && !organization_id && !role && !plan) {
|
|
44
|
+
return existing;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
email,
|
|
48
|
+
organization_id,
|
|
49
|
+
role,
|
|
50
|
+
plan
|
|
51
|
+
};
|
|
52
|
+
}
|
|
18
53
|
constructor() {
|
|
19
54
|
this.configDir = path.join(os.homedir(), '.maas');
|
|
20
55
|
this.configPath = path.join(this.configDir, 'config.json');
|
|
21
56
|
this.lockFile = path.join(this.configDir, 'config.lock');
|
|
22
|
-
|
|
23
|
-
|
|
57
|
+
}
|
|
58
|
+
getApiKeyStorage() {
|
|
59
|
+
if (!this.apiKeyStorage) {
|
|
60
|
+
this.apiKeyStorage = new ApiKeyStorage();
|
|
61
|
+
}
|
|
62
|
+
return this.apiKeyStorage;
|
|
24
63
|
}
|
|
25
64
|
/**
|
|
26
65
|
* Overrides the configuration storage directory. Primarily used for tests.
|
|
@@ -627,6 +666,13 @@ export class CLIConfig {
|
|
|
627
666
|
await this.discoverServices();
|
|
628
667
|
const token = this.getToken();
|
|
629
668
|
const vendorKey = await this.getVendorKeyAsync();
|
|
669
|
+
if (this.config.authMethod === 'vendor_key' && this.isLegacyHashedCredential(vendorKey)) {
|
|
670
|
+
return {
|
|
671
|
+
valid: false,
|
|
672
|
+
method: 'vendor_key',
|
|
673
|
+
reason: this.getLegacyHashedVendorKeyReason()
|
|
674
|
+
};
|
|
675
|
+
}
|
|
630
676
|
if (this.config.authMethod === 'vendor_key' && vendorKey) {
|
|
631
677
|
return this.verifyVendorKeyWithAuthGateway(vendorKey);
|
|
632
678
|
}
|
|
@@ -710,8 +756,9 @@ export class CLIConfig {
|
|
|
710
756
|
}
|
|
711
757
|
// Initialize and store using ApiKeyStorage from @lanonasis/oauth-client
|
|
712
758
|
// This handles encryption automatically (AES-256-GCM with machine-derived key)
|
|
713
|
-
|
|
714
|
-
await
|
|
759
|
+
const apiKeyStorage = this.getApiKeyStorage();
|
|
760
|
+
await apiKeyStorage.initialize();
|
|
761
|
+
await apiKeyStorage.store({
|
|
715
762
|
apiKey: trimmedKey,
|
|
716
763
|
organizationId: this.config.user?.organization_id,
|
|
717
764
|
userId: this.config.user?.email,
|
|
@@ -835,8 +882,9 @@ export class CLIConfig {
|
|
|
835
882
|
*/
|
|
836
883
|
async getVendorKeyAsync() {
|
|
837
884
|
try {
|
|
838
|
-
|
|
839
|
-
|
|
885
|
+
const apiKeyStorage = this.getApiKeyStorage();
|
|
886
|
+
await apiKeyStorage.initialize();
|
|
887
|
+
const stored = await apiKeyStorage.retrieve();
|
|
840
888
|
if (stored) {
|
|
841
889
|
this.vendorKeyCache = stored.apiKey;
|
|
842
890
|
return this.vendorKeyCache;
|
|
@@ -878,12 +926,7 @@ export class CLIConfig {
|
|
|
878
926
|
this.config.tokenExpiry = decoded.exp;
|
|
879
927
|
}
|
|
880
928
|
// Store user info
|
|
881
|
-
this.config.user =
|
|
882
|
-
email: String(decoded.email || ''),
|
|
883
|
-
organization_id: String(decoded.organizationId || ''),
|
|
884
|
-
role: String(decoded.role || ''),
|
|
885
|
-
plan: String(decoded.plan || '')
|
|
886
|
-
};
|
|
929
|
+
this.config.user = this.buildUserProfile(decoded, this.config.user);
|
|
887
930
|
}
|
|
888
931
|
catch {
|
|
889
932
|
// Invalid token, don't store user info or expiry
|
|
@@ -902,6 +945,23 @@ export class CLIConfig {
|
|
|
902
945
|
async getCurrentUser() {
|
|
903
946
|
return this.config.user;
|
|
904
947
|
}
|
|
948
|
+
async updateCurrentUserProfile(profile) {
|
|
949
|
+
const nextUser = this.buildUserProfile(profile, this.config.user);
|
|
950
|
+
if (!nextUser) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const currentUser = this.config.user;
|
|
954
|
+
const changed = !currentUser
|
|
955
|
+
|| currentUser.email !== nextUser.email
|
|
956
|
+
|| currentUser.organization_id !== nextUser.organization_id
|
|
957
|
+
|| currentUser.role !== nextUser.role
|
|
958
|
+
|| currentUser.plan !== nextUser.plan;
|
|
959
|
+
if (!changed) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
this.config.user = nextUser;
|
|
963
|
+
await this.save();
|
|
964
|
+
}
|
|
905
965
|
async isAuthenticated() {
|
|
906
966
|
// Attempt refresh for OAuth sessions before checks (prevents intermittent auth dropouts).
|
|
907
967
|
// This is safe to call even when not using OAuth; it will no-op.
|
|
@@ -912,6 +972,13 @@ export class CLIConfig {
|
|
|
912
972
|
const vendorKey = await this.getVendorKeyAsync();
|
|
913
973
|
if (!vendorKey)
|
|
914
974
|
return false;
|
|
975
|
+
if (this.isLegacyHashedCredential(vendorKey)) {
|
|
976
|
+
if (process.env.CLI_VERBOSE === 'true') {
|
|
977
|
+
console.warn(`⚠️ ${this.getLegacyHashedVendorKeyReason()}`);
|
|
978
|
+
}
|
|
979
|
+
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
915
982
|
// Check in-memory cache first (5-minute TTL)
|
|
916
983
|
if (this.authCheckCache && (Date.now() - this.authCheckCache.timestamp) < this.AUTH_CACHE_TTL) {
|
|
917
984
|
return this.authCheckCache.isValid;
|
|
@@ -1146,10 +1213,13 @@ export class CLIConfig {
|
|
|
1146
1213
|
this.vendorKeyCache = undefined;
|
|
1147
1214
|
this.config.vendorKey = undefined;
|
|
1148
1215
|
this.config.authMethod = undefined;
|
|
1216
|
+
this.config.refresh_token = undefined;
|
|
1217
|
+
this.config.token_expires_at = undefined;
|
|
1149
1218
|
try {
|
|
1150
|
-
|
|
1219
|
+
const apiKeyStorage = this.getApiKeyStorage();
|
|
1220
|
+
await apiKeyStorage.initialize();
|
|
1151
1221
|
// ApiKeyStorage may implement clear() to remove encrypted secrets
|
|
1152
|
-
const storage =
|
|
1222
|
+
const storage = apiKeyStorage;
|
|
1153
1223
|
if (typeof storage.clear === 'function') {
|
|
1154
1224
|
await storage.clear();
|
|
1155
1225
|
}
|
|
@@ -1270,14 +1340,6 @@ export class CLIConfig {
|
|
|
1270
1340
|
if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) {
|
|
1271
1341
|
this.config.token_expires_at = Date.now() + (expiresIn * 1000);
|
|
1272
1342
|
}
|
|
1273
|
-
// Keep the encrypted "vendor key" in sync for MCP/WebSocket clients that use X-API-Key.
|
|
1274
|
-
// This does not change authMethod away from oauth (setVendorKey guards against that).
|
|
1275
|
-
try {
|
|
1276
|
-
await this.setVendorKey(accessToken);
|
|
1277
|
-
}
|
|
1278
|
-
catch {
|
|
1279
|
-
// Non-fatal: bearer token refresh still helps API calls.
|
|
1280
|
-
}
|
|
1281
1343
|
await this.save().catch(() => { });
|
|
1282
1344
|
return;
|
|
1283
1345
|
}
|
|
@@ -1327,9 +1389,23 @@ export class CLIConfig {
|
|
|
1327
1389
|
this.config.user = undefined;
|
|
1328
1390
|
this.config.authMethod = undefined;
|
|
1329
1391
|
this.config.tokenExpiry = undefined;
|
|
1392
|
+
this.config.refresh_token = undefined;
|
|
1393
|
+
this.config.token_expires_at = undefined;
|
|
1330
1394
|
this.config.lastValidated = undefined;
|
|
1331
1395
|
this.config.authFailureCount = 0;
|
|
1332
1396
|
this.config.lastAuthFailure = undefined;
|
|
1397
|
+
this.vendorKeyCache = undefined;
|
|
1398
|
+
try {
|
|
1399
|
+
const apiKeyStorage = this.getApiKeyStorage();
|
|
1400
|
+
await apiKeyStorage.initialize();
|
|
1401
|
+
const storage = apiKeyStorage;
|
|
1402
|
+
if (typeof storage.clear === 'function') {
|
|
1403
|
+
await storage.clear();
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
catch {
|
|
1407
|
+
// Ignore secure storage cleanup failures while invalidating credentials
|
|
1408
|
+
}
|
|
1333
1409
|
await this.save();
|
|
1334
1410
|
}
|
|
1335
1411
|
async incrementFailureCount() {
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -360,6 +360,10 @@ export class MCPClient {
|
|
|
360
360
|
const authMethod = String(this.config.get('authMethod') || '').toLowerCase();
|
|
361
361
|
const token = this.config.get('token');
|
|
362
362
|
const vendorKey = await this.config.getVendorKeyAsync();
|
|
363
|
+
const isLikelyHashedCredential = typeof vendorKey === 'string' && /^[a-f0-9]{64}$/i.test(vendorKey.trim());
|
|
364
|
+
if (authMethod === 'vendor_key' && isLikelyHashedCredential) {
|
|
365
|
+
throw new Error('AUTHENTICATION_INVALID: Stored vendor key is in legacy hashed format. Run "lanonasis auth login --vendor-key <your-key>" to refresh secure storage.');
|
|
366
|
+
}
|
|
363
367
|
if (authMethod === 'vendor_key' && typeof vendorKey === 'string' && vendorKey.trim().length > 0) {
|
|
364
368
|
return { value: vendorKey.trim(), source: 'vendor_key' };
|
|
365
369
|
}
|
|
@@ -51,9 +51,21 @@ export class ConnectionManagerImpl {
|
|
|
51
51
|
try {
|
|
52
52
|
// Load persisted configuration first
|
|
53
53
|
await this.loadConfig();
|
|
54
|
-
//
|
|
54
|
+
// Prefer a configured path only if the file exists (ignore stale machine-specific commits)
|
|
55
55
|
const configuredPath = this.config.localServerPath?.trim();
|
|
56
|
-
|
|
56
|
+
let serverPath = null;
|
|
57
|
+
if (configuredPath) {
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(configuredPath);
|
|
60
|
+
serverPath = configuredPath;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
serverPath = await this.detectServerPath();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
serverPath = await this.detectServerPath();
|
|
68
|
+
}
|
|
57
69
|
if (!serverPath) {
|
|
58
70
|
return {
|
|
59
71
|
success: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/cli",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.12",
|
|
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.
|
|
56
|
-
"@lanonasis/oauth-client": "2.0.
|
|
55
|
+
"@lanonasis/mem-intel-sdk": "2.0.6",
|
|
56
|
+
"@lanonasis/oauth-client": "2.0.4",
|
|
57
57
|
"@lanonasis/security-sdk": "1.0.5",
|
|
58
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
59
|
-
"axios": "^1.13.
|
|
58
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
59
|
+
"axios": "^1.13.6",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-progress": "^3.12.0",
|
|
62
62
|
"cli-table3": "^0.6.5",
|
|
@@ -64,26 +64,40 @@
|
|
|
64
64
|
"date-fns": "^4.1.0",
|
|
65
65
|
"dotenv": "^17.3.1",
|
|
66
66
|
"eventsource": "^4.1.0",
|
|
67
|
-
"inquirer": "^13.2
|
|
67
|
+
"inquirer": "^13.3.2",
|
|
68
68
|
"jwt-decode": "^4.0.0",
|
|
69
69
|
"open": "^11.0.0",
|
|
70
70
|
"ora": "^9.3.0",
|
|
71
71
|
"table": "^6.9.0",
|
|
72
72
|
"word-wrap": "^1.2.5",
|
|
73
|
-
"ws": "^8.
|
|
73
|
+
"ws": "^8.20.0",
|
|
74
74
|
"zod": "^4.3.6"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@jest/globals": "^30.
|
|
77
|
+
"@jest/globals": "^30.3.0",
|
|
78
78
|
"@types/cli-progress": "^3.11.6",
|
|
79
79
|
"@types/inquirer": "^9.0.9",
|
|
80
|
-
"@types/node": "^25.
|
|
80
|
+
"@types/node": "^25.5.0",
|
|
81
81
|
"@types/ws": "^8.18.1",
|
|
82
|
-
"fast-check": "^4.
|
|
83
|
-
"jest": "^30.
|
|
82
|
+
"fast-check": "^4.6.0",
|
|
83
|
+
"jest": "^30.3.0",
|
|
84
84
|
"rimraf": "^6.1.3",
|
|
85
85
|
"ts-jest": "^29.4.6",
|
|
86
|
-
"typescript": "^
|
|
86
|
+
"typescript": "^6.0.2"
|
|
87
|
+
},
|
|
88
|
+
"overrides": {
|
|
89
|
+
"@jest/transform": "^30.3.0",
|
|
90
|
+
"@hono/node-server": "^1.19.10",
|
|
91
|
+
"ajv": "^8.18.0",
|
|
92
|
+
"babel-jest": "^30.3.0",
|
|
93
|
+
"brace-expansion": "^1.1.13",
|
|
94
|
+
"express-rate-limit": "^8.2.2",
|
|
95
|
+
"handlebars": "^4.7.9",
|
|
96
|
+
"hono": "^4.12.8",
|
|
97
|
+
"jest-util": "^30.3.0",
|
|
98
|
+
"picomatch": "^2.3.2",
|
|
99
|
+
"qs": "^6.15.0",
|
|
100
|
+
"test-exclude": "^8.0.0"
|
|
87
101
|
},
|
|
88
102
|
"scripts": {
|
|
89
103
|
"build": "rimraf dist && tsc -p tsconfig.json && chmod +x dist/index.js dist/mcp-server-entry.js 2>/dev/null || true",
|