@lanonasis/memory-client 1.0.1 → 2.0.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.
@@ -0,0 +1,610 @@
1
+ import { CoreMemoryClient } from '../core/client';
2
+ import { exec, execSync } from 'child_process';
3
+ import { promisify } from 'util';
4
+
5
+ /**
6
+ * CLI Integration Module for Memory Client SDK
7
+ *
8
+ * Provides intelligent CLI detection and MCP channel utilization
9
+ * when @lanonasis/cli v1.5.2+ is available in the environment
10
+ *
11
+ * IMPORTANT: This file imports Node.js modules and should only be used in Node.js environments
12
+ */
13
+ const execAsync = promisify(exec);
14
+ /**
15
+ * CLI Detection and Integration Service
16
+ */
17
+ class CLIIntegration {
18
+ constructor() {
19
+ this.cliInfo = null;
20
+ this.detectionPromise = null;
21
+ }
22
+ /**
23
+ * Detect if CLI is available and get its capabilities
24
+ */
25
+ async detectCLI() {
26
+ // Return cached result if already detected
27
+ if (this.cliInfo) {
28
+ return this.cliInfo;
29
+ }
30
+ // Return existing promise if detection is in progress
31
+ if (this.detectionPromise) {
32
+ return this.detectionPromise;
33
+ }
34
+ // Start new detection
35
+ this.detectionPromise = this.performDetection();
36
+ this.cliInfo = await this.detectionPromise;
37
+ return this.cliInfo;
38
+ }
39
+ async performDetection() {
40
+ try {
41
+ // Check if onasis/lanonasis CLI is available
42
+ let versionOutput = '';
43
+ try {
44
+ const { stdout } = await execAsync('onasis --version 2>/dev/null', { timeout: 5000 });
45
+ versionOutput = stdout;
46
+ }
47
+ catch {
48
+ // Try lanonasis if onasis fails
49
+ const { stdout } = await execAsync('lanonasis --version 2>/dev/null', { timeout: 5000 });
50
+ versionOutput = stdout;
51
+ }
52
+ const version = versionOutput.trim();
53
+ // Verify it's v1.5.2 or higher for Golden Contract support
54
+ const versionMatch = version.match(/(\d+)\.(\d+)\.(\d+)/);
55
+ if (!versionMatch) {
56
+ return { available: false };
57
+ }
58
+ const [, major, minor, patch] = versionMatch.map(Number);
59
+ const isCompatible = major > 1 || (major === 1 && minor > 5) || (major === 1 && minor === 5 && patch >= 2);
60
+ if (!isCompatible) {
61
+ return {
62
+ available: true,
63
+ version,
64
+ mcpAvailable: false,
65
+ authenticated: false
66
+ };
67
+ }
68
+ // Check MCP availability
69
+ let mcpAvailable = false;
70
+ try {
71
+ await execAsync('onasis mcp status --output json 2>/dev/null || lanonasis mcp status --output json 2>/dev/null', {
72
+ timeout: 3000
73
+ });
74
+ mcpAvailable = true;
75
+ }
76
+ catch {
77
+ // MCP not available or not configured
78
+ }
79
+ // Check authentication status
80
+ let authenticated = false;
81
+ try {
82
+ const { stdout: authOutput } = await execAsync('onasis auth status --output json 2>/dev/null || lanonasis auth status --output json 2>/dev/null', {
83
+ timeout: 3000
84
+ });
85
+ const authStatus = JSON.parse(authOutput);
86
+ authenticated = authStatus.authenticated === true;
87
+ }
88
+ catch {
89
+ // Authentication check failed
90
+ }
91
+ return {
92
+ available: true,
93
+ version,
94
+ mcpAvailable,
95
+ authenticated
96
+ };
97
+ }
98
+ catch {
99
+ return { available: false };
100
+ }
101
+ }
102
+ /**
103
+ * Execute CLI command and return parsed JSON result
104
+ */
105
+ async executeCLICommand(command, options = {}) {
106
+ const cliInfo = await this.detectCLI();
107
+ if (!cliInfo.available) {
108
+ return { error: 'CLI not available' };
109
+ }
110
+ if (!cliInfo.authenticated) {
111
+ return { error: 'CLI not authenticated. Run: onasis login' };
112
+ }
113
+ try {
114
+ const timeout = options.timeout || 30000;
115
+ const outputFormat = options.outputFormat || 'json';
116
+ const verbose = options.verbose ? '--verbose' : '';
117
+ // Determine which CLI command to use (prefer onasis for Golden Contract)
118
+ const cliCmd = await this.getPreferredCLICommand();
119
+ const fullCommand = `${cliCmd} ${command} --output ${outputFormat} ${verbose}`.trim();
120
+ const { stdout, stderr } = await execAsync(fullCommand, {
121
+ timeout,
122
+ maxBuffer: 1024 * 1024 // 1MB buffer
123
+ });
124
+ if (stderr && stderr.trim()) {
125
+ console.warn('CLI warning:', stderr);
126
+ }
127
+ if (outputFormat === 'json') {
128
+ try {
129
+ const result = JSON.parse(stdout);
130
+ return { data: result };
131
+ }
132
+ catch (parseError) {
133
+ return { error: `Failed to parse CLI JSON output: ${parseError instanceof Error ? parseError.message : 'Unknown error'}` };
134
+ }
135
+ }
136
+ return { data: stdout };
137
+ }
138
+ catch (error) {
139
+ if (error instanceof Error && error.message.includes('timeout')) {
140
+ return { error: 'CLI command timeout' };
141
+ }
142
+ return {
143
+ error: error instanceof Error ? error.message : 'CLI command failed'
144
+ };
145
+ }
146
+ }
147
+ /**
148
+ * Get preferred CLI command (onasis for Golden Contract, fallback to lanonasis)
149
+ */
150
+ async getPreferredCLICommand() {
151
+ try {
152
+ execSync('which onasis', { stdio: 'ignore', timeout: 1000 });
153
+ return 'onasis';
154
+ }
155
+ catch {
156
+ return 'lanonasis';
157
+ }
158
+ }
159
+ /**
160
+ * Memory operations via CLI
161
+ */
162
+ async createMemoryViaCLI(title, content, options = {}) {
163
+ const { memoryType = 'context', tags = [], topicId } = options;
164
+ let command = `memory create --title "${title}" --content "${content}" --memory-type ${memoryType}`;
165
+ if (tags.length > 0) {
166
+ command += ` --tags "${tags.join(',')}"`;
167
+ }
168
+ if (topicId) {
169
+ command += ` --topic-id "${topicId}"`;
170
+ }
171
+ return this.executeCLICommand(command);
172
+ }
173
+ async listMemoriesViaCLI(options = {}) {
174
+ let command = 'memory list';
175
+ if (options.limit) {
176
+ command += ` --limit ${options.limit}`;
177
+ }
178
+ if (options.memoryType) {
179
+ command += ` --memory-type ${options.memoryType}`;
180
+ }
181
+ if (options.tags && options.tags.length > 0) {
182
+ command += ` --tags "${options.tags.join(',')}"`;
183
+ }
184
+ if (options.sortBy) {
185
+ command += ` --sort-by ${options.sortBy}`;
186
+ }
187
+ return this.executeCLICommand(command);
188
+ }
189
+ async searchMemoriesViaCLI(query, options = {}) {
190
+ let command = `memory search "${query}"`;
191
+ if (options.limit) {
192
+ command += ` --limit ${options.limit}`;
193
+ }
194
+ if (options.memoryTypes && options.memoryTypes.length > 0) {
195
+ command += ` --memory-types "${options.memoryTypes.join(',')}"`;
196
+ }
197
+ return this.executeCLICommand(command);
198
+ }
199
+ /**
200
+ * Health check via CLI
201
+ */
202
+ async healthCheckViaCLI() {
203
+ return this.executeCLICommand('health');
204
+ }
205
+ /**
206
+ * MCP-specific operations
207
+ */
208
+ async getMCPStatus() {
209
+ const cliInfo = await this.detectCLI();
210
+ if (!cliInfo.mcpAvailable) {
211
+ return { error: 'MCP not available via CLI' };
212
+ }
213
+ return this.executeCLICommand('mcp status');
214
+ }
215
+ async listMCPTools() {
216
+ const cliInfo = await this.detectCLI();
217
+ if (!cliInfo.mcpAvailable) {
218
+ return { error: 'MCP not available via CLI' };
219
+ }
220
+ return this.executeCLICommand('mcp tools');
221
+ }
222
+ /**
223
+ * Authentication operations
224
+ */
225
+ async getAuthStatus() {
226
+ return this.executeCLICommand('auth status');
227
+ }
228
+ /**
229
+ * Check if specific CLI features are available
230
+ */
231
+ async getCapabilities() {
232
+ const cliInfo = await this.detectCLI();
233
+ return {
234
+ cliAvailable: cliInfo.available,
235
+ version: cliInfo.version,
236
+ mcpSupport: cliInfo.mcpAvailable || false,
237
+ authenticated: cliInfo.authenticated || false,
238
+ goldenContract: cliInfo.available && this.isGoldenContractCompliant(cliInfo.version)
239
+ };
240
+ }
241
+ isGoldenContractCompliant(version) {
242
+ if (!version)
243
+ return false;
244
+ const versionMatch = version.match(/(\d+)\.(\d+)\.(\d+)/);
245
+ if (!versionMatch)
246
+ return false;
247
+ const [, major, minor, patch] = versionMatch.map(Number);
248
+ return major > 1 || (major === 1 && minor > 5) || (major === 1 && minor === 5 && patch >= 2);
249
+ }
250
+ /**
251
+ * Force refresh CLI detection
252
+ */
253
+ async refresh() {
254
+ this.cliInfo = null;
255
+ this.detectionPromise = null;
256
+ return this.detectCLI();
257
+ }
258
+ /**
259
+ * Get cached CLI info without re-detection
260
+ */
261
+ getCachedInfo() {
262
+ return this.cliInfo;
263
+ }
264
+ }
265
+ // Singleton instance for convenient access
266
+ const cliIntegration = new CLIIntegration();
267
+
268
+ /**
269
+ * Enhanced Memory Client with CLI Integration
270
+ *
271
+ * Intelligently routes requests through CLI v1.5.2+ when available,
272
+ * with fallback to direct API for maximum compatibility and performance
273
+ *
274
+ * IMPORTANT: This file uses Node.js-specific features (process.env) and should only be used in Node.js environments
275
+ */
276
+ /**
277
+ * Enhanced Memory Client with intelligent CLI/API routing
278
+ */
279
+ class EnhancedMemoryClient {
280
+ createDefaultCapabilities() {
281
+ return {
282
+ cliAvailable: false,
283
+ mcpSupport: false,
284
+ authenticated: false,
285
+ goldenContract: false
286
+ };
287
+ }
288
+ constructor(config) {
289
+ this.capabilities = null;
290
+ this.config = {
291
+ preferCLI: true,
292
+ enableMCP: true,
293
+ cliDetectionTimeout: 5000,
294
+ fallbackToAPI: true,
295
+ minCLIVersion: '1.5.2',
296
+ verbose: false,
297
+ timeout: 30000,
298
+ apiKey: config.apiKey || process.env.LANONASIS_API_KEY || '',
299
+ authToken: config.authToken || '',
300
+ headers: config.headers || {},
301
+ ...config
302
+ };
303
+ this.directClient = new CoreMemoryClient(config);
304
+ this.cliIntegration = new CLIIntegration();
305
+ }
306
+ /**
307
+ * Initialize the client and detect capabilities
308
+ */
309
+ async initialize() {
310
+ try {
311
+ const detectionPromise = this.cliIntegration.getCapabilities();
312
+ const capabilities = this.config.cliDetectionTimeout > 0
313
+ ? await Promise.race([
314
+ detectionPromise,
315
+ new Promise((resolve) => {
316
+ setTimeout(() => resolve(null), this.config.cliDetectionTimeout);
317
+ })
318
+ ])
319
+ : await detectionPromise;
320
+ if (capabilities) {
321
+ this.capabilities = capabilities;
322
+ if (this.config.verbose && capabilities.cliAvailable && !capabilities.authenticated) {
323
+ const suggestedCommand = capabilities.goldenContract ? 'onasis login' : 'lanonasis login';
324
+ console.warn(`CLI detected but not authenticated. Run '${suggestedCommand}' to enable enhanced SDK features.`);
325
+ }
326
+ }
327
+ else {
328
+ this.capabilities = this.createDefaultCapabilities();
329
+ if (this.config.verbose) {
330
+ console.warn(`CLI detection timed out after ${this.config.cliDetectionTimeout}ms. Falling back to API mode.`);
331
+ }
332
+ }
333
+ }
334
+ catch (error) {
335
+ if (this.config.verbose) {
336
+ console.warn('CLI detection failed:', error);
337
+ }
338
+ this.capabilities = this.createDefaultCapabilities();
339
+ }
340
+ }
341
+ /**
342
+ * Get current capabilities
343
+ */
344
+ async getCapabilities() {
345
+ if (!this.capabilities) {
346
+ await this.initialize();
347
+ }
348
+ if (!this.capabilities) {
349
+ this.capabilities = this.createDefaultCapabilities();
350
+ }
351
+ return this.capabilities;
352
+ }
353
+ /**
354
+ * Determine if operation should use CLI
355
+ */
356
+ async shouldUseCLI() {
357
+ const capabilities = await this.getCapabilities();
358
+ return (this.config.preferCLI &&
359
+ capabilities.cliAvailable &&
360
+ capabilities.authenticated &&
361
+ capabilities.goldenContract);
362
+ }
363
+ /**
364
+ * Execute operation with intelligent routing
365
+ */
366
+ async executeOperation(operation, cliOperation, apiOperation) {
367
+ const useCLI = await this.shouldUseCLI();
368
+ const capabilities = await this.getCapabilities();
369
+ if (useCLI) {
370
+ try {
371
+ const result = await cliOperation();
372
+ if (result.error && this.config.fallbackToAPI) {
373
+ console.warn(`CLI ${operation} failed, falling back to API:`, result.error);
374
+ const apiResult = await apiOperation();
375
+ return {
376
+ ...apiResult,
377
+ source: 'api',
378
+ mcpUsed: false
379
+ };
380
+ }
381
+ return {
382
+ ...result,
383
+ source: 'cli',
384
+ mcpUsed: capabilities.mcpSupport
385
+ };
386
+ }
387
+ catch (error) {
388
+ if (this.config.fallbackToAPI) {
389
+ console.warn(`CLI ${operation} error, falling back to API:`, error);
390
+ const apiResult = await apiOperation();
391
+ return {
392
+ ...apiResult,
393
+ source: 'api',
394
+ mcpUsed: false
395
+ };
396
+ }
397
+ return {
398
+ error: error instanceof Error ? error.message : `CLI ${operation} failed`,
399
+ source: 'cli',
400
+ mcpUsed: false
401
+ };
402
+ }
403
+ }
404
+ else {
405
+ const result = await apiOperation();
406
+ return {
407
+ ...result,
408
+ source: 'api',
409
+ mcpUsed: false
410
+ };
411
+ }
412
+ }
413
+ // Enhanced API Methods
414
+ /**
415
+ * Health check with intelligent routing
416
+ */
417
+ async healthCheck() {
418
+ return this.executeOperation('health check', () => this.cliIntegration.healthCheckViaCLI(), () => this.directClient.healthCheck());
419
+ }
420
+ /**
421
+ * Create memory with CLI/API routing
422
+ */
423
+ async createMemory(memory) {
424
+ return this.executeOperation('create memory', () => this.cliIntegration.createMemoryViaCLI(memory.title, memory.content, {
425
+ memoryType: memory.memory_type,
426
+ tags: memory.tags,
427
+ topicId: memory.topic_id
428
+ }), () => this.directClient.createMemory(memory));
429
+ }
430
+ /**
431
+ * List memories with intelligent routing
432
+ */
433
+ async listMemories(options = {}) {
434
+ return this.executeOperation('list memories', () => this.cliIntegration.listMemoriesViaCLI({
435
+ limit: options.limit,
436
+ memoryType: options.memory_type,
437
+ tags: options.tags,
438
+ sortBy: options.sort
439
+ }), () => this.directClient.listMemories(options));
440
+ }
441
+ /**
442
+ * Search memories with MCP enhancement when available
443
+ */
444
+ async searchMemories(request) {
445
+ return this.executeOperation('search memories', () => this.cliIntegration.searchMemoriesViaCLI(request.query, {
446
+ limit: request.limit,
447
+ memoryTypes: request.memory_types
448
+ }), () => this.directClient.searchMemories(request));
449
+ }
450
+ /**
451
+ * Get memory by ID (API only for now)
452
+ */
453
+ async getMemory(id) {
454
+ // CLI doesn't have get by ID yet, use API
455
+ const result = await this.directClient.getMemory(id);
456
+ return {
457
+ ...result,
458
+ source: 'api',
459
+ mcpUsed: false
460
+ };
461
+ }
462
+ /**
463
+ * Update memory (API only for now)
464
+ */
465
+ async updateMemory(id, updates) {
466
+ // CLI doesn't have update yet, use API
467
+ const result = await this.directClient.updateMemory(id, updates);
468
+ return {
469
+ ...result,
470
+ source: 'api',
471
+ mcpUsed: false
472
+ };
473
+ }
474
+ /**
475
+ * Delete memory (API only for now)
476
+ */
477
+ async deleteMemory(id) {
478
+ // CLI doesn't have delete yet, use API
479
+ const result = await this.directClient.deleteMemory(id);
480
+ return {
481
+ ...result,
482
+ source: 'api',
483
+ mcpUsed: false
484
+ };
485
+ }
486
+ // Topic Operations (API only for now)
487
+ async createTopic(topic) {
488
+ const result = await this.directClient.createTopic(topic);
489
+ return { ...result, source: 'api', mcpUsed: false };
490
+ }
491
+ async getTopics() {
492
+ const result = await this.directClient.getTopics();
493
+ return { ...result, source: 'api', mcpUsed: false };
494
+ }
495
+ async getTopic(id) {
496
+ const result = await this.directClient.getTopic(id);
497
+ return { ...result, source: 'api', mcpUsed: false };
498
+ }
499
+ async updateTopic(id, updates) {
500
+ const result = await this.directClient.updateTopic(id, updates);
501
+ return { ...result, source: 'api', mcpUsed: false };
502
+ }
503
+ async deleteTopic(id) {
504
+ const result = await this.directClient.deleteTopic(id);
505
+ return { ...result, source: 'api', mcpUsed: false };
506
+ }
507
+ /**
508
+ * Get memory statistics
509
+ */
510
+ async getMemoryStats() {
511
+ const result = await this.directClient.getMemoryStats();
512
+ return { ...result, source: 'api', mcpUsed: false };
513
+ }
514
+ // Utility Methods
515
+ /**
516
+ * Force CLI re-detection
517
+ */
518
+ async refreshCLIDetection() {
519
+ this.capabilities = null;
520
+ await this.cliIntegration.refresh();
521
+ await this.initialize();
522
+ }
523
+ /**
524
+ * Get authentication status from CLI
525
+ */
526
+ async getAuthStatus() {
527
+ try {
528
+ const result = await this.cliIntegration.getAuthStatus();
529
+ return { ...result, source: 'cli', mcpUsed: false };
530
+ }
531
+ catch (error) {
532
+ return {
533
+ error: error instanceof Error ? error.message : 'Auth status check failed',
534
+ source: 'cli',
535
+ mcpUsed: false
536
+ };
537
+ }
538
+ }
539
+ /**
540
+ * Get MCP status when available
541
+ */
542
+ async getMCPStatus() {
543
+ const capabilities = await this.getCapabilities();
544
+ if (!capabilities.mcpSupport) {
545
+ return {
546
+ error: 'MCP not available',
547
+ source: 'cli',
548
+ mcpUsed: false
549
+ };
550
+ }
551
+ try {
552
+ const result = await this.cliIntegration.getMCPStatus();
553
+ return { ...result, source: 'cli', mcpUsed: true };
554
+ }
555
+ catch (error) {
556
+ return {
557
+ error: error instanceof Error ? error.message : 'MCP status check failed',
558
+ source: 'cli',
559
+ mcpUsed: false
560
+ };
561
+ }
562
+ }
563
+ /**
564
+ * Update authentication for both CLI and API client
565
+ */
566
+ setAuthToken(token) {
567
+ this.directClient.setAuthToken(token);
568
+ }
569
+ setApiKey(apiKey) {
570
+ this.directClient.setApiKey(apiKey);
571
+ }
572
+ clearAuth() {
573
+ this.directClient.clearAuth();
574
+ }
575
+ /**
576
+ * Update configuration
577
+ */
578
+ updateConfig(updates) {
579
+ this.config = { ...this.config, ...updates };
580
+ this.directClient.updateConfig(updates);
581
+ }
582
+ /**
583
+ * Get configuration summary
584
+ */
585
+ getConfigSummary() {
586
+ return {
587
+ apiUrl: this.config.apiUrl,
588
+ preferCLI: this.config.preferCLI,
589
+ enableMCP: this.config.enableMCP,
590
+ capabilities: this.capabilities || undefined
591
+ };
592
+ }
593
+ }
594
+ /**
595
+ * Factory function to create an enhanced memory client
596
+ */
597
+ async function createNodeMemoryClient(config) {
598
+ const client = new EnhancedMemoryClient(config);
599
+ await client.initialize();
600
+ return client;
601
+ }
602
+ /**
603
+ * Synchronous factory function (initialization happens on first API call)
604
+ */
605
+ function createEnhancedMemoryClient(config) {
606
+ return new EnhancedMemoryClient(config);
607
+ }
608
+
609
+ export { CLIIntegration, EnhancedMemoryClient, cliIntegration, createEnhancedMemoryClient, createNodeMemoryClient };
610
+ //# sourceMappingURL=index.js.map