@lanonasis/cli 3.9.5 → 3.9.6
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 +15 -0
- package/README.md +10 -14
- package/dist/index-simple.js +13 -1
- package/dist/index.js +13 -1
- package/dist/mcp/schemas/tool-schemas.d.ts +24 -24
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.js +140 -7
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.js +211 -33
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog - @lanonasis/cli
|
|
2
2
|
|
|
3
|
+
## [3.9.6] - 2026-02-21
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **Reliable Memory Auth Routing**: Memory CRUD and search operations now consistently route through the API gateway (`https://api.lanonasis.com`) to avoid MCP endpoint contract mismatches.
|
|
8
|
+
- **Legacy Endpoint Compatibility**: Added fallback support for deployments that still expose RPC-style memory routes (`/api/v1/memory/*`) when REST routes return `400/405`.
|
|
9
|
+
- **Auth Status Accuracy**: `status` now validates live auth state against the auth verify endpoint before reporting authenticated session state.
|
|
10
|
+
- **OAuth Session Stability**: Requests proactively refresh OAuth/JWT sessions to reduce intermittent `memory login required` errors during long-running CLI usage.
|
|
11
|
+
- **Response Normalization**: Memory get/list/search handlers normalize wrapped gateway responses (`{ data: ... }`) for consistent CLI behavior across environments.
|
|
12
|
+
|
|
13
|
+
### 📚 Documentation
|
|
14
|
+
|
|
15
|
+
- Clarified auth flow behavior for vendor keys and bearer tokens.
|
|
16
|
+
- Added release notes for endpoint override guidance and memory transport behavior.
|
|
17
|
+
|
|
3
18
|
## [3.9.3] - 2026-02-02
|
|
4
19
|
|
|
5
20
|
### ✨ Features
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# @lanonasis/cli v3.9.
|
|
1
|
+
# @lanonasis/cli v3.9.6 - Auth Routing & Memory 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.6**: Fixed memory auth routing to the API gateway, added compatibility fallbacks for legacy memory endpoints, improved auth status verification, and stabilized OAuth/JWT refresh behavior during CLI memory operations.
|
|
9
9
|
|
|
10
10
|
## 🚀 Quick Start
|
|
11
11
|
|
|
@@ -129,23 +129,19 @@ maas memory list
|
|
|
129
129
|
|
|
130
130
|
## 🔐 Security & Authentication
|
|
131
131
|
|
|
132
|
-
### Enterprise-Grade
|
|
132
|
+
### Enterprise-Grade API Key Handling
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
The CLI uses secure local storage and sends credentials in the expected wire format:
|
|
135
135
|
|
|
136
|
-
- ✅ **
|
|
137
|
-
- ✅ **
|
|
138
|
-
- ✅ **
|
|
139
|
-
- ✅ **
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// Hash utilities are built-in and automatic
|
|
143
|
-
// Your vendor keys are automatically secured
|
|
144
|
-
onasis login --vendor-key pk_xxxxx.sk_xxxxx // ✅ Automatically hashed
|
|
145
|
-
```
|
|
136
|
+
- ✅ **Encrypted At Rest**: Vendor keys are stored in encrypted local storage (keytar when available, encrypted file fallback otherwise).
|
|
137
|
+
- ✅ **Correct On-Wire Format**: Vendor key auth sends the raw vendor key in `X-API-Key` over HTTPS.
|
|
138
|
+
- ✅ **Single Server-Side Hash Validation**: The API validates keys with server-side hashing and does not require client-side hashing.
|
|
139
|
+
- ✅ **Token-First Sessions**: OAuth/JWT sessions use `Authorization: Bearer <token>` and refresh automatically before expiry.
|
|
146
140
|
|
|
147
141
|
### Authentication Methods
|
|
148
142
|
|
|
143
|
+
> Transport note: for memory commands, keep `manualEndpointOverrides=false` so requests route through `https://api.lanonasis.com`.
|
|
144
|
+
|
|
149
145
|
### 1. Vendor Key Authentication (Recommended)
|
|
150
146
|
|
|
151
147
|
Best for API integrations and automation. Copy the vendor key value exactly as shown in your LanOnasis dashboard (keys may vary in format):
|
package/dist/index-simple.js
CHANGED
|
@@ -553,18 +553,30 @@ program
|
|
|
553
553
|
.action(async () => {
|
|
554
554
|
// Initialize config first
|
|
555
555
|
await cliConfig.init();
|
|
556
|
-
const
|
|
556
|
+
const verification = await cliConfig.verifyCurrentCredentialsWithServer().catch((error) => ({
|
|
557
|
+
valid: false,
|
|
558
|
+
method: 'none',
|
|
559
|
+
endpoint: undefined,
|
|
560
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
561
|
+
}));
|
|
562
|
+
const isAuth = verification.valid;
|
|
557
563
|
const apiUrl = cliConfig.getApiUrl();
|
|
558
564
|
console.log(chalk.blue.bold('MaaS CLI Status'));
|
|
559
565
|
console.log(`API URL: ${apiUrl}`);
|
|
560
566
|
console.log(`Authenticated: ${isAuth ? chalk.green('Yes') : chalk.red('No')}`);
|
|
567
|
+
if (process.env.CLI_VERBOSE === 'true' && verification.endpoint) {
|
|
568
|
+
console.log(`Verified via: ${verification.endpoint}`);
|
|
569
|
+
}
|
|
561
570
|
if (isAuth) {
|
|
562
571
|
const user = await cliConfig.getCurrentUser();
|
|
563
572
|
if (user) {
|
|
564
573
|
console.log(`User: ${user.email}`);
|
|
565
574
|
console.log(`Plan: ${user.plan}`);
|
|
566
575
|
}
|
|
576
|
+
return;
|
|
567
577
|
}
|
|
578
|
+
console.log(chalk.yellow(`Auth check: ${verification.reason || 'Credential validation failed'}`));
|
|
579
|
+
console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
|
|
568
580
|
});
|
|
569
581
|
// Health command using the healthCheck function
|
|
570
582
|
program
|
package/dist/index.js
CHANGED
|
@@ -630,18 +630,30 @@ program
|
|
|
630
630
|
.description('Show overall system status')
|
|
631
631
|
.action(async () => {
|
|
632
632
|
await cliConfig.init();
|
|
633
|
-
const
|
|
633
|
+
const verification = await cliConfig.verifyCurrentCredentialsWithServer().catch((error) => ({
|
|
634
|
+
valid: false,
|
|
635
|
+
method: 'none',
|
|
636
|
+
endpoint: undefined,
|
|
637
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
638
|
+
}));
|
|
639
|
+
const isAuth = verification.valid;
|
|
634
640
|
const apiUrl = cliConfig.getApiUrl();
|
|
635
641
|
console.log(chalk.blue.bold('MaaS CLI Status'));
|
|
636
642
|
console.log(`API URL: ${apiUrl}`);
|
|
637
643
|
console.log(`Authenticated: ${isAuth ? chalk.green('Yes') : chalk.red('No')}`);
|
|
644
|
+
if (process.env.CLI_VERBOSE === 'true' && verification.endpoint) {
|
|
645
|
+
console.log(`Verified via: ${verification.endpoint}`);
|
|
646
|
+
}
|
|
638
647
|
if (isAuth) {
|
|
639
648
|
const user = await cliConfig.getCurrentUser();
|
|
640
649
|
if (user) {
|
|
641
650
|
console.log(`User: ${user.email}`);
|
|
642
651
|
console.log(`Plan: ${user.plan}`);
|
|
643
652
|
}
|
|
653
|
+
return;
|
|
644
654
|
}
|
|
655
|
+
console.log(chalk.yellow(`Auth check: ${verification.reason || 'Credential validation failed'}`));
|
|
656
|
+
console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
|
|
645
657
|
});
|
|
646
658
|
// Health command using the healthCheck function
|
|
647
659
|
program
|
|
@@ -14,16 +14,16 @@ export declare const MemoryCreateSchema: z.ZodObject<{
|
|
|
14
14
|
title?: string;
|
|
15
15
|
content?: string;
|
|
16
16
|
tags?: string[];
|
|
17
|
+
memory_type?: "context" | "reference" | "note";
|
|
17
18
|
topic_id?: string;
|
|
18
19
|
metadata?: Record<string, any>;
|
|
19
|
-
memory_type?: "context" | "reference" | "note";
|
|
20
20
|
}, {
|
|
21
21
|
title?: string;
|
|
22
22
|
content?: string;
|
|
23
23
|
tags?: string[];
|
|
24
|
+
memory_type?: "context" | "reference" | "note";
|
|
24
25
|
topic_id?: string;
|
|
25
26
|
metadata?: Record<string, any>;
|
|
26
|
-
memory_type?: "context" | "reference" | "note";
|
|
27
27
|
}>;
|
|
28
28
|
export declare const MemorySearchSchema: z.ZodObject<{
|
|
29
29
|
query: z.ZodString;
|
|
@@ -36,16 +36,16 @@ export declare const MemorySearchSchema: z.ZodObject<{
|
|
|
36
36
|
query?: string;
|
|
37
37
|
tags?: string[];
|
|
38
38
|
limit?: number;
|
|
39
|
+
memory_type?: "context" | "reference" | "note";
|
|
39
40
|
topic_id?: string;
|
|
40
41
|
threshold?: number;
|
|
41
|
-
memory_type?: "context" | "reference" | "note";
|
|
42
42
|
}, {
|
|
43
43
|
query?: string;
|
|
44
44
|
tags?: string[];
|
|
45
45
|
limit?: number;
|
|
46
|
+
memory_type?: "context" | "reference" | "note";
|
|
46
47
|
topic_id?: string;
|
|
47
48
|
threshold?: number;
|
|
48
|
-
memory_type?: "context" | "reference" | "note";
|
|
49
49
|
}>;
|
|
50
50
|
export declare const MemoryUpdateSchema: z.ZodObject<{
|
|
51
51
|
memory_id: z.ZodString;
|
|
@@ -58,16 +58,16 @@ export declare const MemoryUpdateSchema: z.ZodObject<{
|
|
|
58
58
|
title?: string;
|
|
59
59
|
content?: string;
|
|
60
60
|
tags?: string[];
|
|
61
|
+
memory_type?: "context" | "reference" | "note";
|
|
61
62
|
memory_id?: string;
|
|
62
63
|
metadata?: Record<string, any>;
|
|
63
|
-
memory_type?: "context" | "reference" | "note";
|
|
64
64
|
}, {
|
|
65
65
|
title?: string;
|
|
66
66
|
content?: string;
|
|
67
67
|
tags?: string[];
|
|
68
|
+
memory_type?: "context" | "reference" | "note";
|
|
68
69
|
memory_id?: string;
|
|
69
70
|
metadata?: Record<string, any>;
|
|
70
|
-
memory_type?: "context" | "reference" | "note";
|
|
71
71
|
}>;
|
|
72
72
|
export declare const MemoryDeleteSchema: z.ZodObject<{
|
|
73
73
|
memory_id: z.ZodString;
|
|
@@ -90,19 +90,19 @@ export declare const MemoryListSchema: z.ZodObject<{
|
|
|
90
90
|
}, "strip", z.ZodTypeAny, {
|
|
91
91
|
tags?: string[];
|
|
92
92
|
limit?: number;
|
|
93
|
-
topic_id?: string;
|
|
94
|
-
order?: "desc" | "asc";
|
|
95
|
-
memory_type?: "context" | "reference" | "note";
|
|
96
93
|
offset?: number;
|
|
94
|
+
memory_type?: "context" | "reference" | "note";
|
|
95
|
+
topic_id?: string;
|
|
97
96
|
sort_by?: "title" | "created_at" | "updated_at";
|
|
97
|
+
order?: "desc" | "asc";
|
|
98
98
|
}, {
|
|
99
99
|
tags?: string[];
|
|
100
100
|
limit?: number;
|
|
101
|
-
topic_id?: string;
|
|
102
|
-
order?: "desc" | "asc";
|
|
103
|
-
memory_type?: "context" | "reference" | "note";
|
|
104
101
|
offset?: number;
|
|
102
|
+
memory_type?: "context" | "reference" | "note";
|
|
103
|
+
topic_id?: string;
|
|
105
104
|
sort_by?: "title" | "created_at" | "updated_at";
|
|
105
|
+
order?: "desc" | "asc";
|
|
106
106
|
}>;
|
|
107
107
|
export declare const TopicCreateSchema: z.ZodObject<{
|
|
108
108
|
name: z.ZodString;
|
|
@@ -386,16 +386,16 @@ export declare const MCPSchemas: {
|
|
|
386
386
|
title?: string;
|
|
387
387
|
content?: string;
|
|
388
388
|
tags?: string[];
|
|
389
|
+
memory_type?: "context" | "reference" | "note";
|
|
389
390
|
topic_id?: string;
|
|
390
391
|
metadata?: Record<string, any>;
|
|
391
|
-
memory_type?: "context" | "reference" | "note";
|
|
392
392
|
}, {
|
|
393
393
|
title?: string;
|
|
394
394
|
content?: string;
|
|
395
395
|
tags?: string[];
|
|
396
|
+
memory_type?: "context" | "reference" | "note";
|
|
396
397
|
topic_id?: string;
|
|
397
398
|
metadata?: Record<string, any>;
|
|
398
|
-
memory_type?: "context" | "reference" | "note";
|
|
399
399
|
}>;
|
|
400
400
|
search: z.ZodObject<{
|
|
401
401
|
query: z.ZodString;
|
|
@@ -408,16 +408,16 @@ export declare const MCPSchemas: {
|
|
|
408
408
|
query?: string;
|
|
409
409
|
tags?: string[];
|
|
410
410
|
limit?: number;
|
|
411
|
+
memory_type?: "context" | "reference" | "note";
|
|
411
412
|
topic_id?: string;
|
|
412
413
|
threshold?: number;
|
|
413
|
-
memory_type?: "context" | "reference" | "note";
|
|
414
414
|
}, {
|
|
415
415
|
query?: string;
|
|
416
416
|
tags?: string[];
|
|
417
417
|
limit?: number;
|
|
418
|
+
memory_type?: "context" | "reference" | "note";
|
|
418
419
|
topic_id?: string;
|
|
419
420
|
threshold?: number;
|
|
420
|
-
memory_type?: "context" | "reference" | "note";
|
|
421
421
|
}>;
|
|
422
422
|
update: z.ZodObject<{
|
|
423
423
|
memory_id: z.ZodString;
|
|
@@ -430,16 +430,16 @@ export declare const MCPSchemas: {
|
|
|
430
430
|
title?: string;
|
|
431
431
|
content?: string;
|
|
432
432
|
tags?: string[];
|
|
433
|
+
memory_type?: "context" | "reference" | "note";
|
|
433
434
|
memory_id?: string;
|
|
434
435
|
metadata?: Record<string, any>;
|
|
435
|
-
memory_type?: "context" | "reference" | "note";
|
|
436
436
|
}, {
|
|
437
437
|
title?: string;
|
|
438
438
|
content?: string;
|
|
439
439
|
tags?: string[];
|
|
440
|
+
memory_type?: "context" | "reference" | "note";
|
|
440
441
|
memory_id?: string;
|
|
441
442
|
metadata?: Record<string, any>;
|
|
442
|
-
memory_type?: "context" | "reference" | "note";
|
|
443
443
|
}>;
|
|
444
444
|
delete: z.ZodObject<{
|
|
445
445
|
memory_id: z.ZodString;
|
|
@@ -462,19 +462,19 @@ export declare const MCPSchemas: {
|
|
|
462
462
|
}, "strip", z.ZodTypeAny, {
|
|
463
463
|
tags?: string[];
|
|
464
464
|
limit?: number;
|
|
465
|
-
topic_id?: string;
|
|
466
|
-
order?: "desc" | "asc";
|
|
467
|
-
memory_type?: "context" | "reference" | "note";
|
|
468
465
|
offset?: number;
|
|
466
|
+
memory_type?: "context" | "reference" | "note";
|
|
467
|
+
topic_id?: string;
|
|
469
468
|
sort_by?: "title" | "created_at" | "updated_at";
|
|
469
|
+
order?: "desc" | "asc";
|
|
470
470
|
}, {
|
|
471
471
|
tags?: string[];
|
|
472
472
|
limit?: number;
|
|
473
|
-
topic_id?: string;
|
|
474
|
-
order?: "desc" | "asc";
|
|
475
|
-
memory_type?: "context" | "reference" | "note";
|
|
476
473
|
offset?: number;
|
|
474
|
+
memory_type?: "context" | "reference" | "note";
|
|
475
|
+
topic_id?: string;
|
|
477
476
|
sort_by?: "title" | "created_at" | "updated_at";
|
|
477
|
+
order?: "desc" | "asc";
|
|
478
478
|
}>;
|
|
479
479
|
};
|
|
480
480
|
topic: {
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -153,6 +153,7 @@ export declare class APIClient {
|
|
|
153
153
|
private client;
|
|
154
154
|
private config;
|
|
155
155
|
private normalizeMemoryEntry;
|
|
156
|
+
private shouldUseLegacyMemoryRpcFallback;
|
|
156
157
|
constructor();
|
|
157
158
|
login(email: string, password: string): Promise<AuthResponse>;
|
|
158
159
|
register(email: string, password: string, organizationName?: string): Promise<AuthResponse>;
|
package/dist/utils/api.js
CHANGED
|
@@ -25,6 +25,21 @@ export class APIClient {
|
|
|
25
25
|
}
|
|
26
26
|
return payload;
|
|
27
27
|
}
|
|
28
|
+
shouldUseLegacyMemoryRpcFallback(error) {
|
|
29
|
+
const status = error?.response?.status;
|
|
30
|
+
const errorData = error?.response?.data;
|
|
31
|
+
const message = `${errorData?.error || ''} ${errorData?.message || ''}`.toLowerCase();
|
|
32
|
+
if (status === 405) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (status === 400 && message.includes('memory id is required')) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (status === 400 && message.includes('method not allowed')) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
28
43
|
constructor() {
|
|
29
44
|
this.config = new CLIConfig();
|
|
30
45
|
this.client = axios.create({
|
|
@@ -43,12 +58,14 @@ export class APIClient {
|
|
|
43
58
|
const authMethod = this.config.get('authMethod');
|
|
44
59
|
const vendorKey = await this.config.getVendorKeyAsync();
|
|
45
60
|
const token = this.config.getToken();
|
|
61
|
+
const isMemoryEndpoint = typeof config.url === 'string' && config.url.startsWith('/api/v1/memories');
|
|
46
62
|
const forceApiFromEnv = process.env.LANONASIS_FORCE_API === 'true'
|
|
47
63
|
|| process.env.CLI_FORCE_API === 'true'
|
|
48
64
|
|| process.env.ONASIS_FORCE_API === 'true';
|
|
49
65
|
const forceApiFromConfig = this.config.get('forceApi') === true
|
|
50
66
|
|| this.config.get('connectionTransport') === 'api';
|
|
51
|
-
|
|
67
|
+
// Memory CRUD/search endpoints should always use the API gateway path.
|
|
68
|
+
const forceDirectApi = forceApiFromEnv || forceApiFromConfig || isMemoryEndpoint;
|
|
52
69
|
const prefersTokenAuth = Boolean(token) && (authMethod === 'jwt' || authMethod === 'oauth' || authMethod === 'oauth2');
|
|
53
70
|
const useVendorKeyAuth = Boolean(vendorKey) && !prefersTokenAuth;
|
|
54
71
|
// Determine the correct API base URL:
|
|
@@ -183,11 +200,91 @@ export class APIClient {
|
|
|
183
200
|
return response.data;
|
|
184
201
|
}
|
|
185
202
|
catch (error) {
|
|
186
|
-
// Backward-compatible fallback: newer API contracts may reject GET list
|
|
203
|
+
// Backward-compatible fallback: newer API contracts may reject GET list.
|
|
187
204
|
if (error?.response?.status === 405) {
|
|
188
205
|
const limit = Number(params.limit || 20);
|
|
189
206
|
const page = Number(params.page || 1);
|
|
190
207
|
const offset = Number(params.offset ?? Math.max(0, (page - 1) * limit));
|
|
208
|
+
// Preferred fallback: POST list endpoint (avoids triggering vector search for plain listings).
|
|
209
|
+
const listPayload = {
|
|
210
|
+
limit,
|
|
211
|
+
offset
|
|
212
|
+
};
|
|
213
|
+
if (params.memory_type) {
|
|
214
|
+
listPayload.memory_type = params.memory_type;
|
|
215
|
+
}
|
|
216
|
+
if (params.tags) {
|
|
217
|
+
listPayload.tags = Array.isArray(params.tags)
|
|
218
|
+
? params.tags
|
|
219
|
+
: String(params.tags).split(',').map((tag) => tag.trim()).filter(Boolean);
|
|
220
|
+
}
|
|
221
|
+
if (params.topic_id) {
|
|
222
|
+
listPayload.topic_id = params.topic_id;
|
|
223
|
+
}
|
|
224
|
+
if (params.user_id) {
|
|
225
|
+
listPayload.user_id = params.user_id;
|
|
226
|
+
}
|
|
227
|
+
if (params.sort || params.sort_by) {
|
|
228
|
+
listPayload.sort_by = params.sort_by || params.sort;
|
|
229
|
+
}
|
|
230
|
+
if (params.order || params.sort_order) {
|
|
231
|
+
listPayload.sort_order = params.sort_order || params.order;
|
|
232
|
+
}
|
|
233
|
+
for (const endpoint of ['/api/v1/memories/list', '/api/v1/memory/list']) {
|
|
234
|
+
try {
|
|
235
|
+
const listResponse = await this.client.post(endpoint, listPayload);
|
|
236
|
+
const payload = listResponse.data || {};
|
|
237
|
+
const resultsArray = Array.isArray(payload.data)
|
|
238
|
+
? payload.data
|
|
239
|
+
: Array.isArray(payload.memories)
|
|
240
|
+
? payload.memories
|
|
241
|
+
: Array.isArray(payload.results)
|
|
242
|
+
? payload.results
|
|
243
|
+
: [];
|
|
244
|
+
const memories = resultsArray.map((entry) => this.normalizeMemoryEntry(entry));
|
|
245
|
+
const pagination = (payload.pagination && typeof payload.pagination === 'object')
|
|
246
|
+
? payload.pagination
|
|
247
|
+
: {};
|
|
248
|
+
const total = Number.isFinite(Number(pagination.total))
|
|
249
|
+
? Number(pagination.total)
|
|
250
|
+
: Number.isFinite(Number(payload.total))
|
|
251
|
+
? Number(payload.total)
|
|
252
|
+
: memories.length;
|
|
253
|
+
const pages = Number.isFinite(Number(pagination.total_pages))
|
|
254
|
+
? Number(pagination.total_pages)
|
|
255
|
+
: Number.isFinite(Number(pagination.pages))
|
|
256
|
+
? Number(pagination.pages)
|
|
257
|
+
: Math.max(1, Math.ceil(total / limit));
|
|
258
|
+
const currentPage = Number.isFinite(Number(pagination.page))
|
|
259
|
+
? Number(pagination.page)
|
|
260
|
+
: Math.max(1, Math.floor(offset / limit) + 1);
|
|
261
|
+
const hasMore = typeof pagination.has_more === 'boolean'
|
|
262
|
+
? pagination.has_more
|
|
263
|
+
: typeof pagination.has_next === 'boolean'
|
|
264
|
+
? pagination.has_next
|
|
265
|
+
: (offset + memories.length) < total;
|
|
266
|
+
return {
|
|
267
|
+
...payload,
|
|
268
|
+
data: memories,
|
|
269
|
+
memories,
|
|
270
|
+
pagination: {
|
|
271
|
+
total,
|
|
272
|
+
limit,
|
|
273
|
+
offset,
|
|
274
|
+
has_more: hasMore,
|
|
275
|
+
page: currentPage,
|
|
276
|
+
pages
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
catch (listError) {
|
|
281
|
+
if (listError?.response?.status === 404 || listError?.response?.status === 405) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
throw listError;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Secondary fallback: search endpoint for legacy contracts that expose only search.
|
|
191
288
|
const searchPayload = {
|
|
192
289
|
query: '*',
|
|
193
290
|
limit,
|
|
@@ -251,15 +348,51 @@ export class APIClient {
|
|
|
251
348
|
}
|
|
252
349
|
}
|
|
253
350
|
async getMemory(id) {
|
|
254
|
-
|
|
255
|
-
|
|
351
|
+
try {
|
|
352
|
+
const response = await this.client.get(`/api/v1/memories/${id}`);
|
|
353
|
+
return this.normalizeMemoryEntry(response.data);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
357
|
+
const fallback = await this.client.post('/api/v1/memory/get', { id });
|
|
358
|
+
const payload = fallback.data && typeof fallback.data === 'object'
|
|
359
|
+
? fallback.data.data ?? fallback.data
|
|
360
|
+
: fallback.data;
|
|
361
|
+
return this.normalizeMemoryEntry(payload);
|
|
362
|
+
}
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
256
365
|
}
|
|
257
366
|
async updateMemory(id, data) {
|
|
258
|
-
|
|
259
|
-
|
|
367
|
+
try {
|
|
368
|
+
const response = await this.client.put(`/api/v1/memories/${id}`, data);
|
|
369
|
+
return this.normalizeMemoryEntry(response.data);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
373
|
+
const fallback = await this.client.post('/api/v1/memory/update', {
|
|
374
|
+
id,
|
|
375
|
+
...data
|
|
376
|
+
});
|
|
377
|
+
const payload = fallback.data && typeof fallback.data === 'object'
|
|
378
|
+
? fallback.data.data ?? fallback.data
|
|
379
|
+
: fallback.data;
|
|
380
|
+
return this.normalizeMemoryEntry(payload);
|
|
381
|
+
}
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
260
384
|
}
|
|
261
385
|
async deleteMemory(id) {
|
|
262
|
-
|
|
386
|
+
try {
|
|
387
|
+
await this.client.delete(`/api/v1/memories/${id}`);
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
391
|
+
await this.client.post('/api/v1/memory/delete', { id });
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
263
396
|
}
|
|
264
397
|
async searchMemories(query, options = {}) {
|
|
265
398
|
const response = await this.client.post('/api/v1/memories/search', {
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -34,6 +34,12 @@ interface CLIConfigData {
|
|
|
34
34
|
lastAuthFailure?: string | undefined;
|
|
35
35
|
[key: string]: unknown;
|
|
36
36
|
}
|
|
37
|
+
export type RemoteAuthVerification = {
|
|
38
|
+
valid: boolean;
|
|
39
|
+
method: 'token' | 'vendor_key' | 'none';
|
|
40
|
+
endpoint?: string;
|
|
41
|
+
reason?: string;
|
|
42
|
+
};
|
|
37
43
|
export declare class CLIConfig {
|
|
38
44
|
private configDir;
|
|
39
45
|
private configPath;
|
|
@@ -70,6 +76,11 @@ export declare class CLIConfig {
|
|
|
70
76
|
private resolveFallbackEndpoints;
|
|
71
77
|
private logFallbackUsage;
|
|
72
78
|
private pingAuthHealth;
|
|
79
|
+
private getAuthVerificationEndpoints;
|
|
80
|
+
private extractAuthErrorMessage;
|
|
81
|
+
private verifyTokenWithAuthGateway;
|
|
82
|
+
private verifyVendorKeyWithAuthGateway;
|
|
83
|
+
verifyCurrentCredentialsWithServer(): Promise<RemoteAuthVerification>;
|
|
73
84
|
setManualEndpoints(endpoints: Partial<CLIConfigData['discoveredServices']>): Promise<void>;
|
|
74
85
|
hasManualEndpointOverrides(): boolean;
|
|
75
86
|
clearManualEndpointOverrides(): Promise<void>;
|
package/dist/utils/config.js
CHANGED
|
@@ -434,6 +434,200 @@ export class CLIConfig {
|
|
|
434
434
|
}
|
|
435
435
|
throw new Error('Auth health endpoints unreachable');
|
|
436
436
|
}
|
|
437
|
+
getAuthVerificationEndpoints(pathname) {
|
|
438
|
+
const authBase = (this.config.discoveredServices?.auth_base || 'https://auth.lanonasis.com').replace(/\/$/, '');
|
|
439
|
+
return Array.from(new Set([
|
|
440
|
+
`${authBase}${pathname}`,
|
|
441
|
+
`https://auth.lanonasis.com${pathname}`,
|
|
442
|
+
`http://localhost:4000${pathname}`
|
|
443
|
+
]));
|
|
444
|
+
}
|
|
445
|
+
extractAuthErrorMessage(payload) {
|
|
446
|
+
if (!payload || typeof payload !== 'object') {
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
const data = payload;
|
|
450
|
+
const fields = ['message', 'error', 'reason', 'code'];
|
|
451
|
+
for (const field of fields) {
|
|
452
|
+
const value = data[field];
|
|
453
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
async verifyTokenWithAuthGateway(token) {
|
|
460
|
+
const headers = {
|
|
461
|
+
'Authorization': `Bearer ${token}`,
|
|
462
|
+
'X-Project-Scope': 'lanonasis-maas'
|
|
463
|
+
};
|
|
464
|
+
let fallbackReason = 'Unable to verify token with auth gateway';
|
|
465
|
+
// Primary check (required by auth contract): /v1/auth/verify
|
|
466
|
+
for (const endpoint of this.getAuthVerificationEndpoints('/v1/auth/verify')) {
|
|
467
|
+
try {
|
|
468
|
+
const response = await axios.post(endpoint, {}, {
|
|
469
|
+
headers,
|
|
470
|
+
timeout: 5000,
|
|
471
|
+
proxy: false
|
|
472
|
+
});
|
|
473
|
+
const payload = response.data;
|
|
474
|
+
if (payload.valid === true || Boolean(payload.payload)) {
|
|
475
|
+
return { valid: true, method: 'token', endpoint };
|
|
476
|
+
}
|
|
477
|
+
if (payload.valid === false) {
|
|
478
|
+
return {
|
|
479
|
+
valid: false,
|
|
480
|
+
method: 'token',
|
|
481
|
+
endpoint,
|
|
482
|
+
reason: this.extractAuthErrorMessage(payload) || 'Token is invalid'
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
const normalizedError = this.normalizeServiceError(error);
|
|
488
|
+
const responsePayload = normalizedError.response?.data;
|
|
489
|
+
const responseCode = typeof responsePayload?.code === 'string' ? responsePayload.code : undefined;
|
|
490
|
+
const reason = this.extractAuthErrorMessage(responsePayload) || normalizedError.message || fallbackReason;
|
|
491
|
+
fallbackReason = reason;
|
|
492
|
+
// If auth gateway explicitly rejected token, stop early.
|
|
493
|
+
if ((normalizedError.response?.status === 401 || normalizedError.response?.status === 403) &&
|
|
494
|
+
responseCode &&
|
|
495
|
+
responseCode !== 'AUTH_TOKEN_MISSING') {
|
|
496
|
+
return { valid: false, method: 'token', endpoint, reason };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Fallback for deployments where proxy layers strip Authorization headers.
|
|
501
|
+
for (const endpoint of this.getAuthVerificationEndpoints('/v1/auth/verify-token')) {
|
|
502
|
+
try {
|
|
503
|
+
const response = await axios.post(endpoint, { token }, {
|
|
504
|
+
headers: {
|
|
505
|
+
'Content-Type': 'application/json',
|
|
506
|
+
'X-Project-Scope': 'lanonasis-maas'
|
|
507
|
+
},
|
|
508
|
+
timeout: 5000,
|
|
509
|
+
proxy: false
|
|
510
|
+
});
|
|
511
|
+
const payload = response.data;
|
|
512
|
+
if (payload.valid === true) {
|
|
513
|
+
return { valid: true, method: 'token', endpoint };
|
|
514
|
+
}
|
|
515
|
+
if (payload.valid === false) {
|
|
516
|
+
return {
|
|
517
|
+
valid: false,
|
|
518
|
+
method: 'token',
|
|
519
|
+
endpoint,
|
|
520
|
+
reason: this.extractAuthErrorMessage(payload) || 'Token is invalid'
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
const normalizedError = this.normalizeServiceError(error);
|
|
526
|
+
const responsePayload = normalizedError.response?.data;
|
|
527
|
+
fallbackReason = this.extractAuthErrorMessage(responsePayload) || normalizedError.message || fallbackReason;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
valid: false,
|
|
532
|
+
method: 'token',
|
|
533
|
+
reason: fallbackReason
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
async verifyVendorKeyWithAuthGateway(vendorKey) {
|
|
537
|
+
const headers = {
|
|
538
|
+
'X-API-Key': vendorKey,
|
|
539
|
+
'X-Auth-Method': 'vendor_key',
|
|
540
|
+
'X-Project-Scope': 'lanonasis-maas'
|
|
541
|
+
};
|
|
542
|
+
let fallbackReason = 'Unable to verify API key with auth gateway';
|
|
543
|
+
// Primary check (required by auth contract): /v1/auth/verify
|
|
544
|
+
for (const endpoint of this.getAuthVerificationEndpoints('/v1/auth/verify')) {
|
|
545
|
+
try {
|
|
546
|
+
const response = await axios.post(endpoint, {}, {
|
|
547
|
+
headers,
|
|
548
|
+
timeout: 5000,
|
|
549
|
+
proxy: false
|
|
550
|
+
});
|
|
551
|
+
const payload = response.data;
|
|
552
|
+
if (payload.valid === true || Boolean(payload.payload)) {
|
|
553
|
+
return { valid: true, method: 'vendor_key', endpoint };
|
|
554
|
+
}
|
|
555
|
+
if (payload.valid === false) {
|
|
556
|
+
return {
|
|
557
|
+
valid: false,
|
|
558
|
+
method: 'vendor_key',
|
|
559
|
+
endpoint,
|
|
560
|
+
reason: this.extractAuthErrorMessage(payload) || 'API key is invalid'
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
const normalizedError = this.normalizeServiceError(error);
|
|
566
|
+
const responsePayload = normalizedError.response?.data;
|
|
567
|
+
const responseCode = typeof responsePayload?.code === 'string' ? responsePayload.code : undefined;
|
|
568
|
+
const reason = this.extractAuthErrorMessage(responsePayload) || normalizedError.message || fallbackReason;
|
|
569
|
+
fallbackReason = reason;
|
|
570
|
+
// If auth gateway explicitly rejected API key, stop early.
|
|
571
|
+
if ((normalizedError.response?.status === 401 || normalizedError.response?.status === 403) &&
|
|
572
|
+
responseCode &&
|
|
573
|
+
responseCode !== 'AUTH_TOKEN_MISSING') {
|
|
574
|
+
return { valid: false, method: 'vendor_key', endpoint, reason };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// Fallback for deployments where reverse proxies don't forward custom auth headers on /verify.
|
|
579
|
+
for (const endpoint of this.getAuthVerificationEndpoints('/v1/auth/verify-api-key')) {
|
|
580
|
+
try {
|
|
581
|
+
const response = await axios.post(endpoint, {}, {
|
|
582
|
+
headers,
|
|
583
|
+
timeout: 5000,
|
|
584
|
+
proxy: false
|
|
585
|
+
});
|
|
586
|
+
const payload = response.data;
|
|
587
|
+
if (payload.valid === true) {
|
|
588
|
+
return { valid: true, method: 'vendor_key', endpoint };
|
|
589
|
+
}
|
|
590
|
+
if (payload.valid === false) {
|
|
591
|
+
return {
|
|
592
|
+
valid: false,
|
|
593
|
+
method: 'vendor_key',
|
|
594
|
+
endpoint,
|
|
595
|
+
reason: this.extractAuthErrorMessage(payload) || 'API key is invalid'
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
const normalizedError = this.normalizeServiceError(error);
|
|
601
|
+
const responsePayload = normalizedError.response?.data;
|
|
602
|
+
fallbackReason = this.extractAuthErrorMessage(responsePayload) || normalizedError.message || fallbackReason;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
valid: false,
|
|
607
|
+
method: 'vendor_key',
|
|
608
|
+
reason: fallbackReason
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
async verifyCurrentCredentialsWithServer() {
|
|
612
|
+
await this.refreshTokenIfNeeded();
|
|
613
|
+
await this.discoverServices();
|
|
614
|
+
const token = this.getToken();
|
|
615
|
+
const vendorKey = await this.getVendorKeyAsync();
|
|
616
|
+
if (this.config.authMethod === 'vendor_key' && vendorKey) {
|
|
617
|
+
return this.verifyVendorKeyWithAuthGateway(vendorKey);
|
|
618
|
+
}
|
|
619
|
+
if (token) {
|
|
620
|
+
return this.verifyTokenWithAuthGateway(token);
|
|
621
|
+
}
|
|
622
|
+
if (vendorKey) {
|
|
623
|
+
return this.verifyVendorKeyWithAuthGateway(vendorKey);
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
valid: false,
|
|
627
|
+
method: 'none',
|
|
628
|
+
reason: 'No credentials configured'
|
|
629
|
+
};
|
|
630
|
+
}
|
|
437
631
|
// Manual endpoint override functionality
|
|
438
632
|
async setManualEndpoints(endpoints) {
|
|
439
633
|
if (!this.config.discoveredServices) {
|
|
@@ -520,16 +714,12 @@ export class CLIConfig {
|
|
|
520
714
|
return;
|
|
521
715
|
}
|
|
522
716
|
try {
|
|
523
|
-
// Import axios dynamically to avoid circular dependency
|
|
524
|
-
// Ensure service discovery is done
|
|
525
717
|
await this.discoverServices();
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
'X-Project-Scope': 'lanonasis-maas'
|
|
532
|
-
}, { timeout: 10000, proxy: false });
|
|
718
|
+
const verification = await this.verifyVendorKeyWithAuthGateway(vendorKey);
|
|
719
|
+
if (verification.valid) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
throw new Error(verification.reason || 'Authentication failed. The key may be invalid, expired, or revoked.');
|
|
533
723
|
}
|
|
534
724
|
catch (error) {
|
|
535
725
|
const normalizedError = this.normalizeServiceError(error);
|
|
@@ -701,21 +891,17 @@ export class CLIConfig {
|
|
|
701
891
|
}
|
|
702
892
|
// Vendor key not recently validated - verify with server
|
|
703
893
|
try {
|
|
704
|
-
await this.
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
'X-API-Key': vendorKey,
|
|
709
|
-
'X-Auth-Method': 'vendor_key',
|
|
710
|
-
'X-Project-Scope': 'lanonasis-maas'
|
|
711
|
-
}, { timeout: 5000, proxy: false });
|
|
894
|
+
const verification = await this.verifyVendorKeyWithAuthGateway(vendorKey);
|
|
895
|
+
if (!verification.valid) {
|
|
896
|
+
throw new Error(verification.reason || 'Vendor key validation failed');
|
|
897
|
+
}
|
|
712
898
|
// Update last validated timestamp on success
|
|
713
899
|
this.config.lastValidated = new Date().toISOString();
|
|
714
900
|
await this.save().catch(() => { }); // Don't fail auth check if save fails
|
|
715
901
|
this.authCheckCache = { isValid: true, timestamp: Date.now() };
|
|
716
902
|
return true;
|
|
717
903
|
}
|
|
718
|
-
catch
|
|
904
|
+
catch {
|
|
719
905
|
// Server validation failed - check for grace period (7 days offline)
|
|
720
906
|
const gracePeriod = 7 * 24 * 60 * 60 * 1000;
|
|
721
907
|
const withinGracePeriod = lastValidated &&
|
|
@@ -947,22 +1133,14 @@ export class CLIConfig {
|
|
|
947
1133
|
if (!vendorKey && !token) {
|
|
948
1134
|
return false;
|
|
949
1135
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (vendorKey) {
|
|
958
|
-
headers['X-API-Key'] = vendorKey;
|
|
959
|
-
headers['X-Auth-Method'] = 'vendor_key';
|
|
960
|
-
}
|
|
961
|
-
else if (token) {
|
|
962
|
-
headers['Authorization'] = `Bearer ${token}`;
|
|
963
|
-
headers['X-Auth-Method'] = 'jwt';
|
|
1136
|
+
const verification = this.config.authMethod === 'vendor_key' && vendorKey
|
|
1137
|
+
? await this.verifyVendorKeyWithAuthGateway(vendorKey)
|
|
1138
|
+
: token
|
|
1139
|
+
? await this.verifyTokenWithAuthGateway(token)
|
|
1140
|
+
: await this.verifyVendorKeyWithAuthGateway(vendorKey);
|
|
1141
|
+
if (!verification.valid) {
|
|
1142
|
+
throw new Error(verification.reason || 'Stored credentials are invalid');
|
|
964
1143
|
}
|
|
965
|
-
await this.pingAuthHealth(axios, authBase, headers);
|
|
966
1144
|
// Update last validated timestamp
|
|
967
1145
|
this.config.lastValidated = new Date().toISOString();
|
|
968
1146
|
await this.resetFailureCount();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/cli",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.6",
|
|
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",
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
"zod": "^3.24.4"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@jest/globals": "^
|
|
76
|
+
"@jest/globals": "^27.5.1",
|
|
77
77
|
"@types/cli-progress": "^3.11.6",
|
|
78
78
|
"@types/inquirer": "^9.0.7",
|
|
79
79
|
"@types/node": "^22.19.3",
|
|
80
80
|
"@types/ws": "^8.5.12",
|
|
81
81
|
"fast-check": "^3.15.1",
|
|
82
|
-
"jest": "^
|
|
83
|
-
"rimraf": "^
|
|
82
|
+
"jest": "^25.0.0",
|
|
83
|
+
"rimraf": "^6.1.3",
|
|
84
84
|
"ts-jest": "^29.1.1",
|
|
85
85
|
"typescript": "^5.7.2"
|
|
86
86
|
},
|