claude-self-reflect 2.4.1 → 2.4.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,433 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync, spawn, spawnSync } from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import fs from 'fs/promises';
7
+ import fsSync from 'fs';
8
+ import readline from 'readline';
9
+ import path from 'path';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ const projectRoot = join(__dirname, '..');
14
+
15
+ // Parse command line arguments
16
+ const args = process.argv.slice(2);
17
+ let voyageKey = null;
18
+ let debugMode = false;
19
+
20
+ for (const arg of args) {
21
+ if (arg.startsWith('--voyage-key=')) {
22
+ voyageKey = arg.split('=')[1];
23
+ } else if (arg === '--debug') {
24
+ debugMode = true;
25
+ }
26
+ }
27
+
28
+ // Default to local mode unless Voyage key is provided
29
+ let localMode = !voyageKey;
30
+
31
+ // Helper to safely execute commands
32
+ function safeExec(command, args = [], options = {}) {
33
+ const result = spawnSync(command, args, {
34
+ ...options,
35
+ shell: false
36
+ });
37
+
38
+ if (result.error) {
39
+ throw result.error;
40
+ }
41
+
42
+ if (result.status !== 0) {
43
+ const error = new Error(`Command failed: ${command} ${args.join(' ')}`);
44
+ error.stdout = result.stdout;
45
+ error.stderr = result.stderr;
46
+ error.status = result.status;
47
+ throw error;
48
+ }
49
+
50
+ return result.stdout?.toString() || '';
51
+ }
52
+
53
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
54
+
55
+ const rl = isInteractive ? readline.createInterface({
56
+ input: process.stdin,
57
+ output: process.stdout
58
+ }) : null;
59
+
60
+ const question = (query) => {
61
+ if (!isInteractive) {
62
+ console.log(`Non-interactive mode detected. ${query} [Defaulting to 'n']`);
63
+ return Promise.resolve('n');
64
+ }
65
+ return new Promise((resolve) => rl.question(query, resolve));
66
+ };
67
+
68
+ async function checkDocker() {
69
+ console.log('\n🐳 Checking Docker...');
70
+ try {
71
+ safeExec('docker', ['info'], { stdio: 'ignore' });
72
+ console.log('āœ… Docker is installed and running');
73
+
74
+ // Check docker compose
75
+ try {
76
+ safeExec('docker', ['compose', 'version'], { stdio: 'ignore' });
77
+ console.log('āœ… Docker Compose v2 is available');
78
+ return true;
79
+ } catch {
80
+ console.log('āŒ Docker Compose v2 not found');
81
+ console.log(' Please update Docker Desktop to the latest version');
82
+ return false;
83
+ }
84
+ } catch {
85
+ console.log('āŒ Docker is not running or not installed');
86
+ console.log('\nšŸ“‹ Please install Docker:');
87
+ console.log(' • macOS/Windows: https://docker.com/products/docker-desktop');
88
+ console.log(' • Linux: https://docs.docker.com/engine/install/');
89
+ console.log('\n After installation, make sure Docker is running and try again.');
90
+ return false;
91
+ }
92
+ }
93
+
94
+ async function configureEnvironment() {
95
+ console.log('\nšŸ” Configuring environment...');
96
+
97
+ const envPath = join(projectRoot, '.env');
98
+ let envContent = '';
99
+ let hasValidApiKey = false;
100
+
101
+ try {
102
+ envContent = await fs.readFile(envPath, 'utf-8');
103
+ } catch {
104
+ // .env doesn't exist, create it
105
+ }
106
+
107
+ // Check if we have a command line API key
108
+ if (voyageKey) {
109
+ if (voyageKey.startsWith('pa-')) {
110
+ console.log('āœ… Using API key from command line');
111
+ envContent = envContent.replace(/VOYAGE_KEY=.*/g, '');
112
+ envContent += `\nVOYAGE_KEY=${voyageKey}\n`;
113
+ hasValidApiKey = true;
114
+ } else {
115
+ console.log('āŒ Invalid API key format. Voyage keys start with "pa-"');
116
+ process.exit(1);
117
+ }
118
+ } else if (localMode) {
119
+ console.log('šŸ  Running in local mode - no API key required');
120
+ hasValidApiKey = false;
121
+ } else {
122
+ // Check if we already have a valid API key
123
+ const existingKeyMatch = envContent.match(/VOYAGE_KEY=([^\s]+)/);
124
+ if (existingKeyMatch && existingKeyMatch[1] && !existingKeyMatch[1].includes('your-')) {
125
+ console.log('āœ… Found existing Voyage API key in .env file');
126
+ hasValidApiKey = true;
127
+ } else if (isInteractive) {
128
+ console.log('\nšŸ”‘ Voyage AI API Key Setup (Optional)');
129
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
130
+ console.log('For better search accuracy, you can use Voyage AI embeddings.');
131
+ console.log('Skip this to use local embeddings (recommended for privacy).\n');
132
+
133
+ const inputKey = await question('Paste your Voyage AI key (or press Enter to skip): ');
134
+
135
+ if (inputKey && inputKey.trim() && inputKey.trim().startsWith('pa-')) {
136
+ envContent = envContent.replace(/VOYAGE_KEY=.*/g, '');
137
+ envContent += `\nVOYAGE_KEY=${inputKey.trim()}\n`;
138
+ hasValidApiKey = true;
139
+ console.log('āœ… API key saved');
140
+ } else if (inputKey && inputKey.trim()) {
141
+ console.log('āš ļø Invalid key format. Skipping...');
142
+ }
143
+ }
144
+ }
145
+
146
+ // Set default values
147
+ if (!envContent.includes('QDRANT_URL=')) {
148
+ envContent += 'QDRANT_URL=http://localhost:6333\n';
149
+ }
150
+ if (!envContent.includes('ENABLE_MEMORY_DECAY=')) {
151
+ envContent += 'ENABLE_MEMORY_DECAY=false\n';
152
+ }
153
+ if (!envContent.includes('PREFER_LOCAL_EMBEDDINGS=')) {
154
+ envContent += `PREFER_LOCAL_EMBEDDINGS=${localMode ? 'true' : 'false'}\n`;
155
+ }
156
+
157
+ await fs.writeFile(envPath, envContent.trim() + '\n');
158
+ console.log('āœ… Environment configured');
159
+
160
+ return { hasValidApiKey };
161
+ }
162
+
163
+ async function startDockerServices() {
164
+ console.log('\nšŸš€ Starting Docker services...');
165
+
166
+ try {
167
+ // First, ensure any old containers are stopped
168
+ console.log('🧹 Cleaning up old containers...');
169
+ try {
170
+ safeExec('docker', ['compose', 'down'], {
171
+ cwd: projectRoot,
172
+ stdio: 'pipe'
173
+ });
174
+ } catch {
175
+ // Ignore errors if no containers exist
176
+ }
177
+
178
+ // Check for existing bind mount data that needs migration
179
+ const bindMountPath = join(projectRoot, 'data', 'qdrant');
180
+ try {
181
+ await fs.access(bindMountPath);
182
+ const files = await fs.readdir(bindMountPath);
183
+ if (files.length > 0) {
184
+ console.log('\nāš ļø Found existing Qdrant data in ./data/qdrant');
185
+ console.log('šŸ“¦ This will be automatically migrated to Docker volume on first start.');
186
+
187
+ // Create a migration marker
188
+ await fs.writeFile(join(projectRoot, '.needs-migration'), 'true');
189
+ }
190
+ } catch {
191
+ // No existing data, nothing to migrate
192
+ }
193
+
194
+ // Start Qdrant and MCP server
195
+ console.log('šŸ“¦ Starting Qdrant database and MCP server...');
196
+ safeExec('docker', ['compose', '--profile', 'mcp', 'up', '-d'], {
197
+ cwd: projectRoot,
198
+ stdio: 'inherit'
199
+ });
200
+
201
+ // Wait for services to be ready
202
+ console.log('ā³ Waiting for services to start...');
203
+ await new Promise(resolve => setTimeout(resolve, 5000));
204
+
205
+ // Check if we need to migrate data
206
+ try {
207
+ await fs.access(join(projectRoot, '.needs-migration'));
208
+ console.log('\nšŸ”„ Migrating data from bind mount to Docker volume...');
209
+
210
+ // Stop Qdrant to perform migration
211
+ safeExec('docker', ['compose', 'stop', 'qdrant'], {
212
+ cwd: projectRoot,
213
+ stdio: 'pipe'
214
+ });
215
+
216
+ // Copy data from bind mount to Docker volume
217
+ safeExec('docker', ['run', '--rm',
218
+ '-v', `${projectRoot}/data/qdrant:/source:ro`,
219
+ '-v', 'claude-self-reflect_qdrant_data:/target',
220
+ 'alpine', 'sh', '-c', 'cp -R /source/* /target/'
221
+ ], {
222
+ cwd: projectRoot,
223
+ stdio: 'inherit'
224
+ });
225
+
226
+ console.log('āœ… Data migration completed!');
227
+
228
+ // Remove migration marker
229
+ await fs.unlink(join(projectRoot, '.needs-migration'));
230
+
231
+ // Restart Qdrant
232
+ safeExec('docker', ['compose', '--profile', 'mcp', 'up', '-d', 'qdrant'], {
233
+ cwd: projectRoot,
234
+ stdio: 'pipe'
235
+ });
236
+
237
+ await new Promise(resolve => setTimeout(resolve, 3000));
238
+ } catch {
239
+ // No migration needed
240
+ }
241
+
242
+ // Check if services are running
243
+ const psOutput = safeExec('docker', ['compose', 'ps', '--format', 'table'], {
244
+ cwd: projectRoot,
245
+ encoding: 'utf8'
246
+ });
247
+
248
+ console.log('\nšŸ“Š Service Status:');
249
+ console.log(psOutput);
250
+
251
+ return true;
252
+ } catch (error) {
253
+ console.log('āŒ Failed to start Docker services:', error.message);
254
+ return false;
255
+ }
256
+ }
257
+
258
+ async function configureClaude() {
259
+ console.log('\nšŸ¤– Configuring Claude Desktop...');
260
+
261
+ const mcpScript = join(projectRoot, 'mcp-server', 'run-mcp-docker.sh');
262
+
263
+ // Create a script that runs the MCP server in Docker
264
+ const scriptContent = `#!/bin/bash
265
+ docker exec -i claude-reflection-mcp python -m src.server_v2
266
+ `;
267
+
268
+ await fs.writeFile(mcpScript, scriptContent, { mode: 0o755 });
269
+
270
+ // Check if Claude CLI is available
271
+ try {
272
+ safeExec('which', ['claude'], { stdio: 'ignore' });
273
+
274
+ console.log('šŸ”§ Adding MCP to Claude Desktop...');
275
+ try {
276
+ const mcpArgs = ['mcp', 'add', 'claude-self-reflect', mcpScript];
277
+ safeExec('claude', mcpArgs, { stdio: 'inherit' });
278
+ console.log('āœ… MCP added successfully!');
279
+ console.log('\nāš ļø Please restart Claude Desktop for changes to take effect.');
280
+ } catch {
281
+ console.log('āš ļø Could not add MCP automatically');
282
+ showManualConfig(mcpScript);
283
+ }
284
+ } catch {
285
+ console.log('āš ļø Claude CLI not found');
286
+ showManualConfig(mcpScript);
287
+ }
288
+ }
289
+
290
+ function showManualConfig(mcpScript) {
291
+ console.log('\nAdd this to your Claude Desktop config manually:');
292
+ console.log('```json');
293
+ console.log(JSON.stringify({
294
+ "claude-self-reflect": {
295
+ "command": mcpScript
296
+ }
297
+ }, null, 2));
298
+ console.log('```');
299
+ }
300
+
301
+ async function importConversations() {
302
+ console.log('\nšŸ“š Importing conversations...');
303
+
304
+ const answer = await question('Would you like to import your existing Claude conversations? (y/n): ');
305
+
306
+ if (answer.toLowerCase() === 'y') {
307
+ console.log('šŸ”„ Starting import process...');
308
+ console.log(' This may take a few minutes depending on your conversation history');
309
+
310
+ try {
311
+ safeExec('docker', ['compose', 'run', '--rm', 'importer'], {
312
+ cwd: projectRoot,
313
+ stdio: 'inherit'
314
+ });
315
+ console.log('\nāœ… Import completed!');
316
+ } catch {
317
+ console.log('\nāš ļø Import had some issues, but you can continue');
318
+ }
319
+ } else {
320
+ console.log('šŸ“ Skipping import. You can import later with:');
321
+ console.log(' docker compose run --rm importer');
322
+ }
323
+ }
324
+
325
+ async function showFinalInstructions() {
326
+ console.log('\nāœ… Setup complete!');
327
+
328
+ console.log('\nšŸŽÆ Your Claude Self-Reflect System:');
329
+ console.log(' • 🌐 Qdrant Dashboard: http://localhost:6333/dashboard/');
330
+ console.log(' • šŸ“Š Status: All services running');
331
+ console.log(' • šŸ” Search: Semantic search with memory decay enabled');
332
+ console.log(' • šŸš€ Import: Watcher checking every 60 seconds');
333
+
334
+ console.log('\nšŸ“‹ Quick Reference Commands:');
335
+ console.log(' • Check status: docker compose ps');
336
+ console.log(' • View logs: docker compose logs -f');
337
+ console.log(' • Import conversations: docker compose run --rm importer');
338
+ console.log(' • Start watcher: docker compose --profile watch up -d');
339
+ console.log(' • Stop all: docker compose down');
340
+
341
+ console.log('\nšŸŽÆ Next Steps:');
342
+ console.log('1. Restart Claude Desktop');
343
+ console.log('2. Look for "claude-self-reflect" in the MCP tools');
344
+ console.log('3. Try: "Search my past conversations about Python"');
345
+
346
+ console.log('\nšŸ“š Documentation: https://github.com/ramakay/claude-self-reflect');
347
+ }
348
+
349
+ async function checkExistingInstallation() {
350
+ try {
351
+ // Check if services are already running
352
+ const psResult = safeExec('docker', ['compose', '-f', 'docker-compose.yaml', 'ps', '--format', 'json'], {
353
+ cwd: projectRoot,
354
+ encoding: 'utf8'
355
+ });
356
+
357
+ if (psResult && psResult.includes('claude-reflection-')) {
358
+ const services = psResult.split('\n').filter(line => line.trim());
359
+ const runningServices = services.filter(line => line.includes('"State":"running"')).length;
360
+
361
+ if (runningServices >= 2) { // At least Qdrant and MCP should be running
362
+ console.log('āœ… Claude Self-Reflect is already installed and running!\n');
363
+ console.log('šŸŽÆ Your System Status:');
364
+ console.log(' • 🌐 Qdrant Dashboard: http://localhost:6333/dashboard/');
365
+ console.log(' • šŸ“Š Services: ' + runningServices + ' containers running');
366
+ console.log(' • šŸ” Mode: ' + (localMode ? 'Local embeddings (privacy mode)' : 'Cloud embeddings (Voyage AI)'));
367
+ console.log(' • ⚔ Memory decay: Enabled (90-day half-life)');
368
+
369
+ console.log('\nšŸ“‹ Quick Commands:');
370
+ console.log(' • View status: docker compose ps');
371
+ console.log(' • View logs: docker compose logs -f');
372
+ console.log(' • Restart: docker compose restart');
373
+ console.log(' • Stop: docker compose down');
374
+
375
+ console.log('\nšŸ’” To re-run setup, first stop services with: docker compose down');
376
+ return true;
377
+ }
378
+ }
379
+ } catch (err) {
380
+ // Services not running, continue with setup
381
+ }
382
+ return false;
383
+ }
384
+
385
+ async function main() {
386
+ console.log('šŸš€ Claude Self-Reflect Setup (Docker Edition)\n');
387
+
388
+ // Check if already installed
389
+ const alreadyInstalled = await checkExistingInstallation();
390
+ if (alreadyInstalled) {
391
+ if (rl) rl.close();
392
+ process.exit(0);
393
+ }
394
+
395
+ console.log('This simplified setup runs everything in Docker.');
396
+ console.log('No Python installation required!\n');
397
+
398
+ // Check Docker
399
+ const dockerOk = await checkDocker();
400
+ if (!dockerOk) {
401
+ if (rl) rl.close();
402
+ process.exit(1);
403
+ }
404
+
405
+ // Configure environment
406
+ await configureEnvironment();
407
+
408
+ // Start services
409
+ const servicesOk = await startDockerServices();
410
+ if (!servicesOk) {
411
+ console.log('\nāŒ Failed to start services');
412
+ console.log(' Check the Docker logs for details');
413
+ if (rl) rl.close();
414
+ process.exit(1);
415
+ }
416
+
417
+ // Configure Claude
418
+ await configureClaude();
419
+
420
+ // Import conversations
421
+ await importConversations();
422
+
423
+ // Show final instructions
424
+ await showFinalInstructions();
425
+
426
+ if (rl) rl.close();
427
+ }
428
+
429
+ main().catch(error => {
430
+ console.error('āŒ Setup failed:', error);
431
+ if (rl) rl.close();
432
+ process.exit(1);
433
+ });