@lanonasis/cli 3.9.11 → 3.9.13
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 +12 -0
- package/README.md +2 -2
- package/dist/commands/api-keys.js +196 -161
- package/dist/commands/auth.js +8 -0
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/memory.js +123 -10
- package/dist/index.js +14 -0
- package/dist/utils/api.d.ts +8 -2
- package/dist/utils/config.d.ts +7 -0
- package/dist/utils/config.js +84 -52
- package/dist/utils/mcp-client.js +4 -13
- package/dist/ux/implementations/ConnectionManagerImpl.js +14 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog - @lanonasis/cli
|
|
2
2
|
|
|
3
|
+
## [3.9.13] - 2026-04-02
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **JWT/password sessions now refresh through the real auth-gateway contract**: Expiring CLI sessions no longer call the nonexistent `/v1/auth/refresh` route. Refreshable JWT and OAuth sessions now use `POST /oauth/token` with `grant_type=refresh_token`, matching the live auth-gateway implementation.
|
|
8
|
+
- **Password login now persists refresh metadata**: Username/password authentication now saves `refresh_token` and `token_expires_at` from the login response, so successful JWT sessions can actually refresh instead of silently falling back to re-login loops.
|
|
9
|
+
- **MCP client refresh flow no longer drifts from the main CLI auth flow**: Removed the stale `/auth/refresh` path and incorrect `refreshToken` config key lookup in favor of the shared `CLIConfig.refreshTokenIfNeeded()` implementation.
|
|
10
|
+
|
|
11
|
+
### 🔄 Dependency Updates
|
|
12
|
+
|
|
13
|
+
- **Bundled `@lanonasis/mem-intel-sdk` updated to `2.1.0`**: Aligns the CLI with the newly published scoped intelligence query contract and predictive route support.
|
|
14
|
+
|
|
3
15
|
## [3.9.11] - 2026-03-27
|
|
4
16
|
|
|
5
17
|
### 🐛 Bug Fixes
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# @lanonasis/cli v3.9.
|
|
1
|
+
# @lanonasis/cli v3.9.13 - Auth Refresh Reliability
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@lanonasis/cli)
|
|
4
4
|
[](https://www.npmjs.com/package/@lanonasis/cli)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://api.lanonasis.com/.well-known/onasis.json)
|
|
7
7
|
|
|
8
|
-
🎉 **NEW IN v3.9.
|
|
8
|
+
🎉 **NEW IN v3.9.13**: JWT/password CLI sessions now refresh through the real auth-gateway OAuth token contract, password login persists refresh metadata correctly, the MCP client no longer uses stale refresh routes, and the bundled `@lanonasis/mem-intel-sdk` is updated to `2.1.0` for scoped intelligence query support.
|
|
9
9
|
|
|
10
10
|
## 🚀 Quick Start
|
|
11
11
|
|
|
@@ -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
|
@@ -815,8 +815,16 @@ async function handleCredentialsFlow(options, config) {
|
|
|
815
815
|
if (process.env.CLI_VERBOSE === 'true') {
|
|
816
816
|
console.log(chalk.dim(` JWT received (length: ${authToken.length})`));
|
|
817
817
|
}
|
|
818
|
+
const refreshToken = response.refresh_token;
|
|
819
|
+
const expiresIn = response.expires_in;
|
|
818
820
|
// Store JWT token for API authentication
|
|
819
821
|
await config.setToken(authToken);
|
|
822
|
+
if (typeof refreshToken === 'string' && refreshToken.length > 0) {
|
|
823
|
+
config.set('refresh_token', refreshToken);
|
|
824
|
+
}
|
|
825
|
+
if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) {
|
|
826
|
+
config.set('token_expires_at', Date.now() + (expiresIn * 1000));
|
|
827
|
+
}
|
|
820
828
|
await config.setAndSave('authMethod', 'jwt');
|
|
821
829
|
spinner.succeed('Login successful');
|
|
822
830
|
console.log();
|
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
|
@@ -22,6 +22,12 @@ const MEMORY_TYPE_CHOICES = [
|
|
|
22
22
|
'personal',
|
|
23
23
|
'workflow',
|
|
24
24
|
];
|
|
25
|
+
const QUERY_SCOPE_CHOICES = [
|
|
26
|
+
'personal',
|
|
27
|
+
'team',
|
|
28
|
+
'organization',
|
|
29
|
+
'hybrid',
|
|
30
|
+
];
|
|
25
31
|
const coerceMemoryType = (value) => {
|
|
26
32
|
if (typeof value !== 'string')
|
|
27
33
|
return undefined;
|
|
@@ -34,6 +40,48 @@ const coerceMemoryType = (value) => {
|
|
|
34
40
|
}
|
|
35
41
|
return undefined;
|
|
36
42
|
};
|
|
43
|
+
const coerceQueryScope = (value) => {
|
|
44
|
+
if (typeof value !== 'string')
|
|
45
|
+
return undefined;
|
|
46
|
+
const normalized = value.trim().toLowerCase();
|
|
47
|
+
if (QUERY_SCOPE_CHOICES.includes(normalized)) {
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
const parseMemoryTypesOption = (value) => {
|
|
53
|
+
if (!value?.trim())
|
|
54
|
+
return undefined;
|
|
55
|
+
const parsed = value
|
|
56
|
+
.split(',')
|
|
57
|
+
.map((entry) => coerceMemoryType(entry))
|
|
58
|
+
.filter((entry) => Boolean(entry));
|
|
59
|
+
if (parsed.length === 0) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return [...new Set(parsed)];
|
|
63
|
+
};
|
|
64
|
+
const buildIntelligenceContextPayload = (options) => {
|
|
65
|
+
const payload = {};
|
|
66
|
+
if (options.organizationId?.trim()) {
|
|
67
|
+
payload.organization_id = options.organizationId.trim();
|
|
68
|
+
}
|
|
69
|
+
if (options.topicId?.trim()) {
|
|
70
|
+
payload.topic_id = options.topicId.trim();
|
|
71
|
+
}
|
|
72
|
+
if (options.scope) {
|
|
73
|
+
const scope = coerceQueryScope(options.scope);
|
|
74
|
+
if (!scope) {
|
|
75
|
+
throw new Error(`Invalid scope \"${options.scope}\". Expected one of: ${QUERY_SCOPE_CHOICES.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
payload.query_scope = scope;
|
|
78
|
+
}
|
|
79
|
+
const memoryTypes = parseMemoryTypesOption(options.memoryTypes);
|
|
80
|
+
if (memoryTypes?.length) {
|
|
81
|
+
payload.memory_types = memoryTypes;
|
|
82
|
+
}
|
|
83
|
+
return payload;
|
|
84
|
+
};
|
|
37
85
|
const resolveInputMode = async () => {
|
|
38
86
|
const config = new CLIConfig();
|
|
39
87
|
await config.init();
|
|
@@ -103,12 +151,20 @@ const ensureBehaviorActions = (value, fieldName, options = {}) => {
|
|
|
103
151
|
if (!tool) {
|
|
104
152
|
throw new Error(`${fieldName}[${index}].tool is required`);
|
|
105
153
|
}
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
154
|
+
const outcome = typeof entry.outcome === 'string' ? entry.outcome.trim() : '';
|
|
155
|
+
if (!['success', 'partial', 'failed'].includes(outcome)) {
|
|
156
|
+
throw new Error(`${fieldName}[${index}].outcome must be success, partial, or failed`);
|
|
157
|
+
}
|
|
158
|
+
const parsed = {
|
|
159
|
+
tool,
|
|
160
|
+
outcome: outcome,
|
|
161
|
+
};
|
|
162
|
+
const rawParameters = entry.parameters ?? entry.params;
|
|
163
|
+
if (rawParameters !== undefined) {
|
|
164
|
+
if (!isPlainObject(rawParameters)) {
|
|
165
|
+
throw new Error(`${fieldName}[${index}].parameters must be a JSON object`);
|
|
110
166
|
}
|
|
111
|
-
parsed.
|
|
167
|
+
parsed.parameters = rawParameters;
|
|
112
168
|
}
|
|
113
169
|
if (entry.timestamp !== undefined) {
|
|
114
170
|
if (typeof entry.timestamp !== 'string' || !entry.timestamp.trim()) {
|
|
@@ -127,6 +183,33 @@ const ensureBehaviorActions = (value, fieldName, options = {}) => {
|
|
|
127
183
|
return parsed;
|
|
128
184
|
});
|
|
129
185
|
};
|
|
186
|
+
const ensureCompletedSteps = (value, fieldName) => {
|
|
187
|
+
if (value === undefined) {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
if (!Array.isArray(value)) {
|
|
191
|
+
throw new Error(`${fieldName} must be a JSON array`);
|
|
192
|
+
}
|
|
193
|
+
return value.map((entry, index) => {
|
|
194
|
+
if (typeof entry === 'string') {
|
|
195
|
+
const normalized = entry.trim();
|
|
196
|
+
if (!normalized) {
|
|
197
|
+
throw new Error(`${fieldName}[${index}] must be a non-empty string`);
|
|
198
|
+
}
|
|
199
|
+
return normalized;
|
|
200
|
+
}
|
|
201
|
+
if (isPlainObject(entry)) {
|
|
202
|
+
const tool = typeof entry.tool === 'string'
|
|
203
|
+
? entry.tool?.trim()
|
|
204
|
+
: '';
|
|
205
|
+
if (!tool) {
|
|
206
|
+
throw new Error(`${fieldName}[${index}].tool must be a non-empty string`);
|
|
207
|
+
}
|
|
208
|
+
return tool;
|
|
209
|
+
}
|
|
210
|
+
throw new Error(`${fieldName}[${index}] must be a string or an object with a tool field`);
|
|
211
|
+
});
|
|
212
|
+
};
|
|
130
213
|
const clampThreshold = (value) => {
|
|
131
214
|
if (!Number.isFinite(value))
|
|
132
215
|
return 0.55;
|
|
@@ -980,13 +1063,21 @@ export function memoryCommands(program) {
|
|
|
980
1063
|
intelligence
|
|
981
1064
|
.command('health-check')
|
|
982
1065
|
.description('Run memory intelligence health check')
|
|
1066
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1067
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1068
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1069
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
983
1070
|
.option('--json', 'Output raw JSON payload')
|
|
984
1071
|
.action(async (options) => {
|
|
985
1072
|
try {
|
|
986
1073
|
const spinner = ora('Running intelligence health check...').start();
|
|
987
1074
|
const transport = await createIntelligenceTransport();
|
|
988
1075
|
const userId = await resolveCurrentUserId();
|
|
989
|
-
const result = await postIntelligenceEndpoint(transport, '/intelligence/health-check', {
|
|
1076
|
+
const result = await postIntelligenceEndpoint(transport, '/intelligence/health-check', {
|
|
1077
|
+
user_id: userId,
|
|
1078
|
+
response_format: 'json',
|
|
1079
|
+
...buildIntelligenceContextPayload(options),
|
|
1080
|
+
});
|
|
990
1081
|
spinner.stop();
|
|
991
1082
|
printIntelligenceResult('🩺 Intelligence Health Check', result, options);
|
|
992
1083
|
}
|
|
@@ -1001,6 +1092,9 @@ export function memoryCommands(program) {
|
|
|
1001
1092
|
.description('Suggest tags for a memory')
|
|
1002
1093
|
.argument('<memory-id>', 'Memory ID')
|
|
1003
1094
|
.option('--max <number>', 'Maximum suggestions', '8')
|
|
1095
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1096
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1097
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1004
1098
|
.option('--json', 'Output raw JSON payload')
|
|
1005
1099
|
.action(async (memoryId, options) => {
|
|
1006
1100
|
try {
|
|
@@ -1014,6 +1108,7 @@ export function memoryCommands(program) {
|
|
|
1014
1108
|
max_suggestions: maxSuggestions,
|
|
1015
1109
|
include_existing_tags: true,
|
|
1016
1110
|
response_format: 'json',
|
|
1111
|
+
...buildIntelligenceContextPayload(options),
|
|
1017
1112
|
});
|
|
1018
1113
|
spinner.stop();
|
|
1019
1114
|
printIntelligenceResult('🏷️ Tag Suggestions', result, options);
|
|
@@ -1030,6 +1125,10 @@ export function memoryCommands(program) {
|
|
|
1030
1125
|
.argument('<memory-id>', 'Source memory ID')
|
|
1031
1126
|
.option('--limit <number>', 'Maximum related memories', '5')
|
|
1032
1127
|
.option('--threshold <number>', 'Similarity threshold (0-1)', '0.7')
|
|
1128
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1129
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1130
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1131
|
+
.option('--memory-types <types>', `Optional comma-separated candidate memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1033
1132
|
.option('--json', 'Output raw JSON payload')
|
|
1034
1133
|
.action(async (memoryId, options) => {
|
|
1035
1134
|
try {
|
|
@@ -1042,6 +1141,7 @@ export function memoryCommands(program) {
|
|
|
1042
1141
|
limit: Math.max(1, Math.min(20, parseInt(options.limit || '5', 10))),
|
|
1043
1142
|
similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.7'))),
|
|
1044
1143
|
response_format: 'json',
|
|
1144
|
+
...buildIntelligenceContextPayload(options),
|
|
1045
1145
|
});
|
|
1046
1146
|
spinner.stop();
|
|
1047
1147
|
printIntelligenceResult('🔗 Related Memories', result, options);
|
|
@@ -1057,6 +1157,10 @@ export function memoryCommands(program) {
|
|
|
1057
1157
|
.description('Detect duplicate memory entries')
|
|
1058
1158
|
.option('--threshold <number>', 'Similarity threshold (0-1)', '0.88')
|
|
1059
1159
|
.option('--max-pairs <number>', 'Maximum duplicate pairs to inspect', '100')
|
|
1160
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1161
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1162
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1163
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1060
1164
|
.option('--json', 'Output raw JSON payload')
|
|
1061
1165
|
.action(async (options) => {
|
|
1062
1166
|
try {
|
|
@@ -1068,6 +1172,7 @@ export function memoryCommands(program) {
|
|
|
1068
1172
|
similarity_threshold: Math.max(0, Math.min(1, parseFloat(options.threshold || '0.88'))),
|
|
1069
1173
|
max_pairs: Math.max(10, Math.min(500, parseInt(options.maxPairs || '100', 10))),
|
|
1070
1174
|
response_format: 'json',
|
|
1175
|
+
...buildIntelligenceContextPayload(options),
|
|
1071
1176
|
});
|
|
1072
1177
|
spinner.stop();
|
|
1073
1178
|
printIntelligenceResult('🧬 Duplicate Detection', result, options);
|
|
@@ -1084,6 +1189,10 @@ export function memoryCommands(program) {
|
|
|
1084
1189
|
.option('--topic <topic>', 'Optional topic filter')
|
|
1085
1190
|
.option('--type <type>', `Optional memory type filter (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1086
1191
|
.option('--max-memories <number>', 'Maximum memories to analyze', '50')
|
|
1192
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1193
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1194
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1195
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1087
1196
|
.option('--json', 'Output raw JSON payload')
|
|
1088
1197
|
.action(async (options) => {
|
|
1089
1198
|
try {
|
|
@@ -1100,6 +1209,7 @@ export function memoryCommands(program) {
|
|
|
1100
1209
|
memory_type: memoryType,
|
|
1101
1210
|
max_memories: Math.max(5, Math.min(200, parseInt(options.maxMemories || '50', 10))),
|
|
1102
1211
|
response_format: 'json',
|
|
1212
|
+
...buildIntelligenceContextPayload(options),
|
|
1103
1213
|
});
|
|
1104
1214
|
spinner.stop();
|
|
1105
1215
|
printIntelligenceResult('💡 Memory Insights', result, options);
|
|
@@ -1114,6 +1224,10 @@ export function memoryCommands(program) {
|
|
|
1114
1224
|
.command('analyze-patterns')
|
|
1115
1225
|
.description('Analyze memory usage patterns')
|
|
1116
1226
|
.option('--days <number>', 'Days to include in analysis', '30')
|
|
1227
|
+
.option('--organization-id <id>', 'Optional organization context')
|
|
1228
|
+
.option('--topic-id <id>', 'Optional topic context')
|
|
1229
|
+
.option('--scope <scope>', `Optional query scope (${QUERY_SCOPE_CHOICES.join(', ')})`)
|
|
1230
|
+
.option('--memory-types <types>', `Optional comma-separated memory types (${MEMORY_TYPE_CHOICES.join(', ')})`)
|
|
1117
1231
|
.option('--json', 'Output raw JSON payload')
|
|
1118
1232
|
.action(async (options) => {
|
|
1119
1233
|
try {
|
|
@@ -1124,6 +1238,7 @@ export function memoryCommands(program) {
|
|
|
1124
1238
|
user_id: userId,
|
|
1125
1239
|
time_range_days: Math.max(1, Math.min(365, parseInt(options.days || '30', 10))),
|
|
1126
1240
|
response_format: 'json',
|
|
1241
|
+
...buildIntelligenceContextPayload(options),
|
|
1127
1242
|
});
|
|
1128
1243
|
spinner.stop();
|
|
1129
1244
|
printIntelligenceResult('📈 Pattern Analysis', result, options);
|
|
@@ -1215,7 +1330,7 @@ export function memoryCommands(program) {
|
|
|
1215
1330
|
.description('Suggest next actions from learned behavior patterns')
|
|
1216
1331
|
.requiredOption('--task <text>', 'Current task description')
|
|
1217
1332
|
.option('--state <json>', 'Additional current state JSON object')
|
|
1218
|
-
.option('--completed-steps <json>', 'Completed steps JSON array')
|
|
1333
|
+
.option('--completed-steps <json>', 'Completed steps JSON array (strings preferred)')
|
|
1219
1334
|
.option('--max-suggestions <number>', 'Maximum suggestions', '3')
|
|
1220
1335
|
.option('--json', 'Output raw JSON payload')
|
|
1221
1336
|
.action(async (options) => {
|
|
@@ -1226,9 +1341,7 @@ export function memoryCommands(program) {
|
|
|
1226
1341
|
const parsedState = parseJsonOption(options.state, '--state');
|
|
1227
1342
|
const state = ensureJsonObject(parsedState, '--state') || {};
|
|
1228
1343
|
const parsedCompletedSteps = parseJsonOption(options.completedSteps, '--completed-steps');
|
|
1229
|
-
const completedSteps = parsedCompletedSteps
|
|
1230
|
-
? undefined
|
|
1231
|
-
: ensureBehaviorActions(parsedCompletedSteps, '--completed-steps', { allowEmpty: true });
|
|
1344
|
+
const completedSteps = ensureCompletedSteps(parsedCompletedSteps, '--completed-steps');
|
|
1232
1345
|
const result = await postIntelligenceEndpoint(transport, '/intelligence/behavior-suggest', {
|
|
1233
1346
|
user_id: userId,
|
|
1234
1347
|
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
|
@@ -9,8 +9,11 @@ export interface AuthResponse {
|
|
|
9
9
|
created_at: string;
|
|
10
10
|
updated_at: string;
|
|
11
11
|
};
|
|
12
|
-
token
|
|
13
|
-
|
|
12
|
+
token?: string;
|
|
13
|
+
access_token?: string;
|
|
14
|
+
refresh_token?: string;
|
|
15
|
+
expires_in?: number;
|
|
16
|
+
expires_at?: string;
|
|
14
17
|
}
|
|
15
18
|
export interface RegisterRequest {
|
|
16
19
|
email: string;
|
|
@@ -169,7 +172,10 @@ export interface UserProfile {
|
|
|
169
172
|
email: string;
|
|
170
173
|
name: string | null;
|
|
171
174
|
avatar_url: string | null;
|
|
175
|
+
organization_id?: string | null;
|
|
176
|
+
organizationId?: string | null;
|
|
172
177
|
role: string;
|
|
178
|
+
plan?: string | null;
|
|
173
179
|
provider: string | null;
|
|
174
180
|
project_scope: string | null;
|
|
175
181
|
platform: string | null;
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ interface CLIConfigData {
|
|
|
27
27
|
lastManualEndpointUpdate?: string;
|
|
28
28
|
vendorKey?: string | undefined;
|
|
29
29
|
authMethod?: 'jwt' | 'vendor_key' | 'oauth' | 'oauth2' | undefined;
|
|
30
|
+
refresh_token?: string | undefined;
|
|
31
|
+
token_expires_at?: number | string | undefined;
|
|
30
32
|
tokenExpiry?: number | undefined;
|
|
31
33
|
lastValidated?: string | undefined;
|
|
32
34
|
deviceId?: string;
|
|
@@ -52,6 +54,9 @@ export declare class CLIConfig {
|
|
|
52
54
|
private vendorKeyCache?;
|
|
53
55
|
private isLegacyHashedCredential;
|
|
54
56
|
private getLegacyHashedVendorKeyReason;
|
|
57
|
+
private normalizeOptionalString;
|
|
58
|
+
private extractOrganizationId;
|
|
59
|
+
private buildUserProfile;
|
|
55
60
|
constructor();
|
|
56
61
|
private getApiKeyStorage;
|
|
57
62
|
/**
|
|
@@ -120,12 +125,14 @@ export declare class CLIConfig {
|
|
|
120
125
|
getToken(): string | undefined;
|
|
121
126
|
getAuthMethod(): string | undefined;
|
|
122
127
|
getCurrentUser(): Promise<UserProfile | undefined>;
|
|
128
|
+
updateCurrentUserProfile(profile: Record<string, unknown>): Promise<void>;
|
|
123
129
|
isAuthenticated(): Promise<boolean>;
|
|
124
130
|
logout(): Promise<void>;
|
|
125
131
|
clear(): Promise<void>;
|
|
126
132
|
exists(): Promise<boolean>;
|
|
127
133
|
validateStoredCredentials(): Promise<boolean>;
|
|
128
134
|
refreshTokenIfNeeded(): Promise<void>;
|
|
135
|
+
private refreshViaOAuthTokenEndpoint;
|
|
129
136
|
clearInvalidCredentials(): Promise<void>;
|
|
130
137
|
incrementFailureCount(): Promise<void>;
|
|
131
138
|
resetFailureCount(): Promise<void>;
|
package/dist/utils/config.js
CHANGED
|
@@ -21,6 +21,35 @@ export class CLIConfig {
|
|
|
21
21
|
getLegacyHashedVendorKeyReason() {
|
|
22
22
|
return 'Stored vendor key is in legacy hashed format. Run "lanonasis auth login --vendor-key <your-key>" to refresh secure storage.';
|
|
23
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
|
+
}
|
|
24
53
|
constructor() {
|
|
25
54
|
this.configDir = path.join(os.homedir(), '.maas');
|
|
26
55
|
this.configPath = path.join(this.configDir, 'config.json');
|
|
@@ -897,12 +926,7 @@ export class CLIConfig {
|
|
|
897
926
|
this.config.tokenExpiry = decoded.exp;
|
|
898
927
|
}
|
|
899
928
|
// Store user info
|
|
900
|
-
this.config.user =
|
|
901
|
-
email: String(decoded.email || ''),
|
|
902
|
-
organization_id: String(decoded.organizationId || ''),
|
|
903
|
-
role: String(decoded.role || ''),
|
|
904
|
-
plan: String(decoded.plan || '')
|
|
905
|
-
};
|
|
929
|
+
this.config.user = this.buildUserProfile(decoded, this.config.user);
|
|
906
930
|
}
|
|
907
931
|
catch {
|
|
908
932
|
// Invalid token, don't store user info or expiry
|
|
@@ -921,6 +945,23 @@ export class CLIConfig {
|
|
|
921
945
|
async getCurrentUser() {
|
|
922
946
|
return this.config.user;
|
|
923
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
|
+
}
|
|
924
965
|
async isAuthenticated() {
|
|
925
966
|
// Attempt refresh for OAuth sessions before checks (prevents intermittent auth dropouts).
|
|
926
967
|
// This is safe to call even when not using OAuth; it will no-op.
|
|
@@ -1242,9 +1283,9 @@ export class CLIConfig {
|
|
|
1242
1283
|
if (String(this.config.authMethod || '').toLowerCase() === 'vendor_key') {
|
|
1243
1284
|
return;
|
|
1244
1285
|
}
|
|
1286
|
+
const refreshToken = this.get('refresh_token');
|
|
1245
1287
|
// OAuth token refresh (opaque tokens + refresh_token + token_expires_at)
|
|
1246
|
-
if (this.config.authMethod === 'oauth') {
|
|
1247
|
-
const refreshToken = this.get('refresh_token');
|
|
1288
|
+
if (this.config.authMethod === 'oauth' || this.config.authMethod === 'oauth2') {
|
|
1248
1289
|
if (!refreshToken) {
|
|
1249
1290
|
return;
|
|
1250
1291
|
}
|
|
@@ -1269,37 +1310,7 @@ export class CLIConfig {
|
|
|
1269
1310
|
return;
|
|
1270
1311
|
}
|
|
1271
1312
|
await this.discoverServices();
|
|
1272
|
-
|
|
1273
|
-
const resp = await axios.post(`${authBase}/oauth/token`, {
|
|
1274
|
-
grant_type: 'refresh_token',
|
|
1275
|
-
refresh_token: refreshToken,
|
|
1276
|
-
client_id: 'lanonasis-cli'
|
|
1277
|
-
}, {
|
|
1278
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1279
|
-
timeout: 10000,
|
|
1280
|
-
proxy: false
|
|
1281
|
-
});
|
|
1282
|
-
// Some gateways wrap responses as `{ data: { ... } }`.
|
|
1283
|
-
const raw = resp?.data;
|
|
1284
|
-
const payload = raw && typeof raw === 'object' && raw.data && typeof raw.data === 'object'
|
|
1285
|
-
? raw.data
|
|
1286
|
-
: raw;
|
|
1287
|
-
const accessToken = payload?.access_token ?? payload?.token;
|
|
1288
|
-
const refreshedRefreshToken = payload?.refresh_token;
|
|
1289
|
-
const expiresIn = payload?.expires_in;
|
|
1290
|
-
if (typeof accessToken !== 'string' || accessToken.length === 0) {
|
|
1291
|
-
throw new Error('Token refresh response missing access_token');
|
|
1292
|
-
}
|
|
1293
|
-
// setToken() assumes JWT by default; ensure authMethod stays oauth after storing.
|
|
1294
|
-
await this.setToken(accessToken);
|
|
1295
|
-
this.config.authMethod = 'oauth';
|
|
1296
|
-
if (typeof refreshedRefreshToken === 'string' && refreshedRefreshToken.length > 0) {
|
|
1297
|
-
this.config.refresh_token = refreshedRefreshToken;
|
|
1298
|
-
}
|
|
1299
|
-
if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) {
|
|
1300
|
-
this.config.token_expires_at = Date.now() + (expiresIn * 1000);
|
|
1301
|
-
}
|
|
1302
|
-
await this.save().catch(() => { });
|
|
1313
|
+
await this.refreshViaOAuthTokenEndpoint(refreshToken, this.getDiscoveredApiUrl(), 'oauth');
|
|
1303
1314
|
return;
|
|
1304
1315
|
}
|
|
1305
1316
|
// Check if token is JWT and if it's close to expiry
|
|
@@ -1318,20 +1329,11 @@ export class CLIConfig {
|
|
|
1318
1329
|
const exp = typeof decoded.exp === 'number' ? decoded.exp : 0;
|
|
1319
1330
|
// Refresh if token expires within 5 minutes
|
|
1320
1331
|
if (exp > 0 && (exp - now) < 300) {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
const authBase = this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
1324
|
-
// Attempt token refresh
|
|
1325
|
-
const response = await axios.post(`${authBase}/v1/auth/refresh`, {}, {
|
|
1326
|
-
headers: {
|
|
1327
|
-
'Authorization': `Bearer ${token}`,
|
|
1328
|
-
'X-Project-Scope': 'lanonasis-maas'
|
|
1329
|
-
},
|
|
1330
|
-
timeout: 10000
|
|
1331
|
-
});
|
|
1332
|
-
if (response.data.token) {
|
|
1333
|
-
await this.setToken(response.data.token);
|
|
1332
|
+
if (!refreshToken) {
|
|
1333
|
+
return;
|
|
1334
1334
|
}
|
|
1335
|
+
await this.discoverServices();
|
|
1336
|
+
await this.refreshViaOAuthTokenEndpoint(refreshToken, this.getDiscoveredApiUrl(), 'jwt');
|
|
1335
1337
|
}
|
|
1336
1338
|
}
|
|
1337
1339
|
catch (err) {
|
|
@@ -1342,6 +1344,36 @@ export class CLIConfig {
|
|
|
1342
1344
|
}
|
|
1343
1345
|
}
|
|
1344
1346
|
}
|
|
1347
|
+
async refreshViaOAuthTokenEndpoint(refreshToken, authBase, authMethod) {
|
|
1348
|
+
const resp = await axios.post(`${authBase}/oauth/token`, {
|
|
1349
|
+
grant_type: 'refresh_token',
|
|
1350
|
+
refresh_token: refreshToken,
|
|
1351
|
+
client_id: 'lanonasis-cli'
|
|
1352
|
+
}, {
|
|
1353
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1354
|
+
timeout: 10000,
|
|
1355
|
+
proxy: false
|
|
1356
|
+
});
|
|
1357
|
+
const raw = resp?.data;
|
|
1358
|
+
const payload = raw && typeof raw === 'object' && raw.data && typeof raw.data === 'object'
|
|
1359
|
+
? raw.data
|
|
1360
|
+
: raw;
|
|
1361
|
+
const accessToken = payload?.access_token ?? payload?.token;
|
|
1362
|
+
const refreshedRefreshToken = payload?.refresh_token;
|
|
1363
|
+
const expiresIn = payload?.expires_in;
|
|
1364
|
+
if (typeof accessToken !== 'string' || accessToken.length === 0) {
|
|
1365
|
+
throw new Error('Token refresh response missing access_token');
|
|
1366
|
+
}
|
|
1367
|
+
await this.setToken(accessToken);
|
|
1368
|
+
this.config.authMethod = authMethod;
|
|
1369
|
+
if (typeof refreshedRefreshToken === 'string' && refreshedRefreshToken.length > 0) {
|
|
1370
|
+
this.config.refresh_token = refreshedRefreshToken;
|
|
1371
|
+
}
|
|
1372
|
+
if (typeof expiresIn === 'number' && Number.isFinite(expiresIn)) {
|
|
1373
|
+
this.config.token_expires_at = Date.now() + (expiresIn * 1000);
|
|
1374
|
+
}
|
|
1375
|
+
await this.save().catch(() => { });
|
|
1376
|
+
}
|
|
1345
1377
|
async clearInvalidCredentials() {
|
|
1346
1378
|
this.config.token = undefined;
|
|
1347
1379
|
this.config.vendorKey = undefined;
|
package/dist/utils/mcp-client.js
CHANGED
|
@@ -451,20 +451,11 @@ export class MCPClient {
|
|
|
451
451
|
* Refresh token if needed
|
|
452
452
|
*/
|
|
453
453
|
async refreshTokenIfNeeded() {
|
|
454
|
-
const refreshToken = this.config.get('refreshToken');
|
|
455
|
-
if (!refreshToken) {
|
|
456
|
-
throw new Error('No refresh token available. Please re-authenticate.');
|
|
457
|
-
}
|
|
458
454
|
try {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
}, {
|
|
464
|
-
timeout: 10000
|
|
465
|
-
});
|
|
466
|
-
if (response.data.access_token) {
|
|
467
|
-
await this.config.setAndSave('token', response.data.access_token);
|
|
455
|
+
const previousToken = this.config.getToken();
|
|
456
|
+
await this.config.refreshTokenIfNeeded();
|
|
457
|
+
const currentToken = this.config.getToken();
|
|
458
|
+
if (currentToken && currentToken !== previousToken) {
|
|
468
459
|
console.log(chalk.green('✓ Token refreshed successfully'));
|
|
469
460
|
}
|
|
470
461
|
}
|
|
@@ -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.13",
|
|
4
4
|
"description": "Professional CLI for LanOnasis Memory as a Service (MaaS) with MCP support, seamless inline editing, and enterprise-grade security",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lanonasis",
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
"CHANGELOG.md"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@lanonasis/mem-intel-sdk": "2.0
|
|
55
|
+
"@lanonasis/mem-intel-sdk": "2.1.0",
|
|
56
56
|
"@lanonasis/oauth-client": "2.0.4",
|
|
57
57
|
"@lanonasis/security-sdk": "1.0.5",
|
|
58
58
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
59
|
-
"axios": "^1.
|
|
59
|
+
"axios": "^1.14.0",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-progress": "^3.12.0",
|
|
62
62
|
"cli-table3": "^0.6.5",
|