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