claude-self-reflect 2.3.7 → 2.3.9

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/README.md CHANGED
@@ -4,7 +4,7 @@ Claude forgets everything. This fixes that.
4
4
 
5
5
  ## What You Get
6
6
 
7
- Ask Claude about past conversations. Get actual answers. Local-first with no cloud dependencies, but cloud-enhanced search available when you need it.
7
+ Ask Claude about past conversations. Get actual answers. **100% local by default** - your conversations never leave your machine. Cloud-enhanced search available when you need it.
8
8
 
9
9
  **Before**: "I don't have access to previous conversations"
10
10
  **After**:
@@ -143,6 +143,21 @@ Built by developers tired of re-explaining context every conversation.
143
143
  - Python 3.10+
144
144
  - 5 minutes for setup
145
145
 
146
+ ## Upgrading from Earlier Versions
147
+
148
+ **v2.3.7+ includes major improvements:**
149
+ - **Privacy First**: Local embeddings by default - your data never leaves your machine
150
+ - **Smarter Setup**: Handles existing installations gracefully
151
+ - **Better Security**: Automated vulnerability scanning
152
+
153
+ **To upgrade:**
154
+ ```bash
155
+ npm update -g claude-self-reflect
156
+ claude-self-reflect setup # Re-run setup, it handles everything
157
+ ```
158
+
159
+ The setup wizard now detects and fixes common upgrade issues automatically. Your existing conversations remain searchable.
160
+
146
161
  ## Advanced Setup
147
162
 
148
163
  Want to customize? See [Configuration Guide](docs/installation-guide.md).
@@ -208,4 +223,12 @@ Monitor the release at: https://github.com/ramakay/claude-self-reflect/actions
208
223
 
209
224
  Stop reading. Start installing. Your future self will thank you.
210
225
 
