claude-self-reflect 6.0.1 → 6.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.
package/installer/cli.js CHANGED
@@ -10,9 +10,12 @@ const __dirname = dirname(__filename);
10
10
 
11
11
  const commands = {
12
12
  setup: 'Run the setup wizard to configure Claude Self-Reflect',
13
+ update: 'Check for and install missing features or updates',
13
14
  status: 'Get indexing status as JSON (overall + per-project breakdown)',
14
15
  statusline: 'Configure Claude Code statusline integration',
15
16
  doctor: 'Check your installation and diagnose issues',
17
+ version: 'Show version and check for updates',
18
+ uninstall: 'Uninstall Claude Self-Reflect',
16
19
  help: 'Show this help message'
17
20
  };
18
21
 
@@ -185,12 +188,13 @@ function help() {
185
188
  console.log('\nExamples:');
186
189
  console.log(' claude-self-reflect setup --voyage-key=pa-1234567890');
187
190
  console.log(' claude-self-reflect setup --local');
188
- console.log(' claude-self-reflect setup --debug # For troubleshooting');
189
- console.log(' claude-self-reflect status # Get indexing status as JSON');
190
-
191
+ console.log(' claude-self-reflect update # Fix missing features');
192
+ console.log(' claude-self-reflect status # Get indexing status');
193
+ console.log(' claude-self-reflect doctor # Diagnose issues');
194
+
191
195
  console.log('\nFor more information:');
192
196
  console.log(' Documentation: https://github.com/ramakay/claude-self-reflect');
193
- console.log(' Status API: See docs/api-reference.md#cli-status-interface');
197
+ console.log(' Issues: https://github.com/ramakay/claude-self-reflect/issues');
194
198
  }
195
199
 
196
200
  async function statusline() {
@@ -205,6 +209,77 @@ async function statusline() {
205
209
  }
206
210
  }
207
211
 
212
+ // Helper function to run bash scripts with validation
213
+ async function runBashScript(scriptName, errorMessage) {
214
+ const scriptPath = join(__dirname, '..', 'scripts', 'setup', scriptName);
215
+
216
+ // Check bash is available
217
+ try {
218
+ execSync('bash --version', { stdio: 'ignore' });
219
+ } catch {
220
+ console.error('❌ Bash is required but not found');
221
+ console.error(' Please ensure bash is installed');
222
+ process.exit(1);
223
+ }
224
+
225
+ // Check script file exists
226
+ try {
227
+ await fs.access(scriptPath);
228
+ } catch {
229
+ console.error(`❌ Script not found: ${scriptPath}`);
230
+ console.error(' Please reinstall claude-self-reflect');
231
+ process.exit(1);
232
+ }
233
+
234
+ // Run script
235
+ try {
236
+ const child = spawn('bash', [scriptPath], { stdio: 'inherit' });
237
+ child.on('exit', (code) => {
238
+ process.exit(code || 0);
239
+ });
240
+ } catch (error) {
241
+ console.error(`${errorMessage} ${error.message}`);
242
+ process.exit(1);
243
+ }
244
+ }
245
+
246
+ async function update() {
247
+ console.log('🔄 Checking for missing features and updates...\n');
248
+
249
+ try {
250
+ const UpdateManager = (await import('./update-manager.js')).default;
251
+ const manager = new UpdateManager();
252
+ await manager.run();
253
+ } catch (error) {
254
+ // Check if update-manager.js itself is missing (not a nested dependency)
255
+ // Node uses file:// URLs in error messages, not relative paths
256
+ const isUpdateManagerMissing =
257
+ (error.code === 'ERR_MODULE_NOT_FOUND' &&
258
+ (error.message.includes('update-manager.js') || error.message.includes('update-manager'))) ||
259
+ (error.message && error.message.includes('Cannot find module') && error.message.includes('update-manager'));
260
+
261
+ if (isUpdateManagerMissing) {
262
+ console.error('❌ Update manager not found');
263
+ console.error(' Please reinstall: npm install -g claude-self-reflect');
264
+ } else {
265
+ console.error('❌ Update check failed:', error.message);
266
+ console.error(' Run with DEBUG=1 for more details');
267
+ if (process.env.DEBUG) {
268
+ console.error('\nStack trace:', error.stack);
269
+ }
270
+ }
271
+ process.exit(1);
272
+ }
273
+ }
274
+
275
+ async function version() {
276
+ await runBashScript('version.sh', 'Failed to check version:');
277
+ }
278
+
279
+ async function uninstall() {
280
+ await runBashScript('uninstall.sh', 'Failed to run uninstall:');
281
+ }
282
+
208
283
  // Main
209
284
  const command = process.argv[2] || 'help';
210
285
 
