@lanonasis/cli 3.0.1 → 3.0.2

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,5 @@
1
+ /**
2
+ * Enhanced Memory Commands - mem0-inspired advanced operations
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function enhancedMemoryCommands(program: Command): void;
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Enhanced Memory Commands - mem0-inspired advanced operations
3
+ */
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { table } from 'table';
7
+ import inquirer from 'inquirer';
8
+ import { getMCPClient } from '../utils/mcp-client.js';
9
+ import { CLIConfig } from '../utils/config.js';
10
+ export function enhancedMemoryCommands(program) {
11
+ const memory = program.command('memory').description('Enhanced memory operations with mem0-inspired features');
12
+ // Bulk pause memories
13
+ memory.command('bulk-pause')
14
+ .description('Pause multiple memories by criteria')
15
+ .option('--category <category>', 'Pause memories in specific category')
16
+ .option('--app <app_id>', 'Pause memories from specific app')
17
+ .option('--before <date>', 'Pause memories created before date (ISO format)')
18
+ .option('--ids <ids>', 'Comma-separated memory IDs to pause')
19
+ .option('--dry-run', 'Show what would be paused without executing')
20
+ .action(async (options) => {
21
+ const spinner = ora('Processing bulk pause operation...').start();
22
+ try {
23
+ const client = getMCPClient();
24
+ if (!client.isConnectedToServer()) {
25
+ spinner.info('Connecting to MCP server...');
26
+ const config = new CLIConfig();
27
+ await client.connect({ useRemote: !!config.get('token') });
28
+ }
29
+ const bulkArgs = { operation: 'pause' };
30
+ if (options.category)
31
+ bulkArgs.category = options.category;
32
+ if (options.app)
33
+ bulkArgs.app_id = options.app;
34
+ if (options.before)
35
+ bulkArgs.before = options.before;
36
+ if (options.ids)
37
+ bulkArgs.memory_ids = options.ids.split(',').map((id) => id.trim());
38
+ if (options.dryRun) {
39
+ spinner.succeed('Dry run mode - showing affected memories');
40
+ console.log(chalk.yellow('This would pause memories matching the criteria'));
41
+ return;
42
+ }
43
+ const result = await client.callTool('memory_bulk_operations', bulkArgs);
44
+ spinner.succeed(`Bulk pause completed: ${result.result?.affected_count} memories paused`);
45
+ if (result.result?.results && result.result?.results.length > 0) {
46
+ console.log(chalk.cyan('\nšŸ“Š Operation Results:'));
47
+ const tableData = [
48
+ [chalk.bold('Memory ID'), chalk.bold('Status'), chalk.bold('Previous State')]
49
+ ];
50
+ result.result?.results.forEach((r) => {
51
+ tableData.push([
52
+ r.memory_id.substring(0, 8) + '...',
53
+ r.success ? chalk.green('āœ“ Paused') : chalk.red('āœ— Failed'),
54
+ r.previous_state
55
+ ]);
56
+ });
57
+ console.log(table(tableData));
58
+ }
59
+ }
60
+ catch (error) {
61
+ spinner.fail(`Bulk pause failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
62
+ process.exit(1);
63
+ }
64
+ });
65
+ // Archive old memories
66
+ memory.command('archive')
67
+ .description('Archive memories older than specified date')
68
+ .requiredOption('--before <date>', 'Archive memories created before date (ISO format)')
69
+ .option('--app <app_id>', 'Archive memories from specific app')
70
+ .option('--dry-run', 'Show what would be archived without executing')
71
+ .action(async (options) => {
72
+ const spinner = ora('Processing archive operation...').start();
73
+ try {
74
+ const client = getMCPClient();
75
+ if (!client.isConnectedToServer()) {
76
+ spinner.info('Connecting to MCP server...');
77
+ const config = new CLIConfig();
78
+ await client.connect({ useRemote: !!config.get('token') });
79
+ }
80
+ const bulkArgs = {
81
+ operation: 'archive',
82
+ before: options.before
83
+ };
84
+ if (options.app)
85
+ bulkArgs.app_id = options.app;
86
+ if (options.dryRun) {
87
+ spinner.succeed('Dry run mode - showing memories to be archived');
88
+ console.log(chalk.yellow(`This would archive memories created before ${options.before}`));
89
+ return;
90
+ }
91
+ const result = await client.callTool('memory_bulk_operations', bulkArgs);
92
+ spinner.succeed(`Archive completed: ${result.result?.affected_count} memories archived`);
93
+ console.log(chalk.green(`\nāœ“ Successfully archived ${result.result?.affected_count} memories`));
94
+ console.log(chalk.cyan(` Archived memories created before: ${options.before}`));
95
+ if (options.app) {
96
+ console.log(chalk.cyan(` From app: ${options.app}`));
97
+ }
98
+ }
99
+ catch (error) {
100
+ spinner.fail(`Archive failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
101
+ process.exit(1);
102
+ }
103
+ });
104
+ // Find related memories
105
+ memory.command('related')
106
+ .description('Find memories related to a specific memory')
107
+ .argument('<memory_id>', 'Source memory ID')
108
+ .option('-l, --limit <number>', 'Maximum related memories to show', '5')
109
+ .option('-t, --threshold <number>', 'Similarity threshold (0-1)', '0.6')
110
+ .action(async (memoryId, options) => {
111
+ const spinner = ora('Finding related memories...').start();
112
+ try {
113
+ const client = getMCPClient();
114
+ if (!client.isConnectedToServer()) {
115
+ spinner.info('Connecting to MCP server...');
116
+ const config = new CLIConfig();
117
+ await client.connect({ useRemote: !!config.get('token') });
118
+ }
119
+ const result = await client.callTool('memory_find_related', {
120
+ memory_id: memoryId,
121
+ limit: parseInt(options.limit),
122
+ threshold: parseFloat(options.threshold)
123
+ });
124
+ spinner.succeed(`Found ${result.result?.count} related memories`);
125
+ if (result.result?.count === 0) {
126
+ console.log(chalk.yellow('\nNo related memories found'));
127
+ return;
128
+ }
129
+ console.log(chalk.cyan('\nšŸ”— Source Memory:'));
130
+ console.log(` ${chalk.bold(result.result?.source_memory.title)}`);
131
+ console.log(` ${result.result?.source_memory.content.substring(0, 100)}...`);
132
+ console.log(chalk.cyan('\nšŸ” Related Memories:'));
133
+ result.result?.related_memories.forEach((memory, index) => {
134
+ console.log(`\n${chalk.bold(`${index + 1}. ${memory.title}`)}`);
135
+ console.log(` ID: ${chalk.gray(memory.id)}`);
136
+ const similarity = memory.relevance_score ?? memory.score;
137
+ if (similarity !== undefined) {
138
+ console.log(` Similarity: ${chalk.green((similarity * 100).toFixed(1) + '%')}`);
139
+ }
140
+ const content = memory.content ?? memory.metadata?.content ?? '';
141
+ console.log(` Content: ${content
142
+ ? `${content.substring(0, 80)}...`
143
+ : chalk.gray('[no preview]')}`);
144
+ });
145
+ }
146
+ catch (error) {
147
+ spinner.fail(`Related search failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
148
+ process.exit(1);
149
+ }
150
+ });
151
+ // Advanced filtering
152
+ memory.command('filter')
153
+ .description('Filter memories with advanced criteria')
154
+ .option('--app-id <app_id>', 'Filter by application ID')
155
+ .option('--category <category>', 'Filter by category/tag')
156
+ .option('--since <date>', 'Filter memories since date (ISO format)')
157
+ .option('--before <date>', 'Filter memories before date (ISO format)')
158
+ .option('--state <state>', 'Filter by memory state (active, paused, archived)')
159
+ .option('--limit <number>', 'Maximum results', '20')
160
+ .action(async (options) => {
161
+ const spinner = ora('Filtering memories...').start();
162
+ try {
163
+ const client = getMCPClient();
164
+ if (!client.isConnectedToServer()) {
165
+ spinner.info('Connecting to MCP server...');
166
+ const config = new CLIConfig();
167
+ await client.connect({ useRemote: !!config.get('token') });
168
+ }
169
+ const searchArgs = {
170
+ query: '*', // Wildcard search
171
+ limit: parseInt(options.limit)
172
+ };
173
+ if (options.appId)
174
+ searchArgs.app_id = options.appId;
175
+ if (options.category)
176
+ searchArgs.category = options.category;
177
+ if (options.since)
178
+ searchArgs.since = options.since;
179
+ if (options.before)
180
+ searchArgs.before = options.before;
181
+ const result = await client.callTool('memory_search_memories', searchArgs);
182
+ spinner.succeed(`Found ${result.result?.total || result.result?.results.length} memories`);
183
+ if (result.result?.results.length === 0) {
184
+ console.log(chalk.yellow('\nNo memories match the filter criteria'));
185
+ return;
186
+ }
187
+ console.log(chalk.cyan('\nšŸ“‹ Filtered Memories:'));
188
+ const tableData = [
189
+ [chalk.bold('ID'), chalk.bold('Title'), chalk.bold('Type'), chalk.bold('Created')]
190
+ ];
191
+ result.result?.results.forEach((memory) => {
192
+ tableData.push([
193
+ memory.id.substring(0, 8) + '...',
194
+ memory.title.substring(0, 30) + (memory.title.length > 30 ? '...' : ''),
195
+ memory.memory_type || 'context',
196
+ new Date(memory.created_at).toLocaleDateString()
197
+ ]);
198
+ });
199
+ console.log(table(tableData));
200
+ // Show filter summary
201
+ console.log(chalk.cyan('\nšŸ“Š Filter Summary:'));
202
+ if (options.appId)
203
+ console.log(` App ID: ${options.appId}`);
204
+ if (options.category)
205
+ console.log(` Category: ${options.category}`);
206
+ if (options.since)
207
+ console.log(` Since: ${options.since}`);
208
+ if (options.before)
209
+ console.log(` Before: ${options.before}`);
210
+ if (options.state)
211
+ console.log(` State: ${options.state}`);
212
+ }
213
+ catch (error) {
214
+ spinner.fail(`Filter failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
215
+ process.exit(1);
216
+ }
217
+ });
218
+ // Memory analytics
219
+ memory.command('analytics')
220
+ .description('Show memory usage analytics and insights')
221
+ .option('--app <app_id>', 'Analytics for specific app')
222
+ .option('--period <days>', 'Analysis period in days', '30')
223
+ .action(async (options) => {
224
+ const spinner = ora('Generating memory analytics...').start();
225
+ try {
226
+ const client = getMCPClient();
227
+ if (!client.isConnectedToServer()) {
228
+ spinner.info('Connecting to MCP server...');
229
+ const config = new CLIConfig();
230
+ await client.connect({ useRemote: !!config.get('token') });
231
+ }
232
+ // Get all memories for analysis
233
+ const allMemories = await client.callTool('memory_search_memories', {
234
+ query: '*',
235
+ limit: 1000
236
+ });
237
+ spinner.succeed('Analytics generated');
238
+ const memories = allMemories.result?.results || [];
239
+ // Basic statistics
240
+ console.log(chalk.cyan('\nšŸ“Š Memory Analytics'));
241
+ console.log(chalk.cyan('=================='));
242
+ console.log(`Total Memories: ${chalk.bold(memories.length)}`);
243
+ // Memory types breakdown
244
+ const typeBreakdown = {};
245
+ memories.forEach((m) => {
246
+ const type = m.memory_type || 'context';
247
+ typeBreakdown[type] = (typeBreakdown[type] || 0) + 1;
248
+ });
249
+ console.log(chalk.cyan('\nšŸ“ˆ Memory Types:'));
250
+ Object.entries(typeBreakdown).forEach(([type, count]) => {
251
+ const percentage = ((count / memories.length) * 100).toFixed(1);
252
+ console.log(` ${type}: ${chalk.bold(count)} (${percentage}%)`);
253
+ });
254
+ // Recent activity
255
+ const now = new Date();
256
+ const periodDays = parseInt(options.period);
257
+ const cutoffDate = new Date(now.getTime() - (periodDays * 24 * 60 * 60 * 1000));
258
+ const recentMemories = memories.filter((m) => new Date(m.created_at) >= cutoffDate);
259
+ console.log(chalk.cyan(`\nšŸ“… Recent Activity (${periodDays} days):`));
260
+ console.log(` New memories: ${chalk.bold(recentMemories.length)}`);
261
+ console.log(` Daily average: ${chalk.bold((recentMemories.length / periodDays).toFixed(1))}`);
262
+ // App breakdown if not filtering by specific app
263
+ if (!options.app) {
264
+ const appBreakdown = {};
265
+ memories.forEach((m) => {
266
+ const app = m.app_id || 'default';
267
+ appBreakdown[app] = (appBreakdown[app] || 0) + 1;
268
+ });
269
+ console.log(chalk.cyan('\nšŸ”§ App Usage:'));
270
+ Object.entries(appBreakdown)
271
+ .sort(([, a], [, b]) => b - a)
272
+ .slice(0, 5)
273
+ .forEach(([app, count]) => {
274
+ const percentage = ((count / memories.length) * 100).toFixed(1);
275
+ console.log(` ${app}: ${chalk.bold(count)} (${percentage}%)`);
276
+ });
277
+ }
278
+ }
279
+ catch (error) {
280
+ spinner.fail(`Analytics failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
281
+ process.exit(1);
282
+ }
283
+ });
284
+ // Interactive memory management
285
+ memory.command('manage')
286
+ .description('Interactive memory management interface')
287
+ .action(async () => {
288
+ console.log(chalk.cyan('🧠 Interactive Memory Management'));
289
+ console.log(chalk.cyan('================================'));
290
+ try {
291
+ const client = getMCPClient();
292
+ if (!client.isConnectedToServer()) {
293
+ const spinner = ora('Connecting to MCP server...').start();
294
+ const config = new CLIConfig();
295
+ await client.connect({ useRemote: !!config.get('token') });
296
+ spinner.succeed('Connected to MCP server');
297
+ }
298
+ while (true) {
299
+ const { action } = await inquirer.prompt([
300
+ {
301
+ type: 'list',
302
+ name: 'action',
303
+ message: 'What would you like to do?',
304
+ choices: [
305
+ { name: 'šŸ” Search memories', value: 'search' },
306
+ { name: 'šŸ“Š View analytics', value: 'analytics' },
307
+ { name: 'šŸ”— Find related memories', value: 'related' },
308
+ { name: 'āøļø Bulk pause memories', value: 'bulk_pause' },
309
+ { name: 'šŸ“¦ Archive old memories', value: 'archive' },
310
+ { name: '🚪 Exit', value: 'exit' }
311
+ ]
312
+ }
313
+ ]);
314
+ if (action === 'exit') {
315
+ console.log(chalk.green('šŸ‘‹ Goodbye!'));
316
+ break;
317
+ }
318
+ // Handle each action
319
+ switch (action) {
320
+ case 'search':
321
+ case 'search': {
322
+ const { query } = await inquirer.prompt([
323
+ { type: 'input', name: 'query', message: 'Enter search query:' }
324
+ ]);
325
+ if (query) {
326
+ const spinner = ora('Searching...').start();
327
+ const results = await client.callTool('memory_search_memories', { query });
328
+ spinner.succeed(`Found ${results.result?.results.length} memories`);
329
+ results.result?.results.slice(0, 5).forEach((m, i) => {
330
+ console.log(`\n${i + 1}. ${chalk.bold(m.title)}`);
331
+ console.log(` ${m.content.substring(0, 100)}...`);
332
+ });
333
+ }
334
+ break;
335
+ }
336
+ case 'analytics': {
337
+ console.log(chalk.cyan('šŸ“Š Generating analytics...'));
338
+ // Could call the analytics logic here
339
+ break;
340
+ }
341
+ case 'related': {
342
+ const { memoryId } = await inquirer.prompt([
343
+ { type: 'input', name: 'memoryId', message: 'Enter memory ID:' }
344
+ ]);
345
+ if (memoryId) {
346
+ const spinner = ora('Finding related memories...').start();
347
+ const related = await client.callTool('memory_find_related', { memory_id: memoryId });
348
+ spinner.succeed(`Found ${related.result?.count} related memories`);
349
+ related.result?.related_memories?.slice(0, 3).forEach((m, i) => {
350
+ console.log(`\n${i + 1}. ${chalk.bold(m.title)}`);
351
+ console.log(` Similarity: ${chalk.green((m.relevance_score * 100).toFixed(1) + '%')}`);
352
+ });
353
+ }
354
+ break;
355
+ }
356
+ case 'bulk_pause': {
357
+ const { pauseCriteria } = await inquirer.prompt([
358
+ {
359
+ type: 'list',
360
+ name: 'pauseCriteria',
361
+ message: 'Pause memories by:',
362
+ choices: [
363
+ { name: 'Before specific date', value: 'date' },
364
+ { name: 'By category', value: 'category' },
365
+ { name: 'By app', value: 'app' }
366
+ ]
367
+ }
368
+ ]);
369
+ console.log(chalk.yellow(`Selected: ${pauseCriteria}`));
370
+ // Could implement the specific pause logic here
371
+ break;
372
+ }
373
+ case 'archive': {
374
+ const { archiveDate } = await inquirer.prompt([
375
+ {
376
+ type: 'input',
377
+ name: 'archiveDate',
378
+ message: 'Archive memories before date (YYYY-MM-DD):',
379
+ validate: (input) => {
380
+ const date = new Date(input);
381
+ return !isNaN(date.getTime()) || 'Please enter a valid date';
382
+ }
383
+ }
384
+ ]);
385
+ if (archiveDate) {
386
+ const spinner = ora('Archiving memories...').start();
387
+ const result = await client.callTool('memory_bulk_operations', {
388
+ operation: 'archive',
389
+ before: archiveDate
390
+ });
391
+ spinner.succeed(`Archived ${result.result?.affected_count} memories`);
392
+ }
393
+ break;
394
+ }
395
+ }
396
+ console.log(); // Add spacing
397
+ }
398
+ }
399
+ catch (error) {
400
+ console.error(chalk.red(`Management interface failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
401
+ process.exit(1);
402
+ }
403
+ });
404
+ }
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { table } from 'table';
4
4
  import { getMCPClient } from '../utils/mcp-client.js';
5
+ import { EnhancedMCPClient } from '../mcp/client/enhanced-client.js';
5
6
  import { CLIConfig } from '../utils/config.js';
6
7
  export function mcpCommands(program) {
7
8
  const mcp = program
@@ -92,12 +93,32 @@ export function mcpCommands(program) {
92
93
  config.set('mcpServerUrl', options.url);
93
94
  }
94
95
  }
95
- const client = getMCPClient();
96
- const connected = await client.connect({
97
- connectionMode,
98
- serverPath: options.server,
99
- serverUrl: options.url
100
- });
96
+ let connected = false;
97
+ // Use Enhanced MCP Client for better connection handling
98
+ const enhancedClient = new EnhancedMCPClient();
99
+ if (options.url) {
100
+ // Connect to specific URL (WebSocket or remote)
101
+ const serverConfig = {
102
+ name: 'user-specified',
103
+ type: (options.url.startsWith('wss://') ? 'websocket' : 'stdio'),
104
+ url: options.url,
105
+ priority: 1
106
+ };
107
+ connected = await enhancedClient.connectSingle(serverConfig);
108
+ if (connected) {
109
+ spinner.succeed(chalk.green(`Connected to MCP server at ${options.url}`));
110
+ return;
111
+ }
112
+ }
113
+ else {
114
+ // Fall back to old client for local connections
115
+ const client = getMCPClient();
116
+ connected = await client.connect({
117
+ connectionMode,
118
+ serverPath: options.server,
119
+ serverUrl: options.url
120
+ });
121
+ }
101
122
  if (connected) {
102
123
  spinner.succeed(chalk.green(`Connected to MCP server in ${connectionMode} mode`));
103
124
  if (connectionMode === 'remote') {
package/dist/index.js CHANGED
File without changes
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Memory Access Control System
3
+ * Implements granular permissions and audit logging inspired by mem0's ACL system
4
+ */
5
+ export interface AccessControlRule {
6
+ id: string;
7
+ user_id: string;
8
+ app_id: string;
9
+ memory_id?: string;
10
+ permission: 'read' | 'write' | 'delete' | 'admin';
11
+ granted: boolean;
12
+ created_at: string;
13
+ expires_at?: string;
14
+ }
15
+ export interface AccessLog {
16
+ id: string;
17
+ user_id: string;
18
+ app_id: string;
19
+ memory_id: string;
20
+ access_type: string;
21
+ timestamp: string;
22
+ success: boolean;
23
+ metadata?: Record<string, any>;
24
+ }
25
+ export declare class MemoryAccessControl {
26
+ private config;
27
+ private accessRules;
28
+ private accessLogs;
29
+ constructor();
30
+ /**
31
+ * Check if user has access to create memories in an app
32
+ */
33
+ checkCreateAccess(userId: string, appId: string): Promise<boolean>;
34
+ /**
35
+ * Check if user has access to read a specific memory
36
+ */
37
+ checkMemoryAccess(memoryId: string, appId: string): Promise<boolean>;
38
+ /**
39
+ * Get all memories accessible to a user in an app
40
+ */
41
+ getAccessibleMemories(userId: string, appId: string): Promise<string[]>;
42
+ /**
43
+ * Log memory access for audit purposes
44
+ */
45
+ logMemoryAccess(memoryId: string, appId: string, accessType: string, metadata?: Record<string, any>): Promise<void>;
46
+ /**
47
+ * Grant access to a memory or app
48
+ */
49
+ grantAccess(userId: string, appId: string, permission: 'read' | 'write' | 'delete' | 'admin', memoryId?: string, expiresAt?: string): Promise<void>;
50
+ /**
51
+ * Revoke access to a memory or app
52
+ */
53
+ revokeAccess(userId: string, appId: string, memoryId?: string): Promise<void>;
54
+ /**
55
+ * Get access logs for audit purposes
56
+ */
57
+ getAccessLogs(userId?: string, appId?: string, memoryId?: string, limit?: number): AccessLog[];
58
+ /**
59
+ * Private helper methods
60
+ */
61
+ private getAccessRules;
62
+ private isUserApp;
63
+ private getCurrentUserId;
64
+ private getMemoryInfo;
65
+ private getUserMemories;
66
+ private getSharedMemories;
67
+ private generateId;
68
+ }