stigmergy 1.2.13 → 1.3.1

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.
Files changed (88) hide show
  1. package/README.md +39 -3
  2. package/STIGMERGY.md +3 -0
  3. package/config/builtin-skills.json +43 -0
  4. package/config/enhanced-cli-config.json +438 -0
  5. package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
  6. package/docs/DESIGN_CLI_HELP_ANALYZER_REFACTOR.md +726 -0
  7. package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
  8. package/docs/IMPLEMENTATION_CHECKLIST_CLI_HELP_ANALYZER_REFACTOR.md +1268 -0
  9. package/docs/INSTALLER_ARCHITECTURE.md +257 -0
  10. package/docs/LESSONS_LEARNED.md +252 -0
  11. package/docs/SPECS_CLI_HELP_ANALYZER_REFACTOR.md +287 -0
  12. package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
  13. package/docs/correct-skillsio-implementation.md +368 -0
  14. package/docs/development_guidelines.md +276 -0
  15. package/docs/independent-resume-implementation.md +198 -0
  16. package/docs/resumesession-final-implementation.md +195 -0
  17. package/docs/resumesession-usage.md +87 -0
  18. package/package.json +146 -136
  19. package/scripts/analyze-router.js +168 -0
  20. package/scripts/run-comprehensive-tests.js +230 -0
  21. package/scripts/run-quick-tests.js +90 -0
  22. package/scripts/test-runner.js +344 -0
  23. package/skills/resumesession/INDEPENDENT_SKILL.md +403 -0
  24. package/skills/resumesession/README.md +381 -0
  25. package/skills/resumesession/SKILL.md +211 -0
  26. package/skills/resumesession/__init__.py +33 -0
  27. package/skills/resumesession/implementations/simple-resume.js +13 -0
  28. package/skills/resumesession/independent-resume.js +750 -0
  29. package/skills/resumesession/package.json +1 -0
  30. package/skills/resumesession/skill.json +1 -0
  31. package/src/adapters/claude/install_claude_integration.js +9 -1
  32. package/src/adapters/codebuddy/install_codebuddy_integration.js +3 -1
  33. package/src/adapters/codex/install_codex_integration.js +15 -5
  34. package/src/adapters/gemini/install_gemini_integration.js +3 -1
  35. package/src/adapters/qwen/install_qwen_integration.js +3 -1
  36. package/src/cli/commands/autoinstall.js +65 -0
  37. package/src/cli/commands/errors.js +190 -0
  38. package/src/cli/commands/independent-resume.js +395 -0
  39. package/src/cli/commands/install.js +179 -0
  40. package/src/cli/commands/permissions.js +108 -0
  41. package/src/cli/commands/project.js +485 -0
  42. package/src/cli/commands/scan.js +97 -0
  43. package/src/cli/commands/simple-resume.js +377 -0
  44. package/src/cli/commands/skills.js +158 -0
  45. package/src/cli/commands/status.js +113 -0
  46. package/src/cli/commands/stigmergy-resume.js +775 -0
  47. package/src/cli/commands/system.js +301 -0
  48. package/src/cli/commands/universal-resume.js +394 -0
  49. package/src/cli/router-beta.js +471 -0
  50. package/src/cli/utils/environment.js +75 -0
  51. package/src/cli/utils/formatters.js +47 -0
  52. package/src/cli/utils/skills_cache.js +92 -0
  53. package/src/core/cache_cleaner.js +1 -0
  54. package/src/core/cli_adapters.js +345 -0
  55. package/src/core/cli_help_analyzer.js +1236 -680
  56. package/src/core/cli_path_detector.js +702 -709
  57. package/src/core/cli_tools.js +515 -160
  58. package/src/core/coordination/nodejs/CLIIntegrationManager.js +18 -0
  59. package/src/core/coordination/nodejs/HookDeploymentManager.js +242 -412
  60. package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
  61. package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
  62. package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +932 -0
  63. package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1395 -0
  64. package/src/core/coordination/nodejs/generators/index.js +12 -0
  65. package/src/core/enhanced_cli_installer.js +1208 -608
  66. package/src/core/enhanced_cli_parameter_handler.js +402 -0
  67. package/src/core/execution_mode_detector.js +222 -0
  68. package/src/core/installer.js +151 -106
  69. package/src/core/local_skill_scanner.js +732 -0
  70. package/src/core/multilingual/language-pattern-manager.js +1 -1
  71. package/src/core/skills/BuiltinSkillsDeployer.js +188 -0
  72. package/src/core/skills/StigmergySkillManager.js +123 -16
  73. package/src/core/skills/embedded-openskills/SkillParser.js +7 -3
  74. package/src/core/smart_router.js +550 -261
  75. package/src/index.js +10 -4
  76. package/src/utils.js +66 -7
  77. package/test/cli-integration.test.js +304 -0
  78. package/test/direct_smart_router_test.js +88 -0
  79. package/test/enhanced-cli-agent-skill-test.js +485 -0
  80. package/test/simple_test.js +82 -0
  81. package/test/smart_router_test_runner.js +123 -0
  82. package/test/smart_routing_edge_cases.test.js +284 -0
  83. package/test/smart_routing_simple_verification.js +139 -0
  84. package/test/smart_routing_verification.test.js +346 -0
  85. package/test/specific-cli-agent-skill-analysis.js +385 -0
  86. package/test/unit/smart_router.test.js +295 -0
  87. package/test/very_simple_test.js +54 -0
  88. package/src/cli/router.js +0 -1783