@@ -212,6 +287,9 @@ switch (command) {
212
287
  case 'setup':
213
288
  setup();
214
289
  break;
290
+ case 'update':
291
+ update();
292
+ break;
215
293
  case 'status':
216
294
  status();
217
295
  break;
@@ -221,6 +299,12 @@ switch (command) {
221
299
  case 'doctor':
222
300
  doctor();
223
301
  break;
302
+ case 'version':
303
+ version();
304
+ break;
305
+ case 'uninstall':
306
+ uninstall();
307
+ break;
224
308
  case 'help':
225
309
  default:
226
310
  help();
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * FastEmbed Fallback Installer
4
+ * Automatically detects SSL/proxy issues and downloads model from Google Cloud Storage
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { execSync } from 'child_process';
10
+ import https from 'https';
11
+ import os from 'os';
12
+
13
+ class FastEmbedFallback {
14
+ constructor() {
15
+ this.homeDir = os.homedir();
16
+ this.cacheDir = path.join(this.homeDir, '.cache', 'fastembed');
17
+ this.modelName = 'sentence-transformers-all-MiniLM-L6-v2';
18
+ this.modelFile = `${this.modelName}.tar.gz`;
19
+ this.gcsUrl = `https://storage.googleapis.com/qdrant-fastembed/${this.modelFile}`;
20
+ }
21
+
22
+ log(message, type = 'info') {
23
+ const colors = {
24
+ info: '\x1b[36m',
25
+ success: '\x1b[32m',
26
+ warning: '\x1b[33m',
27
+ error: '\x1b[31m'
28
+ };
29
+ const prefix = {
30
+ info: 'ℹ',
31
+ success: '✓',
32
+ warning: '⚠',
33
+ error: '✗'
34
+ };
35
+ console.log(`${colors[type]}${prefix[type]} ${message}\x1b[0m`);
36
+ }
37
+
38
+ checkModelExists() {
39
+ const modelPath = path.join(this.cacheDir, this.modelName);
40
+ return fs.existsSync(modelPath);
41
+ }
42
+
43
+ async testHuggingFace() {
44
+ this.log('Testing HuggingFace connectivity...', 'info');
45
+ try {
46
+ execSync('curl -s -m 5 https://huggingface.co > /dev/null 2>&1', { timeout: 5000 });
47
+ this.log('HuggingFace is accessible', 'success');
48
+ return true;
49
+ } catch (error) {
50
+ this.log('HuggingFace blocked by proxy/firewall', 'warning');
51
+ return false;
52
+ }
53
+ }
54
+
55
+ downloadFromGCS() {
56
+ this.log('Downloading FastEmbed model from Google Cloud Storage...', 'info');
57
+ this.log('(Using GCS mirror to bypass corporate proxies)', 'info');
58
+
59
+ // Create cache directory
60
+ if (!fs.existsSync(this.cacheDir)) {
61
+ fs.mkdirSync(this.cacheDir, { recursive: true });
62
+ }
63
+
64
+ const tarPath = path.join(this.cacheDir, this.modelFile);
65
+
66
+ try {
67
+ // Download with curl (handles proxies better than Node's https)
68
+ this.log(`Downloading ${this.modelFile} (79MB)...`, 'info');
69
+ execSync(`curl -L -o "${tarPath}" "${this.gcsUrl}"`, {
70
+ stdio: 'inherit',
71
+ timeout: 300000 // 5 minute timeout
72
+ });
73
+
74
+ this.log('Download complete. Extracting...', 'success');
75
+
76
+ // Extract (with 2 minute timeout to prevent hanging)
77
+ execSync(`tar -xzf "${tarPath}" -C "${this.cacheDir}"`, {
78
+ stdio: 'inherit',
79
+ timeout: 120000 // 2 minute timeout
80
+ });
81
+
82
+ // Verify extraction
83
+ if (this.checkModelExists()) {
84
+ this.log('FastEmbed model installed successfully!', 'success');
85
+
86
+ // Clean up tar file
87
+ fs.unlinkSync(tarPath);
88
+
89
+ return true;
90
+ } else {
91
+ this.log('Model extraction failed', 'error');
92
+ return false;
93
+ }
94
+ } catch (error) {
95
+ this.log(`Download failed: ${error.message}`, 'error');
96
+ return false;
97
+ }
98
+ }
99
+
100
+ configureDockerCompose() {
101
+ this.log('Configuring docker-compose for offline model...', 'info');
102
+
103
+ const dockerComposePath = path.join(process.cwd(), 'docker-compose.yaml');
104
+
105
+ if (!fs.existsSync(dockerComposePath)) {
106
+ this.log('docker-compose.yaml not found, skipping configuration', 'warning');
107
+ return false;
108
+ }
109
+
110
+ try {
111
+ const content = fs.readFileSync(dockerComposePath, 'utf8');
112
+
113
+ // Check if already configured
114
+ if (content.includes('HF_HUB_OFFLINE')) {
115
+ this.log('docker-compose already configured for offline mode', 'success');
116
+ return true;
117
+ }
118
+
119
+ // Line-by-line processing is more reliable than regex for YAML
120
+ const lines = content.split('\n');
121
+ const services = ['importer', 'watcher', 'streaming-importer', 'async-importer', 'safe-watcher', 'mcp-server'];
122
+ let currentService = null;
123
+ let inEnvironment = false;
124
+ let environmentIndent = '';
125
+
126
+ for (let i = 0; i < lines.length; i++) {
127
+ const line = lines[i];
128
+ const trimmed = line.trim();
129
+
130
+ // Track which service we're in
131
+ for (const service of services) {
132
+ if (trimmed === `${service}:`) {
133
+ currentService = service;
134
+ inEnvironment = false;
135
+ break;
136
+ }
137
+ }
138
+
139
+ // Detect environment section
140
+ if (currentService && trimmed === 'environment:') {
141
+ inEnvironment = true;
142
+ environmentIndent = line.match(/^(\s+)/)?.[1] || ' ';
143
+ }
144
+
145
+ // Add HF_HUB_OFFLINE after first environment entry
146
+ if (inEnvironment && trimmed.startsWith('-') && !content.includes('HF_HUB_OFFLINE')) {
147
+ lines.splice(i + 1, 0, `${environmentIndent} - HF_HUB_OFFLINE=1`);
148
+ inEnvironment = false;
149
+ }
150
+
151
+ // Add volume mount after volumes section
152
+ if (currentService && trimmed === 'volumes:' && !content.includes('.cache/fastembed')) {
153
+ const volumeIndent = line.match(/^(\s+)/)?.[1] || ' ';
154
+ // Find next line with volume entry
155
+ if (lines[i + 1] && lines[i + 1].trim().startsWith('-')) {
156
+ lines.splice(i + 2, 0, `${volumeIndent} - ~/.cache/fastembed:/root/.cache/fastembed:ro`);
157
+ }
158
+ }
159
+
160
+ // Reset when we exit a service (next service or same-level key)
161
+ if (currentService && line.match(/^\s{0,2}\w+:/) && !trimmed.startsWith(`${currentService}:`)) {
162
+ currentService = null;
163
+ inEnvironment = false;
164
+ }
165
+ }
166
+
167
+ // Write back
168
+ fs.writeFileSync(dockerComposePath, lines.join('\n'));
169
+ this.log('docker-compose.yaml updated for offline mode', 'success');
170
+ return true;
171
+ } catch (error) {
172
+ this.log(`Failed to update docker-compose: ${error.message}`, 'error');
173
+ return false;
174
+ }
175
+ }
176
+
177
+ configureMCPServer() {
178
+ this.log('Configuring MCP server for offline model...', 'info');
179
+
180
+ const mcpRunScript = path.join(process.cwd(), 'mcp-server', 'run-mcp.sh');
181
+
182
+ if (!fs.existsSync(mcpRunScript)) {
183
+ this.log('run-mcp.sh not found, skipping configuration', 'warning');
184
+ return false;
185
+ }
186
+
187
+ try {
188
+ let content = fs.readFileSync(mcpRunScript, 'utf8');
189
+
190
+ // Check if already configured
191
+ if (content.includes('HF_HUB_OFFLINE')) {
192
+ this.log('MCP server already configured for offline mode', 'success');
193
+ return true;
194
+ }
195
+
196
+ // Add exports at the beginning
197
+ const exports = `
198
+ # Offline FastEmbed configuration (auto-added by installer)
199
+ export HF_HUB_OFFLINE=1
200
+ export FASTEMBED_CACHE_PATH="$HOME/.cache/fastembed"
201
+
202
+ `;
203
+
204
+ // Insert after shebang
205
+ content = content.replace(/(#!\/bin\/bash\n)/, `$1${exports}`);
206
+
207
+ fs.writeFileSync(mcpRunScript, content);
208
+ this.log('MCP server configured for offline mode', 'success');
209
+ return true;
210
+ } catch (error) {
211
+ this.log(`Failed to update run-mcp.sh: ${error.message}`, 'error');
212
+ return false;
213
+ }
214
+ }
215
+
216
+ async run() {
217
+ this.log('🔍 Checking FastEmbed model availability...', 'info');
218
+
219
+ // Check if model already exists
220
+ if (this.checkModelExists()) {
221
+ this.log('FastEmbed model already installed ✓', 'success');
222
+ return true;
223
+ }
224
+
225
+ // Test HuggingFace connectivity
226
+ const hfAccessible = await this.testHuggingFace();
227
+
228
+ if (!hfAccessible) {
229
+ this.log('Corporate proxy detected - using Google Cloud Storage mirror', 'warning');
230
+
231
+ // Download from GCS
232
+ if (!this.downloadFromGCS()) {
233
+ this.log('Failed to download model from GCS', 'error');
234
+ return false;
235
+ }
236
+
237
+ // Configure for offline use
238
+ this.configureDockerCompose();
239
+ this.configureMCPServer();
240
+
241
+ this.log('✅ FastEmbed configured for offline use', 'success');
242
+ this.log('Your installation will now work behind corporate proxies', 'info');
243
+ return true;
244
+ }
245
+
246
+ // HuggingFace is accessible, let Python handle the download
247
+ this.log('HuggingFace accessible - standard installation will work', 'info');
248
+ return true;
249
+ }
250
+ }
251
+
252
+ // Run if called directly
253
+ if (import.meta.url === `file://${process.argv[1]}`) {
254
+ const fallback = new FastEmbedFallback();
255
+ fallback.run().catch(error => {
256
+ console.error('FastEmbed fallback failed:', error);
257
+ process.exit(1);
258
+ });
259
+ }
260
+
261
+ export default FastEmbedFallback;
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { fileURLToPath } from 'url';
4
4
  import { dirname, join } from 'path';
5
- import StatuslineSetup from './statusline-setup.js';
6
5
 
7
6
  const __filename = fileURLToPath(import.meta.url);
8
7
  const __dirname = dirname(__filename);
@@ -10,18 +9,24 @@ const __dirname = dirname(__filename);
10
9
  // Only show message if not in development
11
10
  if (!process.cwd().includes('claude-self-reflect')) {
12
11
  console.log('\n🎉 Claude Self-Reflect installed!\n');
13
- console.log('Run "claude-self-reflect setup" to configure your installation.\n');
12
+ console.log('🔍 Checking installation...\n');
14
13
 
15
- // Attempt to setup statusline integration automatically
16
- console.log('\n📊 Setting up Claude Code statusline integration...');
17
- const statuslineSetup = new StatuslineSetup();
18
- statuslineSetup.run().then(success => {
19
- if (success) {
20
- console.log('✅ Statusline integration configured automatically!');
21
- } else {
22
- console.log('⚠️ Statusline integration requires manual setup. Run "claude-self-reflect setup" for help.');
23
- }
14
+ // Import and run update manager for comprehensive setup
15
+ import('./update-manager.js').then(module => {
16
+ const UpdateManager = module.default;
17
+ const manager = new UpdateManager();
18
+ manager.run().then(() => {
19
+ console.log('\nInstallation complete!');
20
+ console.log('\n📋 Next steps:');
21
+ console.log(' 1. Run: claude-self-reflect setup');
22
+ console.log(' 2. Configure your embedding preferences');
23
+ console.log(' 3. Start using Claude with perfect memory!\n');
24
+ }).catch(error => {
25
+ console.log('\n⚠️ Setup encountered issues:', error.message);
26
+ console.log(' Run "claude-self-reflect update" to fix any problems\n');
27
+ });
24
28
  }).catch(error => {
25
- console.log('⚠️ Statusline setup skipped:', error.message);
29
+ console.log('⚠️ Could not run automatic setup');
30
+ console.log(' Run "claude-self-reflect setup" to configure manually\n');
26
31
  });
27
32
  }
@@ -23,6 +23,34 @@ class StatuslineSetup {
23
23
  this.statuslineBackup = path.join(this.claudeDir, 'statusline-wrapper.sh.backup');
24
24
  }
25
25
 
26
+ checkCCStatusline() {
27
+ try {
28
+ execSync('npm list -g cc-statusline', { stdio: 'ignore' });
29
+ this.log('cc-statusline is installed', 'success');
30
+ return true;
31
+ } catch {
32
+ this.log('cc-statusline not found', 'warning');
33
+ return false;
34
+ }
35
+ }
36
+
37
+ installCCStatusline() {
38
+ if (this.checkCCStatusline()) {
39
+ return true;
40
+ }
41
+
42
+ this.log('Installing cc-statusline...', 'info');
43
+ try {
44
+ execSync('npm install -g cc-statusline', { stdio: 'inherit' });
45
+ this.log('cc-statusline installed successfully', 'success');
46
+ return true;
47
+ } catch (error) {
48
+ this.log(`Failed to install cc-statusline: ${error.message}`, 'error');
49
+ this.log('Statusline features will not be available', 'warning');
50
+ return false;
51
+ }
52
+ }
53
+
26
54
  log(message, type = 'info') {
27
55
  const colors = {
28
56
  info: '\x1b[36m',
@@ -34,6 +62,15 @@ class StatuslineSetup {
34
62
  }
35
63
 
36
64
  checkPrerequisites() {
65
+ // Check npm is available
66
+ try {
67
+ execSync('npm --version', { stdio: 'ignore' });
68
+ } catch {
69
+ this.log('npm is required but not found', 'error');
70
+ this.log('Please install Node.js and npm from nodejs.org', 'error');
71
+ return false;
72
+ }
73
+
37
74
  // Check if Claude Code directory exists
38
75
  if (!fs.existsSync(this.claudeDir)) {
39
76
  this.log('Claude Code directory not found. Please ensure Claude Code is installed.', 'warning');
@@ -67,15 +104,48 @@ class StatuslineSetup {
67
104
  }
68
105
  }
69
106
 
107
+ // Try user-local installation first (no sudo needed)
108
+ const userBin = path.join(this.homeDir, 'bin');
109
+ const userLocalBin = path.join(userBin, 'csr-status');
110
+
111
+ if (!fs.existsSync(userBin)) {
112
+ fs.mkdirSync(userBin, { recursive: true });
113
+ }
114
+
115
+ // Create symlink in ~/bin
116
+ try {
117
+ if (fs.existsSync(userLocalBin)) {
118
+ fs.unlinkSync(userLocalBin);
119
+ }
120
+ fs.symlinkSync(this.csrScript, userLocalBin);
121
+ // Note: Don't chmod symlink - ensure source script is executable instead
122
+
123
+ this.log('csr-status installed to ~/bin (no sudo required)', 'success');
124
+ this.log('Add ~/bin to PATH: export PATH="$HOME/bin:$PATH"', 'info');
125
+
126
+ // Check if ~/bin is in PATH
127
+ const pathDirs = process.env.PATH.split(':');
128
+ if (!pathDirs.includes(userBin) && !pathDirs.includes('~/bin')) {
129
+ this.log('⚠️ Add this to ~/.bashrc or ~/.zshrc:', 'warning');
130
+ this.log(' export PATH="$HOME/bin:$PATH"', 'info');
131
+ }
132
+
133
+ return true;
134
+ } catch (userError) {
135
+ this.log(`User-local install failed: ${userError.message}`, 'warning');
136
+ }
137
+
138
+ // Fallback to global install if user has sudo access
139
+ if (needsSudo) {
140
+ this.log('Attempting global install (requires sudo)...', 'info');
141
+ this.log('Corporate machines may not allow this - using user-local is fine', 'info');
142
+ }
143
+
70
144
  // Create symlink
71
145
  const cmd = needsSudo
72
146
  ? `sudo ln -sf "${this.csrScript}" "${this.globalBin}"`
73
147
  : `ln -sf "${this.csrScript}" "${this.globalBin}"`;
74
148
 
75
- if (needsSudo) {
76
- this.log('Installing global csr-status command (may require password)...', 'info');
77
- }
78
-
79
149
  execSync(cmd, { stdio: 'inherit' });
80
150
 
81
151
  // Make executable
@@ -87,10 +157,10 @@ class StatuslineSetup {
87
157
  this.log('Global csr-status command installed successfully', 'success');
88
158
  return true;
89
159
  } catch (error) {
90
- this.log(`Failed to install global command: ${error.message}`, 'warning');
91
- this.log('You can manually install by running:', 'info');
92
- this.log(` sudo ln -sf "${this.csrScript}" "${this.globalBin}"`, 'info');
93
- return false;
160
+ this.log('Statusline installation skipped (no sudo access)', 'info');
161
+ this.log('This is normal on corporate machines', 'info');
162
+ this.log('✅ Core MCP search works fine without statusline!', 'success');
163
+ return false; // Return false but don't treat as critical error
94
164
  }
95
165
  }
96
166
 
@@ -228,6 +298,7 @@ class StatuslineSetup {
228
298
  }
229
299
 
230
300
  const steps = [
301
+ { name: 'Install cc-statusline', fn: () => this.installCCStatusline() },
231
302
  { name: 'Install global command', fn: () => this.installGlobalCommand() },
232
303
  { name: 'Patch statusline wrapper', fn: () => this.patchStatuslineWrapper() },
233
304
  { name: 'Validate integration', fn: () => this.validateIntegration() }
@@ -0,0 +1,318 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Update Manager
4
+ * Detects and fixes missing features, updates configurations
5
+ * Ensures user's installation matches the package capabilities
6
+ */
7
+
8
+ import { execSync } from 'child_process';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import os from 'os';
12
+ import { fileURLToPath } from 'url';
13
+ import StatuslineSetup from './statusline-setup.js';
14
+ import FastEmbedFallback from './fastembed-fallback.js';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+
19
+ class UpdateManager {
20
+ constructor() {
21
+ this.homeDir = os.homedir();
22
+ this.packageRoot = path.dirname(__dirname);
23
+ }
24
+
25
+ log(message, type = 'info') {
26
+ const colors = {
27
+ info: '\x1b[36m',
28
+ success: '\x1b[32m',
29
+ warning: '\x1b[33m',
30
+ error: '\x1b[31m'
31
+ };
32
+ const icons = {
33
+ info: 'ℹ',
34
+ success: '✓',
35
+ warning: '⚠',
36
+ error: '✗'
37
+ };
38
+ console.log(`${colors[type]}${icons[type]} ${message}\x1b[0m`);
39
+ }
40
+
41
+ // Feature checks
42
+ async checkCCStatusline() {
43
+ try {
44
+ execSync('npm list -g cc-statusline', { stdio: 'ignore' });
45
+ return { installed: true, name: 'cc-statusline', critical: false };
46
+ } catch {
47
+ return { installed: false, name: 'cc-statusline', critical: false, fix: () => this.installCCStatusline() };
48
+ }
49
+ }
50
+
51
+ async checkCSRStatusScript() {
52
+ const userBin = path.join(this.homeDir, 'bin', 'csr-status');
53
+ const globalBin = '/usr/local/bin/csr-status';
54
+
55
+ const exists = fs.existsSync(userBin) || fs.existsSync(globalBin);
56
+ return {
57
+ installed: exists,
58
+ name: 'csr-status command',
59
+ critical: false,
60
+ fix: () => this.installCSRStatus()
61
+ };
62
+ }
63
+
64
+ async checkFastEmbedModel() {
65
+ const modelPath = path.join(this.homeDir, '.cache', 'fastembed', 'sentence-transformers-all-MiniLM-L6-v2');
66
+ const exists = fs.existsSync(modelPath);
67
+
68
+ return {
69
+ installed: exists,
70
+ name: 'FastEmbed model',
71
+ critical: true,
72
+ fix: () => this.installFastEmbed()
73
+ };
74
+ }
75
+
76
+ async checkASTGrep() {
77
+ // Check for both 'ast-grep' (brew) and 'sg' (npm) binaries
78
+ try {
79
+ execSync('ast-grep --version', { stdio: 'ignore' });
80
+ return { installed: true, name: 'AST-Grep', critical: false };
81
+ } catch {
82
+ // Try 'sg' binary (npm install -g @ast-grep/cli)
83
+ try {
84
+ execSync('sg --version', { stdio: 'ignore' });
85
+ return { installed: true, name: 'AST-Grep (sg)', critical: false };
86
+ } catch {
87
+ return {
88
+ installed: false,
89
+ name: 'AST-Grep (optional)',
90
+ critical: false,
91
+ fix: () => this.suggestASTGrep()
92
+ };
93
+ }
94
+ }
95
+ }
96
+
97
+ async checkDocker() {
98
+ try {
99
+ execSync('docker info', { stdio: 'ignore' });
100
+ return { installed: true, name: 'Docker', critical: true };
101
+ } catch {
102
+ return {
103
+ installed: false,
104
+ name: 'Docker',
105
+ critical: true,
106
+ fix: null,
107
+ error: 'Docker is required. Install Docker Desktop from docker.com'
108
+ };
109
+ }
110
+ }
111
+
112
+ async checkQdrant() {
113
+ try {
114
+ const response = await fetch('http://localhost:6333');
115
+ if (response.ok) {
116
+ return { installed: true, name: 'Qdrant', critical: true };
117
+ }
118
+ } catch {}
119
+
120
+ return {
121
+ installed: false,
122
+ name: 'Qdrant',
123
+ critical: true,
124
+ fix: () => this.startQdrant()
125
+ };
126
+ }
127
+
128
+ async checkDockerComposeConfig() {
129
+ const composePath = path.join(this.packageRoot, 'docker-compose.yaml');
130
+
131
+ if (!fs.existsSync(composePath)) {
132
+ return { installed: false, name: 'docker-compose.yaml', critical: true, fix: null };
133
+ }
134
+
135
+ const content = fs.readFileSync(composePath, 'utf8');
136
+
137
+ // Check for offline FastEmbed configuration
138
+ const hasOfflineConfig = content.includes('HF_HUB_OFFLINE') && content.includes('fastembed:ro');
139
+
140
+ return {
141
+ installed: hasOfflineConfig,
142
+ name: 'Docker offline FastEmbed config',
143
+ critical: false,
144
+ fix: () => this.fixDockerConfig()
145
+ };
146
+ }
147
+
148
+ // Fix functions
149
+ async installCCStatusline() {
150
+ // Check npm is available
151
+ try {
152
+ execSync('npm --version', { stdio: 'ignore' });
153
+ } catch {
154
+ this.log('npm is required but not found', 'error');
155
+ this.log('Please install Node.js and npm from nodejs.org', 'error');
156
+ return false;
157
+ }
158
+
159
+ this.log('Installing cc-statusline...', 'info');
160
+ try {
161
+ execSync('npm install -g cc-statusline', { stdio: 'inherit' });
162
+ this.log('cc-statusline installed', 'success');
163
+ return true;
164
+ } catch (error) {
165
+ // Check for permission errors
166
+ const isPermissionError = error.code === 'EACCES' ||
167
+ error.code === 'EPERM' ||
168
+ (error.stderr && error.stderr.toString().includes('EACCES')) ||
169
+ (error.message && error.message.includes('permission'));
170
+
171
+ if (isPermissionError) {
172
+ this.log('Failed to install cc-statusline: Permission denied', 'error');
173
+ this.log('Try one of:', 'info');
174
+ this.log(' 1. Run with elevated privileges: sudo npm install -g cc-statusline', 'info');
175
+ this.log(' 2. Use a node version manager like nvm', 'info');
176
+ this.log(' 3. Configure npm for user-local installs', 'info');
177
+ } else {
178
+ this.log(`Failed to install cc-statusline: ${error.message}`, 'error');
179
+ }
180
+ return false;
181
+ }
182
+ }
183
+
184
+ async installCSRStatus() {
185
+ this.log('Setting up csr-status command...', 'info');
186
+ const statuslineSetup = new StatuslineSetup();
187
+ return await statuslineSetup.installGlobalCommand();
188
+ }
189
+
190
+ async installFastEmbed() {
191
+ this.log('Setting up FastEmbed model...', 'info');
192
+ const fallback = new FastEmbedFallback();
193
+ return await fallback.run();
194
+ }
195
+
196
+ async suggestASTGrep() {
197
+ this.log('AST-Grep is optional but recommended for code quality features', 'info');
198
+ this.log('Install with: brew install ast-grep (macOS)', 'info');
199
+ this.log(' or: npm install -g @ast-grep/cli', 'info');
200
+ return true; // Not critical
201
+ }
202
+
203
+ async startQdrant() {
204
+ this.log('Starting Qdrant...', 'info');
205
+ try {
206
+ execSync('docker compose up -d qdrant', {
207
+ cwd: this.packageRoot,
208
+ stdio: 'inherit'
209
+ });
210
+ this.log('Qdrant started', 'success');
211
+ return true;
212
+ } catch (error) {
213
+ this.log(`Failed to start Qdrant: ${error.message}`, 'error');
214
+ return false;
215
+ }
216
+ }
217
+
218
+ async fixDockerConfig() {
219
+ this.log('Updating Docker Compose configuration...', 'info');
220
+ const fallback = new FastEmbedFallback();
221
+ return await fallback.configureDockerCompose();
222
+ }
223
+
224
+ async run() {
225
+ this.log('Analyzing installation...', 'info');
226
+ console.log();
227
+
228
+ // Run all checks
229
+ const checks = [
230
+ this.checkDocker(),
231
+ this.checkQdrant(),
232
+ this.checkFastEmbedModel(),
233
+ this.checkDockerComposeConfig(),
234
+ this.checkCCStatusline(),
235
+ this.checkCSRStatusScript(),
236
+ this.checkASTGrep()
237
+ ];
238
+
239
+ const results = await Promise.all(checks);
240
+
241
+ // Categorize results
242
+ const missing = results.filter(r => !r.installed);
243
+ const critical = missing.filter(r => r.critical);
244
+ const optional = missing.filter(r => !r.critical);
245
+
246
+ // Display status
247
+ console.log('📊 Installation Status:\n');
248
+ for (const result of results) {
249
+ const icon = result.installed ? '✅' : (result.critical ? '❌' : '⚠️ ');
250
+ const status = result.installed ? 'Installed' : 'Missing';
251
+ console.log(`${icon} ${result.name}: ${status}`);
252
+ }
253
+ console.log();
254
+
255
+ // Handle critical issues
256
+ const unresolvedCritical = [];
257
+ if (critical.length > 0) {
258
+ this.log(`Found ${critical.length} critical issue(s) that need fixing`, 'error');
259
+ console.log();
260
+
261
+ for (const issue of critical) {
262
+ if (issue.error) {
263
+ this.log(issue.error, 'error');
264
+ unresolvedCritical.push(issue);
265
+ } else if (issue.fix) {
266
+ this.log(`Fixing: ${issue.name}...`, 'info');
267
+ const success = await issue.fix();
268
+ if (!success) {
269
+ this.log(`Failed to fix: ${issue.name}`, 'error');
270
+ unresolvedCritical.push(issue);
271
+ }
272
+ } else {
273
+ // Issue has no fix and no error message - track as unresolved
274
+ this.log(`${issue.name} is missing (no automatic fix available)`, 'error');
275
+ unresolvedCritical.push(issue);
276
+ }
277
+ }
278
+ }
279
+
280
+ // Handle optional issues
281
+ if (optional.length > 0) {
282
+ this.log(`Found ${optional.length} optional feature(s) to install`, 'warning');
283
+ console.log();
284
+
285
+ for (const issue of optional) {
286
+ if (issue.fix) {
287
+ this.log(`Installing: ${issue.name}...`, 'info');
288
+ await issue.fix();
289
+ }
290
+ }
291
+ }
292
+
293
+ // Final status
294
+ console.log();
295
+ if (unresolvedCritical.length === 0 && optional.length === 0) {
296
+ this.log('All features are up to date! ✨', 'success');
297
+ } else if (unresolvedCritical.length === 0) {
298
+ this.log('Core features are working. Optional features installed.', 'success');
299
+ } else {
300
+ this.log('Please address critical issues before using Claude Self-Reflect', 'error');
301
+ process.exit(1);
302
+ }
303
+
304
+ console.log();
305
+ this.log('Run "claude-self-reflect doctor" for detailed diagnostics', 'info');
306
+ }
307
+ }
308
+
309
+ // Run if called directly
310
+ if (import.meta.url === `file://${process.argv[1]}`) {
311
+ const manager = new UpdateManager();
312
+ manager.run().catch(error => {
313
+ console.error('Update failed:', error);
314
+ process.exit(1);
315
+ });
316
+ }
317
+
318
+ export default UpdateManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-self-reflect",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "description": "Give Claude perfect memory of all your conversations - Installation wizard for Python MCP server",
5
5
  "keywords": [
6
6
  "claude",
@@ -0,0 +1,89 @@
1
+ """
2
+ AST-Grep Utilities
3
+ Gracefully handles AST-Grep availability for code quality features
4
+ """
5
+ import shutil
6
+ import subprocess
7
+ from functools import wraps
8
+ from typing import Optional
9
+
10
+ def is_ast_grep_installed() -> bool:
11
+ """Check if ast-grep or sg (npm install) is available in PATH"""
12
+ return shutil.which('ast-grep') is not None or shutil.which('sg') is not None
13
+
14
+ def get_ast_grep_command() -> Optional[str]:
15
+ """Get the available AST-Grep command (ast-grep or sg)"""
16
+ if shutil.which('ast-grep'):
17
+ return 'ast-grep'
18
+ elif shutil.which('sg'):
19
+ return 'sg'
20
+ return None
21
+
22
+ def get_ast_grep_version() -> Optional[str]:
23
+ """Get installed AST-Grep version"""
24
+ cmd = get_ast_grep_command()
25
+ if not cmd:
26
+ return None
27
+
28
+ try:
29
+ result = subprocess.run(
30
+ [cmd, '--version'],
31
+ capture_output=True,
32
+ text=True,
33
+ timeout=5
34
+ )
35
+ if result.returncode == 0:
36
+ return result.stdout.strip()
37
+ except Exception:
38
+ pass
39
+
40
+ return None
41
+
42
+ def check_ast_grep_or_warn(feature_name: str = "Quality analysis") -> bool:
43
+ """
44
+ Check if AST-Grep is installed, warn if not
45
+
46
+ Returns:
47
+ True if installed, False otherwise
48
+ """
49
+ if is_ast_grep_installed():
50
+ return True
51
+
52
+ print(f"⚠️ {feature_name} requires AST-Grep")
53
+ print(" This is an optional feature for advanced code quality analysis")
54
+ print()
55
+ print(" To install:")
56
+ print(" • macOS: brew install ast-grep")
57
+ print(" • Linux: npm install -g @ast-grep/cli")
58
+ print(" • Or: Download from https://github.com/ast-grep/ast-grep/releases")
59
+ print()
60
+ print(" ✅ Core MCP functionality works fine without it!")
61
+ print()
62
+
63
+ return False
64
+
65
+ def ast_grep_required(func):
66
+ """Decorator to mark functions that require AST-Grep"""
67
+ @wraps(func)
68
+ def wrapper(*args, **kwargs):
69
+ if not check_ast_grep_or_warn(func.__name__):
70
+ return None
71
+ return func(*args, **kwargs)
72
+
73
+ return wrapper
74
+
75
+ # Example usage in scripts:
76
+ # from shared.ast_grep_utils import ast_grep_required, check_ast_grep_or_warn
77
+ #
78
+ # @ast_grep_required
79
+ # def run_quality_analysis():
80
+ # # AST-Grep code here
81
+ # pass
82
+ #
83
+ # if __name__ == "__main__":
84
+ # if check_ast_grep_or_warn("Code Quality Scanner"):
85
+ # # Run AST-Grep features
86
+ # pass
87
+ # else:
88
+ # # Fallback to basic analysis
89
+ # print("Running basic quality checks without AST-Grep...")