@lanonasis/memory-client 2.0.0 → 2.1.0

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/dist/index.js CHANGED
@@ -1,6 +1,224 @@
1
1
  'use strict';
2
2
 
3
3
  var zod = require('zod');
4
+ var child_process = require('child_process');
5
+ var util = require('util');
6
+
7
+ /**
8
+ * Memory types supported by the service
9
+ */
10
+ const MEMORY_TYPES = ['context', 'project', 'knowledge', 'reference', 'personal', 'workflow'];
11
+ /**
12
+ * Memory status values
13
+ */
14
+ const MEMORY_STATUSES = ['active', 'archived', 'draft', 'deleted'];
15
+ /**
16
+ * Validation schemas using Zod
17
+ */
18
+ const createMemorySchema = zod.z.object({
19
+ title: zod.z.string().min(1).max(500),
20
+ content: zod.z.string().min(1).max(50000),
21
+ summary: zod.z.string().max(1000).optional(),
22
+ memory_type: zod.z.enum(MEMORY_TYPES).default('context'),
23
+ topic_id: zod.z.string().uuid().optional(),
24
+ project_ref: zod.z.string().max(100).optional(),
25
+ tags: zod.z.array(zod.z.string().min(1).max(50)).max(20).default([]),
26
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
27
+ });
28
+ const updateMemorySchema = zod.z.object({
29
+ title: zod.z.string().min(1).max(500).optional(),
30
+ content: zod.z.string().min(1).max(50000).optional(),
31
+ summary: zod.z.string().max(1000).optional(),
32
+ memory_type: zod.z.enum(MEMORY_TYPES).optional(),
33
+ status: zod.z.enum(MEMORY_STATUSES).optional(),
34
+ topic_id: zod.z.string().uuid().nullable().optional(),
35
+ project_ref: zod.z.string().max(100).nullable().optional(),
36
+ tags: zod.z.array(zod.z.string().min(1).max(50)).max(20).optional(),
37
+ metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
38
+ });
39
+ const searchMemorySchema = zod.z.object({
40
+ query: zod.z.string().min(1).max(1000),
41
+ memory_types: zod.z.array(zod.z.enum(MEMORY_TYPES)).optional(),
42
+ tags: zod.z.array(zod.z.string()).optional(),
43
+ topic_id: zod.z.string().uuid().optional(),
44
+ project_ref: zod.z.string().optional(),
45
+ status: zod.z.enum(MEMORY_STATUSES).default('active'),
46
+ limit: zod.z.number().int().min(1).max(100).default(20),
47
+ threshold: zod.z.number().min(0).max(1).default(0.7)
48
+ });
49
+ const createTopicSchema = zod.z.object({
50
+ name: zod.z.string().min(1).max(100),
51
+ description: zod.z.string().max(500).optional(),
52
+ color: zod.z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
53
+ icon: zod.z.string().max(50).optional(),
54
+ parent_topic_id: zod.z.string().uuid().optional()
55
+ });
56
+ // ========================================
57
+ // Intelligence Feature Types (v2.0)
58
+ // ========================================
59
+ /**
60
+ * Chunking strategies for content preprocessing
61
+ */
62
+ const CHUNKING_STRATEGIES = ['semantic', 'fixed-size', 'paragraph', 'sentence', 'code-block'];
63
+ /**
64
+ * Content types detected or specified
65
+ */
66
+ const CONTENT_TYPES = ['text', 'code', 'markdown', 'json', 'yaml'];
67
+ // ========================================
68
+ // Enhanced Search Types
69
+ // ========================================
70
+ /**
71
+ * Search modes for memory queries
72
+ */
73
+ const SEARCH_MODES = ['vector', 'text', 'hybrid'];
74
+ // ========================================
75
+ // Validation Schemas for Intelligence
76
+ // ========================================
77
+ const preprocessingOptionsSchema = zod.z.object({
78
+ chunking: zod.z.object({
79
+ strategy: zod.z.enum(CHUNKING_STRATEGIES).optional(),
80
+ maxChunkSize: zod.z.number().int().min(100).max(10000).optional(),
81
+ overlap: zod.z.number().int().min(0).max(500).optional()
82
+ }).optional(),
83
+ cleanContent: zod.z.boolean().optional(),
84
+ extractMetadata: zod.z.boolean().optional()
85
+ }).optional();
86
+ const enhancedSearchSchema = zod.z.object({
87
+ query: zod.z.string().min(1).max(1000),
88
+ type: zod.z.enum(MEMORY_TYPES).optional(),
89
+ threshold: zod.z.number().min(0).max(1).default(0.7),
90
+ limit: zod.z.number().int().min(1).max(100).default(20),
91
+ search_mode: zod.z.enum(SEARCH_MODES).default('hybrid'),
92
+ filters: zod.z.object({
93
+ tags: zod.z.array(zod.z.string()).optional(),
94
+ project_id: zod.z.string().uuid().optional(),
95
+ topic_id: zod.z.string().uuid().optional(),
96
+ date_range: zod.z.object({
97
+ from: zod.z.string().optional(),
98
+ to: zod.z.string().optional()
99
+ }).optional()
100
+ }).optional(),
101
+ include_chunks: zod.z.boolean().default(false)
102
+ });
103
+ const analyticsDateRangeSchema = zod.z.object({
104
+ from: zod.z.string().optional(),
105
+ to: zod.z.string().optional(),
106
+ group_by: zod.z.enum(['day', 'week', 'month']).default('day')
107
+ });
108
+
109
+ /**
110
+ * Core Utilities for Memory Client
111
+ * Browser-safe, no Node.js dependencies
112
+ */
113
+ /**
114
+ * Safely parse JSON with detailed error reporting
115
+ * Prevents scattered try/catch blocks throughout the codebase
116
+ */
117
+ function safeJsonParse(input) {
118
+ try {
119
+ const data = JSON.parse(input);
120
+ return { success: true, data };
121
+ }
122
+ catch (error) {
123
+ const message = error instanceof Error
124
+ ? error.message
125
+ : 'Unknown JSON parse error';
126
+ return { success: false, error: `Invalid JSON: ${message}` };
127
+ }
128
+ }
129
+ /**
130
+ * HTTP status code to error code mapping
131
+ */
132
+ function httpStatusToErrorCode(status) {
133
+ switch (status) {
134
+ case 400:
135
+ return 'VALIDATION_ERROR';
136
+ case 401:
137
+ return 'AUTH_ERROR';
138
+ case 403:
139
+ return 'FORBIDDEN';
140
+ case 404:
141
+ return 'NOT_FOUND';
142
+ case 408:
143
+ return 'TIMEOUT_ERROR';
144
+ case 409:
145
+ return 'CONFLICT';
146
+ case 429:
147
+ return 'RATE_LIMIT_ERROR';
148
+ case 500:
149
+ case 502:
150
+ case 503:
151
+ case 504:
152
+ return 'SERVER_ERROR';
153
+ default:
154
+ return 'API_ERROR';
155
+ }
156
+ }
157
+ /**
158
+ * Create a standardized error response from various error sources
159
+ */
160
+ function createErrorResponse(message, code = 'API_ERROR', statusCode, details) {
161
+ return {
162
+ code,
163
+ message,
164
+ statusCode,
165
+ details,
166
+ timestamp: new Date().toISOString()
167
+ };
168
+ }
169
+ /**
170
+ * Create an error response from an HTTP response
171
+ */
172
+ function createErrorFromResponse(status, statusText, body) {
173
+ const code = httpStatusToErrorCode(status);
174
+ // Try to extract message from response body
175
+ let message = `HTTP ${status}: ${statusText}`;
176
+ let details = undefined;
177
+ if (body && typeof body === 'object') {
178
+ const bodyObj = body;
179
+ if (typeof bodyObj.error === 'string') {
180
+ message = bodyObj.error;
181
+ }
182
+ else if (typeof bodyObj.message === 'string') {
183
+ message = bodyObj.message;
184
+ }
185
+ if (bodyObj.details) {
186
+ details = bodyObj.details;
187
+ }
188
+ }
189
+ return createErrorResponse(message, code, status, details);
190
+ }
191
+ /**
192
+ * Sleep utility for retry logic
193
+ */
194
+ function sleep(ms) {
195
+ return new Promise(resolve => setTimeout(resolve, ms));
196
+ }
197
+ /**
198
+ * Calculate retry delay with exponential backoff and jitter
199
+ */
200
+ function calculateRetryDelay(attempt, baseDelay = 1000, backoff = 'exponential', maxDelay = 30000) {
201
+ let delay;
202
+ if (backoff === 'exponential') {
203
+ delay = baseDelay * Math.pow(2, attempt);
204
+ }
205
+ else {
206
+ delay = baseDelay * (attempt + 1);
207
+ }
208
+ // Add jitter (±20%) to prevent thundering herd
209
+ const jitter = delay * 0.2 * (Math.random() * 2 - 1);
210
+ delay = Math.min(delay + jitter, maxDelay);
211
+ return Math.round(delay);
212
+ }
213
+ /**
214
+ * Check if an error is retryable based on status code
215
+ */
216
+ function isRetryableError(statusCode) {
217
+ if (!statusCode)
218
+ return true; // Network errors are retryable
219
+ // Retry on server errors and rate limits
220
+ return statusCode >= 500 || statusCode === 429 || statusCode === 408;
221
+ }
4
222
 