@@ -1,710 +1,703 @@
1
- /**
2
- * CLI Path Detector and Auto-Configurator
3
- *
4
- * This module automatically detects CLI tool paths and configures them
5
- * to resolve common installation issues on different platforms.
6
- */
7
-
8
- const { spawnSync } = require('child_process');
9
- const fs = require('fs/promises');
10
- const path = require('path');
11
- const os = require('os');
12
-
13
- class CLIPathDetector {
14
- constructor() {
15
- this.configDir = path.join(os.homedir(), '.stigmergy', 'cli-paths');
16
- this.pathCacheFile = path.join(this.configDir, 'detected-paths.json');
17
- this.detectedPaths = {};
18
- this.platform = os.platform();
19
-
20
- // Common npm global directories by platform
21
- this.npmGlobalPaths = this.getNPMGlobalPaths();
22
-
23
- // CLI tool name mappings (actual command vs expected)
24
- this.cliNameMap = {
25
- 'claude': ['claude'],
26
- 'gemini': ['gemini'],
27
- 'qwen': ['qwen'],
28
- 'iflow': ['iflow'],
29
- 'qodercli': ['qodercli'],
30
- 'codebuddy': ['codebuddy'],
31
- 'copilot': ['copilot'],
32
- 'codex': ['codex']
33
- };
34
- }
35
-
36
- /**
37
- * Get all possible npm global directories for current platform
38
- */
39
- getNPMGlobalPaths() {
40
- const paths = [];
41
-
42
- if (this.platform === 'win32') {
43
- // Windows paths
44
- paths.push(
45
- path.join(os.homedir(), 'AppData', 'Roaming', 'npm'), // User npm
46
- 'C:/npm_global', // Custom global
47
- 'C:/Program Files/nodejs/npm', // System npm
48
- path.join(process.env.ProgramFiles || 'C:/Program Files', 'npm')
49
- );
50
- } else {
51
- // Unix-like paths - comprehensive coverage
52
- paths.push(
53
- // User-specific npm global paths
54
- path.join(os.homedir(), '.npm-global', 'bin'), // User local with custom prefix
55
- path.join(os.homedir(), '.npm', 'bin'), // User npm
56
- path.join(os.homedir(), 'node_modules', '.bin'), // Local node_modules bin
57
-
58
- // System-wide paths
59
- '/usr/local/bin', // Common system location
60
- '/usr/bin', // System binaries
61
- '/opt/node/bin', // Node.js installed to /opt
62
- '/opt/nodejs/bin', // Alternative system installation
63
-
64
- // Root-specific paths (when running as root)
65
- '/root/.npm-global/bin', // Root user custom prefix
66
- '/root/.npm/bin', // Root user npm
67
- '/root/node_modules/.bin', // Root local node_modules
68
- '/root/.nvm/versions/node/*/bin', // NVM installations for root
69
-
70
- // NVM (Node Version Manager) paths for regular users
71
- path.join(os.homedir(), '.nvm', 'versions', 'node', '*', 'bin'), // NVM user installations
72
- path.join(os.homedir(), '.nvm', 'current', 'bin'), // NVM current version
73
-
74
- // NodeSource installation paths
75
- '/usr/bin/nodejs', // NodeSource package installations
76
- '/usr/local/share/npm/bin', // npm share location
77
-
78
- // Homebrew (macOS) paths
79
- path.join(os.homedir(), '.brew', 'node', 'bin'), // Custom Homebrew
80
- '/opt/homebrew/bin', // Apple Silicon Homebrew
81
- '/usr/local/bin', // Intel Homebrew
82
-
83
- // pkg-config and other package managers
84
- path.join(os.homedir(), '.local', 'bin'), // User local binaries
85
- '/snap/bin', // Snap packages (Ubuntu)
86
- '/var/lib/snapd/snap/bin' // Snap system
87
- );
88
- }
89
-
90
- // Filter paths, handling wildcards for NVM
91
- return paths.filter(p => {
92
- try {
93
- // Handle wildcard paths (NVM versions)
94
- if (p.includes('*')) {
95
- return this.expandWildcardPath(p);
96
- }
97
- return fs.existsSync(p);
98
- } catch {
99
- return false;
100
- }
101
- });
102
- }
103
-
104
- /**
105
- * Expand wildcard paths (e.g., NVM version paths)
106
- */
107
- expandWildcardPath(wildcardPath) {
108
- try {
109
- const { spawnSync } = require('child_process');
110
-
111
- // Use shell to expand wildcards
112
- const result = spawnSync('bash', ['-c', `ls -d ${wildcardPath} 2>/dev/null`], {
113
- encoding: 'utf8',
114
- shell: true
115
- });
116
-
117
- if (result.status === 0 && result.stdout.trim()) {
118
- // Check if any of the expanded paths exist
119
- const expandedPaths = result.stdout.trim().split('\n');
120
- return expandedPaths.some(p => {
121
- try {
122
- return fs.existsSync(p.trim());
123
- } catch {
124
- return false;
125
- }
126
- });
127
- }
128
- return false;
129
- } catch {
130
- return false;
131
- }
132
- }
133
-
134
- /**
135
- * Get current PATH environment variable
136
- */
137
- getCurrentPath() {
138
- return (process.env.PATH || process.env.Path || '').split(path.delimiter);
139
- }
140
-
141
- /**
142
- * Check if a command exists in a specific directory
143
- */
144
- checkCommandInDir(command, dir) {
145
- const fullPath = path.join(dir, command);
146
-
147
- // Windows: check .cmd, .ps1, .exe files
148
- if (this.platform === 'win32') {
149
- const extensions = ['.cmd', '.ps1', '.exe', ''];
150
- for (const ext of extensions) {
151
- const fileWithExt = fullPath + ext;
152
- try {
153
- if (fs.existsSync(fileWithExt)) {
154
- return fileWithExt;
155
- }
156
- } catch {}
157
- }
158
- } else {
159
- // Unix-like: check direct executable
160
- try {
161
- if (fs.existsSync(fullPath)) {
162
- return fullPath;
163
- }
164
- } catch {}
165
- }
166
-
167
- return null;
168
- }
169
-
170
- /**
171
- * Find command in system PATH
172
- */
173
- findCommandInPath(command) {
174
- try {
175
- const result = spawnSync('where', [command], {
176
- encoding: 'utf8',
177
- shell: true
178
- });
179
-
180
- if (result.status === 0 && result.stdout.trim()) {
181
- return result.stdout.trim().split('\n')[0]; // Return first match
182
- }
183
- } catch {}
184
-
185
- // Fallback: manually search PATH
186
- for (const dir of this.getCurrentPath()) {
187
- const found = this.checkCommandInDir(command, dir);
188
- if (found) return found;
189
- }
190
-
191
- return null;
192
- }
193
-
194
- /**
195
- * Find command in npm global directories
196
- */
197
- findCommandInNPMGlobal(command) {
198
- for (const dir of this.npmGlobalPaths) {
199
- const found = this.checkCommandInDir(command, dir);
200
- if (found) return found;
201
- }
202
- return null;
203
- }
204
-
205
- /**
206
- * Detect CLI tool path using multiple methods
207
- */
208
- async detectCLIPath(toolName) {
209
- console.log(`[DETECTOR] Detecting path for ${toolName}...`);
210
-
211
- const commandNames = this.cliNameMap[toolName] || [toolName];
212
-
213
- for (const command of commandNames) {
214
- // Method 0: Use npm to get actual global installation path
215
- let pathFound = await this.findCommandViaNPM(command);
216
- if (pathFound) {
217
- console.log(`[DETECTOR] Found ${toolName} via npm: ${pathFound}`);
218
- return pathFound;
219
- }
220
-
221
- // Method 1: Check system PATH (most common)
222
- pathFound = this.findCommandInPath(command);
223
- if (pathFound) {
224
- console.log(`[DETECTOR] Found ${toolName} in PATH: ${pathFound}`);
225
- return pathFound;
226
- }
227
-
228
- // Method 2: Check npm global directories
229
- pathFound = this.findCommandInNPMGlobal(command);
230
- if (pathFound) {
231
- console.log(`[DETECTOR] Found ${toolName} in npm global: ${pathFound}`);
232
- return pathFound;
233
- }
234
-
235
- // Method 3: Check common installation locations
236
- if (this.platform === 'win32') {
237
- const userNPMPath = path.join(os.homedir(), 'AppData', 'Roaming', 'npm');
238
- pathFound = this.checkCommandInDir(command, userNPMPath);
239
- if (pathFound) {
240
- console.log(`[DETECTOR] Found ${toolName} in user npm: ${pathFound}`);
241
- return pathFound;
242
- }
243
- } else {
244
- // Check multiple Unix-like locations
245
- const unixPaths = [
246
- path.join(os.homedir(), '.npm-global', 'bin'),
247
- path.join(os.homedir(), '.npm', 'bin'),
248
- '/usr/local/bin',
249
- '/usr/bin',
250
- path.join(os.homedir(), '.local', 'bin'),
251
- '/root/.npm-global/bin',
252
- '/root/.npm/bin'
253
- ];
254
-
255
- for (const dir of unixPaths) {
256
- pathFound = this.checkCommandInDir(command, dir);
257
- if (pathFound) {
258
- console.log(`[DETECTOR] Found ${toolName} in ${dir}: ${pathFound}`);
259
- return pathFound;
260
- }
261
- }
262
- }
263
- }
264
-
265
- console.log(`[DETECTOR] ${toolName} not found`);
266
- return null;
267
- }
268
-
269
- /**
270
- * Find command using npm's actual global installation path
271
- */
272
- async findCommandViaNPM(command) {
273
- try {
274
- const { spawnSync } = require('child_process');
275
-
276
- // Get npm global prefix
277
- const npmPrefixResult = spawnSync('npm', ['config', 'get', 'prefix'], {
278
- encoding: 'utf8',
279
- shell: true
280
- });
281
-
282
- if (npmPrefixResult.status === 0 && npmPrefixResult.stdout.trim()) {
283
- const npmPrefix = npmPrefixResult.stdout.trim();
284
- let binDir;
285
-
286
- if (this.platform === 'win32') {
287
- binDir = npmPrefix; // Windows: prefix already points to the directory with executables
288
- } else {
289
- binDir = path.join(npmPrefix, 'bin'); // Unix: bin subdirectory
290
- }
291
-
292
- const commandPath = this.checkCommandInDir(command, binDir);
293
- if (commandPath) {
294
- return commandPath;
295
- }
296
- }
297
-
298
- // Try alternative npm config methods
299
- const npmListResult = spawnSync('npm', ['list', '-g', '--depth=0'], {
300
- encoding: 'utf8',
301
- shell: true
302
- });
303
-
304
- if (npmListResult.status === 0) {
305
- // Extract global prefix from npm list output if needed
306
- const lines = npmListResult.stdout.split('\n');
307
- for (const line of lines) {
308
- if (line.includes(`${command}@`)) {
309
- // Package is installed globally, try to find it in common locations
310
- return null; // Fall back to other methods
311
- }
312
- }
313
- }
314
-
315
- return null;
316
- } catch (error) {
317
- console.log(`[DETECTOR] npm query failed: ${error.message}`);
318
- return null;
319
- }
320
- }
321
-
322
- /**
323
- * Detect all CLI tool paths
324
- */
325
- async detectAllCLIPaths() {
326
- console.log('[DETECTOR] Starting comprehensive CLI path detection...');
327
-
328
- const allPaths = {};
329
-
330
- for (const toolName of Object.keys(this.cliNameMap)) {
331
- allPaths[toolName] = await this.detectCLIPath(toolName);
332
- }
333
-
334
- this.detectedPaths = allPaths;
335
- await this.saveDetectedPaths();
336
-
337
- return allPaths;
338
- }
339
-
340
- /**
341
- * Save detected paths to cache
342
- */
343
- async saveDetectedPaths() {
344
- try {
345
- await fs.mkdir(this.configDir, { recursive: true });
346
-
347
- const cacheData = {
348
- version: '1.0.0',
349
- timestamp: new Date().toISOString(),
350
- platform: this.platform,
351
- npmGlobalPaths: this.npmGlobalPaths,
352
- detectedPaths: this.detectedPaths
353
- };
354
-
355
- await fs.writeFile(
356
- this.pathCacheFile,
357
- JSON.stringify(cacheData, null, 2),
358
- 'utf8'
359
- );
360
-
361
- console.log(`[DETECTOR] Saved path cache to: ${this.pathCacheFile}`);
362
- } catch (error) {
363
- console.log(`[DETECTOR] Warning: Could not save path cache: ${error.message}`);
364
- }
365
- }
366
-
367
- /**
368
- * Load detected paths from cache
369
- */
370
- async loadDetectedPaths() {
371
- try {
372
- if (await fs.access(this.pathCacheFile).then(() => true).catch(() => false)) {
373
- const data = await fs.readFile(this.pathCacheFile, 'utf8');
374
- const cacheData = JSON.parse(data);
375
- this.detectedPaths = cacheData.detectedPaths || {};
376
-
377
- console.log(`[DETECTOR] Loaded ${Object.keys(this.detectedPaths).length} paths from cache`);
378
- return this.detectedPaths;
379
- }
380
- } catch (error) {
381
- console.log(`[DETECTOR] Warning: Could not load path cache: ${error.message}`);
382
- }
383
-
384
- return {};
385
- }
386
-
387
- /**
388
- * Update PATH environment variable if needed
389
- */
390
- async updatePATHIfMissing() {
391
- console.log('[DETECTOR] Checking PATH configuration...');
392
-
393
- const currentPath = this.getCurrentPath();
394
- const missingPaths = [];
395
-
396
- for (const dir of this.npmGlobalPaths) {
397
- if (!currentPath.includes(dir)) {
398
- missingPaths.push(dir);
399
- }
400
- }
401
-
402
- if (missingPaths.length > 0) {
403
- console.log(`[DETECTOR] Found ${missingPaths.length} missing npm global directories in PATH`);
404
- console.log('[DETECTOR] Automatically updating PATH for persistent access...');
405
-
406
- // Create PATH update script first (as backup)
407
- await this.createPATHUpdateScript(missingPaths);
408
-
409
- // Perform automatic PATH update
410
- const updateResult = await this.performAutoPATHUpdate(missingPaths);
411
-
412
- return {
413
- updated: updateResult.success,
414
- missingPaths,
415
- message: updateResult.success ? 'PATH automatically updated' : `PATH update failed: ${updateResult.error}`,
416
- scriptCreated: true,
417
- autoUpdateAttempted: true,
418
- scriptPath: path.join(this.configDir, 'setup-scripts')
419
- };
420
- }
421
-
422
- return {
423
- updated: true,
424
- message: 'PATH already contains all npm global directories'
425
- };
426
- }
427
-
428
- /**
429
- * Create script to update PATH
430
- */
431
- async createPATHUpdateScript(missingPaths) {
432
- const scriptDir = path.join(this.configDir, 'setup-scripts');
433
- await fs.mkdir(scriptDir, { recursive: true });
434
-
435
- if (this.platform === 'win32') {
436
- // Windows PowerShell script
437
- const ps1Script = `
438
- # Stigmergy CLI PATH Update Script
439
- # Run this script in PowerShell as Administrator
440
-
441
- Write-Host "Adding npm global directories to PATH..." -ForegroundColor Green
442
-
443
- $missingPaths = @(
444
- ${missingPaths.map(p => ` "${p}"`).join(',\n')}
445
- )
446
-
447
- $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
448
- $newPath = $currentPath + ";" + ($missingPaths -join ";")
449
-
450
- [Environment]::SetEnvironmentVariable("PATH", $newPath, "User")
451
- Write-Host "PATH updated successfully!" -ForegroundColor Green
452
- Write-Host "Please restart your terminal or run 'refreshenv' to apply changes" -ForegroundColor Yellow
453
- `;
454
-
455
- await fs.writeFile(
456
- path.join(scriptDir, 'update-path.ps1'),
457
- ps1Script,
458
- 'utf8'
459
- );
460
-
461
- // Windows CMD script
462
- const cmdScript = `
463
- @echo off
464
- REM Stigmergy CLI PATH Update Script
465
- REM Run this as Administrator
466
-
467
- echo Adding npm global directories to PATH...
468
- setx PATH "%PATH%;${missingPaths.join(';')}"
469
- echo PATH updated successfully!
470
- echo Please restart your terminal to apply changes
471
- pause
472
- `;
473
-
474
- await fs.writeFile(
475
- path.join(scriptDir, 'update-path.bat'),
476
- cmdScript,
477
- 'utf8'
478
- );
479
-
480
- } else {
481
- // Unix/Linux/Mac script
482
- const missingPathsArray = missingPaths.map(p => `"${p}"`).join('\n');
483
- const shScript = `#!/bin/bash
484
- # Stigmergy CLI PATH Update Script
485
- # Run this script: source update-path.sh
486
-
487
- echo "Adding npm global directories to PATH..."
488
-
489
- missing_paths=(
490
- ${missingPathsArray}
491
- )
492
-
493
- # Add to shell profile
494
- shell_rc="$HOME/.bashrc"
495
- if [ -f "$HOME/.zshrc" ]; then
496
- shell_rc="$HOME/.zshrc"
497
- elif [ -f "$HOME/.profile" ]; then
498
- shell_rc="$HOME/.profile"
499
- fi
500
-
501
- for path in "\${missing_paths[@]}"; do
502
- if ! echo "$PATH" | grep -q "$path"; then
503
- echo "Adding $path to PATH in $shell_rc"
504
- echo 'export PATH="$PATH:'"$path'"' >> "$shell_rc"
505
- export PATH="$PATH:$path"
506
- fi
507
- done
508
-
509
- echo "PATH updated successfully!"
510
- echo "Please restart your terminal or run 'source $shell_rc' to apply changes"
511
- `;
512
-
513
- await fs.writeFile(
514
- path.join(scriptDir, 'update-path.sh'),
515
- shScript,
516
- 'utf8'
517
- );
518
-
519
- // Make script executable
520
- await fs.chmod(path.join(scriptDir, 'update-path.sh'), '755');
521
- }
522
-
523
- console.log(`[DETECTOR] Created PATH update scripts in: ${scriptDir}`);
524
- }
525
-
526
- /**
527
- * Perform automatic PATH update
528
- */
529
- async performAutoPATHUpdate(missingPaths) {
530
- try {
531
- if (this.platform === 'win32') {
532
- return await this.performWindowsPATHUpdate(missingPaths);
533
- } else {
534
- return await this.performUnixPATHUpdate(missingPaths);
535
- }
536
- } catch (error) {
537
- return {
538
- success: false,
539
- error: error.message
540
- };
541
- }
542
- }
543
-
544
- /**
545
- * Perform Windows PATH update
546
- */
547
- async performWindowsPATHUpdate(missingPaths) {
548
- try {
549
- const { spawnSync } = require('child_process');
550
-
551
- console.log('[DETECTOR] Windows: Updating user PATH environment variable...');
552
-
553
- // Use PowerShell to update user PATH permanently
554
- const pathsToAdd = missingPaths.join(';');
555
- const psCommand = `
556
- $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
557
- $newPaths = @(${missingPaths.map(p => `"${p}"`).join(',')})
558
- foreach ($path in $newPaths) {
559
- if ($currentPath -notlike "*$path*") {
560
- $currentPath = $currentPath + ";" + $path
561
- }
562
- }
563
- [Environment]::SetEnvironmentVariable("PATH", $currentPath, "User")
564
- Write-Output "PATH updated successfully"
565
- `;
566
-
567
- const result = spawnSync('powershell', ['-Command', psCommand], {
568
- stdio: 'pipe',
569
- shell: true,
570
- encoding: 'utf8',
571
- timeout: 30000
572
- });
573
-
574
- if (result.status === 0) {
575
- console.log('[DETECTOR] ✓ Windows PATH updated successfully');
576
- console.log('[DETECTOR] ℹ Note: Restart terminal or run refreshenv to apply changes');
577
- return { success: true };
578
- } else {
579
- console.log('[DETECTOR] Windows PATH update failed');
580
- console.log(`[DETECTOR] Error: ${result.stderr || result.stdout}`);
581
- return {
582
- success: false,
583
- error: result.stderr || result.stdout || 'Unknown PowerShell error'
584
- };
585
- }
586
- } catch (error) {
587
- return { success: false, error: error.message };
588
- }
589
- }
590
-
591
- /**
592
- * Perform Unix/Linux/macOS PATH update
593
- */
594
- async performUnixPATHUpdate(missingPaths) {
595
- try {
596
- const fs = require('fs').promises;
597
- const { spawnSync } = require('child_process');
598
-
599
- console.log('[DETECTOR] Unix: Updating shell profile...');
600
-
601
- // Determine which shell profile to update
602
- const shellProfile = await this.determineShellProfile();
603
-
604
- if (!shellProfile) {
605
- return {
606
- success: false,
607
- error: 'Could not determine shell profile to update'
608
- };
609
- }
610
-
611
- // Read existing profile
612
- let profileContent = '';
613
- try {
614
- profileContent = await fs.readFile(shellProfile, 'utf8');
615
- } catch (error) {
616
- // File doesn't exist, create it
617
- profileContent = '';
618
- }
619
-
620
- // Add missing paths to profile
621
- const pathExports = missingPaths.map(path => `export PATH="$PATH:${path}"`).join('\n');
622
-
623
- // Check if paths are already in the profile
624
- const pathsToAdd = missingPaths.filter(path => !profileContent.includes(path));
625
-
626
- if (pathsToAdd.length === 0) {
627
- console.log('[DETECTOR] All paths already present in shell profile');
628
- return { success: true };
629
- }
630
-
631
- const newPathExports = pathsToAdd.map(path => `export PATH="$PATH:${path}"`).join('\n');
632
- const contentToAdd = `\n# Added by Stigmergy CLI - ${new Date().toISOString()}\n${newPathExports}\n`;
633
-
634
- await fs.writeFile(shellProfile, profileContent + contentToAdd, 'utf8');
635
-
636
- console.log(`[DETECTOR] ✓ Updated ${shellProfile} with PATH additions`);
637
- console.log('[DETECTOR] ℹ Note: Restart terminal or run source ~/.bashrc to apply changes');
638
-
639
- return { success: true };
640
- } catch (error) {
641
- return { success: false, error: error.message };
642
- }
643
- }
644
-
645
- /**
646
- * Determine which shell profile to update
647
- */
648
- async determineShellProfile() {
649
- const fs = require('fs').promises;
650
- const os = require('os');
651
-
652
- const homeDir = os.homedir();
653
- const possibleProfiles = [
654
- path.join(homeDir, '.bashrc'),
655
- path.join(homeDir, '.zshrc'),
656
- path.join(homeDir, '.profile'),
657
- path.join(homeDir, '.bash_profile')
658
- ];
659
-
660
- // First check which shell is currently being used
661
- const shellEnv = process.env.SHELL;
662
- if (shellEnv) {
663
- if (shellEnv.includes('zsh')) {
664
- return path.join(homeDir, '.zshrc');
665
- } else if (shellEnv.includes('bash')) {
666
- return path.join(homeDir, '.bashrc');
667
- }
668
- }
669
-
670
- // Fall back to checking which profile exists
671
- for (const profile of possibleProfiles) {
672
- try {
673
- await fs.access(profile);
674
- return profile;
675
- } catch {
676
- // File doesn't exist, continue checking
677
- }
678
- }
679
-
680
- // Default to .bashrc for most systems
681
- return path.join(homeDir, '.bashrc');
682
- }
683
-
684
- /**
685
- * Get detected path for a specific tool
686
- */
687
- getDetectedPath(toolName) {
688
- return this.detectedPaths[toolName] || null;
689
- }
690
-
691
- /**
692
- * Get status report of detected paths
693
- */
694
- getPathStatusReport() {
695
- const report = {
696
- platform: this.platform,
697
- npmGlobalPaths: this.npmGlobalPaths,
698
- detectedPaths: this.detectedPaths,
699
- summary: {
700
- total: Object.keys(this.cliNameMap).length,
701
- found: Object.values(this.detectedPaths).filter(Boolean).length,
702
- missing: Object.values(this.detectedPaths).filter(v => !v).length
703
- }
704
- };
705
-
706
- return report;
707
- }
708
- }
709
-
1
+ /**
2
+ * CLI Path Detector and Auto-Configurator
3
+ *
4
+ * This module automatically detects CLI tool paths and configures them
5
+ * to resolve common installation issues on different platforms.
6
+ */
7
+
8
+ const { spawnSync } = require('child_process');
9
+ const fs = require('fs/promises');
10
+ const path = require('path');
11
+ const os = require('os');
12
+
13
+ class CLIPathDetector {
14
+ constructor() {
15
+ this.configDir = path.join(os.homedir(), '.stigmergy', 'cli-paths');
16
+ this.pathCacheFile = path.join(this.configDir, 'detected-paths.json');
17
+ this.detectedPaths = {};
18
+ this.platform = os.platform();
19
+
20
+ // Common npm global directories by platform
21
+ this.npmGlobalPaths = this.getNPMGlobalPaths();
22
+
23
+ // CLI tool name mappings (actual command vs expected)
24
+ this.cliNameMap = {
25
+ 'claude': ['claude'],
26
+ 'gemini': ['gemini'],
27
+ 'qwen': ['qwen'],
28
+ 'iflow': ['iflow'],
29
+ 'qodercli': ['qodercli'],
30
+ 'codebuddy': ['codebuddy'],
31
+ 'copilot': ['copilot'],
32
+ 'codex': ['codex'],
33
+ 'kode': ['kode'],
34
+ 'resumesession': ['resumesession']
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Get all possible npm global directories for current platform
40
+ */
41
+ getNPMGlobalPaths() {
42
+ const paths = [];
43
+
44
+ if (this.platform === 'win32') {
45
+ // Windows paths
46
+ paths.push(
47
+ path.join(os.homedir(), 'AppData', 'Roaming', 'npm'), // User npm
48
+ 'C:/npm_global', // Custom global
49
+ 'C:/Program Files/nodejs/npm', // System npm
50
+ path.join(process.env.ProgramFiles || 'C:/Program Files', 'npm')
51
+ );
52
+ } else {
53
+ // Unix-like paths - comprehensive coverage
54
+ paths.push(
55
+ // User-specific npm global paths
56
+ path.join(os.homedir(), '.npm-global', 'bin'), // User local with custom prefix
57
+ path.join(os.homedir(), '.npm', 'bin'), // User npm
58
+ path.join(os.homedir(), 'node_modules', '.bin'), // Local node_modules bin
59
+
60
+ // System-wide paths
61
+ '/usr/local/bin', // Common system location
62
+ '/usr/bin', // System binaries
63
+ '/opt/node/bin', // Node.js installed to /opt
64
+ '/opt/nodejs/bin', // Alternative system installation
65
+
66
+ // Root-specific paths (when running as root)
67
+ '/root/.npm-global/bin', // Root user custom prefix
68
+ '/root/.npm/bin', // Root user npm
69
+ '/root/node_modules/.bin', // Root local node_modules
70
+ '/root/.nvm/versions/node/*/bin', // NVM installations for root
71
+
72
+ // NVM (Node Version Manager) paths for regular users
73
+ path.join(os.homedir(), '.nvm', 'versions', 'node', '*', 'bin'), // NVM user installations
74
+ path.join(os.homedir(), '.nvm', 'current', 'bin'), // NVM current version
75
+
76
+ // NodeSource installation paths
77
+ '/usr/bin/nodejs', // NodeSource package installations
78
+ '/usr/local/share/npm/bin', // npm share location
79
+
80
+ // Homebrew (macOS) paths
81
+ path.join(os.homedir(), '.brew', 'node', 'bin'), // Custom Homebrew
82
+ '/opt/homebrew/bin', // Apple Silicon Homebrew
83
+ '/usr/local/bin', // Intel Homebrew
84
+
85
+ // pkg-config and other package managers
86
+ path.join(os.homedir(), '.local', 'bin'), // User local binaries
87
+ '/snap/bin', // Snap packages (Ubuntu)
88
+ '/var/lib/snapd/snap/bin' // Snap system
89
+ );
90
+ }
91
+
92
+ // Filter paths, handling wildcards for NVM
93
+ return paths.filter(p => {
94
+ try {
95
+ // Handle wildcard paths (NVM versions)
96
+ if (p.includes('*')) {
97
+ return this.expandWildcardPath(p);
98
+ }
99
+ return fs.existsSync(p);
100
+ } catch {
101
+ return false;
102
+ }
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Expand wildcard paths (e.g., NVM version paths)
108
+ */
109
+ expandWildcardPath(wildcardPath) {
110
+ try {
111
+ const { spawnSync } = require('child_process');
112
+
113
+ // Use shell to expand wildcards
114
+ const result = spawnSync('bash', ['-c', `ls -d ${wildcardPath} 2>/dev/null`], {
115
+ encoding: 'utf8',
116
+ shell: true
117
+ });
118
+
119
+ if (result.status === 0 && result.stdout.trim()) {
120
+ // Check if any of the expanded paths exist
121
+ const expandedPaths = result.stdout.trim().split('\n');
122
+ return expandedPaths.some(p => {
123
+ try {
124
+ return fs.existsSync(p.trim());
125
+ } catch {
126
+ return false;
127
+ }
128
+ });
129
+ }
130
+ return false;
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Get current PATH environment variable
138
+ */
139
+ getCurrentPath() {
140
+ return (process.env.PATH || process.env.Path || '').split(path.delimiter);
141
+ }
142
+
143
+ /**
144
+ * Check if a command exists in a specific directory
145
+ */
146
+ checkCommandInDir(command, dir) {
147
+ const fullPath = path.join(dir, command);
148
+
149
+ // Windows: check .cmd, .ps1, .exe files
150
+ if (this.platform === 'win32') {
151
+ const extensions = ['.cmd', '.ps1', '.exe', ''];
152
+ for (const ext of extensions) {
153
+ const fileWithExt = fullPath + ext;
154
+ try {
155
+ if (fs.existsSync(fileWithExt)) {
156
+ return fileWithExt;
157
+ }
158
+ } catch {}
159
+ }
160
+ } else {
161
+ // Unix-like: check direct executable
162
+ try {
163
+ if (fs.existsSync(fullPath)) {
164
+ return fullPath;
165
+ }
166
+ } catch {}
167
+ }
168
+
169
+ return null;
170
+ }
171
+
172
+ /**
173
+ * Find command in system PATH
174
+ */
175
+ findCommandInPath(command) {
176
+ try {
177
+ const result = spawnSync('where', [command], {
178
+ encoding: 'utf8',
179
+ shell: true
180
+ });
181
+
182
+ if (result.status === 0 && result.stdout.trim()) {
183
+ return result.stdout.trim().split('\n')[0]; // Return first match
184
+ }
185
+ } catch {}
186
+
187
+ // Fallback: manually search PATH
188
+ for (const dir of this.getCurrentPath()) {
189
+ const found = this.checkCommandInDir(command, dir);
190
+ if (found) return found;
191
+ }
192
+
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * Find command in npm global directories
198
+ */
199
+ findCommandInNPMGlobal(command) {
200
+ for (const dir of this.npmGlobalPaths) {
201
+ const found = this.checkCommandInDir(command, dir);
202
+ if (found) return found;
203
+ }
204
+ return null;
205
+ }
206
+
207
+ /**
208
+ * Detect CLI tool path using multiple methods
209
+ */
210
+ async detectCLIPath(toolName) {
211
+ console.log(`[DETECTOR] Detecting path for ${toolName}...`);
212
+
213
+ const commandNames = this.cliNameMap[toolName] || [toolName];
214
+
215
+ for (const command of commandNames) {
216
+ // Method 0: Use npm to get actual global installation path
217
+ let pathFound = await this.findCommandViaNPM(command);
218
+ if (pathFound) {
219
+ console.log(`[DETECTOR] Found ${toolName} via npm: ${pathFound}`);
220
+ return pathFound;
221
+ }
222
+
223
+ // Method 1: Check system PATH (most common)
224
+ pathFound = this.findCommandInPath(command);
225
+ if (pathFound) {
226
+ console.log(`[DETECTOR] Found ${toolName} in PATH: ${pathFound}`);
227
+ return pathFound;
228
+ }
229
+
230
+ // Method 2: Check npm global directories
231
+ pathFound = this.findCommandInNPMGlobal(command);
232
+ if (pathFound) {
233
+ console.log(`[DETECTOR] Found ${toolName} in npm global: ${pathFound}`);
234
+ return pathFound;
235
+ }
236
+
237
+ // Method 3: Check common installation locations
238
+ if (this.platform === 'win32') {
239
+ const userNPMPath = path.join(os.homedir(), 'AppData', 'Roaming', 'npm');
240
+ pathFound = this.checkCommandInDir(command, userNPMPath);
241
+ if (pathFound) {
242
+ console.log(`[DETECTOR] Found ${toolName} in user npm: ${pathFound}`);
243
+ return pathFound;
244
+ }
245
+ } else {
246
+ // Check multiple Unix-like locations
247
+ const unixPaths = [
248
+ path.join(os.homedir(), '.npm-global', 'bin'),
249
+ path.join(os.homedir(), '.npm', 'bin'),
250
+ '/usr/local/bin',
251
+ '/usr/bin',
252
+ path.join(os.homedir(), '.local', 'bin'),
253
+ '/root/.npm-global/bin',
254
+ '/root/.npm/bin'
255
+ ];
256
+
257
+ for (const dir of unixPaths) {
258
+ pathFound = this.checkCommandInDir(command, dir);
259
+ if (pathFound) {
260
+ console.log(`[DETECTOR] Found ${toolName} in ${dir}: ${pathFound}`);
261
+ return pathFound;
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ console.log(`[DETECTOR] ${toolName} not found`);
268
+ return null;
269
+ }
270
+
271
+ /**
272
+ * Find command using npm's actual global installation path
273
+ */
274
+ async findCommandViaNPM(command) {
275
+ try {
276
+ const { spawnSync } = require('child_process');
277
+
278
+ // Get npm global prefix
279
+ const npmPrefixResult = spawnSync('npm', ['config', 'get', 'prefix'], {
280
+ encoding: 'utf8',
281
+ shell: true
282
+ });
283
+
284
+ if (npmPrefixResult.status === 0 && npmPrefixResult.stdout.trim()) {
285
+ const npmPrefix = npmPrefixResult.stdout.trim();
286
+ let binDir;
287
+
288
+ if (this.platform === 'win32') {
289
+ binDir = npmPrefix; // Windows: prefix already points to the directory with executables
290
+ } else {
291
+ binDir = path.join(npmPrefix, 'bin'); // Unix: bin subdirectory
292
+ }
293
+
294
+ const commandPath = this.checkCommandInDir(command, binDir);
295
+ if (commandPath) {
296
+ return commandPath;
297
+ }
298
+ }
299
+
300
+ } catch (error) {
301
+ console.log(`[DETECTOR] npm query failed: ${error.message}`);
302
+ return null;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Detect all CLI tool paths
308
+ */
309
+ async detectAllCLIPaths() {
310
+ console.log('[DETECTOR] Starting comprehensive CLI path detection...');
311
+
312
+ const allPaths = {};
313
+
314
+ for (const toolName of Object.keys(this.cliNameMap)) {
315
+ allPaths[toolName] = await this.detectCLIPath(toolName);
316
+ }
317
+
318
+ this.detectedPaths = allPaths;
319
+ await this.saveDetectedPaths();
320
+
321
+ return allPaths;
322
+ }
323
+
324
+ /**
325
+ * Save detected paths to cache
326
+ */
327
+ async saveDetectedPaths() {
328
+ try {
329
+ await fs.mkdir(this.configDir, { recursive: true });
330
+
331
+ const cacheData = {
332
+ version: '1.0.0',
333
+ timestamp: new Date().toISOString(),
334
+ platform: this.platform,
335
+ npmGlobalPaths: this.npmGlobalPaths,
336
+ detectedPaths: this.detectedPaths
337
+ };
338
+
339
+ await fs.writeFile(
340
+ this.pathCacheFile,
341
+ JSON.stringify(cacheData, null, 2),
342
+ 'utf8'
343
+ );
344
+
345
+ console.log(`[DETECTOR] Saved path cache to: ${this.pathCacheFile}`);
346
+ } catch (error) {
347
+ console.log(`[DETECTOR] Warning: Could not save path cache: ${error.message}`);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Load detected paths from cache
353
+ */
354
+ async loadDetectedPaths() {
355
+ try {
356
+ if (await fs.access(this.pathCacheFile).then(() => true).catch(() => false)) {
357
+ const data = await fs.readFile(this.pathCacheFile, 'utf8');
358
+ const cacheData = JSON.parse(data);
359
+
360
+ // Check if cache is too old (older than 1 hour) and skip loading if so
361
+ const cacheAge = Date.now() - new Date(cacheData.timestamp).getTime();
362
+ const maxCacheAge = 60 * 60 * 1000; // 1 hour in milliseconds
363
+
364
+ if (cacheAge < maxCacheAge) {
365
+ this.detectedPaths = cacheData.detectedPaths || {};
366
+ console.log(`[DETECTOR] Loaded ${Object.keys(this.detectedPaths).length} paths from cache (age: ${Math.floor(cacheAge/1000)}s)`);
367
+ return this.detectedPaths;
368
+ } else {
369
+ console.log(`[DETECTOR] Cache is too old (${Math.floor(cacheAge/1000)}s), skipping cache`);
370
+ return {};
371
+ }
372
+ }
373
+ } catch (error) {
374
+ console.log(`[DETECTOR] Warning: Could not load path cache: ${error.message}`);
375
+ }
376
+
377
+ return {};
378
+ }
379
+
380
+ /**
381
+ * Update PATH environment variable if needed
382
+ */
383
+ async updatePATHIfMissing() {
384
+ console.log('[DETECTOR] Checking PATH configuration...');
385
+
386
+ const currentPath = this.getCurrentPath();
387
+ const missingPaths = [];
388
+
389
+ for (const dir of this.npmGlobalPaths) {
390
+ if (!currentPath.includes(dir)) {
391
+ missingPaths.push(dir);
392
+ }
393
+ }
394
+
395
+ if (missingPaths.length > 0) {
396
+ console.log(`[DETECTOR] Found ${missingPaths.length} missing npm global directories in PATH`);
397
+ console.log('[DETECTOR] Automatically updating PATH for persistent access...');
398
+
399
+ // Create PATH update script first (as backup)
400
+ await this.createPATHUpdateScript(missingPaths);
401
+
402
+ // Perform automatic PATH update
403
+ const updateResult = await this.performAutoPATHUpdate(missingPaths);
404
+
405
+ return {
406
+ updated: updateResult.success,
407
+ missingPaths,
408
+ message: updateResult.success ? 'PATH automatically updated' : `PATH update failed: ${updateResult.error}`,
409
+ scriptCreated: true,
410
+ autoUpdateAttempted: true,
411
+ scriptPath: path.join(this.configDir, 'setup-scripts')
412
+ };
413
+ }
414
+
415
+ return {
416
+ updated: true,
417
+ message: 'PATH already contains all npm global directories'
418
+ };
419
+ }
420
+
421
+ /**
422
+ * Create script to update PATH
423
+ */
424
+ async createPATHUpdateScript(missingPaths) {
425
+ const scriptDir = path.join(this.configDir, 'setup-scripts');
426
+ await fs.mkdir(scriptDir, { recursive: true });
427
+
428
+ if (this.platform === 'win32') {
429
+ // Windows PowerShell script
430
+ const ps1Script = `
431
+ # Stigmergy CLI PATH Update Script
432
+ # Run this script in PowerShell as Administrator
433
+
434
+ Write-Host "Adding npm global directories to PATH..." -ForegroundColor Green
435
+
436
+ $missingPaths = @(
437
+ ${missingPaths.map(p => ` "${p}"`).join(',\n')}
438
+ )
439
+
440
+ $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
441
+ $newPath = $currentPath + ";" + ($missingPaths -join ";")
442
+
443
+ [Environment]::SetEnvironmentVariable("PATH", $newPath, "User")
444
+ Write-Host "PATH updated successfully!" -ForegroundColor Green
445
+ Write-Host "Please restart your terminal or run 'refreshenv' to apply changes" -ForegroundColor Yellow
446
+ `;
447
+
448
+ await fs.writeFile(
449
+ path.join(scriptDir, 'update-path.ps1'),
450
+ ps1Script,
451
+ 'utf8'
452
+ );
453
+
454
+ // Windows CMD script
455
+ const cmdScript = `
456
+ @echo off
457
+ REM Stigmergy CLI PATH Update Script
458
+ REM Run this as Administrator
459
+
460
+ echo Adding npm global directories to PATH...
461
+ setx PATH "%PATH%;${missingPaths.join(';')}"
462
+ echo PATH updated successfully!
463
+ echo Please restart your terminal to apply changes
464
+ pause
465
+ `;
466
+
467
+ await fs.writeFile(
468
+ path.join(scriptDir, 'update-path.bat'),
469
+ cmdScript,
470
+ 'utf8'
471
+ );
472
+
473
+ } else {
474
+ // Unix/Linux/Mac script
475
+ const missingPathsArray = missingPaths.map(p => `"${p}"`).join('\n');
476
+ const shScript = `#!/bin/bash
477
+ # Stigmergy CLI PATH Update Script
478
+ # Run this script: source update-path.sh
479
+
480
+ echo "Adding npm global directories to PATH..."
481
+
482
+ missing_paths=(
483
+ ${missingPathsArray}
484
+ )
485
+
486
+ # Add to shell profile
487
+ shell_rc="$HOME/.bashrc"
488
+ if [ -f "$HOME/.zshrc" ]; then
489
+ shell_rc="$HOME/.zshrc"
490
+ elif [ -f "$HOME/.profile" ]; then
491
+ shell_rc="$HOME/.profile"
492
+ fi
493
+
494
+ for path in "\${missing_paths[@]}"; do
495
+ if ! echo "$PATH" | grep -q "$path"; then
496
+ echo "Adding $path to PATH in $shell_rc"
497
+ echo 'export PATH="$PATH:'"$path'"' >> "$shell_rc"
498
+ export PATH="$PATH:$path"
499
+ fi
500
+ done
501
+
502
+ echo "PATH updated successfully!"
503
+ echo "Please restart your terminal or run 'source $shell_rc' to apply changes"
504
+ `;
505
+
506
+ await fs.writeFile(
507
+ path.join(scriptDir, 'update-path.sh'),
508
+ shScript,
509
+ 'utf8'
510
+ );
511
+
512
+ // Make script executable
513
+ await fs.chmod(path.join(scriptDir, 'update-path.sh'), '755');
514
+ }
515
+
516
+ console.log(`[DETECTOR] Created PATH update scripts in: ${scriptDir}`);
517
+ }
518
+
519
+ /**
520
+ * Perform automatic PATH update
521
+ */
522
+ async performAutoPATHUpdate(missingPaths) {
523
+ try {
524
+ if (this.platform === 'win32') {
525
+ return await this.performWindowsPATHUpdate(missingPaths);
526
+ } else {
527
+ return await this.performUnixPATHUpdate(missingPaths);
528
+ }
529
+ } catch (error) {
530
+ return {
531
+ success: false,
532
+ error: error.message
533
+ };
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Perform Windows PATH update
539
+ */
540
+ async performWindowsPATHUpdate(missingPaths) {
541
+ try {
542
+ const { spawnSync } = require('child_process');
543
+
544
+ console.log('[DETECTOR] Windows: Updating user PATH environment variable...');
545
+
546
+ // Use PowerShell to update user PATH permanently
547
+ const pathsToAdd = missingPaths.join(';');
548
+ const psCommand = `
549
+ $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
550
+ $newPaths = @(${missingPaths.map(p => `"${p}"`).join(',')})
551
+ foreach ($path in $newPaths) {
552
+ if ($currentPath -notlike "*$path*") {
553
+ $currentPath = $currentPath + ";" + $path
554
+ }
555
+ }
556
+ [Environment]::SetEnvironmentVariable("PATH", $currentPath, "User")
557
+ Write-Output "PATH updated successfully"
558
+ `;
559
+
560
+ const result = spawnSync('powershell', ['-Command', psCommand], {
561
+ stdio: 'pipe',
562
+ shell: true,
563
+ encoding: 'utf8',
564
+ timeout: 30000
565
+ });
566
+
567
+ if (result.status === 0) {
568
+ console.log('[DETECTOR] ✓ Windows PATH updated successfully');
569
+ console.log('[DETECTOR] ℹ Note: Restart terminal or run refreshenv to apply changes');
570
+ return { success: true };
571
+ } else {
572
+ console.log('[DETECTOR] ✗ Windows PATH update failed');
573
+ console.log(`[DETECTOR] Error: ${result.stderr || result.stdout}`);
574
+ return {
575
+ success: false,
576
+ error: result.stderr || result.stdout || 'Unknown PowerShell error'
577
+ };
578
+ }
579
+ } catch (error) {
580
+ return { success: false, error: error.message };
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Perform Unix/Linux/macOS PATH update
586
+ */
587
+ async performUnixPATHUpdate(missingPaths) {
588
+ try {
589
+ const fs = require('fs').promises;
590
+ const { spawnSync } = require('child_process');
591
+
592
+ console.log('[DETECTOR] Unix: Updating shell profile...');
593
+
594
+ // Determine which shell profile to update
595
+ const shellProfile = await this.determineShellProfile();
596
+
597
+ if (!shellProfile) {
598
+ return {
599
+ success: false,
600
+ error: 'Could not determine shell profile to update'
601
+ };
602
+ }
603
+
604
+ // Read existing profile
605
+ let profileContent = '';
606
+ try {
607
+ profileContent = await fs.readFile(shellProfile, 'utf8');
608
+ } catch (error) {
609
+ // File doesn't exist, create it
610
+ profileContent = '';
611
+ }
612
+
613
+ // Add missing paths to profile
614
+ const pathExports = missingPaths.map(path => `export PATH="$PATH:${path}"`).join('\n');
615
+
616
+ // Check if paths are already in the profile
617
+ const pathsToAdd = missingPaths.filter(path => !profileContent.includes(path));
618
+
619
+ if (pathsToAdd.length === 0) {
620
+ console.log('[DETECTOR] All paths already present in shell profile');
621
+ return { success: true };
622
+ }
623
+
624
+ const newPathExports = pathsToAdd.map(path => `export PATH="$PATH:${path}"`).join('\n');
625
+ const contentToAdd = `\n# Added by Stigmergy CLI - ${new Date().toISOString()}\n${newPathExports}\n`;
626
+
627
+ await fs.writeFile(shellProfile, profileContent + contentToAdd, 'utf8');
628
+
629
+ console.log(`[DETECTOR] ✓ Updated ${shellProfile} with PATH additions`);
630
+ console.log('[DETECTOR] ℹ Note: Restart terminal or run source ~/.bashrc to apply changes');
631
+
632
+ return { success: true };
633
+ } catch (error) {
634
+ return { success: false, error: error.message };
635
+ }
636
+ }
637
+
638
+ /**
639
+ * Determine which shell profile to update
640
+ */
641
+ async determineShellProfile() {
642
+ const fs = require('fs').promises;
643
+ const os = require('os');
644
+
645
+ const homeDir = os.homedir();
646
+ const possibleProfiles = [
647
+ path.join(homeDir, '.bashrc'),
648
+ path.join(homeDir, '.zshrc'),
649
+ path.join(homeDir, '.profile'),
650
+ path.join(homeDir, '.bash_profile')
651
+ ];
652
+
653
+ // First check which shell is currently being used
654
+ const shellEnv = process.env.SHELL;
655
+ if (shellEnv) {
656
+ if (shellEnv.includes('zsh')) {
657
+ return path.join(homeDir, '.zshrc');
658
+ } else if (shellEnv.includes('bash')) {
659
+ return path.join(homeDir, '.bashrc');
660
+ }
661
+ }
662
+
663
+ // Fall back to checking which profile exists
664
+ for (const profile of possibleProfiles) {
665
+ try {
666
+ await fs.access(profile);
667
+ return profile;
668
+ } catch {
669
+ // File doesn't exist, continue checking
670
+ }
671
+ }
672
+
673
+ // Default to .bashrc for most systems
674
+ return path.join(homeDir, '.bashrc');
675
+ }
676
+
677
+ /**
678
+ * Get detected path for a specific tool
679
+ */
680
+ getDetectedPath(toolName) {
681
+ return this.detectedPaths[toolName] || null;
682
+ }
683
+
684
+ /**
685
+ * Get status report of detected paths
686
+ */
687
+ getPathStatusReport() {
688
+ const report = {
689
+ platform: this.platform,
690
+ npmGlobalPaths: this.npmGlobalPaths,
691
+ detectedPaths: this.detectedPaths,
692
+ summary: {
693
+ total: Object.keys(this.cliNameMap).length,
694
+ found: Object.values(this.detectedPaths).filter(Boolean).length,
695
+ missing: Object.values(this.detectedPaths).filter(v => !v).length
696
+ }
697
+ };
698
+
699
+ return report;
700
+ }
701
+ }
702
+
710
703
  module.exports = CLIPathDetector;