claude-self-reflect 2.3.2 โ†’ 2.3.4

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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execSync, spawn } from 'child_process';
3
+ 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';
@@ -11,20 +11,42 @@ const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = dirname(__filename);
12
12
  const projectRoot = join(__dirname, '..');
13
13
 
14
+ // Safe command execution helper
15
+ function safeExec(command, args = [], options = {}) {
16
+ const result = spawnSync(command, args, {
17
+ ...options,
18
+ shell: false // Never use shell to prevent injection
19
+ });
20
+
21
+ if (result.error) {
22
+ throw result.error;
23
+ }
24
+
25
+ if (result.status !== 0) {
26
+ const error = new Error(`Command failed: ${command} ${args.join(' ')}`);
27
+ error.stdout = result.stdout;
28
+ error.stderr = result.stderr;
29
+ error.status = result.status;
30
+ throw error;
31
+ }
32
+
33
+ return result.stdout?.toString() || '';
34
+ }
35
+
14
36
  // Parse command line arguments
15
37
  const args = process.argv.slice(2);
16
38
  let voyageKey = null;
17
- let localMode = false;
18
39
  let mcpConfigured = false;
19
40
 
20
41
  for (const arg of args) {
21
42
  if (arg.startsWith('--voyage-key=')) {
22
43
  voyageKey = arg.split('=')[1];
23
- } else if (arg === '--local') {
24
- localMode = true;
25
44
  }
26
45
  }
27
46
 
47
+ // Default to local mode unless Voyage key is provided
48
+ let localMode = !voyageKey;
49
+
28
50
  const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
29
51
 