226
+ ## Contributors & Acknowledgments
227
+
228
+ Special thanks to our contributors and security researchers:
229
+
230
+ - **[@TheGordon](https://github.com/TheGordon)** - Fixed critical timestamp parsing bug for Claude conversation exports (#10)
231
+ - **[@akamalov](https://github.com/akamalov)** - Highlighted Ubuntu WSL bug and helped educate about filesystem nuances
232
+ - **[@kylesnowschwartz](https://github.com/kylesnowschwartz)** - Comprehensive security review leading to v2.3.3+ security improvements (#6)
233
+
211
234
  MIT License. Built with ❤️ for the Claude community.
package/installer/cli.js CHANGED
@@ -118,10 +118,12 @@ function help() {
118
118
  console.log('\nSetup Options:');
119
119
  console.log(' --voyage-key=<key> Provide Voyage AI API key (recommended)');
120
120
  console.log(' --local Run in local mode without API key');
121
+ console.log(' --debug Enable debug output for troubleshooting');
121
122
 
122
123
  console.log('\nExamples:');
123
124
  console.log(' claude-self-reflect setup --voyage-key=pa-1234567890');
124
125
  console.log(' claude-self-reflect setup --local');
126
+ console.log(' claude-self-reflect setup --debug # For troubleshooting');
125
127
 
126
128
  console.log('\nFor more information: https://github.com/ramakay/claude-self-reflect');
127
129
  }
@@ -4,6 +4,7 @@ import { execSync, spawn, spawnSync } from 'child_process';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
6
  import fs from 'fs/promises';
7
+ import fsSync from 'fs';
7
8
  import readline from 'readline';
8
9
  import path from 'path';
9
10
 
@@ -37,16 +38,36 @@ function safeExec(command, args = [], options = {}) {
37
38
  const args = process.argv.slice(2);
38
39
  let voyageKey = null;
39
40
  let mcpConfigured = false;
41
+ let debugMode = false;
40
42
 
41
43
  for (const arg of args) {
42
44
  if (arg.startsWith('--voyage-key=')) {
43
45
  voyageKey = arg.split('=')[1];
46
+ } else if (arg === '--debug') {
47
+ debugMode = true;
44
48
  }
45
49
  }
46
50
 
47
51
  // Default to local mode unless Voyage key is provided
48
52
  let localMode = !voyageKey;
49
53
 
54
+ // WSL Detection
55
+ function isWSL() {
56
+ try {
57
+ const osRelease = fsSync.readFileSync('/proc/version', 'utf8');
58
+ return osRelease.toLowerCase().includes('microsoft') || osRelease.toLowerCase().includes('wsl');
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ // Debug logging helper
65
+ function debugLog(...args) {
66
+ if (debugMode) {
67
+ console.log('[DEBUG]', ...args);
68
+ }
69
+ }
70
+
50
71
  const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
51
72
 
52
73
  const rl = isInteractive ? readline.createInterface({
@@ -238,6 +259,12 @@ async function checkQdrant() {
238
259
  async function setupPythonEnvironment() {
239
260
  console.log('\n🐍 Setting up Python MCP server...');
240
261
 
262
+ // Detect WSL environment
263
+ if (isWSL()) {
264
+ console.log('🐧 WSL environment detected');
265
+ debugLog('Running on WSL, may need special handling');
266
+ }
267
+
241
268
  const mcpPath = join(projectRoot, 'mcp-server');
242
269
  const scriptsPath = join(projectRoot, 'scripts');
243
270
 
@@ -245,47 +272,162 @@ async function setupPythonEnvironment() {
245
272
  // Check if venv already exists
246
273
  const venvPath = join(mcpPath, 'venv');
247
274
  let venvExists = false;
275
+ let venvHealthy = false;
276
+ let needsInstall = false;
277
+
248
278
  try {
249
279
  await fs.access(venvPath);
250
280
  venvExists = true;
251
- console.log('✅ Virtual environment already exists');
281
+
282
+ // Check if existing venv is healthy
283
+ const venvPython = process.platform === 'win32'
284
+ ? join(venvPath, 'Scripts', 'python.exe')
285
+ : join(venvPath, 'bin', 'python');
286
+
287
+ try {
288
+ // Try to run python --version to verify venv is functional
289
+ safeExec(venvPython, ['--version'], { stdio: 'pipe' });
290
+
291
+ // Also check if MCP dependencies are installed
292
+ try {
293
+ safeExec(venvPython, ['-c', 'import fastmcp, qdrant_client'], { stdio: 'pipe' });
294
+ venvHealthy = true;
295
+ console.log('✅ Virtual environment already exists and is healthy');
296
+ } catch {
297
+ // Dependencies not installed, need to install them
298
+ console.log('⚠️ Virtual environment exists but missing dependencies');
299
+ needsInstall = true;
300
+ venvHealthy = true; // venv itself is healthy, just needs deps
301
+ }
302
+ } catch (healthError) {
303
+ console.log('⚠️ Existing virtual environment is corrupted');
304
+ console.log(' Removing and recreating...');
305
+
306
+ // Remove broken venv
307
+ fsSync.rmSync(venvPath, { recursive: true, force: true });
308
+ venvExists = false;
309
+ venvHealthy = false;
310
+ }
252
311
  } catch {
253
312
  // venv doesn't exist, create it
254
313
  }
255
314
 
256
- if (!venvExists) {
315
+ if (!venvExists || !venvHealthy) {
316
+ // Pre-flight checks before creating venv
317
+ console.log('Checking Python environment...');
318
+ const pythonCmd = process.env.PYTHON_PATH || 'python3';
319
+
320
+ // Check if Python is available and get version
321
+ try {
322
+ const pythonVersion = safeExec(pythonCmd, ['--version'], { stdio: 'pipe' });
323
+ debugLog(`Python version: ${pythonVersion.trim()}`);
324
+ } catch (e) {
325
+ console.log('❌ Python not found');
326
+ console.log('📚 Fix: Install Python 3.10 or higher');
327
+ return false;
328
+ }
329
+
330
+ // Check if venv module is available
331
+ debugLog('Checking if venv module is available...');
332
+ try {
333
+ const venvCheck = spawnSync(pythonCmd, ['-c', 'import venv; print("venv module OK")'], {
334
+ cwd: mcpPath,
335
+ encoding: 'utf8'
336
+ });
337
+
338
+ if (venvCheck.status !== 0) {
339
+ debugLog(`venv module check failed: ${venvCheck.stderr}`);
340
+
341
+ // WSL-specific guidance
342
+ if (isWSL()) {
343
+ console.log('\n⚠️ WSL-specific issue detected');
344
+ console.log('Common WSL fixes:');
345
+ console.log('1. Install python3-venv: sudo apt install python3.10-venv');
346
+ console.log('2. Use specific Python version: export PYTHON_PATH=python3.10');
347
+ console.log('3. Check WSL Python paths: which python3');
348
+ }
349
+
350
+ throw new Error('venv module not available');
351
+ }
352
+ debugLog('venv module is available');
353
+ } catch (moduleError) {
354
+ console.log('❌ Python venv module not found');
355
+ console.log('📚 Fix: Install python3-venv package');
356
+ console.log(' Ubuntu/Debian: sudo apt install python3-venv');
357
+ console.log(' WSL Ubuntu: sudo apt install python3.10-venv');
358
+ console.log(' macOS: Should be included with Python');
359
+ return false;
360
+ }
361
+
257
362
  // Create virtual environment
258
363
  console.log('Creating virtual environment...');
259
- const pythonCmd = process.env.PYTHON_PATH || 'python3';
260
364
  try {
261
- // Use spawn with proper path handling instead of shell execution
262
- const { spawnSync } = require('child_process');
365
+ debugLog(`Running: ${pythonCmd} -m venv venv in ${mcpPath}`);
366
+
263
367
  const result = spawnSync(pythonCmd, ['-m', 'venv', 'venv'], {
264
368
  cwd: mcpPath,
265
- stdio: 'inherit'
369
+ encoding: 'utf8'
266
370
  });
267
- if (result.error) throw result.error;
371
+
372
+ if (result.error) {
373
+ debugLog(`spawnSync error: ${result.error.message}`);
374
+ throw result.error;
375
+ }
376
+
377
+ if (result.status !== 0) {
378
+ debugLog(`venv creation failed with status ${result.status}`);
379
+ debugLog(`stdout: ${result.stdout}`);
380
+ debugLog(`stderr: ${result.stderr}`);
381
+
382
+ // Check for common WSL issues
383
+ if (isWSL() && result.stderr && result.stderr.includes('ensurepip')) {
384
+ console.log('\n⚠️ WSL pip installation issue detected');
385
+ console.log('Try: sudo apt install python3-pip python3.10-distutils');
386
+ }
387
+
388
+ throw new Error(`venv creation failed: ${result.stderr}`);
389
+ }
390
+
391
+ console.log('✅ Virtual environment created');
392
+ needsInstall = true; // Mark that we need to install dependencies
268
393
  } catch (venvError) {
269
394
  console.log('⚠️ Failed to create venv with python3, trying python...');
270
395
  try {
271
- const { spawnSync } = require('child_process');
272
396
  const result = spawnSync('python', ['-m', 'venv', 'venv'], {
273
397
  cwd: mcpPath,
274
- stdio: 'inherit'
398
+ encoding: 'utf8'
275
399
  });
400
+
276
401
  if (result.error) throw result.error;
277
- } catch {
402
+ if (result.status !== 0) {
403
+ debugLog(`Python venv creation failed: ${result.stderr}`);
404
+ throw new Error(result.stderr);
405
+ }
406
+
407
+ needsInstall = true; // Mark that we need to install dependencies
408
+ } catch (fallbackError) {
278
409
  console.log('❌ Failed to create virtual environment');
279
- console.log('📚 Fix: Install python3-venv package');
410
+
411
+ if (debugMode) {
412
+ console.log('\n🔍 Debug Information:');
413
+ console.log(`Error: ${fallbackError.message}`);
414
+ console.log(`Working directory: ${mcpPath}`);
415
+ console.log(`Python command: ${pythonCmd}`);
416
+ }
417
+
418
+ console.log('\n📚 Troubleshooting:');
419
+ console.log('1. Check Python installation: python3 --version');
420
+ console.log('2. Install venv package:');
280
421
  console.log(' Ubuntu/Debian: sudo apt install python3-venv');
281
- console.log(' macOS: Should be included with Python');
422
+ console.log(' WSL: sudo apt install python3.10-venv python3-pip');
423
+ console.log('3. Try with specific Python: PYTHON_PATH=python3.10 npm run setup');
424
+
282
425
  return false;
283
426
  }
284
427
  }
285
428
  }
286
429
 
287
430
  // Setup paths for virtual environment
288
- console.log('Setting up pip in virtual environment...');
289
431
  const venvPython = process.platform === 'win32'
290
432
  ? join(mcpPath, 'venv', 'Scripts', 'python.exe')
291
433
  : join(mcpPath, 'venv', 'bin', 'python');
@@ -293,15 +435,19 @@ async function setupPythonEnvironment() {
293
435
  ? join(mcpPath, 'venv', 'Scripts', 'pip.exe')
294
436
  : join(mcpPath, 'venv', 'bin', 'pip');
295
437
 
296
- // First, try to install certifi to help with SSL issues
297
- console.log('Installing certificate handler...');
298
- try {
299
- safeExec(venvPip, [
300
- 'install', '--trusted-host', 'pypi.org', '--trusted-host', 'files.pythonhosted.org', 'certifi'
301
- ], { cwd: mcpPath, stdio: 'pipe' });
302
- } catch {
303
- // Continue even if certifi fails
304
- }
438
+ // Only install dependencies if we just created a new venv
439
+ if (needsInstall) {
440
+ console.log('Setting up pip in virtual environment...');
441
+
442
+ // First, try to install certifi to help with SSL issues
443
+ console.log('Installing certificate handler...');
444
+ try {
445
+ safeExec(venvPip, [
446
+ 'install', '--trusted-host', 'pypi.org', '--trusted-host', 'files.pythonhosted.org', 'certifi'
447
+ ], { cwd: mcpPath, stdio: 'pipe' });
448
+ } catch {
449
+ // Continue even if certifi fails
450
+ }
305
451
 
306
452
  // Upgrade pip and install wheel first
307
453
  try {
@@ -369,9 +515,9 @@ async function setupPythonEnvironment() {
369
515
  }
370
516
  }
371
517
 
372
- // Install script dependencies
373
- console.log('Installing import script dependencies...');
374
- try {
518
+ // Install script dependencies
519
+ console.log('Installing import script dependencies...');
520
+ try {
375
521
  safeExec(venvPip, [
376
522
  'install', '-r', join(scriptsPath, 'requirements.txt')
377
523
  ], { cwd: mcpPath, stdio: 'inherit' });
@@ -387,6 +533,7 @@ async function setupPythonEnvironment() {
387
533
  console.log(' You may need to install them manually later');
388
534
  }
389
535
  }
536
+ } // End of needsInstall block
390
537
 
391
538
  console.log('✅ Python environment setup complete');
392
539
  return true;
@@ -679,7 +826,7 @@ async function importConversations() {
679
826
 
680
827
  try {
681
828
  const pythonCmd = process.env.PYTHON_PATH || 'python3';
682
- const importScript = join(projectRoot, 'scripts', 'import-conversations-voyage.py');
829
+ const importScript = join(projectRoot, 'scripts', 'import-conversations-unified.py');
683
830
 
684
831
  // Use the venv Python directly - platform specific
685
832
  let venvPython;
@@ -961,12 +1108,13 @@ async function showSystemDashboard() {
961
1108
  async function setupWatcher() {
962
1109
  console.log('\n⚙️ Setting up Continuous Import (Watcher)...');
963
1110
 
964
- const watcherScript = join(projectRoot, 'scripts', 'import-watcher.py');
1111
+ // Check if Docker compose file exists
1112
+ const dockerComposeFile = join(projectRoot, 'docker-compose.yaml');
965
1113
 
966
- // Check if watcher exists
1114
+ // Check if Docker watcher is available
967
1115
  try {
968
- await fs.access(watcherScript);
969
- console.log('✅ Watcher script found');
1116
+ await fs.access(dockerComposeFile);
1117
+ console.log('✅ Docker-based watcher available');
970
1118
 
971
1119
  // Watcher works with both local and cloud embeddings
972
1120
  console.log(localMode ? '🏠 Watcher will use local embeddings' : '🌐 Watcher will use Voyage AI embeddings');
@@ -1079,15 +1227,15 @@ async function setupWatcher() {
1079
1227
  // Fallback to manual Python execution
1080
1228
  console.log('\n📝 To enable the watcher, run:');
1081
1229
  console.log(' cd claude-self-reflect');
1082
- console.log(' source mcp-server/venv/bin/activate');
1083
- console.log(' python scripts/import-watcher.py &');
1230
+ console.log(' docker compose --profile watch up -d');
1084
1231
  }
1085
1232
  } else {
1086
1233
  console.log('\n📝 You can enable the watcher later by running:');
1087
1234
  console.log(' docker compose --profile watch up -d');
1088
1235
  }
1089
1236
  } catch {
1090
- console.log('⚠️ Watcher script not found');
1237
+ console.log('⚠️ Docker compose not found');
1238
+ console.log(' The watcher requires Docker to run continuously');
1091
1239
  }
1092
1240
  }
1093
1241
 
@@ -222,10 +222,14 @@ async def reflect_on_past(
222
222
 
223
223
  # Process results from native decay search
224
224
  for point in results.points:
225
+ # Clean timestamp for proper parsing
226
+ raw_timestamp = point.payload.get('timestamp', datetime.now().isoformat())
227
+ clean_timestamp = raw_timestamp.replace('Z', '+00:00') if raw_timestamp.endswith('Z') else raw_timestamp
228
+
225
229
  all_results.append(SearchResult(
226
230
  id=str(point.id),
227
231
  score=point.score, # Score already includes decay
228
- timestamp=point.payload.get('timestamp', datetime.now().isoformat()),
232
+ timestamp=clean_timestamp,
229
233
  role=point.payload.get('start_role', point.payload.get('role', 'unknown')),
230
234
  excerpt=(point.payload.get('text', '')[:500] + '...'),
231
235
  project_name=point.payload.get('project', collection_name.replace('conv_', '').replace('_voyage', '').replace('_local', '')),
@@ -283,10 +287,14 @@ async def reflect_on_past(
283
287
 
284
288
  # Convert to SearchResult format
285
289
  for adjusted_score, point in decay_results[:limit]:
290
+ # Clean timestamp for proper parsing
291
+ raw_timestamp = point.payload.get('timestamp', datetime.now().isoformat())
292
+ clean_timestamp = raw_timestamp.replace('Z', '+00:00') if raw_timestamp.endswith('Z') else raw_timestamp
293
+
286
294
  all_results.append(SearchResult(
287
295
  id=str(point.id),
288
296
  score=adjusted_score, # Use adjusted score
289
- timestamp=point.payload.get('timestamp', datetime.now().isoformat()),
297
+ timestamp=clean_timestamp,
290
298
  role=point.payload.get('start_role', point.payload.get('role', 'unknown')),
291
299
  excerpt=(point.payload.get('text', '')[:500] + '...'),
292
300
  project_name=point.payload.get('project', collection_name.replace('conv_', '').replace('_voyage', '').replace('_local', '')),
@@ -304,10 +312,14 @@ async def reflect_on_past(
304
312
  )
305
313
 
306
314
  for point in results:
315
+ # Clean timestamp for proper parsing
316
+ raw_timestamp = point.payload.get('timestamp', datetime.now().isoformat())
317
+ clean_timestamp = raw_timestamp.replace('Z', '+00:00') if raw_timestamp.endswith('Z') else raw_timestamp
318
+
307
319
  all_results.append(SearchResult(
308
320
  id=str(point.id),
309
321
  score=point.score,
310
- timestamp=point.payload.get('timestamp', datetime.now().isoformat()),
322
+ timestamp=clean_timestamp,
311
323
  role=point.payload.get('start_role', point.payload.get('role', 'unknown')),
312
324
  excerpt=(point.payload.get('text', '')[:500] + '...'),
313
325
  project_name=point.payload.get('project', collection_name.replace('conv_', '').replace('_voyage', '').replace('_local', '')),
@@ -330,7 +342,9 @@ async def reflect_on_past(
330
342
  result_text = f"Found {len(all_results)} relevant conversation(s) for '{query}':\n\n"
331
343
  for i, result in enumerate(all_results):
332
344
  result_text += f"**Result {i+1}** (Score: {result.score:.3f})\n"
333
- result_text += f"Time: {datetime.fromisoformat(result.timestamp).strftime('%Y-%m-%d %H:%M:%S')}\n"
345
+ # Handle timezone suffix 'Z' properly
346
+ timestamp_clean = result.timestamp.replace('Z', '+00:00') if result.timestamp.endswith('Z') else result.timestamp
347
+ result_text += f"Time: {datetime.fromisoformat(timestamp_clean).strftime('%Y-%m-%d %H:%M:%S')}\n"
334
348
  result_text += f"Project: {result.project_name}\n"
335
349
  result_text += f"Role: {result.role}\n"
336
350
  result_text += f"Excerpt: {result.excerpt}\n"
@@ -400,4 +414,4 @@ async def store_reflection(
400
414
 
401
415
 
402
416
  # Debug output
403
- print(f"[DEBUG] FastMCP server created with name: {mcp.name}")
417
+ print(f"[DEBUG] FastMCP server created with name: {mcp.name}")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-self-reflect",
3
- "version": "2.3.7",
3
+ "version": "2.3.9",
4
4
  "description": "Give Claude perfect memory of all your conversations - Installation wizard for Python MCP server",
5
5
  "keywords": [
6
6
  "claude",
@@ -47,4 +47,4 @@
47
47
  "access": "public",
48
48
  "registry": "https://registry.npmjs.org/"
49
49
  }
50
- }
50
+ }