@soulcraft/brainy 3.22.0 → 3.23.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.
@@ -1,538 +0,0 @@
1
- /**
2
- * šŸ’¬ Conversation CLI Commands
3
- *
4
- * CLI interface for infinite agent memory and conversation management
5
- */
6
- import inquirer from 'inquirer';
7
- import chalk from 'chalk';
8
- import ora from 'ora';
9
- import * as fs from '../../universal/fs.js';
10
- import * as path from '../../universal/path.js';
11
- import { Brainy } from '../../brainy.js';
12
- export const conversationCommand = {
13
- command: 'conversation [action]',
14
- describe: 'šŸ’¬ Conversation and context management',
15
- builder: (yargs) => {
16
- return yargs
17
- .positional('action', {
18
- describe: 'Conversation operation to perform',
19
- type: 'string',
20
- choices: ['setup', 'remove', 'search', 'context', 'thread', 'stats', 'export', 'import']
21
- })
22
- .option('conversation-id', {
23
- describe: 'Conversation ID',
24
- type: 'string',
25
- alias: 'c'
26
- })
27
- .option('query', {
28
- describe: 'Search query or context query',
29
- type: 'string',
30
- alias: 'q'
31
- })
32
- .option('role', {
33
- describe: 'Filter by message role',
34
- type: 'string',
35
- choices: ['user', 'assistant', 'system', 'tool'],
36
- alias: 'r'
37
- })
38
- .option('limit', {
39
- describe: 'Maximum results',
40
- type: 'number',
41
- default: 10,
42
- alias: 'l'
43
- })
44
- .option('format', {
45
- describe: 'Output format',
46
- type: 'string',
47
- choices: ['json', 'table', 'text'],
48
- default: 'table',
49
- alias: 'f'
50
- })
51
- .option('output', {
52
- describe: 'Output file path',
53
- type: 'string',
54
- alias: 'o'
55
- })
56
- .example('$0 conversation setup', 'Set up MCP server for Claude Code')
57
- .example('$0 conversation search -q "authentication" -l 5', 'Search messages')
58
- .example('$0 conversation context -q "how to implement JWT"', 'Get relevant context')
59
- .example('$0 conversation thread -c conv_123', 'Get conversation thread')
60
- .example('$0 conversation stats', 'Show conversation statistics');
61
- },
62
- handler: async (argv) => {
63
- const action = argv.action || 'setup';
64
- try {
65
- switch (action) {
66
- case 'setup':
67
- await handleSetup(argv);
68
- break;
69
- case 'remove':
70
- await handleRemove(argv);
71
- break;
72
- case 'search':
73
- await handleSearch(argv);
74
- break;
75
- case 'context':
76
- await handleContext(argv);
77
- break;
78
- case 'thread':
79
- await handleThread(argv);
80
- break;
81
- case 'stats':
82
- await handleStats(argv);
83
- break;
84
- case 'export':
85
- await handleExport(argv);
86
- break;
87
- case 'import':
88
- await handleImport(argv);
89
- break;
90
- default:
91
- console.log(chalk.yellow(`Unknown action: ${action}`));
92
- console.log('Run "brainy conversation --help" for usage information');
93
- }
94
- }
95
- catch (error) {
96
- console.error(chalk.red(`Error: ${error.message}`));
97
- process.exit(1);
98
- }
99
- }
100
- };
101
- /**
102
- * Handle setup command - Set up MCP server for Claude Code
103
- */
104
- async function handleSetup(argv) {
105
- console.log(chalk.bold.cyan('\n🧠 Brainy Infinite Memory Setup\n'));
106
- // Check for existing setup
107
- const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
108
- const brainyDir = path.join(homeDir, '.brainy-memory');
109
- const dataDir = path.join(brainyDir, 'data');
110
- const serverPath = path.join(brainyDir, 'mcp-server.js');
111
- const configPath = path.join(homeDir, '.config', 'claude-code', 'mcp-servers.json');
112
- // Check if already set up
113
- if (await fs.exists(brainyDir)) {
114
- const { overwrite } = await inquirer.prompt([
115
- {
116
- type: 'confirm',
117
- name: 'overwrite',
118
- message: 'Brainy memory setup already exists. Overwrite?',
119
- default: false
120
- }
121
- ]);
122
- if (!overwrite) {
123
- console.log(chalk.yellow('Setup cancelled'));
124
- return;
125
- }
126
- }
127
- const spinner = ora('Creating Brainy memory directory...').start();
128
- try {
129
- // Create directories
130
- await fs.mkdir(brainyDir, { recursive: true });
131
- await fs.mkdir(dataDir, { recursive: true });
132
- spinner.succeed('Created Brainy memory directory');
133
- // Create MCP server script
134
- spinner.start('Creating MCP server script...');
135
- const serverScript = `#!/usr/bin/env node
136
-
137
- /**
138
- * Brainy Infinite Memory MCP Server
139
- *
140
- * This server provides conversation and context management
141
- * for Claude Code through the Model Control Protocol (MCP).
142
- */
143
-
144
- import { Brainy } from '@soulcraft/brainy'
145
- import { BrainyMCPService } from '@soulcraft/brainy'
146
- import { MCPConversationToolset } from '@soulcraft/brainy'
147
-
148
- async function main() {
149
- try {
150
- // Initialize Brainy with filesystem storage
151
- const brain = new Brainy({
152
- storage: {
153
- type: 'filesystem',
154
- path: '${dataDir.replace(/\\/g, '/')}'
155
- },
156
- silent: true // Suppress console output
157
- })
158
-
159
- await brain.init()
160
-
161
- // Create MCP service
162
- const mcpService = new BrainyMCPService(brain, {
163
- enableAuth: false // Local usage, no auth needed
164
- })
165
-
166
- // Create conversation toolset
167
- const conversationTools = new MCPConversationToolset(brain)
168
- await conversationTools.init()
169
-
170
- // Register conversation tools
171
- const tools = await conversationTools.getAvailableTools()
172
-
173
- console.error('🧠 Brainy Memory Server started')
174
- console.error(\`šŸ“Š \${tools.length} conversation tools available\`)
175
- console.error('āœ… Ready for Claude Code integration')
176
-
177
- // Handle MCP requests via stdio
178
- process.stdin.on('data', async (data) => {
179
- try {
180
- const request = JSON.parse(data.toString())
181
-
182
- // Route conversation tool requests
183
- let response
184
- if (request.toolName && request.toolName.startsWith('conversation_')) {
185
- response = await conversationTools.handleRequest(request)
186
- } else {
187
- response = await mcpService.handleRequest(request)
188
- }
189
-
190
- // Write response to stdout
191
- process.stdout.write(JSON.stringify(response) + '\\n')
192
- } catch (error) {
193
- console.error('Error handling request:', error)
194
- }
195
- })
196
-
197
- // Handle shutdown gracefully
198
- process.on('SIGINT', () => {
199
- console.error('\\nšŸ›‘ Shutting down Brainy Memory Server')
200
- process.exit(0)
201
- })
202
-
203
- } catch (error) {
204
- console.error('Failed to start Brainy Memory Server:', error)
205
- process.exit(1)
206
- }
207
- }
208
-
209
- main()
210
- `;
211
- await fs.writeFile(serverPath, serverScript, 'utf8');
212
- // Make executable on Unix systems
213
- try {
214
- await import('fs').then(fsModule => {
215
- fsModule.promises.chmod(serverPath, 0o755).catch(() => { });
216
- });
217
- }
218
- catch {
219
- // Windows doesn't need chmod
220
- }
221
- spinner.succeed('Created MCP server script');
222
- // Create Claude Code config
223
- spinner.start('Configuring Claude Code...');
224
- const configDir = path.dirname(configPath);
225
- await fs.mkdir(configDir, { recursive: true });
226
- let mcpConfig = {};
227
- if (await fs.exists(configPath)) {
228
- const existingConfig = await fs.readFile(configPath, 'utf8');
229
- mcpConfig = JSON.parse(existingConfig);
230
- }
231
- mcpConfig['brainy-memory'] = {
232
- command: 'node',
233
- args: [serverPath],
234
- env: {
235
- NODE_ENV: 'production'
236
- }
237
- };
238
- await fs.writeFile(configPath, JSON.stringify(mcpConfig, null, 2), 'utf8');
239
- spinner.succeed('Configured Claude Code');
240
- // Initialize Brainy database
241
- spinner.start('Initializing Brainy database...');
242
- const brain = new Brainy({
243
- storage: {
244
- type: 'filesystem',
245
- options: {
246
- path: dataDir
247
- }
248
- },
249
- silent: true
250
- });
251
- await brain.init();
252
- spinner.succeed('Initialized Brainy database');
253
- // Shutdown Brainy to release resources
254
- await brain.close();
255
- // Success!
256
- console.log(chalk.bold.green('\nāœ… Setup complete!\n'));
257
- console.log(chalk.cyan('šŸ“ Memory storage:'), brainyDir);
258
- console.log(chalk.cyan('šŸ”§ MCP server:'), serverPath);
259
- console.log(chalk.cyan('āš™ļø Claude Code config:'), configPath);
260
- console.log();
261
- console.log(chalk.bold('šŸš€ Next steps:'));
262
- console.log(' 1. Restart Claude Code to load the MCP server');
263
- console.log(' 2. Start a new conversation - your history will be saved automatically!');
264
- console.log(' 3. Claude will use past context to help you work faster');
265
- console.log();
266
- console.log(chalk.dim('Run "brainy conversation stats" to see your conversation statistics'));
267
- }
268
- catch (error) {
269
- spinner.fail('Setup failed');
270
- throw error;
271
- }
272
- }
273
- /**
274
- * Handle remove command - Remove MCP server and optionally data
275
- */
276
- async function handleRemove(argv) {
277
- console.log(chalk.bold.cyan('\nšŸ—‘ļø Brainy Infinite Memory Removal\n'));
278
- const homeDir = process.env.HOME || process.env.USERPROFILE || '~';
279
- const brainyDir = path.join(homeDir, '.brainy-memory');
280
- const configPath = path.join(homeDir, '.config', 'claude-code', 'mcp-servers.json');
281
- // Check if setup exists
282
- const brainyExists = await fs.exists(brainyDir);
283
- const configExists = await fs.exists(configPath);
284
- if (!brainyExists && !configExists) {
285
- console.log(chalk.yellow('No Brainy memory setup found. Nothing to remove.'));
286
- return;
287
- }
288
- // Show what will be removed
289
- console.log(chalk.white('The following will be removed:'));
290
- if (brainyExists) {
291
- console.log(chalk.dim(` • ${brainyDir} (memory data and MCP server)`));
292
- }
293
- if (configExists) {
294
- console.log(chalk.dim(` • MCP config entry in ${configPath}`));
295
- }
296
- console.log();
297
- // Confirm removal
298
- const { confirm } = await inquirer.prompt([
299
- {
300
- type: 'confirm',
301
- name: 'confirm',
302
- message: 'Are you sure you want to remove Brainy infinite memory?',
303
- default: false
304
- }
305
- ]);
306
- if (!confirm) {
307
- console.log(chalk.yellow('Removal cancelled'));
308
- return;
309
- }
310
- const spinner = ora('Removing Brainy memory setup...').start();
311
- try {
312
- // Remove Brainy directory
313
- if (brainyExists) {
314
- // Use Node.js fs for rm operation as universal fs doesn't have it
315
- const nodefs = await import('fs');
316
- await nodefs.promises.rm(brainyDir, { recursive: true, force: true });
317
- spinner.text = 'Removed memory directory...';
318
- }
319
- // Remove MCP config entry
320
- if (configExists) {
321
- try {
322
- const configContent = await fs.readFile(configPath, 'utf8');
323
- const mcpConfig = JSON.parse(configContent);
324
- if (mcpConfig['brainy-memory']) {
325
- delete mcpConfig['brainy-memory'];
326
- await fs.writeFile(configPath, JSON.stringify(mcpConfig, null, 2), 'utf8');
327
- spinner.text = 'Removed MCP configuration...';
328
- }
329
- }
330
- catch (error) {
331
- // If config file is corrupted or empty, skip
332
- spinner.warn('Could not update MCP config file');
333
- }
334
- }
335
- spinner.succeed('Successfully removed Brainy infinite memory');
336
- console.log();
337
- console.log(chalk.bold('āœ… Cleanup complete'));
338
- console.log();
339
- console.log(chalk.white('All conversation data and MCP configuration have been removed.'));
340
- console.log(chalk.dim('Run "brainy conversation setup" to set up again.'));
341
- console.log();
342
- }
343
- catch (error) {
344
- spinner.fail('Removal failed');
345
- console.error(chalk.red('Error:'), error.message);
346
- throw error;
347
- }
348
- }
349
- /**
350
- * Handle search command - Search messages
351
- */
352
- async function handleSearch(argv) {
353
- if (!argv.query) {
354
- console.log(chalk.yellow('Query required. Use -q or --query'));
355
- return;
356
- }
357
- const spinner = ora('Searching conversations...').start();
358
- const brain = new Brainy();
359
- await brain.init();
360
- const conv = brain.conversation();
361
- await conv.init();
362
- const results = await conv.searchMessages({
363
- query: argv.query,
364
- limit: argv.limit || 10,
365
- role: argv.role,
366
- includeContent: true,
367
- includeMetadata: true
368
- });
369
- spinner.succeed(`Found ${results.length} messages`);
370
- if (results.length === 0) {
371
- console.log(chalk.yellow('No messages found'));
372
- await brain.close();
373
- return;
374
- }
375
- // Display results
376
- console.log();
377
- for (const result of results) {
378
- console.log(chalk.bold.cyan(`${result.message.role}:`), result.snippet);
379
- console.log(chalk.dim(` Score: ${result.score.toFixed(3)} | Conv: ${result.conversationId}`));
380
- console.log();
381
- }
382
- await brain.close();
383
- }
384
- /**
385
- * Handle context command - Get relevant context
386
- */
387
- async function handleContext(argv) {
388
- if (!argv.query) {
389
- console.log(chalk.yellow('Query required. Use -q or --query'));
390
- return;
391
- }
392
- const spinner = ora('Retrieving relevant context...').start();
393
- const brain = new Brainy();
394
- await brain.init();
395
- const conv = brain.conversation();
396
- await conv.init();
397
- const context = await conv.getRelevantContext(argv.query, {
398
- limit: argv.limit || 10,
399
- includeArtifacts: true,
400
- includeSimilarConversations: true
401
- });
402
- spinner.succeed(`Retrieved ${context.messages.length} relevant messages`);
403
- if (context.messages.length === 0) {
404
- console.log(chalk.yellow('No relevant context found'));
405
- await brain.close();
406
- return;
407
- }
408
- // Display context
409
- console.log();
410
- console.log(chalk.bold('šŸ“Š Context Statistics:'));
411
- console.log(chalk.dim(` Messages: ${context.messages.length}`));
412
- console.log(chalk.dim(` Tokens: ${context.totalTokens}`));
413
- console.log(chalk.dim(` Query time: ${context.metadata.queryTime}ms`));
414
- console.log();
415
- console.log(chalk.bold('šŸ’¬ Relevant Messages:'));
416
- for (const msg of context.messages) {
417
- console.log();
418
- console.log(chalk.cyan(`${msg.role} (score: ${msg.relevanceScore.toFixed(3)}):`));
419
- console.log(msg.content.substring(0, 200) + (msg.content.length > 200 ? '...' : ''));
420
- }
421
- if (context.similarConversations && context.similarConversations.length > 0) {
422
- console.log();
423
- console.log(chalk.bold('šŸ”— Similar Conversations:'));
424
- for (const conv of context.similarConversations) {
425
- console.log(chalk.dim(` - ${conv.title || conv.id} (${conv.relevance.toFixed(2)})`));
426
- }
427
- }
428
- await brain.close();
429
- }
430
- /**
431
- * Handle thread command - Get conversation thread
432
- */
433
- async function handleThread(argv) {
434
- if (!argv.conversationId) {
435
- console.log(chalk.yellow('Conversation ID required. Use -c or --conversation-id'));
436
- return;
437
- }
438
- const spinner = ora('Loading conversation thread...').start();
439
- const brain = new Brainy();
440
- await brain.init();
441
- const conv = brain.conversation();
442
- await conv.init();
443
- const thread = await conv.getConversationThread(argv.conversationId, {
444
- includeArtifacts: true
445
- });
446
- spinner.succeed(`Loaded ${thread.messages.length} messages`);
447
- // Display thread
448
- console.log();
449
- console.log(chalk.bold('šŸ“Š Thread Information:'));
450
- console.log(chalk.dim(` Conversation: ${thread.id}`));
451
- console.log(chalk.dim(` Messages: ${thread.metadata.messageCount}`));
452
- console.log(chalk.dim(` Tokens: ${thread.metadata.totalTokens}`));
453
- console.log(chalk.dim(` Started: ${new Date(thread.metadata.startTime).toLocaleString()}`));
454
- console.log();
455
- console.log(chalk.bold('šŸ’¬ Messages:'));
456
- for (const msg of thread.messages) {
457
- console.log();
458
- console.log(chalk.cyan(`${msg.role}:`), msg.content);
459
- console.log(chalk.dim(` ${new Date(msg.createdAt).toLocaleString()}`));
460
- }
461
- await brain.close();
462
- }
463
- /**
464
- * Handle stats command - Show statistics
465
- */
466
- async function handleStats(argv) {
467
- const spinner = ora('Calculating statistics...').start();
468
- const brain = new Brainy();
469
- await brain.init();
470
- const conv = brain.conversation();
471
- await conv.init();
472
- const stats = await conv.getConversationStats();
473
- spinner.succeed('Statistics calculated');
474
- // Display stats
475
- console.log();
476
- console.log(chalk.bold.cyan('šŸ“Š Conversation Statistics\n'));
477
- console.log(chalk.bold('Overall:'));
478
- console.log(chalk.dim(` Conversations: ${stats.totalConversations}`));
479
- console.log(chalk.dim(` Messages: ${stats.totalMessages}`));
480
- console.log(chalk.dim(` Total Tokens: ${stats.totalTokens.toLocaleString()}`));
481
- console.log(chalk.dim(` Avg Messages/Conversation: ${stats.averageMessagesPerConversation.toFixed(1)}`));
482
- console.log(chalk.dim(` Avg Tokens/Message: ${stats.averageTokensPerMessage.toFixed(1)}`));
483
- console.log();
484
- if (Object.keys(stats.roles).length > 0) {
485
- console.log(chalk.bold('By Role:'));
486
- for (const [role, count] of Object.entries(stats.roles)) {
487
- console.log(chalk.dim(` ${role}: ${count}`));
488
- }
489
- console.log();
490
- }
491
- if (Object.keys(stats.phases).length > 0) {
492
- console.log(chalk.bold('By Phase:'));
493
- for (const [phase, count] of Object.entries(stats.phases)) {
494
- console.log(chalk.dim(` ${phase}: ${count}`));
495
- }
496
- }
497
- await brain.close();
498
- }
499
- /**
500
- * Handle export command - Export conversation
501
- */
502
- async function handleExport(argv) {
503
- if (!argv.conversationId) {
504
- console.log(chalk.yellow('Conversation ID required. Use -c or --conversation-id'));
505
- return;
506
- }
507
- const spinner = ora('Exporting conversation...').start();
508
- const brain = new Brainy();
509
- await brain.init();
510
- const conv = brain.conversation();
511
- await conv.init();
512
- const exported = await conv.exportConversation(argv.conversationId);
513
- const output = argv.output || `conversation_${argv.conversationId}.json`;
514
- await fs.writeFile(output, JSON.stringify(exported, null, 2), 'utf8');
515
- spinner.succeed(`Exported to ${output}`);
516
- await brain.close();
517
- }
518
- /**
519
- * Handle import command - Import conversation
520
- */
521
- async function handleImport(argv) {
522
- const inputFile = argv.output;
523
- if (!inputFile) {
524
- console.log(chalk.yellow('Input file required. Use -o or --output'));
525
- return;
526
- }
527
- const spinner = ora('Importing conversation...').start();
528
- const brain = new Brainy();
529
- await brain.init();
530
- const conv = brain.conversation();
531
- await conv.init();
532
- const data = JSON.parse(await fs.readFile(inputFile, 'utf8'));
533
- const conversationId = await conv.importConversation(data);
534
- spinner.succeed(`Imported as conversation ${conversationId}`);
535
- await brain.close();
536
- }
537
- export default conversationCommand;
538
- //# sourceMappingURL=conversation.js.map