2626use Symfony \Component \Finder \Finder ;
2727use Symfony \Component \Process \Process ;
2828
29+ use function Laravel \Prompts \confirm ;
2930use function Laravel \Prompts \intro ;
3031use function Laravel \Prompts \multiselect ;
3132use function Laravel \Prompts \note ;
32- use function Laravel \Prompts \select ;
3333
3434#[AsCommand('boost:install ' , 'Install Laravel Boost ' )]
3535class InstallCommand extends Command
@@ -134,7 +134,7 @@ protected function collectInstallationPreferences(): void
134134 $ this ->selectedAiGuidelines = $ this ->selectAiGuidelines ();
135135 $ this ->selectedTargetMcpClient = $ this ->selectTargetMcpClients ();
136136 $ this ->selectedTargetAgents = $ this ->selectTargetAgents ();
137- $ this ->enforceTests = $ this ->determineTestEnforcement (ask: false );
137+ $ this ->enforceTests = $ this ->determineTestEnforcement ();
138138 }
139139
140140 protected function performInstallation (): void
@@ -143,7 +143,7 @@ protected function performInstallation(): void
143143
144144 usleep (750000 );
145145
146- if (( $ this -> shouldInstallMcp () || $ this -> shouldInstallHerdMcp ()) && $ this ->selectedTargetMcpClient ->isNotEmpty ()) {
146+ if ($ this ->selectedTargetMcpClient ->isNotEmpty ()) {
147147 $ this ->installMcpServerConfig ();
148148 }
149149 }
@@ -179,9 +179,8 @@ protected function outro(): void
179179 $ boostFeatures = $ this ->selectedBoostFeatures ->map (fn ($ feature ): string => 'b: ' .$ feature )->toArray ();
180180
181181 $ guidelines = [];
182- if ($ this ->shouldInstallAiGuidelines ()) {
183- $ guidelines [] = 'g:ai ' ;
184- }
182+
183+ $ guidelines [] = 'g:ai ' ;
185184
186185 if ($ this ->shouldInstallStyleGuidelines ()) {
187186 $ guidelines [] = 'g:style ' ;
@@ -210,7 +209,7 @@ protected function hyperlink(string $label, string $url): string
210209 * won't have the CI setup to make use of them anyway, so we're just wasting their
211210 * tokens/money by enforcing them.
212211 */
213- protected function determineTestEnforcement (bool $ ask = true ): bool
212+ protected function determineTestEnforcement (): bool
214213 {
215214 $ hasMinimumTests = false ;
216215
@@ -226,14 +225,6 @@ protected function determineTestEnforcement(bool $ask = true): bool
226225 ->count () >= self ::MIN_TEST_COUNT ;
227226 }
228227
229- if (! $ hasMinimumTests && $ ask ) {
230- return select (
231- label: 'Should AI always create tests? ' ,
232- options: ['Yes ' , 'No ' ],
233- default: 'Yes '
234- ) === 'Yes ' ;
235- }
236-
237228 return $ hasMinimumTests ;
238229 }
239230
@@ -242,88 +233,61 @@ protected function determineTestEnforcement(bool $ask = true): bool
242233 */
243234 protected function selectBoostFeatures (): Collection
244235 {
245- $ defaultInstallOptions = [
236+ $ features = collect ( [
246237 'mcp_server ' ,
247- ... $ this -> config -> exists () === false || $ this -> config -> getAiGuidelines () !== [] ? [ 'ai_guidelines ' ] : [] ,
248- ];
238+ 'ai_guidelines ' ,
239+ ]) ;
249240
250- $ installOptions = [
251- 'mcp_server ' => 'Boost MCP Server (with 15+ tools) ' ,
252- 'ai_guidelines ' => 'Boost AI Guidelines (for Laravel, Inertia, and more) ' ,
253- ];
241+ if ($ this ->herd ->isMcpAvailable () === false ) {
242+ return $ features ;
243+ }
254244
255- if ($ this ->herd ->isMcpAvailable ()) {
256- $ installOptions ['herd_mcp ' ] = 'Herd MCP Server ' ;
245+ if (confirm (
246+ label: 'Would you like to install Herd MCP alongside Boost MCP? ' ,
247+ default: $ this ->config ->getHerdMcp (),
248+ hint: 'Herd MCP provides additional tools like browser logs, that can help AI understand issues better ' ,
249+ )) {
250+ $ features ->push ('herd_mcp ' );
257251 }
258252
259- return collect (multiselect (
260- label: 'What do you want to install? ' ,
261- options: $ installOptions ,
262- default: $ defaultInstallOptions ,
263- required: true ,
264- ));
253+ return $ features ;
265254 }
266255
267256 /**
268257 * @return Collection<int, string>
269258 */
270259 protected function selectAiGuidelines (): Collection
271260 {
272- if (! $ this ->shouldInstallAiGuidelines ()) {
273- return collect ();
274- }
275-
276- $ aiGuidelines = collect ($ this ->config ->getAiGuidelines ());
277-
278- $ options = app (GuidelineComposer::class)->guidelines ();
279- $ defaults = $ aiGuidelines ->isNotEmpty ()
280- ? $ aiGuidelines
281- : $ options ->reject (fn (array $ guideline ) => $ guideline ['third_party ' ])->keys ();
261+ $ options = app (GuidelineComposer::class)->guidelines ()
262+ ->reject (fn (array $ guideline ) => $ guideline ['third_party ' ] === false );
282263
283264 if ($ options ->isEmpty ()) {
284265 return collect ();
285266 }
286267
287268 return collect (multiselect (
288- label: 'Which AI guidelines do you want to install? ' ,
269+ label: 'Which Third Party AI Guidelines do you want to install? ' ,
289270 // @phpstan-ignore-next-line
290271 options: $ options ->mapWithKeys (function (array $ guideline , string $ name ) {
291272 $ humanName = str_replace ('/core ' , '' , $ name );
292273
293274 return [$ name => "{$ humanName } (~ {$ guideline ['tokens ' ]} tokens) {$ guideline ['description ' ]}" ];
294275 }),
295- default: $ defaults ,
276+ default: collect ( $ this -> config -> getGuidelines ()) ,
296277 scroll: 10 ,
297278 hint: 'You can add or remove them later by running this command again ' ,
298- required: true ,
299279 ));
300280 }
301281
302- /**
303- * @return array<int, string>
304- */
305- protected function boostToolsToDisable (): array
306- {
307- return multiselect (
308- label: 'Do you need to disable any Boost provided tools? ' ,
309- options: $ this ->discoverTools (),
310- scroll: 4 ,
311- hint: 'You can exclude or include them later in the config file ' ,
312- );
313- }
314-
315282 /**
316283 * @return Collection<int, CodeEnvironment>
317284 */
318285 protected function selectTargetMcpClients (): Collection
319286 {
320- if (! $ this ->shouldInstallMcp () && ! $ this ->shouldInstallHerdMcp ()) {
321- return collect ();
322- }
323-
324287 return $ this ->selectCodeEnvironments (
325288 McpClient::class,
326- sprintf ('Which code editors do you use to work on %s? ' , $ this ->projectName )
289+ sprintf ('Which code editors do you use to work on %s? ' , $ this ->projectName ),
290+ $ this ->config ->getEditors (),
327291 );
328292 }
329293
@@ -332,13 +296,10 @@ protected function selectTargetMcpClients(): Collection
332296 */
333297 protected function selectTargetAgents (): Collection
334298 {
335- if (! $ this ->shouldInstallAiGuidelines ()) {
336- return collect ();
337- }
338-
339299 return $ this ->selectCodeEnvironments (
340300 Agent::class,
341- sprintf ('Which agents need AI guidelines for %s? ' , $ this ->projectName )
301+ sprintf ('Which agents need AI guidelines for %s? ' , $ this ->projectName ),
302+ $ this ->config ->getAgents (),
342303 );
343304 }
344305
@@ -357,9 +318,10 @@ protected function getSelectionConfig(string $contractClass): array
357318 }
358319
359320 /**
321+ * @param array<int, string> $defaults
360322 * @return Collection<int, CodeEnvironment>
361323 */
362- protected function selectCodeEnvironments (string $ contractClass , string $ label ): Collection
324+ protected function selectCodeEnvironments (string $ contractClass , string $ label, array $ defaults ): Collection
363325 {
364326 $ allEnvironments = $ this ->codeEnvironmentsDetector ->getCodeEnvironments ();
365327 $ config = $ this ->getSelectionConfig ($ contractClass );
@@ -374,49 +336,48 @@ protected function selectCodeEnvironments(string $contractClass, string $label):
374336 $ displayMethod = $ config ['displayMethod ' ];
375337 $ displayText = $ environment ->{$ displayMethod }();
376338
377- return [$ environment::class => $ displayText ];
339+ return [$ environment-> name () => $ displayText ];
378340 })->sort ();
379341
380- $ detectedClasses = [];
381342 $ installedEnvNames = array_unique (array_merge (
382343 $ this ->projectInstalledCodeEnvironments ,
383344 $ this ->systemInstalledCodeEnvironments
384345 ));
385346
386- foreach ($ installedEnvNames as $ envKey ) {
387- $ matchingEnv = $ availableEnvironments ->first (fn (CodeEnvironment $ env ): bool => strtolower ((string ) $ envKey ) === strtolower ($ env ->name ()));
388- if ($ matchingEnv ) {
389- $ detectedClasses [] = $ matchingEnv ::class;
347+ $ detectedDefaults = [];
348+
349+ if ($ defaults === []) {
350+ foreach ($ installedEnvNames as $ envKey ) {
351+ $ matchingEnv = $ availableEnvironments ->first (fn (CodeEnvironment $ env ): bool => strtolower ((string ) $ envKey ) === strtolower ($ env ->name ()));
352+ if ($ matchingEnv ) {
353+ $ detectedDefaults [] = $ matchingEnv ->name ();
354+ }
390355 }
391356 }
392357
393- $ selectedClasses = collect (multiselect (
358+ $ selectedCodeEnvironments = collect (multiselect (
394359 label: $ label ,
395360 options: $ options ->toArray (),
396- default: array_unique ( $ detectedClasses ) ,
361+ default: $ defaults === [] ? $ detectedDefaults : $ defaults ,
397362 scroll: $ config ['scroll ' ],
398363 required: $ config ['required ' ],
399- hint: $ detectedClasses === [] ? '' : sprintf ('Auto-detected %s for you ' ,
364+ hint: $ defaults === [] || $ detectedDefaults === [] ? '' : sprintf ('Auto-detected %s for you ' ,
400365 Arr::join (array_map (function ($ className ) use ($ availableEnvironments , $ config ) {
401- $ env = $ availableEnvironments ->first (fn ($ env ): bool => $ env::class === $ className );
366+ $ env = $ availableEnvironments ->first (fn ($ env ): bool => $ env-> name () === $ className );
402367 $ displayMethod = $ config ['displayMethod ' ];
403368
404369 return $ env ->{$ displayMethod }();
405- }, $ detectedClasses ), ', ' , ' & ' )
370+ }, $ detectedDefaults ), ', ' , ' & ' )
406371 )
407372 ))->sort ();
408373
409- return $ selectedClasses ->map (fn ($ className ) => $ availableEnvironments ->first (fn ($ env ): bool => $ env ::class === $ className ));
374+ return $ selectedCodeEnvironments ->map (
375+ fn (string $ name ) => $ availableEnvironments ->first (fn ($ env ): bool => $ env ->name () === $ name ),
376+ )->filter ()->values ();
410377 }
411378
412379 protected function installGuidelines (): void
413380 {
414- if (! $ this ->shouldInstallAiGuidelines ()) {
415- $ this ->config ->setAiGuidelines ([]);
416-
417- return ;
418- }
419-
420381 if ($ this ->selectedTargetAgents ->isEmpty ()) {
421382 $ this ->info (' No agents selected for guideline installation. ' );
422383
@@ -479,37 +440,35 @@ protected function installGuidelines(): void
479440 }
480441 }
481442
482- $ this ->config ->setAiGuidelines (
483- $ this ->selectedAiGuidelines -> values ()-> toArray ()
443+ $ this ->config ->setHerdMcp (
444+ $ this ->shouldInstallHerdMcp ()
484445 );
485- }
486446
487- protected function shouldInstallAiGuidelines (): bool
488- {
489- return $ this ->selectedBoostFeatures ->contains ('ai_guidelines ' );
447+ $ this ->config ->setEditors (
448+ $ this ->selectedTargetMcpClient ->map (fn (McpClient $ mcpClient ) => $ mcpClient ->name ())->values ()->toArray ()
449+ );
450+
451+ $ this ->config ->setAgents (
452+ $ this ->selectedTargetAgents ->map (fn (Agent $ agent ) => $ agent ->name ())->values ()->toArray ()
453+ );
454+
455+ $ this ->config ->setGuidelines (
456+ $ this ->selectedAiGuidelines ->values ()->toArray ()
457+ );
490458 }
491459
492460 protected function shouldInstallStyleGuidelines (): bool
493461 {
494462 return false ;
495463 }
496464
497- protected function shouldInstallMcp (): bool
498- {
499- return $ this ->selectedBoostFeatures ->contains ('mcp_server ' );
500- }
501-
502465 protected function shouldInstallHerdMcp (): bool
503466 {
504467 return $ this ->selectedBoostFeatures ->contains ('herd_mcp ' );
505468 }
506469
507470 protected function installMcpServerConfig (): void
508471 {
509- if (! $ this ->shouldInstallMcp () && ! $ this ->shouldInstallHerdMcp ()) {
510- return ;
511- }
512-
513472 if ($ this ->selectedTargetMcpClient ->isEmpty ()) {
514473 $ this ->info ('No agents selected for guideline installation. ' );
515474
@@ -537,32 +496,30 @@ protected function installMcpServerConfig(): void
537496 $ this ->output ->write (" {$ ideDisplay }... " );
538497 $ results = [];
539498
540- if ($ this ->shouldInstallMcp ()) {
541- $ inWsl = $ this ->isRunningInWsl ();
542- $ mcp = array_filter ([
543- 'laravel-boost ' ,
544- $ inWsl ? 'wsl ' : false ,
545- $ mcpClient ->getPhpPath ($ inWsl ),
546- $ mcpClient ->getArtisanPath ($ inWsl ),
547- 'boost:mcp ' ,
548- ]);
549- try {
550- $ result = $ mcpClient ->installMcp (
551- array_shift ($ mcp ),
552- array_shift ($ mcp ),
553- $ mcp
554- );
555-
556- if ($ result ) {
557- $ results [] = $ this ->greenTick .' Boost ' ;
558- } else {
559- $ results [] = $ this ->redCross .' Boost ' ;
560- $ failed [$ ideName ]['boost ' ] = 'Failed to write configuration ' ;
561- }
562- } catch (Exception $ e ) {
499+ $ inWsl = $ this ->isRunningInWsl ();
500+ $ mcp = array_filter ([
501+ 'laravel-boost ' ,
502+ $ inWsl ? 'wsl ' : false ,
503+ $ mcpClient ->getPhpPath ($ inWsl ),
504+ $ mcpClient ->getArtisanPath ($ inWsl ),
505+ 'boost:mcp ' ,
506+ ]);
507+ try {
508+ $ result = $ mcpClient ->installMcp (
509+ array_shift ($ mcp ),
510+ array_shift ($ mcp ),
511+ $ mcp
512+ );
513+
514+ if ($ result ) {
515+ $ results [] = $ this ->greenTick .' Boost ' ;
516+ } else {
563517 $ results [] = $ this ->redCross .' Boost ' ;
564- $ failed [$ ideName ]['boost ' ] = $ e -> getMessage () ;
518+ $ failed [$ ideName ]['boost ' ] = ' Failed to write configuration ' ;
565519 }
520+ } catch (Exception $ e ) {
521+ $ results [] = $ this ->redCross .' Boost ' ;
522+ $ failed [$ ideName ]['boost ' ] = $ e ->getMessage ();
566523 }
567524
568525 // Install Herd MCP if enabled
0 commit comments