@lanonasis/cli 3.9.6 → 3.9.7
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 +34 -0
- package/README.md +20 -4
- package/dist/core/welcome.js +4 -4
- package/dist/index.js +117 -12
- package/dist/mcp/schemas/tool-schemas.d.ts +186 -538
- package/dist/mcp/schemas/tool-schemas.js +7 -7
- package/dist/utils/api.d.ts +24 -0
- package/dist/utils/api.js +21 -0
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.js +53 -16
- package/package.json +22 -22
|
@@ -22,7 +22,7 @@ export const MemoryCreateSchema = z.object({
|
|
|
22
22
|
.uuid()
|
|
23
23
|
.optional()
|
|
24
24
|
.describe("Optional topic ID for organization"),
|
|
25
|
-
metadata: z.record(z.any())
|
|
25
|
+
metadata: z.record(z.string(), z.any())
|
|
26
26
|
.optional()
|
|
27
27
|
.describe("Additional metadata")
|
|
28
28
|
});
|
|
@@ -71,7 +71,7 @@ export const MemoryUpdateSchema = z.object({
|
|
|
71
71
|
tags: z.array(z.string())
|
|
72
72
|
.optional()
|
|
73
73
|
.describe("New tags (replaces existing)"),
|
|
74
|
-
metadata: z.record(z.any())
|
|
74
|
+
metadata: z.record(z.string(), z.any())
|
|
75
75
|
.optional()
|
|
76
76
|
.describe("New metadata (merges with existing)")
|
|
77
77
|
});
|
|
@@ -229,7 +229,7 @@ export const BulkOperationSchema = z.object({
|
|
|
229
229
|
.describe("Bulk operation type"),
|
|
230
230
|
entity_type: z.enum(["memory", "topic", "apikey"])
|
|
231
231
|
.describe("Entity type for bulk operation"),
|
|
232
|
-
items: z.array(z.record(z.any()))
|
|
232
|
+
items: z.array(z.record(z.string(), z.any()))
|
|
233
233
|
.min(1)
|
|
234
234
|
.max(100)
|
|
235
235
|
.describe("Items for bulk operation"),
|
|
@@ -249,7 +249,7 @@ export const ImportExportSchema = z.object({
|
|
|
249
249
|
file_path: z.string()
|
|
250
250
|
.optional()
|
|
251
251
|
.describe("File path for import/export"),
|
|
252
|
-
filters: z.record(z.any())
|
|
252
|
+
filters: z.record(z.string(), z.any())
|
|
253
253
|
.optional()
|
|
254
254
|
.describe("Filters for export")
|
|
255
255
|
});
|
|
@@ -257,7 +257,7 @@ export const ImportExportSchema = z.object({
|
|
|
257
257
|
export const ToolExecutionSchema = z.object({
|
|
258
258
|
tool_name: z.string()
|
|
259
259
|
.describe("Name of the tool to execute"),
|
|
260
|
-
arguments: z.record(z.any())
|
|
260
|
+
arguments: z.record(z.string(), z.any())
|
|
261
261
|
.describe("Tool arguments"),
|
|
262
262
|
timeout: z.number()
|
|
263
263
|
.positive()
|
|
@@ -308,7 +308,7 @@ export class SchemaValidator {
|
|
|
308
308
|
}
|
|
309
309
|
catch (error) {
|
|
310
310
|
if (error instanceof z.ZodError) {
|
|
311
|
-
throw new Error(`Validation error: ${error.
|
|
311
|
+
throw new Error(`Validation error: ${error.issues
|
|
312
312
|
.map(e => `${e.path.join('.')}: ${e.message}`)
|
|
313
313
|
.join(', ')}`);
|
|
314
314
|
}
|
|
@@ -326,7 +326,7 @@ export class SchemaValidator {
|
|
|
326
326
|
else {
|
|
327
327
|
return {
|
|
328
328
|
success: false,
|
|
329
|
-
errors: result.error.
|
|
329
|
+
errors: result.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)
|
|
330
330
|
};
|
|
331
331
|
}
|
|
332
332
|
}
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -149,9 +149,27 @@ export interface ApiErrorResponse {
|
|
|
149
149
|
status_code: number;
|
|
150
150
|
details?: Record<string, unknown>;
|
|
151
151
|
}
|
|
152
|
+
export interface UserProfile {
|
|
153
|
+
id: string;
|
|
154
|
+
email: string;
|
|
155
|
+
name: string | null;
|
|
156
|
+
avatar_url: string | null;
|
|
157
|
+
role: string;
|
|
158
|
+
provider: string | null;
|
|
159
|
+
project_scope: string | null;
|
|
160
|
+
platform: string | null;
|
|
161
|
+
created_at: string | null;
|
|
162
|
+
last_sign_in_at: string | null;
|
|
163
|
+
metadata?: {
|
|
164
|
+
locale: string | null;
|
|
165
|
+
timezone: string | null;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
152
168
|
export declare class APIClient {
|
|
153
169
|
private client;
|
|
154
170
|
private config;
|
|
171
|
+
/** When true, throw on 401/403 instead of printing+exiting (for callers that handle errors) */
|
|
172
|
+
noExit: boolean;
|
|
155
173
|
private normalizeMemoryEntry;
|
|
156
174
|
private shouldUseLegacyMemoryRpcFallback;
|
|
157
175
|
constructor();
|
|
@@ -171,6 +189,12 @@ export declare class APIClient {
|
|
|
171
189
|
updateTopic(id: string, data: UpdateTopicRequest): Promise<MemoryTopic>;
|
|
172
190
|
deleteTopic(id: string): Promise<void>;
|
|
173
191
|
getHealth(): Promise<HealthStatus>;
|
|
192
|
+
/**
|
|
193
|
+
* Fetch the current user's profile from the auth gateway (GET /v1/auth/me).
|
|
194
|
+
* Works for all auth methods: OAuth Bearer token, vendor key (X-API-Key), and JWT.
|
|
195
|
+
* The /auth/ prefix causes the request interceptor to route this to auth_base.
|
|
196
|
+
*/
|
|
197
|
+
getUserProfile(): Promise<UserProfile>;
|
|
174
198
|
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
175
199
|
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
176
200
|
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
package/dist/utils/api.js
CHANGED
|
@@ -5,6 +5,8 @@ import { CLIConfig } from './config.js';
|
|
|
5
5
|
export class APIClient {
|
|
6
6
|
client;
|
|
7
7
|
config;
|
|
8
|
+
/** When true, throw on 401/403 instead of printing+exiting (for callers that handle errors) */
|
|
9
|
+
noExit = false;
|
|
8
10
|
normalizeMemoryEntry(payload) {
|
|
9
11
|
// API responses are inconsistent across gateways:
|
|
10
12
|
// - Some return the memory entry directly
|
|
@@ -148,11 +150,21 @@ export class APIClient {
|
|
|
148
150
|
if (error.response) {
|
|
149
151
|
const { status, data } = error.response;
|
|
150
152
|
if (status === 401) {
|
|
153
|
+
// Invalidate the local auth cache so the next isAuthenticated() call
|
|
154
|
+
// performs a fresh server check rather than returning a stale result.
|
|
155
|
+
this.config.invalidateAuthCache().catch(() => { });
|
|
156
|
+
if (this.noExit) {
|
|
157
|
+
// Caller handles the error (e.g. auth status probe) — throw so try/catch fires
|
|
158
|
+
return Promise.reject(error);
|
|
159
|
+
}
|
|
151
160
|
console.error(chalk.red('✖ Authentication failed'));
|
|
152
161
|
console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
|
|
153
162
|
process.exit(1);
|
|
154
163
|
}
|
|
155
164
|
if (status === 403) {
|
|
165
|
+
if (this.noExit) {
|
|
166
|
+
return Promise.reject(error);
|
|
167
|
+
}
|
|
156
168
|
console.error(chalk.red('✖ Permission denied'));
|
|
157
169
|
if (data.message) {
|
|
158
170
|
console.error(chalk.gray(data.message));
|
|
@@ -436,6 +448,15 @@ export class APIClient {
|
|
|
436
448
|
const response = await this.client.get('/health');
|
|
437
449
|
return response.data;
|
|
438
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Fetch the current user's profile from the auth gateway (GET /v1/auth/me).
|
|
453
|
+
* Works for all auth methods: OAuth Bearer token, vendor key (X-API-Key), and JWT.
|
|
454
|
+
* The /auth/ prefix causes the request interceptor to route this to auth_base.
|
|
455
|
+
*/
|
|
456
|
+
async getUserProfile() {
|
|
457
|
+
const response = await this.client.get('/v1/auth/me');
|
|
458
|
+
return response.data;
|
|
459
|
+
}
|
|
439
460
|
// Generic HTTP methods
|
|
440
461
|
async get(url, config) {
|
|
441
462
|
const response = await this.client.get(url, config);
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -83,9 +83,18 @@ export declare class CLIConfig {
|
|
|
83
83
|
verifyCurrentCredentialsWithServer(): Promise<RemoteAuthVerification>;
|
|
84
84
|
setManualEndpoints(endpoints: Partial<CLIConfigData['discoveredServices']>): Promise<void>;
|
|
85
85
|
hasManualEndpointOverrides(): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Clears the in-memory auth cache and removes the `lastValidated` timestamp.
|
|
88
|
+
* Called after a definitive 401 from the memory API so that the next
|
|
89
|
+
* `isAuthenticated()` call performs a fresh server verification rather than
|
|
90
|
+
* returning a stale cached result.
|
|
91
|
+
*/
|
|
92
|
+
invalidateAuthCache(): Promise<void>;
|
|
86
93
|
clearManualEndpointOverrides(): Promise<void>;
|
|
87
94
|
getDiscoveredApiUrl(): string;
|
|
88
|
-
setVendorKey(vendorKey: string
|
|
95
|
+
setVendorKey(vendorKey: string, options?: {
|
|
96
|
+
skipServerValidation?: boolean;
|
|
97
|
+
}): Promise<void>;
|
|
89
98
|
validateVendorKeyFormat(vendorKey: string): string | boolean;
|
|
90
99
|
private validateVendorKeyWithServer;
|
|
91
100
|
getVendorKey(): string | undefined;
|
package/dist/utils/config.js
CHANGED
|
@@ -190,6 +190,10 @@ export class CLIConfig {
|
|
|
190
190
|
}
|
|
191
191
|
// Enhanced Service Discovery Integration
|
|
192
192
|
async discoverServices(verbose = false) {
|
|
193
|
+
// Honour manually configured endpoints — skip auto-discovery so we don't clobber them.
|
|
194
|
+
if (this.config.manualEndpointOverrides) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
193
197
|
const isTestEnvironment = process.env.NODE_ENV === 'test';
|
|
194
198
|
const forceDiscovery = process.env.FORCE_SERVICE_DISCOVERY === 'true';
|
|
195
199
|
if ((isTestEnvironment && !forceDiscovery) || process.env.SKIP_SERVICE_DISCOVERY === 'true') {
|
|
@@ -534,6 +538,16 @@ export class CLIConfig {
|
|
|
534
538
|
};
|
|
535
539
|
}
|
|
536
540
|
async verifyVendorKeyWithAuthGateway(vendorKey) {
|
|
541
|
+
// Detect whether the stored "vendor key" is actually an OAuth/JWT access token
|
|
542
|
+
// (3-part base64url string separated by dots). OAuth tokens must be verified via the
|
|
543
|
+
// Bearer token path (/v1/auth/verify-token), not as API keys (/v1/auth/verify-api-key),
|
|
544
|
+
// because they are not stored in the api_keys table.
|
|
545
|
+
const isJwtFormat = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(vendorKey.trim());
|
|
546
|
+
if (isJwtFormat) {
|
|
547
|
+
// Delegate to token verification — the auth gateway's UAI router accepts Bearer tokens
|
|
548
|
+
// on /v1/auth/verify (requireAuth) and /v1/auth/verify-token (public, body-based).
|
|
549
|
+
return this.verifyTokenWithAuthGateway(vendorKey);
|
|
550
|
+
}
|
|
537
551
|
const headers = {
|
|
538
552
|
'X-API-Key': vendorKey,
|
|
539
553
|
'X-Auth-Method': 'vendor_key',
|
|
@@ -655,6 +669,17 @@ export class CLIConfig {
|
|
|
655
669
|
hasManualEndpointOverrides() {
|
|
656
670
|
return !!this.config.manualEndpointOverrides;
|
|
657
671
|
}
|
|
672
|
+
/**
|
|
673
|
+
* Clears the in-memory auth cache and removes the `lastValidated` timestamp.
|
|
674
|
+
* Called after a definitive 401 from the memory API so that the next
|
|
675
|
+
* `isAuthenticated()` call performs a fresh server verification rather than
|
|
676
|
+
* returning a stale cached result.
|
|
677
|
+
*/
|
|
678
|
+
async invalidateAuthCache() {
|
|
679
|
+
this.authCheckCache = null;
|
|
680
|
+
delete this.config.lastValidated;
|
|
681
|
+
await this.save().catch(() => { });
|
|
682
|
+
}
|
|
658
683
|
async clearManualEndpointOverrides() {
|
|
659
684
|
delete this.config.manualEndpointOverrides;
|
|
660
685
|
delete this.config.lastManualEndpointUpdate;
|
|
@@ -667,15 +692,20 @@ export class CLIConfig {
|
|
|
667
692
|
'https://auth.lanonasis.com';
|
|
668
693
|
}
|
|
669
694
|
// Enhanced authentication support
|
|
670
|
-
async setVendorKey(vendorKey) {
|
|
695
|
+
async setVendorKey(vendorKey, options = {}) {
|
|
671
696
|
const trimmedKey = typeof vendorKey === 'string' ? vendorKey.trim() : '';
|
|
672
697
|
// Minimal format validation (non-empty); rely on server-side checks for everything else
|
|
673
698
|
const formatValidation = this.validateVendorKeyFormat(trimmedKey);
|
|
674
699
|
if (formatValidation !== true) {
|
|
675
700
|
throw new Error(typeof formatValidation === 'string' ? formatValidation : 'Vendor key is invalid');
|
|
676
701
|
}
|
|
677
|
-
//
|
|
678
|
-
|
|
702
|
+
// Skip server-side validation when the caller already holds a valid auth credential
|
|
703
|
+
// (e.g. an OAuth access token being stored for MCP/API access — auth-gateway won't
|
|
704
|
+
// recognise it as a vendor key even though the memory API accepts it).
|
|
705
|
+
const isOAuthContext = ['oauth', 'oauth2'].includes(this.config.authMethod || '');
|
|
706
|
+
if (!options.skipServerValidation && !isOAuthContext) {
|
|
707
|
+
await this.validateVendorKeyWithServer(trimmedKey);
|
|
708
|
+
}
|
|
679
709
|
// Initialize and store using ApiKeyStorage from @lanonasis/oauth-client
|
|
680
710
|
// This handles encryption automatically (AES-256-GCM with machine-derived key)
|
|
681
711
|
await this.apiKeyStorage.initialize();
|
|
@@ -877,23 +907,21 @@ export class CLIConfig {
|
|
|
877
907
|
const vendorKey = await this.getVendorKeyAsync();
|
|
878
908
|
if (!vendorKey)
|
|
879
909
|
return false;
|
|
880
|
-
// Check cache first
|
|
910
|
+
// Check in-memory cache first (5-minute TTL)
|
|
881
911
|
if (this.authCheckCache && (Date.now() - this.authCheckCache.timestamp) < this.AUTH_CACHE_TTL) {
|
|
882
912
|
return this.authCheckCache.isValid;
|
|
883
913
|
}
|
|
884
|
-
//
|
|
914
|
+
// Track lastValidated for the offline grace period used in the catch block.
|
|
915
|
+
// The 24-hour skip-server-validation gate has been removed: it allowed expired/revoked
|
|
916
|
+
// keys to appear valid as long as they had been validated within the past day.
|
|
885
917
|
const lastValidated = this.config.lastValidated;
|
|
886
|
-
|
|
887
|
-
(Date.now() - new Date(lastValidated).getTime()) < (24 * 60 * 60 * 1000);
|
|
888
|
-
if (recentlyValidated) {
|
|
889
|
-
this.authCheckCache = { isValid: true, timestamp: Date.now() };
|
|
890
|
-
return true;
|
|
891
|
-
}
|
|
892
|
-
// Vendor key not recently validated - verify with server
|
|
918
|
+
// Verify with server on every cache-miss
|
|
893
919
|
try {
|
|
894
920
|
const verification = await this.verifyVendorKeyWithAuthGateway(vendorKey);
|
|
895
921
|
if (!verification.valid) {
|
|
896
|
-
|
|
922
|
+
// Auth gateway explicitly rejected the key — no grace period applies.
|
|
923
|
+
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
924
|
+
return false;
|
|
897
925
|
}
|
|
898
926
|
// Update last validated timestamp on success
|
|
899
927
|
this.config.lastValidated = new Date().toISOString();
|
|
@@ -901,8 +929,18 @@ export class CLIConfig {
|
|
|
901
929
|
this.authCheckCache = { isValid: true, timestamp: Date.now() };
|
|
902
930
|
return true;
|
|
903
931
|
}
|
|
904
|
-
catch {
|
|
905
|
-
//
|
|
932
|
+
catch (err) {
|
|
933
|
+
// verifyVendorKeyWithAuthGateway throws only on network/timeout errors
|
|
934
|
+
// (explicit 401/403 returns {valid:false} without throwing).
|
|
935
|
+
// Apply the 7-day offline grace ONLY for genuine network failures.
|
|
936
|
+
const normalizedErr = this.normalizeServiceError(err);
|
|
937
|
+
const httpStatus = normalizedErr.response?.status ?? 0;
|
|
938
|
+
if (httpStatus === 401 || httpStatus === 403) {
|
|
939
|
+
// Explicit auth rejection propagated as an exception — definitely invalid.
|
|
940
|
+
this.authCheckCache = { isValid: false, timestamp: Date.now() };
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
// Network / server error — apply offline grace period
|
|
906
944
|
const gracePeriod = 7 * 24 * 60 * 60 * 1000;
|
|
907
945
|
const withinGracePeriod = lastValidated &&
|
|
908
946
|
(Date.now() - new Date(lastValidated).getTime()) < gracePeriod;
|
|
@@ -913,7 +951,6 @@ export class CLIConfig {
|
|
|
913
951
|
this.authCheckCache = { isValid: true, timestamp: Date.now() };
|
|
914
952
|
return true;
|
|
915
953
|
}
|
|
916
|
-
// Grace period expired - require server validation
|
|
917
954
|
if (process.env.CLI_VERBOSE === 'true') {
|
|
918
955
|
console.warn('⚠️ Vendor key validation failed and grace period expired');
|
|
919
956
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/cli",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.7",
|
|
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,37 +52,37 @@
|
|
|
52
52
|
"CHANGELOG.md"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@lanonasis/oauth-client": "
|
|
55
|
+
"@lanonasis/oauth-client": "2.0.0",
|
|
56
56
|
"@lanonasis/security-sdk": "1.0.5",
|
|
57
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
58
|
-
"axios": "^1.
|
|
59
|
-
"chalk": "^5.
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
58
|
+
"axios": "^1.13.5",
|
|
59
|
+
"chalk": "^5.6.2",
|
|
60
60
|
"cli-progress": "^3.12.0",
|
|
61
|
-
"cli-table3": "^0.6.
|
|
62
|
-
"commander": "^
|
|
61
|
+
"cli-table3": "^0.6.5",
|
|
62
|
+
"commander": "^14.0.3",
|
|
63
63
|
"date-fns": "^4.1.0",
|
|
64
|
-
"dotenv": "^
|
|
65
|
-
"eventsource": "^4.
|
|
66
|
-
"inquirer": "^
|
|
64
|
+
"dotenv": "^17.3.1",
|
|
65
|
+
"eventsource": "^4.1.0",
|
|
66
|
+
"inquirer": "^13.2.5",
|
|
67
67
|
"jwt-decode": "^4.0.0",
|
|
68
|
-
"open": "^
|
|
69
|
-
"ora": "^
|
|
68
|
+
"open": "^11.0.0",
|
|
69
|
+
"ora": "^9.3.0",
|
|
70
70
|
"table": "^6.9.0",
|
|
71
71
|
"word-wrap": "^1.2.5",
|
|
72
|
-
"ws": "^8.
|
|
73
|
-
"zod": "^3.
|
|
72
|
+
"ws": "^8.19.0",
|
|
73
|
+
"zod": "^4.3.6"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@jest/globals": "^
|
|
76
|
+
"@jest/globals": "^30.2.0",
|
|
77
77
|
"@types/cli-progress": "^3.11.6",
|
|
78
|
-
"@types/inquirer": "^9.0.
|
|
79
|
-
"@types/node": "^
|
|
80
|
-
"@types/ws": "^8.
|
|
81
|
-
"fast-check": "^
|
|
82
|
-
"jest": "^
|
|
78
|
+
"@types/inquirer": "^9.0.9",
|
|
79
|
+
"@types/node": "^25.3.0",
|
|
80
|
+
"@types/ws": "^8.18.1",
|
|
81
|
+
"fast-check": "^4.5.3",
|
|
82
|
+
"jest": "^30.2.0",
|
|
83
83
|
"rimraf": "^6.1.3",
|
|
84
|
-
"ts-jest": "^29.
|
|
85
|
-
"typescript": "^5.
|
|
84
|
+
"ts-jest": "^29.4.6",
|
|
85
|
+
"typescript": "^5.9.3"
|
|
86
86
|
},
|
|
87
87
|
"scripts": {
|
|
88
88
|
"build": "rimraf dist && tsc -p tsconfig.json",
|