30
52
  const rl = isInteractive ? readline.createInterface({
@@ -43,31 +65,39 @@ const question = (query) => {
43
65
  async function checkPython() {
44
66
  console.log('\n๐Ÿ“ฆ Checking Python installation...');
45
67
  try {
46
- const version = execSync('python3 --version').toString().trim();
68
+ const version = safeExec('python3', ['--version']).trim();
47
69
  console.log(`โœ… Found ${version}`);
48
70
 
49
71
  // Check if SSL module works
50
72
  try {
51
- execSync('python3 -c "import ssl"', { stdio: 'pipe' });
73
+ safeExec('python3', ['-c', 'import ssl'], { stdio: 'pipe' });
52
74
  return true;
53
75
  } catch (sslError) {
54
76
  console.log('โš ๏ธ Python SSL module not working');
55
77
 
56
78
  // Check if we're using pyenv
57
- const whichPython = execSync('which python3').toString().trim();
79
+ const whichPython = safeExec('which', ['python3']).trim();
58
80
  if (whichPython.includes('pyenv')) {
59
81
  console.log('๐Ÿ” Detected pyenv Python with broken SSL');
60
82
 
61
83
  // Check if brew Python is available
62
84
  try {
63
- const brewPrefix = execSync('brew --prefix python@3.11 2>/dev/null || brew --prefix python@3.10 2>/dev/null || brew --prefix python@3.12 2>/dev/null', { shell: true }).toString().trim();
85
+ let brewPrefix = '';
86
+ for (const pythonVersion of ['python@3.11', 'python@3.10', 'python@3.12']) {
87
+ try {
88
+ brewPrefix = safeExec('brew', ['--prefix', pythonVersion]).trim();
89
+ if (brewPrefix) break;
90
+ } catch {}
91
+ }
64
92
  if (brewPrefix) {
65
93
  // Find the actual python executable
66
94
  let pythonPath = null;
67
95
  for (const exe of ['python3.11', 'python3.10', 'python3.12', 'python3']) {
68
96
  try {
69
97
  const fullPath = `${brewPrefix}/bin/${exe}`;
70
- execSync(`test -f ${fullPath}`);
98
+ // Use fs.existsSync instead of shell test command
99
+ const { existsSync } = await import('fs');
100
+ if (!existsSync(fullPath)) throw new Error('File not found');
71
101
  pythonPath = fullPath;
72
102
  break;
73
103
  } catch {}
@@ -77,7 +107,7 @@ async function checkPython() {
77
107
  console.log(`โœ… Found brew Python at ${pythonPath}`);
78
108
  // Test if SSL works with brew Python
79
109
  try {
80
- execSync(`${pythonPath} -c "import ssl"`, { stdio: 'pipe' });
110
+ safeExec(pythonPath, ['-c', 'import ssl'], { stdio: 'pipe' });
81
111
  process.env.PYTHON_PATH = pythonPath;
82
112
  return true;
83
113
  } catch {
@@ -89,8 +119,8 @@ async function checkPython() {
89
119
 
90
120
  console.log('\n๐Ÿ”ง Attempting to install Python with brew...');
91
121
  try {
92
- execSync('brew install python@3.11', { stdio: 'inherit' });
93
- const brewPython = execSync('brew --prefix python@3.11').toString().trim();
122
+ safeExec('brew', ['install', 'python@3.11'], { stdio: 'inherit' });
123
+ const brewPython = safeExec('brew', ['--prefix', 'python@3.11']).trim();
94
124
  process.env.PYTHON_PATH = `${brewPython}/bin/python3`;
95
125
  console.log('โœ… Installed Python 3.11 with brew');
96
126
  return true;
@@ -110,7 +140,7 @@ async function checkPython() {
110
140
 
111
141
  async function checkDocker() {
112
142
  try {
113
- execSync('docker info', { stdio: 'ignore' });
143
+ safeExec('docker', ['info'], { stdio: 'ignore' });
114
144
  return true;
115
145
  } catch {
116
146
  return false;
@@ -152,15 +182,15 @@ async function checkQdrant() {
152
182
  try {
153
183
  // Check if a container named 'qdrant' already exists
154
184
  try {
155
- execSync('docker container inspect qdrant', { stdio: 'ignore' });
185
+ safeExec('docker', ['container', 'inspect', 'qdrant'], { stdio: 'ignore' });
156
186
  console.log('Removing existing Qdrant container...');
157
- execSync('docker rm -f qdrant', { stdio: 'ignore' });
187
+ safeExec('docker', ['rm', '-f', 'qdrant'], { stdio: 'ignore' });
158
188
  } catch {
159
189
  // Container doesn't exist, which is fine
160
190
  }
161
191
 
162
192
  console.log('Starting Qdrant...');
163
- execSync('docker run -d --name qdrant -p 6333:6333 -v qdrant_storage:/qdrant/storage qdrant/qdrant:latest', { stdio: 'inherit' });
193
+ safeExec('docker', ['run', '-d', '--name', 'qdrant', '-p', '6333:6333', '-v', 'qdrant_storage:/qdrant/storage', 'qdrant/qdrant:latest'], { stdio: 'inherit' });
164
194
 
165
195
  // Wait for Qdrant to be ready
166
196
  console.log('Waiting for Qdrant to start...');
@@ -181,7 +211,9 @@ async function checkQdrant() {
181
211
  console.log(` Still waiting... (${retries} seconds left)`);
182
212
  // Check if container is still running
183
213
  try {
184
- execSync('docker ps | grep qdrant', { stdio: 'pipe' });
214
+ // Check if container is running without using shell pipes
215
+ const psOutput = safeExec('docker', ['ps', '--filter', 'name=qdrant', '--format', '{{.Names}}'], { stdio: 'pipe' });
216
+ if (!psOutput.includes('qdrant')) throw new Error('Container not running');
185
217
  } catch {
186
218
  console.log('โŒ Qdrant container stopped unexpectedly');
187
219
  return false;
@@ -226,11 +258,22 @@ async function setupPythonEnvironment() {
226
258
  console.log('Creating virtual environment...');
227
259
  const pythonCmd = process.env.PYTHON_PATH || 'python3';
228
260
  try {
229
- execSync(`cd "${mcpPath}" && ${pythonCmd} -m venv venv`, { stdio: 'inherit' });
261
+ // Use spawn with proper path handling instead of shell execution
262
+ const { spawnSync } = require('child_process');
263
+ const result = spawnSync(pythonCmd, ['-m', 'venv', 'venv'], {
264
+ cwd: mcpPath,
265
+ stdio: 'inherit'
266
+ });
267
+ if (result.error) throw result.error;
230
268
  } catch (venvError) {
231
269
  console.log('โš ๏ธ Failed to create venv with python3, trying python...');
232
270
  try {
233
- execSync(`cd "${mcpPath}" && python -m venv venv`, { stdio: 'inherit' });
271
+ const { spawnSync } = require('child_process');
272
+ const result = spawnSync('python', ['-m', 'venv', 'venv'], {
273
+ cwd: mcpPath,
274
+ stdio: 'inherit'
275
+ });
276
+ if (result.error) throw result.error;
234
277
  } catch {
235
278
  console.log('โŒ Failed to create virtual environment');
236
279
  console.log('๐Ÿ“š Fix: Install python3-venv package');
@@ -241,19 +284,21 @@ async function setupPythonEnvironment() {
241
284
  }
242
285
  }
243
286
 
244
- // Activate and upgrade pip first to avoid SSL issues
287
+ // Setup paths for virtual environment
245
288
  console.log('Setting up pip in virtual environment...');
246
- const activateCmd = process.platform === 'win32'
247
- ? 'venv\\Scripts\\activate'
248
- : 'source venv/bin/activate';
289
+ const venvPython = process.platform === 'win32'
290
+ ? join(mcpPath, 'venv', 'Scripts', 'python.exe')
291
+ : join(mcpPath, 'venv', 'bin', 'python');
292
+ const venvPip = process.platform === 'win32'
293
+ ? join(mcpPath, 'venv', 'Scripts', 'pip.exe')
294
+ : join(mcpPath, 'venv', 'bin', 'pip');
249
295
 
250
296
  // First, try to install certifi to help with SSL issues
251
297
  console.log('Installing certificate handler...');
252
298
  try {
253
- execSync(`cd "${mcpPath}" && ${activateCmd} && pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org certifi`, {
254
- stdio: 'pipe',
255
- shell: true
256
- });
299
+ safeExec(venvPip, [
300
+ 'install', '--trusted-host', 'pypi.org', '--trusted-host', 'files.pythonhosted.org', 'certifi'
301
+ ], { cwd: mcpPath, stdio: 'pipe' });
257
302
  } catch {
258
303
  // Continue even if certifi fails
259
304
  }
@@ -261,10 +306,9 @@ async function setupPythonEnvironment() {
261
306
  // Upgrade pip and install wheel first
262
307
  try {
263
308
  // Use --no-cache-dir and --timeout to fail faster
264
- execSync(`cd "${mcpPath}" && ${activateCmd} && python -m pip install --no-cache-dir --timeout 5 --retries 1 --upgrade pip wheel setuptools`, {
265
- stdio: 'pipe',
266
- shell: true
267
- });
309
+ safeExec(venvPython, [
310
+ '-m', 'pip', 'install', '--no-cache-dir', '--timeout', '5', '--retries', '1', '--upgrade', 'pip', 'wheel', 'setuptools'
311
+ ], { cwd: mcpPath, stdio: 'pipe' });
268
312
  console.log('โœ… Pip upgraded successfully');
269
313
  } catch {
270
314
  // If upgrade fails due to SSL, skip it and continue
@@ -274,10 +318,9 @@ async function setupPythonEnvironment() {
274
318
  // Now install dependencies
275
319
  console.log('Installing MCP server dependencies...');
276
320
  try {
277
- execSync(`cd "${mcpPath}" && ${activateCmd} && pip install --no-cache-dir --timeout 10 --retries 1 -e .`, {
278
- stdio: 'pipe',
279
- shell: true
280
- });
321
+ safeExec(venvPip, [
322
+ 'install', '--no-cache-dir', '--timeout', '10', '--retries', '1', '-e', '.'
323
+ ], { cwd: mcpPath, stdio: 'pipe' });
281
324
  console.log('โœ… MCP server dependencies installed');
282
325
  } catch (error) {
283
326
  // Check for SSL errors
@@ -286,29 +329,31 @@ async function setupPythonEnvironment() {
286
329
  console.log('โš ๏ธ SSL error detected. Attempting automatic fix...');
287
330
 
288
331
  // Try different approaches to fix SSL
289
- const fixes = [
332
+ const sslFixes = [
290
333
  {
291
334
  name: 'Using trusted host flags',
292
- cmd: `${activateCmd} && pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org --no-cache-dir -e .`
335
+ install: () => safeExec(venvPip, [
336
+ 'install', '--trusted-host', 'pypi.org', '--trusted-host', 'files.pythonhosted.org',
337
+ '--no-cache-dir', '-e', '.'
338
+ ], { cwd: mcpPath, stdio: 'pipe' })
293
339
  },
294
340
  {
295
341
  name: 'Using index-url without SSL',
296
- cmd: `${activateCmd} && pip config set global.index-url https://pypi.org/simple/ && pip config set global.trusted-host "pypi.org files.pythonhosted.org" && pip install --no-cache-dir -e .`
297
- },
298
- {
299
- name: 'Using system certificates',
300
- cmd: `${activateCmd} && export SSL_CERT_FILE=$(python -m certifi) && pip install --no-cache-dir -e .`
342
+ install: () => {
343
+ safeExec(venvPip, ['config', 'set', 'global.index-url', 'https://pypi.org/simple/'],
344
+ { cwd: mcpPath, stdio: 'pipe' });
345
+ safeExec(venvPip, ['config', 'set', 'global.trusted-host', 'pypi.org files.pythonhosted.org'],
346
+ { cwd: mcpPath, stdio: 'pipe' });
347
+ return safeExec(venvPip, ['install', '--no-cache-dir', '-e', '.'],
348
+ { cwd: mcpPath, stdio: 'pipe' });
349
+ }
301
350
  }
302
351
  ];
303
352
 
304
- for (const fix of fixes) {
353
+ for (const fix of sslFixes) {
305
354
  console.log(`\n Trying: ${fix.name}...`);
306
355
  try {
307
- execSync(`cd "${mcpPath}" && ${fix.cmd}`, {
308
- stdio: 'pipe',
309
- shell: true,
310
- env: { ...process.env, PYTHONWARNINGS: 'ignore:Unverified HTTPS request' }
311
- });
356
+ fix.install();
312
357
  console.log(' โœ… Success! Dependencies installed using workaround');
313
358
  return true;
314
359
  } catch (e) {
@@ -327,17 +372,16 @@ async function setupPythonEnvironment() {
327
372
  // Install script dependencies
328
373
  console.log('Installing import script dependencies...');
329
374
  try {
330
- execSync(`cd "${mcpPath}" && ${activateCmd} && pip install -r "${scriptsPath}/requirements.txt"`, {
331
- stdio: 'inherit',
332
- shell: true
333
- });
375
+ safeExec(venvPip, [
376
+ 'install', '-r', join(scriptsPath, 'requirements.txt')
377
+ ], { cwd: mcpPath, stdio: 'inherit' });
334
378
  } catch (error) {
335
379
  // Try with trusted host if SSL error
336
380
  try {
337
- execSync(`cd "${mcpPath}" && ${activateCmd} && pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org -r "${scriptsPath}/requirements.txt"`, {
338
- stdio: 'inherit',
339
- shell: true
340
- });
381
+ safeExec(venvPip, [
382
+ 'install', '--trusted-host', 'pypi.org', '--trusted-host', 'files.pythonhosted.org',
383
+ '-r', join(scriptsPath, 'requirements.txt')
384
+ ], { cwd: mcpPath, stdio: 'inherit' });
341
385
  } catch {
342
386
  console.log('โš ๏ธ Could not install script dependencies automatically');
343
387
  console.log(' You may need to install them manually later');
@@ -446,6 +490,9 @@ async function configureEnvironment() {
446
490
  if (!envContent.includes('DECAY_SCALE_DAYS=')) {
447
491
  envContent += 'DECAY_SCALE_DAYS=90\n';
448
492
  }
493
+ if (!envContent.includes('PREFER_LOCAL_EMBEDDINGS=')) {
494
+ envContent += `PREFER_LOCAL_EMBEDDINGS=${localMode ? 'true' : 'false'}\n`;
495
+ }
449
496
 
450
497
  await fs.writeFile(envPath, envContent.trim() + '\n');
451
498
  console.log('โœ… Environment file created/updated');
@@ -460,7 +507,7 @@ async function setupClaude() {
460
507
 
461
508
  // Check if Claude CLI is available
462
509
  try {
463
- execSync('which claude', { stdio: 'ignore' });
510
+ safeExec('which', ['claude'], { stdio: 'ignore' });
464
511
 
465
512
  // Try to add the MCP automatically
466
513
  try {
@@ -479,7 +526,16 @@ async function setupClaude() {
479
526
  ? `claude mcp add claude-self-reflect "${runScript}" -e QDRANT_URL="http://localhost:6333"`
480
527
  : `claude mcp add claude-self-reflect "${runScript}" -e VOYAGE_KEY="${voyageKeyValue}" -e QDRANT_URL="http://localhost:6333"`;
481
528
 
482
- execSync(mcpCommand, { stdio: 'inherit' });
529
+ // Parse the MCP command properly
530
+ const mcpArgs = ['mcp', 'add', 'claude-self-reflect', runScript];
531
+ if (voyageKeyValue) {
532
+ mcpArgs.push('-e', `VOYAGE_KEY=${voyageKeyValue}`);
533
+ }
534
+ mcpArgs.push('-e', 'QDRANT_URL=http://localhost:6333');
535
+ if (localMode) {
536
+ mcpArgs.push('-e', 'PREFER_LOCAL_EMBEDDINGS=true');
537
+ }
538
+ safeExec('claude', mcpArgs, { stdio: 'inherit' });
483
539
  console.log('โœ… MCP added successfully!');
484
540
  console.log('\nโš ๏ธ You may need to restart Claude Code for the changes to take effect.');
485
541
 
@@ -540,7 +596,24 @@ async function showPreSetupInstructions() {
540
596
  console.log('๐Ÿ“‹ Before we begin, you\'ll need:');
541
597
  console.log(' 1. Docker Desktop installed and running');
542
598
  console.log(' 2. Python 3.10 or higher');
543
- console.log(' 3. A Voyage AI API key (we\'ll help you get one)\n');
599
+
600
+ console.log('\nโš ๏ธ IMPORTANT: Embedding Mode Choice');
601
+ console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”');
602
+ console.log('You must choose between Local or Cloud embeddings:');
603
+ console.log('\n๐Ÿ”’ Local Mode (Default):');
604
+ console.log(' โ€ข Privacy: All processing on your machine');
605
+ console.log(' โ€ข No API costs or internet required');
606
+ console.log(' โ€ข Good accuracy for most use cases');
607
+ console.log('\nโ˜๏ธ Cloud Mode (Voyage AI):');
608
+ console.log(' โ€ข Better search accuracy');
609
+ console.log(' โ€ข Requires API key and internet');
610
+ console.log(' โ€ข Conversations sent to Voyage for processing');
611
+ console.log('\nโš ๏ธ This choice is SEMI-PERMANENT. Switching later requires:');
612
+ console.log(' โ€ข Re-importing all conversations (30+ minutes)');
613
+ console.log(' โ€ข Separate storage for each mode');
614
+ console.log(' โ€ข Cannot search across modes\n');
615
+ console.log('For Cloud mode, run with: --voyage-key=<your-key>');
616
+ console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n');
544
617
 
545
618
  if (isInteractive) {
546
619
  await question('Press Enter to continue...');
@@ -549,12 +622,7 @@ async function showPreSetupInstructions() {
549
622
 
550
623
  async function importConversations() {
551
624
  console.log('\n๐Ÿ“š Import Claude Conversations...');
552
-
553
- // Skip import in local mode
554
- if (localMode) {
555
- console.log('๐Ÿ  Skipping import in local mode (no API key for embeddings)');
556
- return;
557
- }
625
+ console.log(localMode ? '๐Ÿ  Using local embeddings for import' : '๐ŸŒ Using Voyage AI embeddings');
558
626
 
559
627
  // Check if Claude logs directory exists
560
628
  const logsDir = join(process.env.HOME || process.env.USERPROFILE, '.claude', 'projects');
@@ -694,7 +762,7 @@ async function showSystemDashboard() {
694
762
 
695
763
  // Docker status
696
764
  try {
697
- execSync('docker info', { stdio: 'ignore' });
765
+ safeExec('docker', ['info'], { stdio: 'ignore' });
698
766
  status.docker = true;
699
767
  } catch {}
700
768
 
@@ -710,7 +778,7 @@ async function showSystemDashboard() {
710
778
  // Python status
711
779
  try {
712
780
  const pythonCmd = process.env.PYTHON_PATH || 'python3';
713
- execSync(`${pythonCmd} --version`, { stdio: 'ignore' });
781
+ safeExec(pythonCmd, ['--version'], { stdio: 'ignore' });
714
782
  status.python = true;
715
783
  } catch {}
716
784
 
@@ -773,9 +841,9 @@ async function showSystemDashboard() {
773
841
  status.watcherErrors = [];
774
842
  status.lastImportTime = null;
775
843
  try {
776
- const watcherLogs = execSync('docker logs claude-reflection-watcher --tail 50 2>&1', {
777
- encoding: 'utf-8'
778
- }).toString();
844
+ const watcherLogs = safeExec('docker', [
845
+ 'logs', 'claude-reflection-watcher', '--tail', '50'
846
+ ], { encoding: 'utf-8' });
779
847
 
780
848
  // Check for recent errors
781
849
  const errorMatches = watcherLogs.match(/ERROR.*Import failed.*/g);
@@ -800,10 +868,9 @@ async function showSystemDashboard() {
800
868
 
801
869
  // Check if watcher is running via Docker
802
870
  try {
803
- const dockerStatus = execSync('docker ps --filter "name=claude-reflection-watcher" --format "{{.Names}}" 2>/dev/null', {
804
- cwd: projectRoot,
805
- encoding: 'utf-8'
806
- }).toString().trim();
871
+ const dockerStatus = safeExec('docker', [
872
+ 'ps', '--filter', 'name=claude-reflection-watcher', '--format', '{{.Names}}'
873
+ ], { cwd: projectRoot, encoding: 'utf-8' }).trim();
807
874
 
808
875
  if (dockerStatus.includes('watcher')) {
809
876
  status.watcherRunning = true;
@@ -901,11 +968,8 @@ async function setupWatcher() {
901
968
  await fs.access(watcherScript);
902
969
  console.log('โœ… Watcher script found');
903
970
 
904
- // Skip in local mode
905
- if (localMode) {
906
- console.log('๐Ÿ  Skipping watcher in local mode');
907
- return;
908
- }
971
+ // Watcher works with both local and cloud embeddings
972
+ console.log(localMode ? '๐Ÿ  Watcher will use local embeddings' : '๐ŸŒ Watcher will use Voyage AI embeddings');
909
973
 
910
974
  // Ask if user wants to enable watcher
911
975
  let enableWatcher = 'y';
@@ -933,20 +997,26 @@ async function setupWatcher() {
933
997
  console.log('๐Ÿงน Cleaning up existing containers...');
934
998
  try {
935
999
  // Stop all claude-reflection containers
936
- execSync('docker compose down 2>/dev/null || true', {
937
- cwd: projectRoot,
938
- stdio: 'pipe'
939
- });
1000
+ try {
1001
+ safeExec('docker', ['compose', 'down'], {
1002
+ cwd: projectRoot,
1003
+ stdio: 'pipe'
1004
+ });
1005
+ } catch {}
940
1006
 
941
1007
  // Also stop any standalone containers
942
- execSync('docker stop claude-reflection-watcher claude-reflection-qdrant qdrant 2>/dev/null || true', {
943
- stdio: 'pipe'
944
- });
1008
+ try {
1009
+ safeExec('docker', ['stop', 'claude-reflection-watcher', 'claude-reflection-qdrant', 'qdrant'], {
1010
+ stdio: 'pipe'
1011
+ });
1012
+ } catch {}
945
1013
 
946
1014
  // Remove them
947
- execSync('docker rm claude-reflection-watcher claude-reflection-qdrant qdrant 2>/dev/null || true', {
948
- stdio: 'pipe'
949
- });
1015
+ try {
1016
+ safeExec('docker', ['rm', 'claude-reflection-watcher', 'claude-reflection-qdrant', 'qdrant'], {
1017
+ stdio: 'pipe'
1018
+ });
1019
+ } catch {}
950
1020
 
951
1021
  // Wait a moment for cleanup
952
1022
  await new Promise(resolve => setTimeout(resolve, 2000));
@@ -954,7 +1024,7 @@ async function setupWatcher() {
954
1024
 
955
1025
  // Start both services with compose
956
1026
  console.log('๐Ÿš€ Starting Qdrant and Watcher services...');
957
- execSync('docker compose --profile watch up -d', {
1027
+ safeExec('docker', ['compose', '--profile', 'watch', 'up', '-d'], {
958
1028
  cwd: projectRoot,
959
1029
  stdio: 'pipe' // Use pipe to capture output
960
1030
  });
@@ -964,10 +1034,9 @@ async function setupWatcher() {
964
1034
 
965
1035
  // Check container status
966
1036
  try {
967
- const psOutput = execSync('docker ps --filter "name=claude-reflection" --format "table {{.Names}}\t{{.Status}}"', {
968
- cwd: projectRoot,
969
- encoding: 'utf8'
970
- });
1037
+ const psOutput = safeExec('docker', [
1038
+ 'ps', '--filter', 'name=claude-reflection', '--format', 'table {{.Names}}\t{{.Status}}'
1039
+ ], { cwd: projectRoot, encoding: 'utf8' });
971
1040
 
972
1041
  const qdrantReady = psOutput.includes('claude-reflection-qdrant') && psOutput.includes('Up');
973
1042
  const watcherReady = psOutput.includes('claude-reflection-watcher') && psOutput.includes('Up');
@@ -1033,7 +1102,7 @@ async function verifyMCP() {
1033
1102
 
1034
1103
  try {
1035
1104
  // Check if MCP is listed
1036
- const mcpList = execSync('claude mcp list', { encoding: 'utf8' });
1105
+ const mcpList = safeExec('claude', ['mcp', 'list'], { encoding: 'utf8' });
1037
1106
  if (!mcpList.includes('claude-self-reflect')) {
1038
1107
  console.log('โŒ MCP not found in Claude Code');
1039
1108
  return;
@@ -1103,10 +1172,15 @@ asyncio.run(test_mcp())
1103
1172
  // Run the test
1104
1173
  console.log('\n๐Ÿงช Testing MCP functionality...');
1105
1174
  try {
1106
- const testResult = execSync(`cd "${projectRoot}" && source mcp-server/venv/bin/activate && python test-mcp.py`, {
1107
- encoding: 'utf8',
1108
- shell: '/bin/bash'
1175
+ // Create test script that activates venv and runs test
1176
+ const testScriptPath = join(projectRoot, 'run-test.sh');
1177
+ const testScript = `#!/bin/bash\nsource mcp-server/venv/bin/activate\npython test-mcp.py`;
1178
+ await fs.writeFile(testScriptPath, testScript, { mode: 0o755 });
1179
+ const testResult = safeExec('bash', [testScriptPath], {
1180
+ cwd: projectRoot,
1181
+ encoding: 'utf8'
1109
1182
  });
1183
+ await fs.unlink(testScriptPath);
1110
1184
  console.log(testResult);
1111
1185
 
1112
1186
  // Clean up test script
@@ -1151,18 +1225,23 @@ async function main() {
1151
1225
  }
1152
1226
  }
1153
1227
 
1154
- // Check for non-interactive mode without required flags
1155
- if (!isInteractive && !voyageKey && !localMode) {
1156
- console.log('โŒ Non-interactive mode requires either --voyage-key or --local flag\n');
1157
- console.log('Usage:');
1158
- console.log(' claude-self-reflect setup --voyage-key=<your-key>');
1159
- console.log(' claude-self-reflect setup --local\n');
1160
- console.log('Get your free API key at: https://www.voyageai.com/');
1161
- process.exit(1);
1228
+ // In non-interactive mode, just use defaults (local mode unless key provided)
1229
+ if (!isInteractive) {
1230
+ console.log(voyageKey ? '๐ŸŒ Using Voyage AI embeddings' : '๐Ÿ”’ Using local embeddings for privacy');
1231
+ } else if (!voyageKey) {
1232
+ // In interactive mode without a key, confirm local mode choice
1233
+ await showPreSetupInstructions();
1234
+ const confirmLocal = await question('Continue with Local embeddings (privacy mode)? (y/n): ');
1235
+ if (confirmLocal.toLowerCase() !== 'y') {
1236
+ console.log('\nTo use Cloud mode, restart with: claude-self-reflect setup --voyage-key=<your-key>');
1237
+ console.log('Get your free API key at: https://www.voyageai.com/');
1238
+ if (rl) rl.close();
1239
+ process.exit(0);
1240
+ }
1241
+ } else {
1242
+ await showPreSetupInstructions();
1162
1243
  }
1163
1244
 
1164
- await showPreSetupInstructions();
1165
-
1166
1245
  // Check prerequisites
1167
1246
  const pythonOk = await checkPython();
1168
1247
  if (!pythonOk) {
@@ -9,11 +9,12 @@ authors = [
9
9
  ]
10
10
  dependencies = [
11
11
  "fastmcp>=0.0.7",
12
- "qdrant-client>=1.7.0",
13
- "voyageai>=0.1.0",
14
- "python-dotenv>=1.0.0",
15
- "pydantic>=2.0.0",
16
- "pydantic-settings>=2.0.0",
12
+ "qdrant-client>=1.7.0,<2.0.0",
13
+ "voyageai>=0.1.0,<1.0.0",
14
+ "python-dotenv>=1.0.0,<2.0.0",
15
+ "pydantic>=2.9.2,<3.0.0", # Pin to avoid CVE-2024-3772
16
+ "pydantic-settings>=2.0.0,<3.0.0",
17
+ "fastembed>=0.4.0,<1.0.0",
17
18
  ]
18
19
 
19
20
  [project.scripts]