@lanonasis/cli 3.9.5 → 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 +49 -0
- package/README.md +28 -16
- package/dist/core/welcome.js +4 -4
- package/dist/index-simple.js +13 -1
- package/dist/index.js +130 -13
- 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 +25 -0
- package/dist/utils/api.js +161 -7
- package/dist/utils/config.d.ts +21 -1
- package/dist/utils/config.js +262 -47
- package/package.json +23 -23
|
@@ -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,10 +149,29 @@ 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;
|
|
174
|
+
private shouldUseLegacyMemoryRpcFallback;
|
|
156
175
|
constructor();
|
|
157
176
|
login(email: string, password: string): Promise<AuthResponse>;
|
|
158
177
|
register(email: string, password: string, organizationName?: string): Promise<AuthResponse>;
|
|
@@ -170,6 +189,12 @@ export declare class APIClient {
|
|
|
170
189
|
updateTopic(id: string, data: UpdateTopicRequest): Promise<MemoryTopic>;
|
|
171
190
|
deleteTopic(id: string): Promise<void>;
|
|
172
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>;
|
|
173
198
|
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
174
199
|
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
175
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
|
|
@@ -25,6 +27,21 @@ export class APIClient {
|
|
|
25
27
|
}
|
|
26
28
|
return payload;
|
|
27
29
|
}
|
|
30
|
+
shouldUseLegacyMemoryRpcFallback(error) {
|
|
31
|
+
const status = error?.response?.status;
|
|
32
|
+
const errorData = error?.response?.data;
|
|
33
|
+
const message = `${errorData?.error || ''} ${errorData?.message || ''}`.toLowerCase();
|
|
34
|
+
if (status === 405) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (status === 400 && message.includes('memory id is required')) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (status === 400 && message.includes('method not allowed')) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
28
45
|
constructor() {
|
|
29
46
|
this.config = new CLIConfig();
|
|
30
47
|
this.client = axios.create({
|
|
@@ -43,12 +60,14 @@ export class APIClient {
|
|
|
43
60
|
const authMethod = this.config.get('authMethod');
|
|
44
61
|
const vendorKey = await this.config.getVendorKeyAsync();
|
|
45
62
|
const token = this.config.getToken();
|
|
63
|
+
const isMemoryEndpoint = typeof config.url === 'string' && config.url.startsWith('/api/v1/memories');
|
|
46
64
|
const forceApiFromEnv = process.env.LANONASIS_FORCE_API === 'true'
|
|
47
65
|
|| process.env.CLI_FORCE_API === 'true'
|
|
48
66
|
|| process.env.ONASIS_FORCE_API === 'true';
|
|
49
67
|
const forceApiFromConfig = this.config.get('forceApi') === true
|
|
50
68
|
|| this.config.get('connectionTransport') === 'api';
|
|
51
|
-
|
|
69
|
+
// Memory CRUD/search endpoints should always use the API gateway path.
|
|
70
|
+
const forceDirectApi = forceApiFromEnv || forceApiFromConfig || isMemoryEndpoint;
|
|
52
71
|
const prefersTokenAuth = Boolean(token) && (authMethod === 'jwt' || authMethod === 'oauth' || authMethod === 'oauth2');
|
|
53
72
|
const useVendorKeyAuth = Boolean(vendorKey) && !prefersTokenAuth;
|
|
54
73
|
// Determine the correct API base URL:
|
|
@@ -131,11 +150,21 @@ export class APIClient {
|
|
|
131
150
|
if (error.response) {
|
|
132
151
|
const { status, data } = error.response;
|
|
133
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
|
+
}
|
|
134
160
|
console.error(chalk.red('✖ Authentication failed'));
|
|
135
161
|
console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
|
|
136
162
|
process.exit(1);
|
|
137
163
|
}
|
|
138
164
|
if (status === 403) {
|
|
165
|
+
if (this.noExit) {
|
|
166
|
+
return Promise.reject(error);
|
|
167
|
+
}
|
|
139
168
|
console.error(chalk.red('✖ Permission denied'));
|
|
140
169
|
if (data.message) {
|
|
141
170
|
console.error(chalk.gray(data.message));
|
|
@@ -183,11 +212,91 @@ export class APIClient {
|
|
|
183
212
|
return response.data;
|
|
184
213
|
}
|
|
185
214
|
catch (error) {
|
|
186
|
-
// Backward-compatible fallback: newer API contracts may reject GET list
|
|
215
|
+
// Backward-compatible fallback: newer API contracts may reject GET list.
|
|
187
216
|
if (error?.response?.status === 405) {
|
|
188
217
|
const limit = Number(params.limit || 20);
|
|
189
218
|
const page = Number(params.page || 1);
|
|
190
219
|
const offset = Number(params.offset ?? Math.max(0, (page - 1) * limit));
|
|
220
|
+
// Preferred fallback: POST list endpoint (avoids triggering vector search for plain listings).
|
|
221
|
+
const listPayload = {
|
|
222
|
+
limit,
|
|
223
|
+
offset
|
|
224
|
+
};
|
|
225
|
+
if (params.memory_type) {
|
|
226
|
+
listPayload.memory_type = params.memory_type;
|
|
227
|
+
}
|
|
228
|
+
if (params.tags) {
|
|
229
|
+
listPayload.tags = Array.isArray(params.tags)
|
|
230
|
+
? params.tags
|
|
231
|
+
: String(params.tags).split(',').map((tag) => tag.trim()).filter(Boolean);
|
|
232
|
+
}
|
|
233
|
+
if (params.topic_id) {
|
|
234
|
+
listPayload.topic_id = params.topic_id;
|
|
235
|
+
}
|
|
236
|
+
if (params.user_id) {
|
|
237
|
+
listPayload.user_id = params.user_id;
|
|
238
|
+
}
|
|
239
|
+
if (params.sort || params.sort_by) {
|
|
240
|
+
listPayload.sort_by = params.sort_by || params.sort;
|
|
241
|
+
}
|
|
242
|
+
if (params.order || params.sort_order) {
|
|
243
|
+
listPayload.sort_order = params.sort_order || params.order;
|
|
244
|
+
}
|
|
245
|
+
for (const endpoint of ['/api/v1/memories/list', '/api/v1/memory/list']) {
|
|
246
|
+
try {
|
|
247
|
+
const listResponse = await this.client.post(endpoint, listPayload);
|
|
248
|
+
const payload = listResponse.data || {};
|
|
249
|
+
const resultsArray = Array.isArray(payload.data)
|
|
250
|
+
? payload.data
|
|
251
|
+
: Array.isArray(payload.memories)
|
|
252
|
+
? payload.memories
|
|
253
|
+
: Array.isArray(payload.results)
|
|
254
|
+
? payload.results
|
|
255
|
+
: [];
|
|
256
|
+
const memories = resultsArray.map((entry) => this.normalizeMemoryEntry(entry));
|
|
257
|
+
const pagination = (payload.pagination && typeof payload.pagination === 'object')
|
|
258
|
+
? payload.pagination
|
|
259
|
+
: {};
|
|
260
|
+
const total = Number.isFinite(Number(pagination.total))
|
|
261
|
+
? Number(pagination.total)
|
|
262
|
+
: Number.isFinite(Number(payload.total))
|
|
263
|
+
? Number(payload.total)
|
|
264
|
+
: memories.length;
|
|
265
|
+
const pages = Number.isFinite(Number(pagination.total_pages))
|
|
266
|
+
? Number(pagination.total_pages)
|
|
267
|
+
: Number.isFinite(Number(pagination.pages))
|
|
268
|
+
? Number(pagination.pages)
|
|
269
|
+
: Math.max(1, Math.ceil(total / limit));
|
|
270
|
+
const currentPage = Number.isFinite(Number(pagination.page))
|
|
271
|
+
? Number(pagination.page)
|
|
272
|
+
: Math.max(1, Math.floor(offset / limit) + 1);
|
|
273
|
+
const hasMore = typeof pagination.has_more === 'boolean'
|
|
274
|
+
? pagination.has_more
|
|
275
|
+
: typeof pagination.has_next === 'boolean'
|
|
276
|
+
? pagination.has_next
|
|
277
|
+
: (offset + memories.length) < total;
|
|
278
|
+
return {
|
|
279
|
+
...payload,
|
|
280
|
+
data: memories,
|
|
281
|
+
memories,
|
|
282
|
+
pagination: {
|
|
283
|
+
total,
|
|
284
|
+
limit,
|
|
285
|
+
offset,
|
|
286
|
+
has_more: hasMore,
|
|
287
|
+
page: currentPage,
|
|
288
|
+
pages
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
catch (listError) {
|
|
293
|
+
if (listError?.response?.status === 404 || listError?.response?.status === 405) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
throw listError;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Secondary fallback: search endpoint for legacy contracts that expose only search.
|
|
191
300
|
const searchPayload = {
|
|
192
301
|
query: '*',
|
|
193
302
|
limit,
|
|
@@ -251,15 +360,51 @@ export class APIClient {
|
|
|
251
360
|
}
|
|
252
361
|
}
|
|
253
362
|
async getMemory(id) {
|
|
254
|
-
|
|
255
|
-
|
|
363
|
+
try {
|
|
364
|
+
const response = await this.client.get(`/api/v1/memories/${id}`);
|
|
365
|
+
return this.normalizeMemoryEntry(response.data);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
369
|
+
const fallback = await this.client.post('/api/v1/memory/get', { id });
|
|
370
|
+
const payload = fallback.data && typeof fallback.data === 'object'
|
|
371
|
+
? fallback.data.data ?? fallback.data
|
|
372
|
+
: fallback.data;
|
|
373
|
+
return this.normalizeMemoryEntry(payload);
|
|
374
|
+
}
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
256
377
|
}
|
|
257
378
|
async updateMemory(id, data) {
|
|
258
|
-
|
|
259
|
-
|
|
379
|
+
try {
|
|
380
|
+
const response = await this.client.put(`/api/v1/memories/${id}`, data);
|
|
381
|
+
return this.normalizeMemoryEntry(response.data);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
385
|
+
const fallback = await this.client.post('/api/v1/memory/update', {
|
|
386
|
+
id,
|
|
387
|
+
...data
|
|
388
|
+
});
|
|
389
|
+
const payload = fallback.data && typeof fallback.data === 'object'
|
|
390
|
+
? fallback.data.data ?? fallback.data
|
|
391
|
+
: fallback.data;
|
|
392
|
+
return this.normalizeMemoryEntry(payload);
|
|
393
|
+
}
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
260
396
|
}
|
|
261
397
|
async deleteMemory(id) {
|
|
262
|
-
|
|
398
|
+
try {
|
|
399
|
+
await this.client.delete(`/api/v1/memories/${id}`);
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
if (this.shouldUseLegacyMemoryRpcFallback(error)) {
|
|
403
|
+
await this.client.post('/api/v1/memory/delete', { id });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
263
408
|
}
|
|
264
409
|
async searchMemories(query, options = {}) {
|
|
265
410
|
const response = await this.client.post('/api/v1/memories/search', {
|
|
@@ -303,6 +448,15 @@ export class APIClient {
|
|
|
303
448
|
const response = await this.client.get('/health');
|
|
304
449
|
return response.data;
|
|
305
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
|
+
}
|
|
306
460
|
// Generic HTTP methods
|
|
307
461
|
async get(url, config) {
|
|
308
462
|
const response = await this.client.get(url, config);
|
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,11 +76,25 @@ 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;
|
|
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>;
|
|
75
93
|
clearManualEndpointOverrides(): Promise<void>;
|
|
76
94
|
getDiscoveredApiUrl(): string;
|
|
77
|
-
setVendorKey(vendorKey: string
|
|
95
|
+
setVendorKey(vendorKey: string, options?: {
|
|
96
|
+
skipServerValidation?: boolean;
|
|
97
|
+
}): Promise<void>;
|
|
78
98
|
validateVendorKeyFormat(vendorKey: string): string | boolean;
|
|
79
99
|
private validateVendorKeyWithServer;
|
|
80
100
|
getVendorKey(): string | undefined;
|