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.
- package/README.md +39 -3
- package/STIGMERGY.md +3 -0
- package/config/builtin-skills.json +43 -0
- package/config/enhanced-cli-config.json +438 -0
- package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
- package/docs/DESIGN_CLI_HELP_ANALYZER_REFACTOR.md +726 -0
- package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
- package/docs/IMPLEMENTATION_CHECKLIST_CLI_HELP_ANALYZER_REFACTOR.md +1268 -0
- package/docs/INSTALLER_ARCHITECTURE.md +257 -0
- package/docs/LESSONS_LEARNED.md +252 -0
- package/docs/SPECS_CLI_HELP_ANALYZER_REFACTOR.md +287 -0
- package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
- package/docs/correct-skillsio-implementation.md +368 -0
- package/docs/development_guidelines.md +276 -0
- package/docs/independent-resume-implementation.md +198 -0
- package/docs/resumesession-final-implementation.md +195 -0
- package/docs/resumesession-usage.md +87 -0
- package/package.json +146 -136
- package/scripts/analyze-router.js +168 -0
- package/scripts/run-comprehensive-tests.js +230 -0
- package/scripts/run-quick-tests.js +90 -0
- package/scripts/test-runner.js +344 -0
- package/skills/resumesession/INDEPENDENT_SKILL.md +403 -0
- package/skills/resumesession/README.md +381 -0
- package/skills/resumesession/SKILL.md +211 -0
- package/skills/resumesession/__init__.py +33 -0
- package/skills/resumesession/implementations/simple-resume.js +13 -0
- package/skills/resumesession/independent-resume.js +750 -0
- package/skills/resumesession/package.json +1 -0
- package/skills/resumesession/skill.json +1 -0
- package/src/adapters/claude/install_claude_integration.js +9 -1
- package/src/adapters/codebuddy/install_codebuddy_integration.js +3 -1
- package/src/adapters/codex/install_codex_integration.js +15 -5
- package/src/adapters/gemini/install_gemini_integration.js +3 -1
- package/src/adapters/qwen/install_qwen_integration.js +3 -1
- package/src/cli/commands/autoinstall.js +65 -0
- package/src/cli/commands/errors.js +190 -0
- package/src/cli/commands/independent-resume.js +395 -0
- package/src/cli/commands/install.js +179 -0
- package/src/cli/commands/permissions.js +108 -0
- package/src/cli/commands/project.js +485 -0
- package/src/cli/commands/scan.js +97 -0
- package/src/cli/commands/simple-resume.js +377 -0
- package/src/cli/commands/skills.js +158 -0
- package/src/cli/commands/status.js +113 -0
- package/src/cli/commands/stigmergy-resume.js +775 -0
- package/src/cli/commands/system.js +301 -0
- package/src/cli/commands/universal-resume.js +394 -0
- package/src/cli/router-beta.js +471 -0
- package/src/cli/utils/environment.js +75 -0
- package/src/cli/utils/formatters.js +47 -0
- package/src/cli/utils/skills_cache.js +92 -0
- package/src/core/cache_cleaner.js +1 -0
- package/src/core/cli_adapters.js +345 -0
- package/src/core/cli_help_analyzer.js +1236 -680
- package/src/core/cli_path_detector.js +702 -709
- package/src/core/cli_tools.js +515 -160
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +18 -0
- package/src/core/coordination/nodejs/HookDeploymentManager.js +242 -412
- package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
- package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
- package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +932 -0
- package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1395 -0
- package/src/core/coordination/nodejs/generators/index.js +12 -0
- package/src/core/enhanced_cli_installer.js +1208 -608
- package/src/core/enhanced_cli_parameter_handler.js +402 -0
- package/src/core/execution_mode_detector.js +222 -0
- package/src/core/installer.js +151 -106
- package/src/core/local_skill_scanner.js +732 -0
- package/src/core/multilingual/language-pattern-manager.js +1 -1
- package/src/core/skills/BuiltinSkillsDeployer.js +188 -0
- package/src/core/skills/StigmergySkillManager.js +123 -16
- package/src/core/skills/embedded-openskills/SkillParser.js +7 -3
- package/src/core/smart_router.js +550 -261
- package/src/index.js +10 -4
- package/src/utils.js +66 -7
- package/test/cli-integration.test.js +304 -0
- package/test/direct_smart_router_test.js +88 -0
- package/test/enhanced-cli-agent-skill-test.js +485 -0
- package/test/simple_test.js +82 -0
- package/test/smart_router_test_runner.js +123 -0
- package/test/smart_routing_edge_cases.test.js +284 -0
- package/test/smart_routing_simple_verification.js +139 -0
- package/test/smart_routing_verification.test.js +346 -0
- package/test/specific-cli-agent-skill-analysis.js +385 -0
- package/test/unit/smart_router.test.js +295 -0
- package/test/very_simple_test.js +54 -0
- package/src/cli/router.js +0 -1783
|
@@ -1,609 +1,1209 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Optimized Enhanced CLI Installer with Batch Permission Handling
|
|
3
|
-
*
|
|
4
|
-
* This version optimizes permission handling by:
|
|
5
|
-
* 1. Initial permission setup once at the beginning of the process
|
|
6
|
-
* 2. Streamlined tool installation without per-tool permission checks
|
|
7
|
-
* 3. Automatic permission escalation only when needed
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const fs = require('fs').promises;
|
|
12
|
-
const { spawn, spawnSync } = require('child_process');
|
|
13
|
-
const os = require('os');
|
|
14
|
-
const chalk = require('chalk');
|
|
15
|
-
|
|
16
|
-
class EnhancedCLIInstaller {
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.options = {
|
|
19
|
-
verbose: options.verbose || false,
|
|
20
|
-
skipPermissionCheck: options.skipPermissionCheck || false,
|
|
21
|
-
autoRetry: options.autoRetry !== false,
|
|
22
|
-
maxRetries: options.maxRetries || 3,
|
|
23
|
-
timeout: options.timeout || 300000, // 5 minutes
|
|
24
|
-
...options
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
this.log('
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Optimized Enhanced CLI Installer with Batch Permission Handling
|
|
3
|
+
*
|
|
4
|
+
* This version optimizes permission handling by:
|
|
5
|
+
* 1. Initial permission setup once at the beginning of the process
|
|
6
|
+
* 2. Streamlined tool installation without per-tool permission checks
|
|
7
|
+
* 3. Automatic permission escalation only when needed
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs').promises;
|
|
12
|
+
const { spawn, spawnSync } = require('child_process');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const chalk = require('chalk');
|
|
15
|
+
|
|
16
|
+
class EnhancedCLIInstaller {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.options = {
|
|
19
|
+
verbose: options.verbose || false,
|
|
20
|
+
skipPermissionCheck: options.skipPermissionCheck || false,
|
|
21
|
+
autoRetry: options.autoRetry !== false,
|
|
22
|
+
maxRetries: options.maxRetries || 3,
|
|
23
|
+
timeout: options.timeout || 300000, // 5 minutes
|
|
24
|
+
...options
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Expose commonly used options as properties for easier access
|
|
28
|
+
this.verbose = this.options.verbose;
|
|
29
|
+
this.force = options.force || false;
|
|
30
|
+
this.homeDir = options.homeDir || require('os').homedir();
|
|
31
|
+
this.results = {
|
|
32
|
+
permissionSetup: null,
|
|
33
|
+
installations: {},
|
|
34
|
+
failedInstallations: [],
|
|
35
|
+
npmConfigured: false,
|
|
36
|
+
workingDirectory: null,
|
|
37
|
+
permissionMode: 'standard', // 'standard', 'elevated', 'failed'
|
|
38
|
+
errors: []
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
this.cliTools = require('./cli_tools').CLI_TOOLS;
|
|
42
|
+
this.permissionConfigured = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Log messages with color and formatting
|
|
47
|
+
*/
|
|
48
|
+
log(level, message, data = null) {
|
|
49
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
50
|
+
let prefix = '';
|
|
51
|
+
let color = chalk.white;
|
|
52
|
+
|
|
53
|
+
switch (level) {
|
|
54
|
+
case 'info':
|
|
55
|
+
prefix = `[INFO] ${timestamp}`;
|
|
56
|
+
color = chalk.blue;
|
|
57
|
+
break;
|
|
58
|
+
case 'success':
|
|
59
|
+
prefix = `[SUCCESS] ${timestamp}`;
|
|
60
|
+
color = chalk.green;
|
|
61
|
+
break;
|
|
62
|
+
case 'warn':
|
|
63
|
+
prefix = `[WARN] ${timestamp}`;
|
|
64
|
+
color = chalk.yellow;
|
|
65
|
+
break;
|
|
66
|
+
case 'error':
|
|
67
|
+
prefix = `[ERROR] ${timestamp}`;
|
|
68
|
+
color = chalk.red;
|
|
69
|
+
break;
|
|
70
|
+
case 'debug':
|
|
71
|
+
prefix = `[DEBUG] ${timestamp}`;
|
|
72
|
+
color = chalk.gray;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const logMessage = color(`${prefix} ${message}`);
|
|
77
|
+
if (level === 'error') {
|
|
78
|
+
console.error(logMessage);
|
|
79
|
+
} else {
|
|
80
|
+
console.log(logMessage);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (data && this.options.verbose) {
|
|
84
|
+
console.log(' Data:', JSON.stringify(data, null, 2));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* One-time permission setup at the beginning of the process
|
|
90
|
+
*/
|
|
91
|
+
async setupPermissions() {
|
|
92
|
+
if (this.permissionConfigured) {
|
|
93
|
+
return { success: true, mode: this.permissionMode };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.log('info', 'Setting up permissions for CLI tool installation...');
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Check if we're already in an elevated context
|
|
100
|
+
const isElevated = await this.checkElevatedContext();
|
|
101
|
+
|
|
102
|
+
if (isElevated) {
|
|
103
|
+
this.log('info', 'Already running with elevated permissions');
|
|
104
|
+
this.permissionMode = 'elevated';
|
|
105
|
+
this.permissionConfigured = true;
|
|
106
|
+
return { success: true, mode: 'elevated' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if we're in a container environment (common indicators)
|
|
110
|
+
const inContainer = await this.checkContainerEnvironment();
|
|
111
|
+
if (inContainer) {
|
|
112
|
+
this.log('info', 'Detected container environment, using user-space installation');
|
|
113
|
+
this.permissionMode = 'user-space';
|
|
114
|
+
this.permissionConfigured = true;
|
|
115
|
+
return { success: true, mode: 'user-space' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Attempt standard installation first
|
|
119
|
+
const testResult = await this.attemptTestInstallation();
|
|
120
|
+
|
|
121
|
+
if (testResult.success) {
|
|
122
|
+
this.log('success', 'Standard permissions are sufficient');
|
|
123
|
+
this.permissionMode = 'standard';
|
|
124
|
+
this.permissionConfigured = true;
|
|
125
|
+
return { success: true, mode: 'standard' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If standard installation fails, check if it's a permission issue
|
|
129
|
+
if (this.isPermissionError(testResult.error)) {
|
|
130
|
+
this.log('warn', 'Permission issue detected, setting up elevated context...');
|
|
131
|
+
|
|
132
|
+
const elevatedSetup = await this.setupElevatedContext();
|
|
133
|
+
if (elevatedSetup.success) {
|
|
134
|
+
this.log('success', 'Elevated permissions configured');
|
|
135
|
+
this.permissionMode = 'elevated';
|
|
136
|
+
this.permissionConfigured = true;
|
|
137
|
+
return { success: true, mode: 'elevated' };
|
|
138
|
+
} else {
|
|
139
|
+
this.log('error', 'Failed to set up elevated permissions');
|
|
140
|
+
this.log('info', 'Falling back to user-space installation');
|
|
141
|
+
this.permissionMode = 'user-space';
|
|
142
|
+
this.permissionConfigured = true;
|
|
143
|
+
return { success: true, mode: 'user-space' };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Other types of errors
|
|
148
|
+
this.log('error', `Non-permission error during setup: ${testResult.error}`);
|
|
149
|
+
this.permissionMode = 'failed';
|
|
150
|
+
this.permissionConfigured = true;
|
|
151
|
+
return { success: false, mode: 'failed', error: testResult.error };
|
|
152
|
+
|
|
153
|
+
} catch (error) {
|
|
154
|
+
this.log('error', `Permission setup failed: ${error.message}`);
|
|
155
|
+
this.log('info', 'Falling back to user-space installation');
|
|
156
|
+
this.permissionMode = 'user-space';
|
|
157
|
+
this.permissionConfigured = true;
|
|
158
|
+
return { success: true, mode: 'user-space', error: error.message };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if we're already running in an elevated context
|
|
164
|
+
*/
|
|
165
|
+
async checkElevatedContext() {
|
|
166
|
+
const platform = process.platform;
|
|
167
|
+
|
|
168
|
+
if (platform === 'win32') {
|
|
169
|
+
// Windows: Check if running as administrator
|
|
170
|
+
try {
|
|
171
|
+
const { execSync } = require('child_process');
|
|
172
|
+
const result = execSync('net session', { encoding: 'utf8' });
|
|
173
|
+
return result.includes('Administrator');
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
// Unix-like systems: Check if we have root privileges
|
|
179
|
+
return process.getuid && process.getuid() === 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if we're running in a container environment
|
|
185
|
+
*/
|
|
186
|
+
async checkContainerEnvironment() {
|
|
187
|
+
try {
|
|
188
|
+
// Check for container indicators
|
|
189
|
+
const fs = require('fs');
|
|
190
|
+
|
|
191
|
+
// Check for .dockerenv file
|
|
192
|
+
if (fs.existsSync('/.dockerenv')) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check for container environment variables
|
|
197
|
+
if (process.env.container || process.env.DOCKER_CONTAINER) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check cgroup for container indicators
|
|
202
|
+
try {
|
|
203
|
+
if (fs.existsSync('/proc/1/cgroup')) {
|
|
204
|
+
const cgroupContent = fs.readFileSync('/proc/1/cgroup', 'utf8');
|
|
205
|
+
if (cgroupContent.includes('docker') || cgroupContent.includes('containerd')) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {
|
|
210
|
+
// Ignore errors reading cgroup
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check for container-specific files
|
|
214
|
+
const containerIndicators = [
|
|
215
|
+
'/run/.containerenv', // Podman/Docker
|
|
216
|
+
'/sys/fs/cgroup/cpu/cpu.cfs_quota_us', // Common in containers
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
for (const indicator of containerIndicators) {
|
|
220
|
+
try {
|
|
221
|
+
if (fs.existsSync(indicator)) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
} catch (e) {
|
|
225
|
+
// Ignore errors
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return false;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
// If we can't determine, assume not in container
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Attempt a test installation to check permissions
|
|
238
|
+
*/
|
|
239
|
+
async attemptTestInstallation() {
|
|
240
|
+
try {
|
|
241
|
+
// Try a simple npm command to test permissions
|
|
242
|
+
const result = spawnSync('npm', ['config', 'get', 'prefix'], {
|
|
243
|
+
stdio: 'pipe',
|
|
244
|
+
shell: true,
|
|
245
|
+
encoding: 'utf8',
|
|
246
|
+
timeout: 10000
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (result.status === 0) {
|
|
250
|
+
return { success: true, error: null };
|
|
251
|
+
} else {
|
|
252
|
+
return { success: false, error: result.stderr || result.stdout };
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return { success: false, error: error.message };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Setup elevated context based on platform
|
|
261
|
+
*/
|
|
262
|
+
async setupElevatedContext() {
|
|
263
|
+
const platform = process.platform;
|
|
264
|
+
|
|
265
|
+
if (platform === 'win32') {
|
|
266
|
+
return this.setupWindowsElevatedContext();
|
|
267
|
+
} else {
|
|
268
|
+
return this.setupUnixElevatedContext();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Setup Windows elevated context
|
|
274
|
+
*/
|
|
275
|
+
async setupWindowsElevatedContext() {
|
|
276
|
+
this.log('info', 'Windows: Preparing for elevated installation...');
|
|
277
|
+
|
|
278
|
+
// On Windows, we'll handle elevation per-installation
|
|
279
|
+
// since we can't maintain elevated state across commands
|
|
280
|
+
return {
|
|
281
|
+
success: true,
|
|
282
|
+
platform: 'windows',
|
|
283
|
+
note: 'Will elevate per installation as needed'
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Setup Unix elevated context
|
|
289
|
+
*/
|
|
290
|
+
async setupUnixElevatedContext() {
|
|
291
|
+
this.log('info', 'Unix: Checking privilege escalation methods...');
|
|
292
|
+
|
|
293
|
+
// List of privilege escalation tools to check (in order of preference)
|
|
294
|
+
const privilegeEscalationTools = [
|
|
295
|
+
{ name: 'sudo', testCmd: 'sudo', testArgs: ['-n', 'true'] },
|
|
296
|
+
{ name: 'doas', testCmd: 'doas', testArgs: ['-n', 'true'] },
|
|
297
|
+
{ name: 'run0', testCmd: 'run0', testArgs: ['-n', 'true'] },
|
|
298
|
+
{ name: 'pkexec', testCmd: 'pkexec', testArgs: ['--help'] },
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
let availableTool = null;
|
|
302
|
+
let requiresPassword = false;
|
|
303
|
+
|
|
304
|
+
for (const tool of privilegeEscalationTools) {
|
|
305
|
+
try {
|
|
306
|
+
const result = spawnSync(tool.testCmd, tool.testArgs, {
|
|
307
|
+
stdio: 'pipe',
|
|
308
|
+
timeout: 5000
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Tool exists
|
|
312
|
+
if (result.status === 0 || (result.status !== null && result.error?.code !== 'ENOENT')) {
|
|
313
|
+
// Check if it's passwordless
|
|
314
|
+
if (result.status === 0) {
|
|
315
|
+
availableTool = tool.name;
|
|
316
|
+
requiresPassword = false;
|
|
317
|
+
this.log('success', `Found ${tool.name} (passwordless)`);
|
|
318
|
+
break;
|
|
319
|
+
} else if (result.error?.code !== 'ENOENT') {
|
|
320
|
+
// Tool exists but requires password
|
|
321
|
+
availableTool = tool.name;
|
|
322
|
+
requiresPassword = true;
|
|
323
|
+
this.log('info', `Found ${tool.name} (requires password)`);
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
// Tool doesn't exist, continue to next
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (availableTool) {
|
|
334
|
+
return {
|
|
335
|
+
success: true,
|
|
336
|
+
platform: 'unix',
|
|
337
|
+
privilegeTool: availableTool,
|
|
338
|
+
requiresPassword: requiresPassword,
|
|
339
|
+
note: requiresPassword ? 'Will prompt for password per installation' : 'Passwordless access available'
|
|
340
|
+
};
|
|
341
|
+
} else {
|
|
342
|
+
// No privilege escalation tool found
|
|
343
|
+
this.log('warn', 'No privilege escalation tool found (sudo, doas, run0, pkexec)');
|
|
344
|
+
this.log('info', 'Will attempt user-space installation without privileges');
|
|
345
|
+
return {
|
|
346
|
+
success: true,
|
|
347
|
+
platform: 'unix',
|
|
348
|
+
privilegeTool: null,
|
|
349
|
+
requiresPassword: false,
|
|
350
|
+
userSpaceOnly: true,
|
|
351
|
+
note: 'Installation will be performed in user directory without privileges'
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Install a tool using the pre-configured permission mode
|
|
358
|
+
*/
|
|
359
|
+
async installTool(toolName, toolInfo, retryCount = 0) {
|
|
360
|
+
// Check if install command exists
|
|
361
|
+
if (!toolInfo.install) {
|
|
362
|
+
this.log('warn', `Tool ${toolName} has no install command, skipping...`);
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Ensure permissions are configured first
|
|
367
|
+
if (!this.permissionConfigured) {
|
|
368
|
+
await this.setupPermissions();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Check if we're in a container environment and force user-space mode if needed
|
|
372
|
+
if (this.permissionMode !== 'user-space') {
|
|
373
|
+
const inContainer = await this.checkContainerEnvironment();
|
|
374
|
+
if (inContainer) {
|
|
375
|
+
this.log('info', 'Detected container environment, switching to user-space installation');
|
|
376
|
+
this.permissionMode = 'user-space';
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this.log('info', `Installing ${toolInfo.name} (${toolName})...`);
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const installResult = await this.executeInstallation(toolInfo);
|
|
384
|
+
|
|
385
|
+
if (installResult.success) {
|
|
386
|
+
this.log('success', `Successfully installed ${toolInfo.name}`);
|
|
387
|
+
this.results.installations[toolName] = {
|
|
388
|
+
success: true,
|
|
389
|
+
tool: toolInfo.name,
|
|
390
|
+
command: toolInfo.install,
|
|
391
|
+
duration: Date.now() - (this.results.installations[toolName]?.startTime || Date.now()),
|
|
392
|
+
permissionMode: this.permissionMode,
|
|
393
|
+
permissionHandled: this.permissionMode === 'elevated'
|
|
394
|
+
};
|
|
395
|
+
return true;
|
|
396
|
+
} else {
|
|
397
|
+
throw new Error(installResult.error);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
} catch (error) {
|
|
401
|
+
this.log('error', `Failed to install ${toolInfo.name}: ${error.message}`);
|
|
402
|
+
|
|
403
|
+
this.results.installations[toolName] = {
|
|
404
|
+
success: false,
|
|
405
|
+
tool: toolInfo.name,
|
|
406
|
+
command: toolInfo.install,
|
|
407
|
+
error: error.message,
|
|
408
|
+
retryCount: retryCount,
|
|
409
|
+
permissionMode: this.permissionMode
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
this.results.failedInstallations.push(toolName);
|
|
413
|
+
|
|
414
|
+
// Retry logic for non-permission errors
|
|
415
|
+
if (this.options.autoRetry && retryCount < this.options.maxRetries) {
|
|
416
|
+
this.log('warn', `Retrying installation of ${toolInfo.name} (${retryCount + 1}/${this.options.maxRetries})...`);
|
|
417
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
418
|
+
return await this.installTool(toolName, toolInfo, retryCount + 1);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Execute installation based on pre-configured permission mode
|
|
427
|
+
*/
|
|
428
|
+
async executeInstallation(toolInfo) {
|
|
429
|
+
switch (this.permissionMode) {
|
|
430
|
+
case 'standard':
|
|
431
|
+
return await this.executeStandardInstallation(toolInfo);
|
|
432
|
+
case 'elevated':
|
|
433
|
+
return await this.executeElevatedInstallation(toolInfo);
|
|
434
|
+
case 'user-space':
|
|
435
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
436
|
+
case 'failed':
|
|
437
|
+
return await this.executeFallbackInstallation(toolInfo);
|
|
438
|
+
default:
|
|
439
|
+
// Try standard first, then escalate if needed
|
|
440
|
+
const standardResult = await this.executeStandardInstallation(toolInfo);
|
|
441
|
+
if (standardResult.success) {
|
|
442
|
+
return standardResult;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (this.isPermissionError(standardResult.error)) {
|
|
446
|
+
this.log('warn', `Permission error, escalating to elevated installation...`);
|
|
447
|
+
this.permissionMode = 'elevated';
|
|
448
|
+
return await this.executeElevatedInstallation(toolInfo);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return standardResult;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Execute standard installation without permission elevation
|
|
457
|
+
*/
|
|
458
|
+
async executeStandardInstallation(toolInfo) {
|
|
459
|
+
try {
|
|
460
|
+
// Check if install command exists
|
|
461
|
+
if (!toolInfo.install) {
|
|
462
|
+
return {
|
|
463
|
+
success: false,
|
|
464
|
+
error: `No install command specified for ${toolInfo.name || 'unknown tool'}`
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if tool requires bun runtime
|
|
469
|
+
if (toolInfo.requiresBun) {
|
|
470
|
+
const bunAvailable = await this.checkBunAvailable();
|
|
471
|
+
if (!bunAvailable) {
|
|
472
|
+
this.log('warn', `${toolInfo.name} requires bun, but bun is not installed`);
|
|
473
|
+
this.log('info', `Installing bun first...`);
|
|
474
|
+
|
|
475
|
+
const bunResult = await this.executeInstallationCommand('npm install bun -g');
|
|
476
|
+
|
|
477
|
+
if (!bunResult.success) {
|
|
478
|
+
return {
|
|
479
|
+
success: false,
|
|
480
|
+
error: `Failed to install bun (required for ${toolInfo.name}): ${bunResult.error}`
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
this.log('success', 'Bun installed successfully');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check if install command contains multiple steps (&&)
|
|
489
|
+
if (toolInfo.install.includes('&&')) {
|
|
490
|
+
return await this.executeMultiStepInstallation(toolInfo);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const [command, ...args] = toolInfo.install.split(' ');
|
|
494
|
+
|
|
495
|
+
this.log('debug', `Executing: ${toolInfo.install}`);
|
|
496
|
+
|
|
497
|
+
const result = spawnSync(command, args, {
|
|
498
|
+
stdio: this.options.verbose ? 'inherit' : 'pipe',
|
|
499
|
+
shell: true,
|
|
500
|
+
encoding: 'utf8',
|
|
501
|
+
timeout: this.options.timeout,
|
|
502
|
+
env: {
|
|
503
|
+
...process.env,
|
|
504
|
+
npm_config_prefix: process.env.npm_config_prefix,
|
|
505
|
+
npm_config_global: 'true',
|
|
506
|
+
npm_config_update: 'false',
|
|
507
|
+
npm_config_progress: 'false'
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
if (result.status === 0) {
|
|
512
|
+
return { success: true, error: null };
|
|
513
|
+
} else {
|
|
514
|
+
const errorMessage = result.stderr || result.stdout || `Exit code ${result.status}`;
|
|
515
|
+
|
|
516
|
+
// Check if this is a permission error and switch to user-space if needed
|
|
517
|
+
if (this.isPermissionError(errorMessage)) {
|
|
518
|
+
this.log('warn', `Standard installation failed due to permission error, switching to user-space installation...`);
|
|
519
|
+
this.permissionMode = 'user-space';
|
|
520
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return { success: false, error: errorMessage };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
} catch (error) {
|
|
527
|
+
// Check if this is a permission error and switch to user-space if needed
|
|
528
|
+
if (this.isPermissionError(error.message)) {
|
|
529
|
+
this.log('warn', `Standard installation failed due to permission error, switching to user-space installation...`);
|
|
530
|
+
this.permissionMode = 'user-space';
|
|
531
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return { success: false, error: error.message };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Execute installation with permission elevation
|
|
540
|
+
*/
|
|
541
|
+
async executeElevatedInstallation(toolInfo) {
|
|
542
|
+
const platform = process.platform;
|
|
543
|
+
|
|
544
|
+
if (platform === 'win32') {
|
|
545
|
+
return await this.executeWindowsElevatedInstallation(toolInfo);
|
|
546
|
+
} else {
|
|
547
|
+
return await this.executeUnixElevatedInstallation(toolInfo);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Execute Windows elevated installation
|
|
553
|
+
*/
|
|
554
|
+
async executeWindowsElevatedInstallation(toolInfo) {
|
|
555
|
+
const command = toolInfo.install;
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
this.log('info', `Creating Windows elevated installation for: ${toolInfo.name}`);
|
|
559
|
+
|
|
560
|
+
const scriptPath = path.join(os.tmpdir(), `stigmergy-install-${Date.now()}.ps1`);
|
|
561
|
+
const scriptContent = `
|
|
562
|
+
Write-Host "以管理员权限安装: ${toolInfo.name}" -ForegroundColor Yellow
|
|
563
|
+
try {
|
|
564
|
+
${command}
|
|
565
|
+
if ($LASTEXITCODE -eq 0) {
|
|
566
|
+
Write-Host "安装成功: ${toolInfo.name}" -ForegroundColor Green
|
|
567
|
+
exit 0
|
|
568
|
+
} else {
|
|
569
|
+
Write-Host "安装失败: ${toolInfo.name}" -ForegroundColor Red
|
|
570
|
+
exit $LASTEXITCODE
|
|
571
|
+
}
|
|
572
|
+
} catch {
|
|
573
|
+
Write-Host "安装异常: ${toolInfo.name}" -ForegroundColor Red
|
|
574
|
+
Write-Host $\_.Exception.Message -ForegroundColor Red
|
|
575
|
+
exit 1
|
|
576
|
+
}
|
|
577
|
+
`;
|
|
578
|
+
|
|
579
|
+
require('fs').writeFileSync(scriptPath, scriptContent, 'utf8');
|
|
580
|
+
|
|
581
|
+
const result = spawnSync('powershell', [
|
|
582
|
+
'-Command', `Start-Process PowerShell -Verb RunAs -ArgumentList "-File '${scriptPath}'" -Wait`
|
|
583
|
+
], {
|
|
584
|
+
stdio: 'pipe',
|
|
585
|
+
timeout: this.options.timeout * 2,
|
|
586
|
+
encoding: 'utf8'
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
require('fs').unlinkSync(scriptPath);
|
|
591
|
+
} catch (cleanupError) {
|
|
592
|
+
this.log('warn', `Could not clean up temp script: ${cleanupError.message}`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (result.status === 0) {
|
|
596
|
+
return {
|
|
597
|
+
success: true,
|
|
598
|
+
error: null
|
|
599
|
+
};
|
|
600
|
+
} else {
|
|
601
|
+
// If elevated installation failed, try user-space installation
|
|
602
|
+
this.log('warn', `Elevated installation failed, trying user-space installation...`);
|
|
603
|
+
this.permissionMode = 'user-space';
|
|
604
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
605
|
+
}
|
|
606
|
+
} catch (error) {
|
|
607
|
+
// If elevated installation failed, try user-space installation
|
|
608
|
+
this.log('warn', `Elevated installation failed (${error.message}), trying user-space installation...`);
|
|
609
|
+
this.permissionMode = 'user-space';
|
|
610
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Execute Unix elevated installation
|
|
616
|
+
*/
|
|
617
|
+
async executeUnixElevatedInstallation(toolInfo) {
|
|
618
|
+
// Use the detected privilege escalation tool
|
|
619
|
+
const privilegeSetup = await this.setupUnixElevatedContext();
|
|
620
|
+
|
|
621
|
+
if (!privilegeSetup.success) {
|
|
622
|
+
this.log('warn', 'No privilege escalation tool available, using user-space installation...');
|
|
623
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// If no privilege escalation tool is available, use user-space installation
|
|
627
|
+
if (privilegeSetup.userSpaceOnly) {
|
|
628
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Use the detected privilege escalation tool
|
|
632
|
+
const privilegeTool = privilegeSetup.privilegeTool || 'sudo';
|
|
633
|
+
const command = `${privilegeTool} ${toolInfo.install}`;
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
this.log('info', `Using ${privilegeTool} for elevated installation of: ${toolInfo.name}`);
|
|
637
|
+
|
|
638
|
+
const result = spawnSync('bash', ['-c', command], {
|
|
639
|
+
stdio: this.options.verbose ? 'inherit' : 'pipe',
|
|
640
|
+
timeout: this.options.timeout * 2,
|
|
641
|
+
encoding: 'utf8'
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
if (result.status === 0) {
|
|
645
|
+
return { success: true, error: null };
|
|
646
|
+
} else {
|
|
647
|
+
const errorMessage = result.stderr || result.stdout || `Exit code ${result.status}`;
|
|
648
|
+
// If privilege escalation failed, try user-space installation as fallback
|
|
649
|
+
this.log('warn', `Privilege escalation failed, trying user-space installation...`);
|
|
650
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
651
|
+
}
|
|
652
|
+
} catch (error) {
|
|
653
|
+
this.log('warn', `Privilege escalation error: ${error.message}, trying user-space installation...`);
|
|
654
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Execute user-space installation (no privileges required)
|
|
660
|
+
*/
|
|
661
|
+
async executeUserSpaceInstallation(toolInfo) {
|
|
662
|
+
try {
|
|
663
|
+
// Install to user directory using --prefix flag
|
|
664
|
+
const os = require('os');
|
|
665
|
+
const path = require('path');
|
|
666
|
+
|
|
667
|
+
// Get user's npm global directory
|
|
668
|
+
let userNpmDir = process.env.NPM_CONFIG_PREFIX ||
|
|
669
|
+
path.join(os.homedir(), '.npm-global');
|
|
670
|
+
|
|
671
|
+
// Ensure directory exists
|
|
672
|
+
const fs = require('fs');
|
|
673
|
+
if (!fs.existsSync(userNpmDir)) {
|
|
674
|
+
fs.mkdirSync(userNpmDir, { recursive: true, mode: 0o755 });
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Extract package name from install command
|
|
678
|
+
// Format: "npm install -g @package/tool" or "npm install -g tool"
|
|
679
|
+
const installMatch = toolInfo.install.match(/npm\s+(?:install|upgrade)\s+(?:-g\s+)?(.+)/);
|
|
680
|
+
if (!installMatch) {
|
|
681
|
+
throw new Error('Cannot parse install command');
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const packageName = installMatch[1].trim();
|
|
685
|
+
|
|
686
|
+
// Create user-space install command
|
|
687
|
+
const userCommand = `npm install -g --prefix "${userNpmDir}" ${packageName}`;
|
|
688
|
+
|
|
689
|
+
this.log('info', `Installing ${toolInfo.name} to user directory: ${userNpmDir}`);
|
|
690
|
+
this.log('info', `Command: ${userCommand}`);
|
|
691
|
+
|
|
692
|
+
// Set PATH to include user npm directory
|
|
693
|
+
const env = {
|
|
694
|
+
...process.env,
|
|
695
|
+
PATH: `${path.join(userNpmDir, 'bin')}:${process.env.PATH}`
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
const result = spawnSync('bash', ['-c', userCommand], {
|
|
699
|
+
stdio: this.options.verbose ? 'inherit' : 'pipe',
|
|
700
|
+
timeout: this.options.timeout * 3, // Longer timeout for user-space install
|
|
701
|
+
encoding: 'utf8',
|
|
702
|
+
env: env
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
if (result.status === 0) {
|
|
706
|
+
this.log('success', `Successfully installed ${toolInfo.name} to user directory`);
|
|
707
|
+
|
|
708
|
+
// Provide PATH setup instructions
|
|
709
|
+
const binDir = path.join(userNpmDir, 'bin');
|
|
710
|
+
this.log('info', '⚠️ Make sure to add the bin directory to your PATH:');
|
|
711
|
+
this.log('info', ` export PATH="${binDir}:$PATH"`);
|
|
712
|
+
|
|
713
|
+
// Add to shell config files automatically
|
|
714
|
+
await this.addPathToShellConfig(binDir);
|
|
715
|
+
|
|
716
|
+
return { success: true, error: null, userSpace: true, binDir };
|
|
717
|
+
} else {
|
|
718
|
+
const errorMessage = result.stderr || result.stdout || `Exit code ${result.status}`;
|
|
719
|
+
return { success: false, error: `User-space installation failed: ${errorMessage}` };
|
|
720
|
+
}
|
|
721
|
+
} catch (error) {
|
|
722
|
+
return { success: false, error: error.message };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Add PATH to shell configuration files
|
|
728
|
+
*/
|
|
729
|
+
async addPathToShellConfig(binDir) {
|
|
730
|
+
const os = require('os');
|
|
731
|
+
const path = require('path');
|
|
732
|
+
const fs = require('fs/promises');
|
|
733
|
+
|
|
734
|
+
const shellConfigs = [
|
|
735
|
+
{ file: path.join(os.homedir(), '.bashrc'), marker: '# Stigmergy CLI PATH' },
|
|
736
|
+
{ file: path.join(os.homedir(), '.zshrc'), marker: '# Stigmergy CLI PATH' },
|
|
737
|
+
{ file: path.join(os.homedir(), '.profile'), marker: '# Stigmergy CLI PATH' },
|
|
738
|
+
];
|
|
739
|
+
|
|
740
|
+
const pathLine = `export PATH="${binDir}:$PATH"\n`;
|
|
741
|
+
|
|
742
|
+
for (const config of shellConfigs) {
|
|
743
|
+
try {
|
|
744
|
+
let content = '';
|
|
745
|
+
try {
|
|
746
|
+
content = await fs.readFile(config.file, 'utf8');
|
|
747
|
+
} catch (err) {
|
|
748
|
+
// File doesn't exist, will create it
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Check if PATH is already configured
|
|
752
|
+
if (content.includes(config.marker)) {
|
|
753
|
+
continue; // Already configured
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Append PATH configuration
|
|
757
|
+
await fs.appendFile(config.file, `\n${config.marker}\n${pathLine}`);
|
|
758
|
+
this.log('info', `Added PATH to ${path.basename(config.file)}`);
|
|
759
|
+
} catch (error) {
|
|
760
|
+
// Ignore errors for config files
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Fallback installation method
|
|
767
|
+
*/
|
|
768
|
+
async executeFallbackInstallation(toolInfo) {
|
|
769
|
+
this.log('warn', 'Attempting fallback installation method...');
|
|
770
|
+
|
|
771
|
+
// Check if install command exists
|
|
772
|
+
if (!toolInfo.install) {
|
|
773
|
+
return {
|
|
774
|
+
success: false,
|
|
775
|
+
error: `No install command specified for ${toolInfo.name || 'unknown tool'}`
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Try without some npm flags that might cause permission issues
|
|
780
|
+
const [command, ...args] = toolInfo.install.split(' ');
|
|
781
|
+
const fallbackArgs = args.filter(arg => !arg.startsWith('--'));
|
|
782
|
+
|
|
783
|
+
try {
|
|
784
|
+
const result = spawnSync(command, fallbackArgs, {
|
|
785
|
+
stdio: 'inherit',
|
|
786
|
+
shell: true,
|
|
787
|
+
encoding: 'utf8',
|
|
788
|
+
timeout: this.options.timeout
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
if (result.status === 0) {
|
|
792
|
+
return { success: true, error: null };
|
|
793
|
+
} else {
|
|
794
|
+
const errorMessage = result.stderr || `Fallback failed with exit code ${result.status}`;
|
|
795
|
+
// If fallback failed due to permissions, try user-space installation
|
|
796
|
+
if (this.isPermissionError(errorMessage)) {
|
|
797
|
+
this.log('warn', `Fallback installation failed due to permission error, switching to user-space installation...`);
|
|
798
|
+
this.permissionMode = 'user-space';
|
|
799
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
800
|
+
}
|
|
801
|
+
return { success: false, error: errorMessage };
|
|
802
|
+
}
|
|
803
|
+
} catch (error) {
|
|
804
|
+
// If fallback failed due to permissions, try user-space installation
|
|
805
|
+
if (this.isPermissionError(error.message)) {
|
|
806
|
+
this.log('warn', `Fallback installation failed due to permission error, switching to user-space installation...`);
|
|
807
|
+
this.permissionMode = 'user-space';
|
|
808
|
+
return await this.executeUserSpaceInstallation(toolInfo);
|
|
809
|
+
}
|
|
810
|
+
return { success: false, error: error.message };
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Check if an error is related to permissions
|
|
816
|
+
*/
|
|
817
|
+
isPermissionError(errorMessage) {
|
|
818
|
+
if (!errorMessage || typeof errorMessage !== 'string') {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const permissionIndicators = [
|
|
823
|
+
'EACCES', 'EPERM', 'permission denied',
|
|
824
|
+
'access denied', 'unauthorized', 'EISDIR',
|
|
825
|
+
'operation not permitted', 'code EACCES',
|
|
826
|
+
'code EPERM', 'permission error', 'cannot create directory',
|
|
827
|
+
'write EACCES', 'mkdir', 'denied'
|
|
828
|
+
];
|
|
829
|
+
|
|
830
|
+
const lowerError = errorMessage.toLowerCase();
|
|
831
|
+
return permissionIndicators.some(indicator =>
|
|
832
|
+
lowerError.includes(indicator.toLowerCase())
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Install multiple CLI tools
|
|
838
|
+
*/
|
|
839
|
+
async installTools(toolNames, toolInfos) {
|
|
840
|
+
this.log('info', 'Starting batch installation of CLI tools...');
|
|
841
|
+
|
|
842
|
+
// One-time permission setup for the entire batch
|
|
843
|
+
const permissionSetup = await this.setupPermissions();
|
|
844
|
+
if (!permissionSetup.success && permissionSetup.mode !== 'elevated') {
|
|
845
|
+
this.log('warn', 'Permission setup failed, but proceeding with individual installations...');
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
let successCount = 0;
|
|
849
|
+
const totalCount = toolNames.length;
|
|
850
|
+
|
|
851
|
+
this.log('info', `Installing ${totalCount} CLI tools in ${this.permissionMode} mode...`);
|
|
852
|
+
|
|
853
|
+
for (const toolName of toolNames) {
|
|
854
|
+
const toolInfo = toolInfos[toolName];
|
|
855
|
+
if (!toolInfo) continue;
|
|
856
|
+
|
|
857
|
+
// Skip tools without install command (internal functions)
|
|
858
|
+
if (!toolInfo.install) {
|
|
859
|
+
this.log('debug', `Tool ${toolName} has no install command, skipping...`);
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
this.results.installations[toolName] = {
|
|
864
|
+
startTime: Date.now(),
|
|
865
|
+
...this.results.installations[toolName]
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const success = await this.installTool(toolName, toolInfo);
|
|
869
|
+
if (success) {
|
|
870
|
+
successCount++;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
this.log('info', `Batch installation completed: ${successCount}/${totalCount} successful`);
|
|
875
|
+
|
|
876
|
+
return {
|
|
877
|
+
success: successCount === totalCount,
|
|
878
|
+
total: totalCount,
|
|
879
|
+
successful: successCount,
|
|
880
|
+
failed: totalCount - successCount,
|
|
881
|
+
permissionMode: this.permissionMode,
|
|
882
|
+
results: this.results
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Upgrade CLI tools
|
|
888
|
+
*/
|
|
889
|
+
async upgradeTools(toolNames, toolInfos) {
|
|
890
|
+
this.log('info', 'Starting batch upgrade of CLI tools...');
|
|
891
|
+
|
|
892
|
+
// One-time permission setup for the entire batch
|
|
893
|
+
const permissionSetup = await this.setupPermissions();
|
|
894
|
+
if (!permissionSetup.success && permissionSetup.mode !== 'elevated') {
|
|
895
|
+
this.log('warn', 'Permission setup failed, but proceeding with individual upgrades...');
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
let successCount = 0;
|
|
899
|
+
const totalCount = toolNames.length;
|
|
900
|
+
|
|
901
|
+
this.log('info', `Upgrading ${totalCount} CLI tools in ${this.permissionMode} mode...`);
|
|
902
|
+
|
|
903
|
+
for (const toolName of toolNames) {
|
|
904
|
+
const originalInfo = toolInfos[toolName];
|
|
905
|
+
if (!originalInfo) {
|
|
906
|
+
this.log('warn', `Tool ${toolName} not found in toolInfos, skipping...`);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Skip tools without install command (internal functions)
|
|
911
|
+
if (!originalInfo.install) {
|
|
912
|
+
this.log('debug', `Tool ${toolName} has no install command, skipping upgrade...`);
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Determine the appropriate upgrade command based on permission mode
|
|
917
|
+
let upgradeCommand;
|
|
918
|
+
if (this.permissionMode === 'user-space') {
|
|
919
|
+
// For user-space installations, upgrade to user directory
|
|
920
|
+
const os = require('os');
|
|
921
|
+
const path = require('path');
|
|
922
|
+
let userNpmDir = process.env.NPM_CONFIG_PREFIX || path.join(os.homedir(), '.npm-global');
|
|
923
|
+
upgradeCommand = `npm install -g --prefix "${userNpmDir}" ${toolName}`;
|
|
924
|
+
} else {
|
|
925
|
+
upgradeCommand = `npm upgrade -g ${toolName}`;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const toolInfo = {
|
|
929
|
+
...originalInfo,
|
|
930
|
+
install: upgradeCommand,
|
|
931
|
+
name: `${originalInfo.name} (Upgrade)`
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
const success = await this.installTool(toolName, toolInfo);
|
|
935
|
+
if (success) {
|
|
936
|
+
successCount++;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
this.log('info', `Batch upgrade completed: ${successCount}/${totalCount} successful`);
|
|
941
|
+
|
|
942
|
+
return {
|
|
943
|
+
success: successCount === totalCount,
|
|
944
|
+
total: totalCount,
|
|
945
|
+
successful: successCount,
|
|
946
|
+
failed: totalCount - successCount,
|
|
947
|
+
permissionMode: this.permissionMode,
|
|
948
|
+
results: this.results
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Get installation results
|
|
954
|
+
*/
|
|
955
|
+
getResults() {
|
|
956
|
+
return this.results;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Detect permission availability for the current platform
|
|
961
|
+
*/
|
|
962
|
+
detectPermissionAvailability() {
|
|
963
|
+
const platform = process.platform;
|
|
964
|
+
|
|
965
|
+
if (platform === 'win32') {
|
|
966
|
+
// Windows: Check if running as administrator
|
|
967
|
+
try {
|
|
968
|
+
const { execSync } = require('child_process');
|
|
969
|
+
const result = execSync('net session', { encoding: 'utf8' });
|
|
970
|
+
return result.includes('Administrator');
|
|
971
|
+
} catch {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
// Unix-like systems: Check available privilege escalation tools
|
|
976
|
+
const privilegeEscalationTools = ['sudo', 'doas', 'run0', 'pkexec'];
|
|
977
|
+
for (const tool of privilegeEscalationTools) {
|
|
978
|
+
try {
|
|
979
|
+
const result = spawnSync(tool, ['--version'], {
|
|
980
|
+
stdio: 'pipe',
|
|
981
|
+
timeout: 5000
|
|
982
|
+
});
|
|
983
|
+
if (result.status !== null || result.error?.code !== 'ENOENT') {
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
986
|
+
} catch {
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Detect available privilege escalation tools
|
|
996
|
+
*/
|
|
997
|
+
detectPrivilegeTools() {
|
|
998
|
+
const platform = process.platform;
|
|
999
|
+
const availableTools = [];
|
|
1000
|
+
|
|
1001
|
+
if (platform === 'win32') {
|
|
1002
|
+
// Windows: Check for admin privileges
|
|
1003
|
+
try {
|
|
1004
|
+
const { execSync } = require('child_process');
|
|
1005
|
+
const result = execSync('net session', { encoding: 'utf8' });
|
|
1006
|
+
if (result.includes('Administrator')) {
|
|
1007
|
+
availableTools.push('admin');
|
|
1008
|
+
}
|
|
1009
|
+
} catch {
|
|
1010
|
+
// Not running as admin
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
// Unix-like systems: Check privilege escalation tools
|
|
1014
|
+
const tools = [
|
|
1015
|
+
{ name: 'sudo', checkCmd: ['-n', 'true'] },
|
|
1016
|
+
{ name: 'doas', checkCmd: ['-n', 'true'] },
|
|
1017
|
+
{ name: 'run0', checkCmd: ['-n', 'true'] },
|
|
1018
|
+
{ name: 'pkexec', checkCmd: ['--help'] }
|
|
1019
|
+
];
|
|
1020
|
+
|
|
1021
|
+
for (const tool of tools) {
|
|
1022
|
+
try {
|
|
1023
|
+
const result = spawnSync(tool.name, tool.checkCmd, {
|
|
1024
|
+
stdio: 'pipe',
|
|
1025
|
+
timeout: 5000
|
|
1026
|
+
});
|
|
1027
|
+
if (result.status !== null || result.error?.code !== 'ENOENT') {
|
|
1028
|
+
availableTools.push(tool.name);
|
|
1029
|
+
}
|
|
1030
|
+
} catch {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return availableTools;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Upgrade a single tool
|
|
1041
|
+
*/
|
|
1042
|
+
async upgradeTool(toolName, toolInfo) {
|
|
1043
|
+
this.log('info', `Upgrading ${toolName}...`);
|
|
1044
|
+
|
|
1045
|
+
if (!toolInfo || !toolInfo.install) {
|
|
1046
|
+
this.log('warn', `Tool ${toolName} has no install command, skipping upgrade...`);
|
|
1047
|
+
this.results.errors.push({
|
|
1048
|
+
tool: toolName,
|
|
1049
|
+
error: 'No install command specified',
|
|
1050
|
+
timestamp: new Date().toISOString()
|
|
1051
|
+
});
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Determine the appropriate upgrade command based on permission mode
|
|
1056
|
+
let upgradeCommand;
|
|
1057
|
+
if (this.permissionMode === 'user-space') {
|
|
1058
|
+
// For user-space installations, upgrade to user directory
|
|
1059
|
+
const os = require('os');
|
|
1060
|
+
const path = require('path');
|
|
1061
|
+
let userNpmDir = process.env.NPM_CONFIG_PREFIX || path.join(os.homedir(), '.npm-global');
|
|
1062
|
+
upgradeCommand = `npm install -g --prefix "${userNpmDir}" ${toolName}`;
|
|
1063
|
+
} else {
|
|
1064
|
+
upgradeCommand = `npm upgrade -g ${toolName}`;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
this.results.installations[toolName] = {
|
|
1068
|
+
startTime: Date.now(),
|
|
1069
|
+
...this.results.installations[toolName]
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
try {
|
|
1073
|
+
const success = await this.installTool(toolName, {
|
|
1074
|
+
...toolInfo,
|
|
1075
|
+
install: upgradeCommand,
|
|
1076
|
+
name: `${toolInfo.name} (Upgrade)`
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
return success;
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
this.log('error', `Failed to upgrade ${toolName}: ${error.message}`);
|
|
1082
|
+
this.results.errors.push({
|
|
1083
|
+
tool: toolName,
|
|
1084
|
+
error: error.message,
|
|
1085
|
+
timestamp: new Date().toISOString()
|
|
1086
|
+
});
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Get installation summary
|
|
1093
|
+
*/
|
|
1094
|
+
getInstallationSummary() {
|
|
1095
|
+
const installations = this.results.installations || {};
|
|
1096
|
+
const errors = this.results.errors || [];
|
|
1097
|
+
|
|
1098
|
+
const total = Object.keys(installations).length;
|
|
1099
|
+
const successful = Object.values(installations).filter(i => i.success !== false).length;
|
|
1100
|
+
const failed = total - successful;
|
|
1101
|
+
|
|
1102
|
+
return {
|
|
1103
|
+
total,
|
|
1104
|
+
successful,
|
|
1105
|
+
failed,
|
|
1106
|
+
permissionMode: this.permissionMode,
|
|
1107
|
+
npmConfigured: this.results.npmConfigured,
|
|
1108
|
+
workingDirectory: this.results.workingDirectory,
|
|
1109
|
+
errors: errors.map(e => ({
|
|
1110
|
+
tool: e.tool,
|
|
1111
|
+
error: e.error,
|
|
1112
|
+
timestamp: e.timestamp
|
|
1113
|
+
})),
|
|
1114
|
+
details: installations
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* Check if bun runtime is available
|
|
1120
|
+
* @returns {Promise<boolean>} True if bun is available
|
|
1121
|
+
*/
|
|
1122
|
+
async checkBunAvailable() {
|
|
1123
|
+
const { spawnSync } = require('child_process');
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
const result = spawnSync('bun', ['--version'], {
|
|
1127
|
+
stdio: 'pipe',
|
|
1128
|
+
shell: true
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
return result.status === 0 || result.error === undefined;
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Execute multi-step installation (commands with &&)
|
|
1139
|
+
* @param {Object} toolInfo - Tool information
|
|
1140
|
+
* @returns {Promise<Object>} Installation result
|
|
1141
|
+
*/
|
|
1142
|
+
async executeMultiStepInstallation(toolInfo) {
|
|
1143
|
+
const steps = toolInfo.install.split('&&').map(s => s.trim());
|
|
1144
|
+
|
|
1145
|
+
this.log('info', `Executing multi-step installation (${steps.length} steps) for ${toolInfo.name}...`);
|
|
1146
|
+
|
|
1147
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1148
|
+
const step = steps[i];
|
|
1149
|
+
const stepNumber = i + 1;
|
|
1150
|
+
|
|
1151
|
+
this.log('info', `Step ${stepNumber}/${steps.length}: ${step}`);
|
|
1152
|
+
|
|
1153
|
+
const result = await this.executeInstallationCommand(step);
|
|
1154
|
+
|
|
1155
|
+
if (!result.success) {
|
|
1156
|
+
this.log('error', `Step ${stepNumber} failed: ${result.error}`);
|
|
1157
|
+
return {
|
|
1158
|
+
success: false,
|
|
1159
|
+
error: `Installation failed at step ${stepNumber}: ${result.error}`,
|
|
1160
|
+
failedAtStep: stepNumber,
|
|
1161
|
+
failedCommand: step
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
this.log('success', `Step ${stepNumber}/${steps.length} completed`);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
this.log('success', `All ${steps.length} steps completed successfully for ${toolInfo.name}`);
|
|
1169
|
+
|
|
1170
|
+
return {
|
|
1171
|
+
success: true,
|
|
1172
|
+
stepsCompleted: steps.length
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Execute a single installation command
|
|
1178
|
+
* @param {string} command - Installation command
|
|
1179
|
+
* @returns {Promise<Object>} Execution result
|
|
1180
|
+
*/
|
|
1181
|
+
async executeInstallationCommand(command) {
|
|
1182
|
+
const { executeCommand } = require('../utils');
|
|
1183
|
+
|
|
1184
|
+
try {
|
|
1185
|
+
const result = await executeCommand(command, [], {
|
|
1186
|
+
stdio: this.verbose ? 'inherit' : 'pipe',
|
|
1187
|
+
shell: true,
|
|
1188
|
+
timeout: 300000 // 5 minutes
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
if (result.success) {
|
|
1192
|
+
return { success: true };
|
|
1193
|
+
} else {
|
|
1194
|
+
return {
|
|
1195
|
+
success: false,
|
|
1196
|
+
error: result.error || `Command exited with code ${result.code}`,
|
|
1197
|
+
code: result.code
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
return {
|
|
1202
|
+
success: false,
|
|
1203
|
+
error: error.message
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
609
1209
|
module.exports = EnhancedCLIInstaller;
|