5
223
  /**
6
224
  * Core Memory Client - Pure Browser-Safe Implementation
@@ -10,6 +228,18 @@ var zod = require('zod');
10
228
  *
11
229
  * Bundle size: ~15KB gzipped
12
230
  */
231
+ /**
232
+ * Helper to check if response has error
233
+ */
234
+ function hasError(response) {
235
+ return response.error !== undefined;
236
+ }
237
+ /**
238
+ * Helper to check if response has data
239
+ */
240
+ function hasData(response) {
241
+ return response.data !== undefined;
242
+ }
13
243
  /**
14
244
  * Core Memory Client class for interacting with the Memory as a Service API
15
245
  *
@@ -25,6 +255,7 @@ class CoreMemoryClient {
25
255
  this.baseHeaders = {
26
256
  'Content-Type': 'application/json',
27
257
  'User-Agent': '@lanonasis/memory-client/2.0.0',
258
+ 'X-Project-Scope': 'lanonasis-maas', // Required by backend auth middleware
28
259
  ...config.headers
29
260
  };
30
261
  // Set authentication headers
@@ -61,10 +292,13 @@ class CoreMemoryClient {
61
292
  return body;
62
293
  }
63
294
  /**
64
- * Make an HTTP request to the API
295
+ * Make an HTTP request to the API with retry support
65
296
  */
66
297
  async request(endpoint, options = {}) {
67
298
  const startTime = Date.now();
299
+ const maxRetries = this.config.retry?.maxRetries ?? 3;
300
+ const baseDelay = this.config.retry?.retryDelay ?? 1000;
301
+ const backoff = this.config.retry?.backoff ?? 'exponential';
68
302
  // Call onRequest hook if provided
69
303
  if (this.config.onRequest) {
70
304
  try {
@@ -79,85 +313,123 @@ class CoreMemoryClient {
79
313
  ? this.config.apiUrl.replace('/api', '')
80
314
  : this.config.apiUrl;
81
315
  const url = `${baseUrl}/api/v1${endpoint}`;
82
- try {
83
- const controller = new AbortController();
84
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
85
- const response = await fetch(url, {
86
- headers: { ...this.baseHeaders, ...options.headers },
87
- signal: controller.signal,
88
- ...options,
89
- });
90
- clearTimeout(timeoutId);
91
- let data;
92
- const contentType = response.headers.get('content-type');
93
- if (contentType && contentType.includes('application/json')) {
94
- data = await response.json();
95
- }
96
- else {
97
- data = await response.text();
98
- }
99
- if (!response.ok) {
100
- const error = {
101
- message: data?.error || `HTTP ${response.status}: ${response.statusText}`,
102
- statusCode: response.status,
103
- code: 'API_ERROR'
104
- };
105
- // Call onError hook if provided
106
- if (this.config.onError) {
316
+ let lastError;
317
+ let attempt = 0;
318
+ while (attempt <= maxRetries) {
319
+ try {
320
+ const controller = new AbortController();
321
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
322
+ const response = await fetch(url, {
323
+ headers: { ...this.baseHeaders, ...options.headers },
324
+ signal: controller.signal,
325
+ ...options,
326
+ });
327
+ clearTimeout(timeoutId);
328
+ let data;
329
+ const contentType = response.headers.get('content-type');
330
+ if (contentType && contentType.includes('application/json')) {
331
+ data = await response.json();
332
+ }
333
+ else {
334
+ data = await response.text();
335
+ }
336
+ if (!response.ok) {
337
+ const error = createErrorFromResponse(response.status, response.statusText, data);
338
+ // Only retry on retryable errors (5xx, 429, 408)
339
+ if (isRetryableError(response.status) && attempt < maxRetries) {
340
+ lastError = error;
341
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
342
+ await sleep(delay);
343
+ attempt++;
344
+ continue;
345
+ }
346
+ // Call onError hook if provided
347
+ if (this.config.onError) {
348
+ try {
349
+ this.config.onError(error);
350
+ }
351
+ catch (hookError) {
352
+ console.warn('onError hook error:', hookError);
353
+ }
354
+ }
355
+ return { error, meta: { duration: Date.now() - startTime, retries: attempt } };
356
+ }
357
+ // Call onResponse hook if provided
358
+ if (this.config.onResponse) {
107
359
  try {
108
- this.config.onError(error);
360
+ const duration = Date.now() - startTime;
361
+ this.config.onResponse(endpoint, duration);
109
362
  }
110
- catch (hookError) {
111
- console.warn('onError hook error:', hookError);
363
+ catch (error) {
364
+ console.warn('onResponse hook error:', error);
112
365
  }
113
366
  }
114
- return { error: error.message };
367
+ return { data, meta: { duration: Date.now() - startTime, retries: attempt } };
115
368
  }
116
- // Call onResponse hook if provided
117
- if (this.config.onResponse) {
118
- try {
119
- const duration = Date.now() - startTime;
120
- this.config.onResponse(endpoint, duration);
369
+ catch (error) {
370
+ if (error instanceof Error && error.name === 'AbortError') {
371
+ const timeoutError = createErrorResponse('Request timeout', 'TIMEOUT_ERROR', 408);
372
+ // Retry on timeout
373
+ if (attempt < maxRetries) {
374
+ lastError = timeoutError;
375
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
376
+ await sleep(delay);
377
+ attempt++;
378
+ continue;
379
+ }
380
+ if (this.config.onError) {
381
+ try {
382
+ this.config.onError(timeoutError);
383
+ }
384
+ catch (hookError) {
385
+ console.warn('onError hook error:', hookError);
386
+ }
387
+ }
388
+ return { error: timeoutError, meta: { duration: Date.now() - startTime, retries: attempt } };
121
389
  }
122
- catch (error) {
123
- console.warn('onResponse hook error:', error);
390
+ const networkError = createErrorResponse(error instanceof Error ? error.message : 'Network error', 'NETWORK_ERROR');
391
+ // Retry on network errors
392
+ if (attempt < maxRetries) {
393
+ lastError = networkError;
394
+ const delay = calculateRetryDelay(attempt, baseDelay, backoff);
395
+ await sleep(delay);
396
+ attempt++;
397
+ continue;
124
398
  }
125
- }
126
- return { data };
127
- }
128
- catch (error) {
129
- if (error instanceof Error && error.name === 'AbortError') {
130
- const timeoutError = {
131
- message: 'Request timeout',
132
- code: 'TIMEOUT_ERROR',
133
- statusCode: 408
134
- };
135
399
  if (this.config.onError) {
136
400
  try {
137
- this.config.onError(timeoutError);
401
+ this.config.onError(networkError);
138
402
  }
139
403
  catch (hookError) {
140
404
  console.warn('onError hook error:', hookError);
141
405
  }
142
406
  }
143
- return { error: 'Request timeout' };
144
- }
145
- const networkError = {
146
- message: error instanceof Error ? error.message : 'Network error',
147
- code: 'NETWORK_ERROR'
148
- };
149
- if (this.config.onError) {
150
- try {
151
- this.config.onError(networkError);
152
- }
153
- catch (hookError) {
154
- console.warn('onError hook error:', hookError);
155
- }
407
+ return { error: networkError, meta: { duration: Date.now() - startTime, retries: attempt } };
156
408
  }
409
+ }
410
+ // Should never reach here, but handle it gracefully
411
+ return {
412
+ error: lastError ?? createErrorResponse('Max retries exceeded', 'API_ERROR'),
413
+ meta: { duration: Date.now() - startTime, retries: attempt }
414
+ };
415
+ }
416
+ /**
417
+ * Validate input using Zod schema and return validation error if invalid
418
+ */
419
+ validateInput(schema, data) {
420
+ const result = schema.safeParse(data);
421
+ if (!result.success) {
422
+ // Extract error details from Zod error
423
+ const zodError = result.error;
424
+ const details = zodError?.issues?.map(issue => ({
425
+ field: issue.path.map(String).join('.'),
426
+ message: issue.message
427
+ })) ?? [];
157
428
  return {
158
- error: error instanceof Error ? error.message : 'Network error'
429
+ error: createErrorResponse('Validation failed', 'VALIDATION_ERROR', 400, details)
159
430
  };
160
431
  }
432
+ return null;
161
433
  }
162
434
  /**
163
435
  * Test the API connection and authentication
@@ -167,9 +439,14 @@ class CoreMemoryClient {
167
439
  }
168
440
  // Memory Operations
169
441
  /**
170
- * Create a new memory
442
+ * Create a new memory with validation
171
443
  */
172
444
  async createMemory(memory) {
445
+ // Validate input before making request
446
+ const validationError = this.validateInput(createMemorySchema, memory);
447
+ if (validationError) {
448
+ return { error: validationError.error };
449
+ }
173
450
  const enrichedMemory = this.enrichWithOrgContext(memory);
174
451
  return this.request('/memory', {
175
452
  method: 'POST',
@@ -183,9 +460,14 @@ class CoreMemoryClient {
183
460
  return this.request(`/memory/${encodeURIComponent(id)}`);
184
461
  }
185
462
  /**
186
- * Update an existing memory
463
+ * Update an existing memory with validation
187
464
  */
188
465
  async updateMemory(id, updates) {
466
+ // Validate input before making request
467
+ const validationError = this.validateInput(updateMemorySchema, updates);
468
+ if (validationError) {
469
+ return { error: validationError.error };
470
+ }
189
471
  return this.request(`/memory/${encodeURIComponent(id)}`, {
190
472
  method: 'PUT',
191
473
  body: JSON.stringify(updates)
@@ -219,9 +501,15 @@ class CoreMemoryClient {
219
501
  return this.request(endpoint);
220
502
  }
221
503
  /**
222
- * Search memories using semantic search
504
+ * Search memories using semantic search with validation
223
505
  */
224
506
  async searchMemories(request) {
507
+ // Validate input before making request
508
+ const validationError = this.validateInput(searchMemorySchema, request);
509
+ if (validationError) {
510
+ // Return error response (data will be undefined, only error is set)
511
+ return { error: validationError.error };
512
+ }
225
513
  const enrichedRequest = this.enrichWithOrgContext(request);
226
514
  return this.request('/memory/search', {
227
515
  method: 'POST',
@@ -240,9 +528,14 @@ class CoreMemoryClient {
240
528
  }
241
529
  // Topic Operations
242
530
  /**
243
- * Create a new topic
531
+ * Create a new topic with validation
244
532
  */
245
533
  async createTopic(topic) {
534
+ // Validate input before making request
535
+ const validationError = this.validateInput(createTopicSchema, topic);
536
+ if (validationError) {
537
+ return { error: validationError.error };
538
+ }
246
539
  const enrichedTopic = this.enrichWithOrgContext(topic);
247
540
  return this.request('/topics', {
248
541
  method: 'POST',
@@ -284,6 +577,182 @@ class CoreMemoryClient {
284
577
  async getMemoryStats() {
285
578
  return this.request('/memory/stats');
286
579
  }
580
+ // ========================================
581
+ // Intelligence Features (v2.0)
582
+ // ========================================
583
+ /**
584
+ * Create a memory with preprocessing options (chunking, intelligence extraction)
585
+ *
586
+ * @example
587
+ * ```typescript
588
+ * const result = await client.createMemoryWithPreprocessing({
589
+ * title: 'Auth System Docs',
590
+ * content: 'Long content...',
591
+ * memory_type: 'knowledge',
592
+ * preprocessing: {
593
+ * chunking: { strategy: 'semantic', maxChunkSize: 1000 },
594
+ * extractMetadata: true
595
+ * }
596
+ * });
597
+ * ```
598
+ */
599
+ async createMemoryWithPreprocessing(memory) {
600
+ // Validate base memory fields
601
+ const validationError = this.validateInput(createMemorySchema, memory);
602
+ if (validationError) {
603
+ return { error: validationError.error };
604
+ }
605
+ const enrichedMemory = this.enrichWithOrgContext(memory);
606
+ return this.request('/memory', {
607
+ method: 'POST',
608
+ body: JSON.stringify(enrichedMemory)
609
+ });
610
+ }
611
+ /**
612
+ * Update a memory with re-chunking and embedding regeneration
613
+ *
614
+ * @example
615
+ * ```typescript
616
+ * const result = await client.updateMemoryWithPreprocessing('mem_123', {
617
+ * content: 'Updated content...',
618
+ * rechunk: true,
619
+ * regenerate_embedding: true
620
+ * });
621
+ * ```
622
+ */
623
+ async updateMemoryWithPreprocessing(id, updates) {
624
+ const validationError = this.validateInput(updateMemorySchema, updates);
625
+ if (validationError) {
626
+ return { error: validationError.error };
627
+ }
628
+ return this.request(`/memory/${encodeURIComponent(id)}`, {
629
+ method: 'PUT',
630
+ body: JSON.stringify(updates)
631
+ });
632
+ }
633
+ /**
634
+ * Enhanced semantic search with hybrid mode (vector + text)
635
+ *
636
+ * @example
637
+ * ```typescript
638
+ * const result = await client.enhancedSearch({
639
+ * query: 'authentication flow',
640
+ * search_mode: 'hybrid',
641
+ * filters: { tags: ['auth'], project_id: 'proj_123' },
642
+ * include_chunks: true
643
+ * });
644
+ * ```
645
+ */
646
+ async enhancedSearch(request) {
647
+ const validationError = this.validateInput(enhancedSearchSchema, request);
648
+ if (validationError) {
649
+ return { error: validationError.error };
650
+ }
651
+ const enrichedRequest = this.enrichWithOrgContext(request);
652
+ return this.request('/memory/search', {
653
+ method: 'POST',
654
+ body: JSON.stringify(enrichedRequest)
655
+ });
656
+ }
657
+ // ========================================
658
+ // Analytics Operations
659
+ // ========================================
660
+ /**
661
+ * Get search analytics data
662
+ *
663
+ * @example
664
+ * ```typescript
665
+ * const analytics = await client.getSearchAnalytics({
666
+ * from: '2025-01-01',
667
+ * to: '2025-12-31',
668
+ * group_by: 'day'
669
+ * });
670
+ * ```
671
+ */
672
+ async getSearchAnalytics(options = {}) {
673
+ const validationError = this.validateInput(analyticsDateRangeSchema, options);
674
+ if (validationError) {
675
+ return { error: validationError.error };
676
+ }
677
+ const params = new URLSearchParams();
678
+ if (options.from)
679
+ params.append('from', options.from);
680
+ if (options.to)
681
+ params.append('to', options.to);
682
+ if (options.group_by)
683
+ params.append('group_by', options.group_by);
684
+ const queryString = params.toString();
685
+ const endpoint = queryString ? `/analytics/search?${queryString}` : '/analytics/search';
686
+ return this.request(endpoint);
687
+ }
688
+ /**
689
+ * Get memory access patterns
690
+ *
691
+ * @example
692
+ * ```typescript
693
+ * const patterns = await client.getAccessPatterns({
694
+ * from: '2025-01-01',
695
+ * to: '2025-12-31'
696
+ * });
697
+ * console.log(patterns.data?.most_accessed);
698
+ * ```
699
+ */
700
+ async getAccessPatterns(options = {}) {
701
+ const params = new URLSearchParams();
702
+ if (options.from)
703
+ params.append('from', options.from);
704
+ if (options.to)
705
+ params.append('to', options.to);
706
+ const queryString = params.toString();
707
+ const endpoint = queryString ? `/analytics/access?${queryString}` : '/analytics/access';
708
+ return this.request(endpoint);
709
+ }
710
+ /**
711
+ * Get extended memory statistics with storage and activity metrics
712
+ *
713
+ * @example
714
+ * ```typescript
715
+ * const stats = await client.getExtendedStats();
716
+ * console.log(`Total chunks: ${stats.data?.storage.total_chunks}`);
717
+ * console.log(`Created today: ${stats.data?.activity.created_today}`);
718
+ * ```
719
+ */
720
+ async getExtendedStats() {
721
+ return this.request('/analytics/stats');
722
+ }
723
+ /**
724
+ * Get topic with its memories
725
+ *
726
+ * @example
727
+ * ```typescript
728
+ * const topic = await client.getTopicWithMemories('topic_123');
729
+ * console.log(topic.data?.memories);
730
+ * ```
731
+ */
732
+ async getTopicWithMemories(topicId, options = {}) {
733
+ const params = new URLSearchParams();
734
+ if (options.limit)
735
+ params.append('limit', String(options.limit));
736
+ if (options.offset)
737
+ params.append('offset', String(options.offset));
738
+ const queryString = params.toString();
739
+ const endpoint = queryString
740
+ ? `/topics/${encodeURIComponent(topicId)}/memories?${queryString}`
741
+ : `/topics/${encodeURIComponent(topicId)}/memories`;
742
+ return this.request(endpoint);
743
+ }
744
+ /**
745
+ * Get topics in hierarchical structure
746
+ *
747
+ * @example
748
+ * ```typescript
749
+ * const topics = await client.getTopicsHierarchy();
750
+ * // Returns nested topic tree with children
751
+ * ```
752
+ */
753
+ async getTopicsHierarchy() {
754
+ return this.request('/topics?include_hierarchy=true');
755
+ }
287
756
  // Utility Methods
288
757
  /**
289
758
  * Update authentication token
@@ -332,64 +801,40 @@ function createMemoryClient(config) {
332
801
  }
333
802
 
334
803
  /**
335
- * Memory types supported by the service
336
- */
337
- const MEMORY_TYPES = ['context', 'project', 'knowledge', 'reference', 'personal', 'workflow'];
338
- /**
339
- * Memory status values
804
+ * Error handling for Memory Client
805
+ * Browser-safe, no Node.js dependencies
340
806
  */
341
- const MEMORY_STATUSES = ['active', 'archived', 'draft', 'deleted'];
342
807
  /**
343
- * Validation schemas using Zod
808
+ * Standardized error codes for programmatic error handling
344
809
  */
345
- const createMemorySchema = zod.z.object({
346
- title: zod.z.string().min(1).max(500),
347
- content: zod.z.string().min(1).max(50000),
348
- summary: zod.z.string().max(1000).optional(),
349
- memory_type: zod.z.enum(MEMORY_TYPES).default('context'),
350
- topic_id: zod.z.string().uuid().optional(),
351
- project_ref: zod.z.string().max(100).optional(),
352
- tags: zod.z.array(zod.z.string().min(1).max(50)).max(20).default([]),
353
- metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
354
- });
355
- const updateMemorySchema = zod.z.object({
356
- title: zod.z.string().min(1).max(500).optional(),
357
- content: zod.z.string().min(1).max(50000).optional(),
358
- summary: zod.z.string().max(1000).optional(),
359
- memory_type: zod.z.enum(MEMORY_TYPES).optional(),
360
- status: zod.z.enum(MEMORY_STATUSES).optional(),
361
- topic_id: zod.z.string().uuid().nullable().optional(),
362
- project_ref: zod.z.string().max(100).nullable().optional(),
363
- tags: zod.z.array(zod.z.string().min(1).max(50)).max(20).optional(),
364
- metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
365
- });
366
- const searchMemorySchema = zod.z.object({
367
- query: zod.z.string().min(1).max(1000),
368
- memory_types: zod.z.array(zod.z.enum(MEMORY_TYPES)).optional(),
369
- tags: zod.z.array(zod.z.string()).optional(),
370
- topic_id: zod.z.string().uuid().optional(),
371
- project_ref: zod.z.string().optional(),
372
- status: zod.z.enum(MEMORY_STATUSES).default('active'),
373
- limit: zod.z.number().int().min(1).max(100).default(20),
374
- threshold: zod.z.number().min(0).max(1).default(0.7)
375
- });
376
- const createTopicSchema = zod.z.object({
377
- name: zod.z.string().min(1).max(100),
378
- description: zod.z.string().max(500).optional(),
379
- color: zod.z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
380
- icon: zod.z.string().max(50).optional(),
381
- parent_topic_id: zod.z.string().uuid().optional()
382
- });
383
-
810
+ const ERROR_CODES = [
811
+ 'API_ERROR',
812
+ 'AUTH_ERROR',
813
+ 'VALIDATION_ERROR',
814
+ 'TIMEOUT_ERROR',
815
+ 'RATE_LIMIT_ERROR',
816
+ 'NOT_FOUND',
817
+ 'NETWORK_ERROR',
818
+ 'FORBIDDEN',
819
+ 'CONFLICT',
820
+ 'SERVER_ERROR'
821
+ ];
384
822
  /**
385
- * Error handling for Memory Client
386
- * Browser-safe, no Node.js dependencies
823
+ * Type guard to check if an object is an ApiErrorResponse
387
824
  */
825
+ function isApiErrorResponse(value) {
826
+ return (typeof value === 'object' &&
827
+ value !== null &&
828
+ 'code' in value &&
829
+ 'message' in value &&
830
+ typeof value.code === 'string' &&
831
+ typeof value.message === 'string');
832
+ }
388
833
  /**
389
834
  * Base error class for Memory Client errors
390
835
  */
391
836
  class MemoryClientError extends Error {
392
- constructor(message, code, statusCode, details) {
837
+ constructor(message, code = 'API_ERROR', statusCode, details) {
393
838
  super(message);
394
839
  this.code = code;
395
840
  this.statusCode = statusCode;
@@ -400,6 +845,18 @@ class MemoryClientError extends Error {
400
845
  Error.captureStackTrace(this, MemoryClientError);
401
846
  }
402
847
  }
848
+ /**
849
+ * Convert to ApiErrorResponse for consistent API responses
850
+ */
851
+ toResponse() {
852
+ return {
853
+ code: this.code,
854
+ message: this.message,
855
+ statusCode: this.statusCode,
856
+ details: this.details,
857
+ timestamp: new Date().toISOString()
858
+ };
859
+ }
403
860
  }
404
861
  /**
405
862
  * Network/API error
@@ -409,6 +866,26 @@ class ApiError extends MemoryClientError {
409
866
  super(message, 'API_ERROR', statusCode, details);
410
867
  this.name = 'ApiError';
411
868
  }
869
+ /**
870
+ * Create from an HTTP response
871
+ */
872
+ static fromResponse(status, statusText, body) {
873
+ let message = `HTTP ${status}: ${statusText}`;
874
+ let details = undefined;
875
+ if (body && typeof body === 'object') {
876
+ const bodyObj = body;
877
+ if (typeof bodyObj.error === 'string') {
878
+ message = bodyObj.error;
879
+ }
880
+ else if (typeof bodyObj.message === 'string') {
881
+ message = bodyObj.message;
882
+ }
883
+ if (bodyObj.details) {
884
+ details = bodyObj.details;
885
+ }
886
+ }
887
+ return new ApiError(message, status, details);
888
+ }
412
889
  }
413
890
  /**
414
891
  * Authentication error
@@ -420,12 +897,30 @@ class AuthenticationError extends MemoryClientError {
420
897
  }
421
898
  }
422
899
  /**
423
- * Validation error
900
+ * Validation error with field-level details
424
901
  */
425
902
  class ValidationError extends MemoryClientError {
426
903
  constructor(message, details) {
427
904
  super(message, 'VALIDATION_ERROR', 400, details);
428
905
  this.name = 'ValidationError';
906
+ // Parse validation details into field errors
907
+ this.validationErrors = [];
908
+ if (Array.isArray(details)) {
909
+ this.validationErrors = details.filter((item) => typeof item === 'object' &&
910
+ item !== null &&
911
+ typeof item.field === 'string' &&
912
+ typeof item.message === 'string');
913
+ }
914
+ }
915
+ /**
916
+ * Create from Zod error
917
+ */
918
+ static fromZodError(error) {
919
+ const details = error.issues.map(issue => ({
920
+ field: issue.path.join('.'),
921
+ message: issue.message
922
+ }));
923
+ return new ValidationError('Validation failed', details);
429
924
  }
430
925
  }
431
926
  /**
@@ -438,12 +933,13 @@ class TimeoutError extends MemoryClientError {
438
933
  }
439
934
  }
440
935
  /**
441
- * Rate limit error
936
+ * Rate limit error with retry-after info
442
937
  */
443
938
  class RateLimitError extends MemoryClientError {
444
- constructor(message = 'Rate limit exceeded') {
445
- super(message, 'RATE_LIMIT_ERROR', 429);
939
+ constructor(message = 'Rate limit exceeded', retryAfter) {
940
+ super(message, 'RATE_LIMIT_ERROR', 429, { retryAfter });
446
941
  this.name = 'RateLimitError';
942
+ this.retryAfter = retryAfter;
447
943
  }
448
944
  }
449
945
  /**
@@ -453,6 +949,49 @@ class NotFoundError extends MemoryClientError {
453
949
  constructor(resource) {
454
950
  super(`${resource} not found`, 'NOT_FOUND', 404);
455
951
  this.name = 'NotFoundError';
952
+ this.resource = resource;
953
+ }
954
+ }
955
+ /**
956
+ * Network error (no response received)
957
+ */
958
+ class NetworkError extends MemoryClientError {
959
+ constructor(message = 'Network error') {
960
+ super(message, 'NETWORK_ERROR');
961
+ this.name = 'NetworkError';
962
+ }
963
+ }
964
+ /**
965
+ * Server error (5xx responses)
966
+ */
967
+ class ServerError extends MemoryClientError {
968
+ constructor(message, statusCode = 500) {
969
+ super(message, 'SERVER_ERROR', statusCode);
970
+ this.name = 'ServerError';
971
+ }
972
+ }
973
+ /**
974
+ * Create appropriate error class from status code
975
+ */
976
+ function createErrorFromStatus(status, message, details) {
977
+ switch (status) {
978
+ case 400:
979
+ return new ValidationError(message, details);
980
+ case 401:
981
+ return new AuthenticationError(message);
982
+ case 404:
983
+ return new NotFoundError(message);
984
+ case 408:
985
+ return new TimeoutError(message);
986
+ case 429:
987
+ return new RateLimitError(message);
988
+ case 500:
989
+ case 502:
990
+ case 503:
991
+ case 504:
992
+ return new ServerError(message, status);
993
+ default:
994
+ return new ApiError(message, status, details);
456
995
  }
457
996
  }
458
997
 
@@ -488,7 +1027,7 @@ class NotFoundError extends MemoryClientError {
488
1027
  * ```
489
1028
  */
490
1029
  // ========================================
491
- // Core Exports (Browser-Safe)
1030
+ // Internal Imports (for use in this file)
492
1031
  // ========================================
493
1032
  // ========================================
494
1033
  // Constants
@@ -500,6 +1039,66 @@ const CLIENT_NAME = '@lanonasis/memory-client';
500
1039
  // ========================================
501
1040
  const isBrowser = typeof window !== 'undefined';
502
1041
  const isNode = typeof globalThis !== 'undefined' && 'process' in globalThis && globalThis.process?.versions?.node;
1042
+ const isEdge = !isBrowser && !isNode;
1043
+ /**
1044
+ * Get the current runtime environment
1045
+ */
1046
+ function getEnvironment() {
1047
+ if (isBrowser)
1048
+ return 'browser';
1049
+ if (isNode)
1050
+ return 'node';
1051
+ return 'edge';
1052
+ }
1053
+ /**
1054
+ * Auto-detecting client factory - "Drop In and Sleep" architecture
1055
+ *
1056
+ * Automatically detects the runtime environment and returns the appropriate client:
1057
+ * - Browser/Edge: Returns CoreMemoryClient (lightweight, browser-safe)
1058
+ * - Node.js: Returns EnhancedMemoryClient (with CLI/MCP support)
1059
+ *
1060
+ * @example
1061
+ * ```typescript
1062
+ * import { createClient } from '@lanonasis/memory-client';
1063
+ *
1064
+ * // Works in any environment!
1065
+ * const client = await createClient({
1066
+ * apiUrl: 'https://api.lanonasis.com',
1067
+ * apiKey: 'your-key'
1068
+ * });
1069
+ *
1070
+ * const memories = await client.listMemories();
1071
+ * ```
1072
+ */
1073
+ async function createClient(config) {
1074
+ const environment = getEnvironment();
1075
+ if (environment === 'node') {
1076
+ try {
1077
+ // Dynamic import for Node.js client to avoid bundling in browser
1078
+ const { createNodeMemoryClient } = await Promise.resolve().then(function () { return index; });
1079
+ return await createNodeMemoryClient({
1080
+ ...config,
1081
+ preferCLI: config.preferCLI ?? true,
1082
+ enableMCP: config.enableMCP ?? true
1083
+ });
1084
+ }
1085
+ catch {
1086
+ // Fallback to core client if Node module fails to load
1087
+ console.warn('Failed to load Node.js client, falling back to core client');
1088
+ }
1089
+ }
1090
+ // Browser, Edge, or fallback
1091
+ const clientConfig = {
1092
+ apiUrl: config.apiUrl,
1093
+ apiKey: config.apiKey,
1094
+ authToken: config.authToken,
1095
+ organizationId: config.organizationId,
1096
+ timeout: config.timeout ?? (environment === 'edge' ? 5000 : 30000),
1097
+ headers: config.headers,
1098
+ retry: config.retry
1099
+ };
1100
+ return createMemoryClient(clientConfig);
1101
+ }
503
1102
  // ========================================
504
1103
  // Default Configurations
505
1104
  // ========================================
@@ -613,25 +1212,664 @@ const defaultConfigs = {
613
1212
  * - Examples: https://github.com/lanonasis/examples
614
1213
  */
615
1214
 
616
- exports.ApiErrorClass = ApiError;
1215
+ /**
1216
+ * CLI Integration Module for Memory Client SDK
1217
+ *
1218
+ * Provides intelligent CLI detection and MCP channel utilization
1219
+ * when @lanonasis/cli v1.5.2+ is available in the environment
1220
+ *
1221
+ * IMPORTANT: This file imports Node.js modules and should only be used in Node.js environments
1222
+ */
1223
+ const execAsync = util.promisify(child_process.exec);
1224
+ /**
1225
+ * CLI Detection and Integration Service
1226
+ */
1227
+ class CLIIntegration {
1228
+ constructor() {
1229
+ this.cliInfo = null;
1230
+ this.detectionPromise = null;
1231
+ }
1232
+ /**
1233
+ * Detect if CLI is available and get its capabilities
1234
+ */
1235
+ async detectCLI() {
1236
+ // Return cached result if already detected
1237
+ if (this.cliInfo) {
1238
+ return this.cliInfo;
1239
+ }
1240
+ // Return existing promise if detection is in progress
1241
+ if (this.detectionPromise) {
1242
+ return this.detectionPromise;
1243
+ }
1244
+ // Start new detection
1245
+ this.detectionPromise = this.performDetection();
1246
+ this.cliInfo = await this.detectionPromise;
1247
+ return this.cliInfo;
1248
+ }
1249
+ async performDetection() {
1250
+ try {
1251
+ // Check if onasis/lanonasis CLI is available
1252
+ let versionOutput = '';
1253
+ try {
1254
+ const { stdout } = await execAsync('onasis --version 2>/dev/null', { timeout: 5000 });
1255
+ versionOutput = stdout;
1256
+ }
1257
+ catch {
1258
+ // Try lanonasis if onasis fails
1259
+ const { stdout } = await execAsync('lanonasis --version 2>/dev/null', { timeout: 5000 });
1260
+ versionOutput = stdout;
1261
+ }
1262
+ const version = versionOutput.trim();
1263
+ // Verify it's v1.5.2 or higher for Golden Contract support
1264
+ const versionMatch = version.match(/(\d+)\.(\d+)\.(\d+)/);
1265
+ if (!versionMatch) {
1266
+ return { available: false };
1267
+ }
1268
+ const [, major, minor, patch] = versionMatch.map(Number);
1269
+ const isCompatible = major > 1 || (major === 1 && minor > 5) || (major === 1 && minor === 5 && patch >= 2);
1270
+ if (!isCompatible) {
1271
+ return {
1272
+ available: true,
1273
+ version,
1274
+ mcpAvailable: false,
1275
+ authenticated: false
1276
+ };
1277
+ }
1278
+ // Check MCP availability
1279
+ let mcpAvailable = false;
1280
+ try {
1281
+ await execAsync('onasis mcp status --output json 2>/dev/null || lanonasis mcp status --output json 2>/dev/null', {
1282
+ timeout: 3000
1283
+ });
1284
+ mcpAvailable = true;
1285
+ }
1286
+ catch {
1287
+ // MCP not available or not configured
1288
+ }
1289
+ // Check authentication status
1290
+ let authenticated = false;
1291
+ try {
1292
+ const { stdout: authOutput } = await execAsync('onasis auth status --output json 2>/dev/null || lanonasis auth status --output json 2>/dev/null', {
1293
+ timeout: 3000
1294
+ });
1295
+ const parseResult = safeJsonParse(authOutput);
1296
+ if (parseResult.success) {
1297
+ authenticated = parseResult.data.authenticated === true;
1298
+ }
1299
+ }
1300
+ catch {
1301
+ // Authentication check failed
1302
+ }
1303
+ return {
1304
+ available: true,
1305
+ version,
1306
+ mcpAvailable,
1307
+ authenticated
1308
+ };
1309
+ }
1310
+ catch {
1311
+ return { available: false };
1312
+ }
1313
+ }
1314
+ /**
1315
+ * Execute CLI command and return parsed JSON result
1316
+ */
1317
+ async executeCLICommand(command, options = {}) {
1318
+ const cliInfo = await this.detectCLI();
1319
+ if (!cliInfo.available) {
1320
+ return { error: createErrorResponse('CLI not available', 'API_ERROR') };
1321
+ }
1322
+ if (!cliInfo.authenticated) {
1323
+ return { error: createErrorResponse('CLI not authenticated. Run: onasis login', 'AUTH_ERROR', 401) };
1324
+ }
1325
+ try {
1326
+ const timeout = options.timeout || 30000;
1327
+ const outputFormat = options.outputFormat || 'json';
1328
+ const verbose = options.verbose ? '--verbose' : '';
1329
+ // Determine which CLI command to use (prefer onasis for Golden Contract)
1330
+ const cliCmd = await this.getPreferredCLICommand();
1331
+ const fullCommand = `${cliCmd} ${command} --output ${outputFormat} ${verbose}`.trim();
1332
+ const { stdout, stderr } = await execAsync(fullCommand, {
1333
+ timeout,
1334
+ maxBuffer: 1024 * 1024 // 1MB buffer
1335
+ });
1336
+ if (stderr && stderr.trim()) {
1337
+ console.warn('CLI warning:', stderr);
1338
+ }
1339
+ if (outputFormat === 'json') {
1340
+ const parseResult = safeJsonParse(stdout);
1341
+ if (parseResult.success) {
1342
+ return { data: parseResult.data };
1343
+ }
1344
+ return { error: createErrorResponse(parseResult.error, 'VALIDATION_ERROR', 400) };
1345
+ }
1346
+ return { data: stdout };
1347
+ }
1348
+ catch (error) {
1349
+ if (error instanceof Error && error.message.includes('timeout')) {
1350
+ return { error: createErrorResponse('CLI command timeout', 'TIMEOUT_ERROR', 408) };
1351
+ }
1352
+ return {
1353
+ error: createErrorResponse(error instanceof Error ? error.message : 'CLI command failed', 'API_ERROR')
1354
+ };
1355
+ }
1356
+ }
1357
+ /**
1358
+ * Get preferred CLI command (onasis for Golden Contract, fallback to lanonasis)
1359
+ */
1360
+ async getPreferredCLICommand() {
1361
+ try {
1362
+ child_process.execSync('which onasis', { stdio: 'ignore', timeout: 1000 });
1363
+ return 'onasis';
1364
+ }
1365
+ catch {
1366
+ return 'lanonasis';
1367
+ }
1368
+ }
1369
+ /**
1370
+ * Memory operations via CLI
1371
+ */
1372
+ async createMemoryViaCLI(title, content, options = {}) {
1373
+ const { memoryType = 'context', tags = [], topicId } = options;
1374
+ let command = `memory create --title "${title}" --content "${content}" --memory-type ${memoryType}`;
1375
+ if (tags.length > 0) {
1376
+ command += ` --tags "${tags.join(',')}"`;
1377
+ }
1378
+ if (topicId) {
1379
+ command += ` --topic-id "${topicId}"`;
1380
+ }
1381
+ return this.executeCLICommand(command);
1382
+ }
1383
+ async listMemoriesViaCLI(options = {}) {
1384
+ let command = 'memory list';
1385
+ if (options.limit) {
1386
+ command += ` --limit ${options.limit}`;
1387
+ }
1388
+ if (options.memoryType) {
1389
+ command += ` --memory-type ${options.memoryType}`;
1390
+ }
1391
+ if (options.tags && options.tags.length > 0) {
1392
+ command += ` --tags "${options.tags.join(',')}"`;
1393
+ }
1394
+ if (options.sortBy) {
1395
+ command += ` --sort-by ${options.sortBy}`;
1396
+ }
1397
+ return this.executeCLICommand(command);
1398
+ }
1399
+ async searchMemoriesViaCLI(query, options = {}) {
1400
+ let command = `memory search "${query}"`;
1401
+ if (options.limit) {
1402
+ command += ` --limit ${options.limit}`;
1403
+ }
1404
+ if (options.memoryTypes && options.memoryTypes.length > 0) {
1405
+ command += ` --memory-types "${options.memoryTypes.join(',')}"`;
1406
+ }
1407
+ return this.executeCLICommand(command);
1408
+ }
1409
+ /**
1410
+ * Health check via CLI
1411
+ */
1412
+ async healthCheckViaCLI() {
1413
+ return this.executeCLICommand('health');
1414
+ }
1415
+ /**
1416
+ * MCP-specific operations
1417
+ */
1418
+ async getMCPStatus() {
1419
+ const cliInfo = await this.detectCLI();
1420
+ if (!cliInfo.mcpAvailable) {
1421
+ return { error: createErrorResponse('MCP not available via CLI', 'API_ERROR') };
1422
+ }
1423
+ return this.executeCLICommand('mcp status');
1424
+ }
1425
+ async listMCPTools() {
1426
+ const cliInfo = await this.detectCLI();
1427
+ if (!cliInfo.mcpAvailable) {
1428
+ return { error: createErrorResponse('MCP not available via CLI', 'API_ERROR') };
1429
+ }
1430
+ return this.executeCLICommand('mcp tools');
1431
+ }
1432
+ /**
1433
+ * Authentication operations
1434
+ */
1435
+ async getAuthStatus() {
1436
+ return this.executeCLICommand('auth status');
1437
+ }
1438
+ /**
1439
+ * Check if specific CLI features are available
1440
+ */
1441
+ async getCapabilities() {
1442
+ const cliInfo = await this.detectCLI();
1443
+ return {
1444
+ cliAvailable: cliInfo.available,
1445
+ version: cliInfo.version,
1446
+ mcpSupport: cliInfo.mcpAvailable || false,
1447
+ authenticated: cliInfo.authenticated || false,
1448
+ goldenContract: cliInfo.available && this.isGoldenContractCompliant(cliInfo.version)
1449
+ };
1450
+ }
1451
+ isGoldenContractCompliant(version) {
1452
+ if (!version)
1453
+ return false;
1454
+ const versionMatch = version.match(/(\d+)\.(\d+)\.(\d+)/);
1455
+ if (!versionMatch)
1456
+ return false;
1457
+ const [, major, minor, patch] = versionMatch.map(Number);
1458
+ return major > 1 || (major === 1 && minor > 5) || (major === 1 && minor === 5 && patch >= 2);
1459
+ }
1460
+ /**
1461
+ * Force refresh CLI detection
1462
+ */
1463
+ async refresh() {
1464
+ this.cliInfo = null;
1465
+ this.detectionPromise = null;
1466
+ return this.detectCLI();
1467
+ }
1468
+ /**
1469
+ * Get cached CLI info without re-detection
1470
+ */
1471
+ getCachedInfo() {
1472
+ return this.cliInfo;
1473
+ }
1474
+ }
1475
+
1476
+ /**
1477
+ * Enhanced Memory Client with CLI Integration
1478
+ *
1479
+ * Intelligently routes requests through CLI v1.5.2+ when available,
1480
+ * with fallback to direct API for maximum compatibility and performance
1481
+ *
1482
+ * IMPORTANT: This file uses Node.js-specific features (process.env) and should only be used in Node.js environments
1483
+ */
1484
+ /**
1485
+ * Enhanced Memory Client with intelligent CLI/API routing
1486
+ */
1487
+ class EnhancedMemoryClient {
1488
+ createDefaultCapabilities() {
1489
+ return {
1490
+ cliAvailable: false,
1491
+ mcpSupport: false,
1492
+ authenticated: false,
1493
+ goldenContract: false
1494
+ };
1495
+ }
1496
+ constructor(config) {
1497
+ this.capabilities = null;
1498
+ // Merge config with defaults, ensuring all required fields are present
1499
+ // Spread config first, then apply defaults only for undefined values
1500
+ const mergedConfig = {
1501
+ ...config,
1502
+ preferCLI: config.preferCLI ?? true,
1503
+ enableMCP: config.enableMCP ?? true,
1504
+ cliDetectionTimeout: config.cliDetectionTimeout ?? 5000,
1505
+ fallbackToAPI: config.fallbackToAPI ?? true,
1506
+ minCLIVersion: config.minCLIVersion ?? '1.5.2',
1507
+ verbose: config.verbose ?? false,
1508
+ timeout: config.timeout ?? 30000,
1509
+ apiUrl: config.apiUrl || 'https://api.lanonasis.com',
1510
+ apiKey: config.apiKey || process.env.LANONASIS_API_KEY || '',
1511
+ authToken: config.authToken || '',
1512
+ headers: config.headers || {}
1513
+ };
1514
+ this.config = mergedConfig;
1515
+ this.directClient = new CoreMemoryClient(config);
1516
+ this.cliIntegration = new CLIIntegration();
1517
+ }
1518
+ /**
1519
+ * Initialize the client and detect capabilities
1520
+ */
1521
+ async initialize() {
1522
+ try {
1523
+ const detectionPromise = this.cliIntegration.getCapabilities();
1524
+ const capabilities = this.config.cliDetectionTimeout > 0
1525
+ ? await Promise.race([
1526
+ detectionPromise,
1527
+ new Promise((resolve) => {
1528
+ setTimeout(() => resolve(null), this.config.cliDetectionTimeout);
1529
+ })
1530
+ ])
1531
+ : await detectionPromise;
1532
+ if (capabilities) {
1533
+ this.capabilities = capabilities;
1534
+ if (this.config.verbose && capabilities.cliAvailable && !capabilities.authenticated) {
1535
+ const suggestedCommand = capabilities.goldenContract ? 'onasis login' : 'lanonasis login';
1536
+ console.warn(`CLI detected but not authenticated. Run '${suggestedCommand}' to enable enhanced SDK features.`);
1537
+ }
1538
+ }
1539
+ else {
1540
+ this.capabilities = this.createDefaultCapabilities();
1541
+ if (this.config.verbose) {
1542
+ console.warn(`CLI detection timed out after ${this.config.cliDetectionTimeout}ms. Falling back to API mode.`);
1543
+ }
1544
+ }
1545
+ }
1546
+ catch (error) {
1547
+ if (this.config.verbose) {
1548
+ console.warn('CLI detection failed:', error);
1549
+ }
1550
+ this.capabilities = this.createDefaultCapabilities();
1551
+ }
1552
+ }
1553
+ /**
1554
+ * Get current capabilities
1555
+ */
1556
+ async getCapabilities() {
1557
+ if (!this.capabilities) {
1558
+ await this.initialize();
1559
+ }
1560
+ if (!this.capabilities) {
1561
+ this.capabilities = this.createDefaultCapabilities();
1562
+ }
1563
+ return this.capabilities;
1564
+ }
1565
+ /**
1566
+ * Determine if operation should use CLI
1567
+ */
1568
+ async shouldUseCLI() {
1569
+ const capabilities = await this.getCapabilities();
1570
+ return (this.config.preferCLI &&
1571
+ capabilities.cliAvailable &&
1572
+ capabilities.authenticated &&
1573
+ capabilities.goldenContract);
1574
+ }
1575
+ /**
1576
+ * Execute operation with intelligent routing
1577
+ */
1578
+ async executeOperation(operation, cliOperation, apiOperation) {
1579
+ const useCLI = await this.shouldUseCLI();
1580
+ const capabilities = await this.getCapabilities();
1581
+ if (useCLI) {
1582
+ try {
1583
+ const result = await cliOperation();
1584
+ if (result.error && this.config.fallbackToAPI) {
1585
+ console.warn(`CLI ${operation} failed, falling back to API:`, result.error);
1586
+ const apiResult = await apiOperation();
1587
+ return {
1588
+ ...apiResult,
1589
+ source: 'api',
1590
+ mcpUsed: false
1591
+ };
1592
+ }
1593
+ return {
1594
+ ...result,
1595
+ source: 'cli',
1596
+ mcpUsed: capabilities.mcpSupport
1597
+ };
1598
+ }
1599
+ catch (error) {
1600
+ if (this.config.fallbackToAPI) {
1601
+ console.warn(`CLI ${operation} error, falling back to API:`, error);
1602
+ const apiResult = await apiOperation();
1603
+ return {
1604
+ ...apiResult,
1605
+ source: 'api',
1606
+ mcpUsed: false
1607
+ };
1608
+ }
1609
+ return {
1610
+ error: createErrorResponse(error instanceof Error ? error.message : `CLI ${operation} failed`, 'API_ERROR'),
1611
+ source: 'cli',
1612
+ mcpUsed: false
1613
+ };
1614
+ }
1615
+ }
1616
+ else {
1617
+ const result = await apiOperation();
1618
+ return {
1619
+ ...result,
1620
+ source: 'api',
1621
+ mcpUsed: false
1622
+ };
1623
+ }
1624
+ }
1625
+ // Enhanced API Methods
1626
+ /**
1627
+ * Health check with intelligent routing
1628
+ */
1629
+ async healthCheck() {
1630
+ return this.executeOperation('health check', () => this.cliIntegration.healthCheckViaCLI(), () => this.directClient.healthCheck());
1631
+ }
1632
+ /**
1633
+ * Create memory with CLI/API routing
1634
+ */
1635
+ async createMemory(memory) {
1636
+ return this.executeOperation('create memory', () => this.cliIntegration.createMemoryViaCLI(memory.title, memory.content, {
1637
+ memoryType: memory.memory_type,
1638
+ tags: memory.tags,
1639
+ topicId: memory.topic_id
1640
+ }), () => this.directClient.createMemory(memory));
1641
+ }
1642
+ /**
1643
+ * List memories with intelligent routing
1644
+ */
1645
+ async listMemories(options = {}) {
1646
+ return this.executeOperation('list memories', () => this.cliIntegration.listMemoriesViaCLI({
1647
+ limit: options.limit,
1648
+ memoryType: options.memory_type,
1649
+ tags: options.tags,
1650
+ sortBy: options.sort
1651
+ }), () => this.directClient.listMemories(options));
1652
+ }
1653
+ /**
1654
+ * Search memories with MCP enhancement when available
1655
+ */
1656
+ async searchMemories(request) {
1657
+ return this.executeOperation('search memories', () => this.cliIntegration.searchMemoriesViaCLI(request.query, {
1658
+ limit: request.limit,
1659
+ memoryTypes: request.memory_types
1660
+ }), () => this.directClient.searchMemories(request));
1661
+ }
1662
+ /**
1663
+ * Get memory by ID (API only for now)
1664
+ */
1665
+ async getMemory(id) {
1666
+ // CLI doesn't have get by ID yet, use API
1667
+ const result = await this.directClient.getMemory(id);
1668
+ return {
1669
+ ...result,
1670
+ source: 'api',
1671
+ mcpUsed: false
1672
+ };
1673
+ }
1674
+ /**
1675
+ * Update memory (API only for now)
1676
+ */
1677
+ async updateMemory(id, updates) {
1678
+ // CLI doesn't have update yet, use API
1679
+ const result = await this.directClient.updateMemory(id, updates);
1680
+ return {
1681
+ ...result,
1682
+ source: 'api',
1683
+ mcpUsed: false
1684
+ };
1685
+ }
1686
+ /**
1687
+ * Delete memory (API only for now)
1688
+ */
1689
+ async deleteMemory(id) {
1690
+ // CLI doesn't have delete yet, use API
1691
+ const result = await this.directClient.deleteMemory(id);
1692
+ return {
1693
+ ...result,
1694
+ source: 'api',
1695
+ mcpUsed: false
1696
+ };
1697
+ }
1698
+ // Topic Operations (API only for now)
1699
+ async createTopic(topic) {
1700
+ const result = await this.directClient.createTopic(topic);
1701
+ return { ...result, source: 'api', mcpUsed: false };
1702
+ }
1703
+ async getTopics() {
1704
+ const result = await this.directClient.getTopics();
1705
+ return { ...result, source: 'api', mcpUsed: false };
1706
+ }
1707
+ async getTopic(id) {
1708
+ const result = await this.directClient.getTopic(id);
1709
+ return { ...result, source: 'api', mcpUsed: false };
1710
+ }
1711
+ async updateTopic(id, updates) {
1712
+ const result = await this.directClient.updateTopic(id, updates);
1713
+ return { ...result, source: 'api', mcpUsed: false };
1714
+ }
1715
+ async deleteTopic(id) {
1716
+ const result = await this.directClient.deleteTopic(id);
1717
+ return { ...result, source: 'api', mcpUsed: false };
1718
+ }
1719
+ /**
1720
+ * Get memory statistics
1721
+ */
1722
+ async getMemoryStats() {
1723
+ const result = await this.directClient.getMemoryStats();
1724
+ return { ...result, source: 'api', mcpUsed: false };
1725
+ }
1726
+ // Utility Methods
1727
+ /**
1728
+ * Force CLI re-detection
1729
+ */
1730
+ async refreshCLIDetection() {
1731
+ this.capabilities = null;
1732
+ await this.cliIntegration.refresh();
1733
+ await this.initialize();
1734
+ }
1735
+ /**
1736
+ * Get authentication status from CLI
1737
+ */
1738
+ async getAuthStatus() {
1739
+ try {
1740
+ const result = await this.cliIntegration.getAuthStatus();
1741
+ return { ...result, source: 'cli', mcpUsed: false };
1742
+ }
1743
+ catch (error) {
1744
+ return {
1745
+ error: createErrorResponse(error instanceof Error ? error.message : 'Auth status check failed', 'API_ERROR'),
1746
+ source: 'cli',
1747
+ mcpUsed: false
1748
+ };
1749
+ }
1750
+ }
1751
+ /**
1752
+ * Get MCP status when available
1753
+ */
1754
+ async getMCPStatus() {
1755
+ const capabilities = await this.getCapabilities();
1756
+ if (!capabilities.mcpSupport) {
1757
+ return {
1758
+ error: createErrorResponse('MCP not available', 'API_ERROR'),
1759
+ source: 'cli',
1760
+ mcpUsed: false
1761
+ };
1762
+ }
1763
+ try {
1764
+ const result = await this.cliIntegration.getMCPStatus();
1765
+ return { ...result, source: 'cli', mcpUsed: true };
1766
+ }
1767
+ catch (error) {
1768
+ return {
1769
+ error: createErrorResponse(error instanceof Error ? error.message : 'MCP status check failed', 'API_ERROR'),
1770
+ source: 'cli',
1771
+ mcpUsed: false
1772
+ };
1773
+ }
1774
+ }
1775
+ /**
1776
+ * Update authentication for both CLI and API client
1777
+ */
1778
+ setAuthToken(token) {
1779
+ this.directClient.setAuthToken(token);
1780
+ }
1781
+ setApiKey(apiKey) {
1782
+ this.directClient.setApiKey(apiKey);
1783
+ }
1784
+ clearAuth() {
1785
+ this.directClient.clearAuth();
1786
+ }
1787
+ /**
1788
+ * Update configuration
1789
+ */
1790
+ updateConfig(updates) {
1791
+ this.config = { ...this.config, ...updates };
1792
+ this.directClient.updateConfig(updates);
1793
+ }
1794
+ /**
1795
+ * Get configuration summary
1796
+ */
1797
+ getConfigSummary() {
1798
+ return {
1799
+ apiUrl: this.config.apiUrl,
1800
+ preferCLI: this.config.preferCLI,
1801
+ enableMCP: this.config.enableMCP,
1802
+ capabilities: this.capabilities || undefined
1803
+ };
1804
+ }
1805
+ }
1806
+ /**
1807
+ * Factory function to create an enhanced memory client
1808
+ */
1809
+ async function createNodeMemoryClient(config) {
1810
+ const client = new EnhancedMemoryClient(config);
1811
+ await client.initialize();
1812
+ return client;
1813
+ }
1814
+
1815
+ /**
1816
+ * @lanonasis/memory-client/node
1817
+ *
1818
+ * Node.js-enhanced client with CLI integration
1819
+ * ONLY import this in Node.js environments
1820
+ *
1821
+ * Provides intelligent CLI detection and MCP channel utilization
1822
+ * when @lanonasis/cli v1.5.2+ is available
1823
+ */
1824
+ // Enhanced client with CLI support
1825
+
1826
+ var index = /*#__PURE__*/Object.freeze({
1827
+ __proto__: null,
1828
+ CLIIntegration: CLIIntegration,
1829
+ EnhancedMemoryClient: EnhancedMemoryClient,
1830
+ createNodeMemoryClient: createNodeMemoryClient
1831
+ });
1832
+
1833
+ exports.ApiError = ApiError;
617
1834
  exports.AuthenticationError = AuthenticationError;
1835
+ exports.CHUNKING_STRATEGIES = CHUNKING_STRATEGIES;
618
1836
  exports.CLIENT_NAME = CLIENT_NAME;
1837
+ exports.CONTENT_TYPES = CONTENT_TYPES;
619
1838
  exports.CoreMemoryClient = CoreMemoryClient;
1839
+ exports.ERROR_CODES = ERROR_CODES;
620
1840
  exports.MEMORY_STATUSES = MEMORY_STATUSES;
621
1841
  exports.MEMORY_TYPES = MEMORY_TYPES;
622
1842
  exports.MemoryClient = CoreMemoryClient;
623
1843
  exports.MemoryClientError = MemoryClientError;
1844
+ exports.NetworkError = NetworkError;
624
1845
  exports.NotFoundError = NotFoundError;
625
1846
  exports.RateLimitError = RateLimitError;
1847
+ exports.SEARCH_MODES = SEARCH_MODES;
1848
+ exports.ServerError = ServerError;
626
1849
  exports.TimeoutError = TimeoutError;
627
1850
  exports.VERSION = VERSION;
628
1851
  exports.ValidationError = ValidationError;
1852
+ exports.analyticsDateRangeSchema = analyticsDateRangeSchema;
1853
+ exports.calculateRetryDelay = calculateRetryDelay;
1854
+ exports.createClient = createClient;
1855
+ exports.createErrorFromStatus = createErrorFromStatus;
1856
+ exports.createErrorResponse = createErrorResponse;
629
1857
  exports.createMemoryClient = createMemoryClient;
630
1858
  exports.createMemorySchema = createMemorySchema;
631
1859
  exports.createTopicSchema = createTopicSchema;
632
1860
  exports.defaultConfigs = defaultConfigs;
1861
+ exports.enhancedSearchSchema = enhancedSearchSchema;
1862
+ exports.getEnvironment = getEnvironment;
1863
+ exports.hasData = hasData;
1864
+ exports.hasError = hasError;
1865
+ exports.httpStatusToErrorCode = httpStatusToErrorCode;
1866
+ exports.isApiErrorResponse = isApiErrorResponse;
633
1867
  exports.isBrowser = isBrowser;
1868
+ exports.isEdge = isEdge;
634
1869
  exports.isNode = isNode;
1870
+ exports.isRetryableError = isRetryableError;
1871
+ exports.preprocessingOptionsSchema = preprocessingOptionsSchema;
1872
+ exports.safeJsonParse = safeJsonParse;
635
1873
  exports.searchMemorySchema = searchMemorySchema;
636
1874
  exports.updateMemorySchema = updateMemorySchema;
637
1875
  //# sourceMappingURL=index.js.map