Skip to content

Commit d911705

Browse files
committed
refactor: simplify InstallCommand by consolidating CodeEnvironment handling and removing legacy agents
- Removed `Agents` directory and deprecated agent-related classes. - Consolidated IDE and agent selection logic into `CodeEnvironment`. - Implemented MCP installation strategies in `CodeEnvironment`. - Added `McpInstallationStrategy` enum for shell, file, or none-based configurations. - Updated `InstallCommand` to use consolidated `CodeEnvironment` functionality. - Enhanced `CodeEnvironment` with support for MCP installation and guideline paths.
1 parent 216342e commit d911705

19 files changed

+396
-374
lines changed

src/Console/InstallCommand.php

Lines changed: 59 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@
99
use Illuminate\Support\Arr;
1010
use Illuminate\Support\Collection;
1111
use Illuminate\Support\Str;
12-
use Laravel\Boost\Contracts\Agent;
13-
use Laravel\Boost\Contracts\Ide;
1412
use Laravel\Boost\Install\Cli\DisplayHelper;
13+
use Laravel\Boost\Install\CodeEnvironment\CodeEnvironment;
1514
use Laravel\Boost\Install\CodeEnvironmentsDetector;
1615
use Laravel\Boost\Install\GuidelineComposer;
1716
use Laravel\Boost\Install\GuidelineConfig;
1817
use Laravel\Boost\Install\GuidelineWriter;
1918
use Laravel\Boost\Install\Herd;
2019
use Laravel\Prompts\Concerns\Colors;
2120
use Laravel\Prompts\Terminal;
22-
use ReflectionClass;
2321
use Symfony\Component\Console\Attribute\AsCommand;
2422
use Symfony\Component\Finder\Finder;
2523

@@ -39,10 +37,10 @@ class InstallCommand extends Command
3937

4038
private Terminal $terminal;
4139

42-
/** @var Collection<int, Agent> */
40+
/** @var Collection<int, CodeEnvironment> */
4341
private Collection $selectedTargetAgents;
4442

45-
/** @var Collection<int, Ide> */
43+
/** @var Collection<int, CodeEnvironment> */
4644
private Collection $selectedTargetIdes;
4745

4846
/** @var Collection<int, string> */
@@ -57,8 +55,6 @@ class InstallCommand extends Command
5755

5856
private bool $enforceTests = true;
5957

60-
private array $projectInstalledAgents = [];
61-
6258
private string $greenTick;
6359

6460
private string $redCross;
@@ -114,7 +110,6 @@ private function discoverEnvironment(): void
114110
{
115111
$this->systemInstalledCodeEnvironments = $this->codeEnvironmentsDetector->discoverSystemInstalledCodeEnvironments();
116112
$this->projectInstalledCodeEnvironments = $this->codeEnvironmentsDetector->discoverProjectInstalledCodeEnvironments(base_path());
117-
$this->projectInstalledAgents = $this->discoverProjectAgents();
118113
}
119114

120115
private function collectInstallationPreferences(): void
@@ -127,7 +122,7 @@ private function collectInstallationPreferences(): void
127122

128123
private function enact(): void
129124
{
130-
if ($this->shouldInstallAiGuidelines() && ! empty($this->selectedTargetAgents)) {
125+
if ($this->shouldInstallAiGuidelines() && $this->selectedTargetAgents->isNotEmpty()) {
131126
$this->enactGuidelines();
132127
}
133128

@@ -163,8 +158,8 @@ private function outro(): void
163158
{
164159
$label = 'https://boost.laravel.com/installed';
165160

166-
$ideNames = $this->selectedTargetIdes->map(fn ($ide) => 'i:'.class_basename($ide))->toArray();
167-
$agentNames = $this->selectedTargetAgents->map(fn ($agent) => 'a:'.class_basename($agent))->toArray();
161+
$ideNames = $this->selectedTargetIdes->map(fn ($ide) => 'i:'.$ide->ideName())->toArray();
162+
$agentNames = $this->selectedTargetAgents->map(fn ($agent) => 'a:'.$agent->agentName())->toArray();
168163
$boostFeatures = $this->selectedBoostFeatures->map(fn ($feature) => 'b:'.$feature)->toArray();
169164

170165
// Guidelines installed (prefix: g)
@@ -261,137 +256,86 @@ protected function boostToolsToDisable(): array
261256
/**
262257
* @return array<int, string>
263258
*/
264-
private function discoverProjectAgents(): array
265-
{
266-
$agents = [];
267-
$projectAgents = $this->codeEnvironmentsDetector->discoverProjectInstalledCodeEnvironments(base_path());
268-
269-
// Map IDE detections to their corresponding agents
270-
$ideToAgentMap = [
271-
'phpstorm' => 'junie',
272-
'claudecode' => 'claudecode',
273-
'cursor' => 'cursor',
274-
'copilot' => 'copilot',
275-
];
276-
277-
foreach ($projectAgents as $app) {
278-
if (isset($ideToAgentMap[$app])) {
279-
$agents[] = $ideToAgentMap[$app];
280-
}
281-
}
282-
283-
foreach ($this->systemInstalledCodeEnvironments as $ide) {
284-
if (isset($ideToAgentMap[$ide]) && ! in_array($ideToAgentMap[$ide], $agents)) {
285-
$agents[] = $ideToAgentMap[$ide];
286-
}
287-
}
288-
289-
return array_unique($agents);
290-
}
291259

292260
/**
293-
* @return Collection<int, Ide>
261+
* @return Collection<int, CodeEnvironment>
294262
*/
295263
private function selectTargetIdes(): Collection
296264
{
297-
$ides = [];
298265
if (! $this->shouldInstallMcp() && ! $this->shouldInstallHerdMcp()) {
299266
return collect();
300267
}
301268

302-
$agentDir = implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'Install', 'Agents']);
303-
304-
$finder = Finder::create()
305-
->in($agentDir)
306-
->files()
307-
->name('*.php');
308-
309-
foreach ($finder as $ideFile) {
310-
$className = 'Laravel\\Boost\\Install\\Agents\\'.$ideFile->getBasename('.php');
311-
312-
if (class_exists($className)) {
313-
$reflection = new ReflectionClass($className);
314-
315-
if ($reflection->implementsInterface(Ide::class) && ! $reflection->isAbstract()) {
316-
$ides[$className] = Str::headline($ideFile->getBasename('.php'));
317-
}
318-
}
319-
}
320-
321-
ksort($ides);
322-
323-
$detectedClasses = [];
324-
foreach ($this->projectInstalledCodeEnvironments as $ideKey) {
325-
foreach ($ides as $className => $displayName) {
326-
if (strtolower($ideKey) === strtolower(class_basename($className))) {
327-
$detectedClasses[] = $className;
328-
break;
329-
}
330-
}
331-
}
332-
333-
$selectedIdeClasses = collect(multiselect(
334-
label: sprintf('Which code editors do you use in %s?', $this->projectName),
335-
options: $ides,
336-
default: $detectedClasses,
337-
scroll: 5,
338-
required: true,
339-
hint: sprintf('Auto-detected %s for you', Arr::join(array_map(fn ($c) => class_basename($c), $detectedClasses), ', ', ' & '))
340-
))->sort();
341-
342-
return $selectedIdeClasses->map(fn ($ideClass) => new $ideClass);
269+
return $this->selectCodeEnvironments(
270+
'ide',
271+
sprintf('Which code editors do you use in %s?', $this->projectName)
272+
);
343273
}
344274

345275
/**
346-
* @return Collection<int, Agent>
276+
* @return Collection<int, CodeEnvironment>
347277
*/
348278
private function selectTargetAgents(): Collection
349279
{
350-
$agents = [];
351280
if (! $this->shouldInstallAiGuidelines()) {
352281
return collect();
353282
}
354283

355-
$agentDir = implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'Install', 'Agents']);
356-
357-
$finder = Finder::create()
358-
->in($agentDir)
359-
->files()
360-
->name('*.php');
284+
return $this->selectCodeEnvironments(
285+
'agent',
286+
sprintf('Which agents need AI guidelines for %s?', $this->projectName)
287+
);
288+
}
361289

362-
foreach ($finder as $agentFile) {
363-
$className = 'Laravel\\Boost\\Install\\Agents\\'.$agentFile->getBasename('.php');
290+
/**
291+
* @return Collection<int, CodeEnvironment>
292+
*/
293+
private function selectCodeEnvironments(string $type, string $label): Collection
294+
{
295+
$allEnvironments = $this->codeEnvironmentsDetector->getCodeEnvironments();
364296

365-
if (class_exists($className)) {
366-
$reflection = new ReflectionClass($className);
297+
// Filter by type capability
298+
$availableEnvironments = $allEnvironments->filter(function (CodeEnvironment $environment) use ($type) {
299+
return ($type === 'ide' && $environment->supportsIde()) ||
300+
($type === 'agent' && $environment->supportsAgent());
301+
});
367302

368-
if ($reflection->implementsInterface(Agent::class)) {
369-
$agents[$className] = Str::headline($agentFile->getBasename('.php'));
370-
}
371-
}
303+
if ($availableEnvironments->isEmpty()) {
304+
return collect();
372305
}
373306

374-
ksort($agents);
307+
// Build options for multiselect
308+
$options = $availableEnvironments->mapWithKeys(function (CodeEnvironment $environment) {
309+
return [get_class($environment) => $environment->displayName()];
310+
})->sort();
375311

376-
// Map detected agent keys to class names
312+
// Auto-detect installed environments
377313
$detectedClasses = [];
378-
foreach ($this->projectInstalledAgents as $agentKey) {
379-
foreach ($agents as $className => $displayName) {
380-
if (strtolower($agentKey) === strtolower(class_basename($className))) {
381-
$detectedClasses[] = $className;
382-
break;
383-
}
314+
$installedEnvNames = array_unique(array_merge(
315+
$this->projectInstalledCodeEnvironments,
316+
$this->systemInstalledCodeEnvironments
317+
));
318+
319+
foreach ($installedEnvNames as $envKey) {
320+
$matchingEnv = $availableEnvironments->first(fn (CodeEnvironment $env) => strtolower($envKey) === strtolower($env->name())
321+
);
322+
if ($matchingEnv) {
323+
$detectedClasses[] = get_class($matchingEnv);
384324
}
385325
}
386326

387-
$selectedAgentClasses = collect(multiselect(
388-
label: sprintf('Which agents need AI guidelines for %s?', $this->projectName),
389-
options: $agents,
390-
default: $detectedClasses,
391-
scroll: 4,
327+
$selectedClasses = collect(multiselect(
328+
label: $label,
329+
options: $options->toArray(),
330+
default: array_unique($detectedClasses),
331+
scroll: $type === 'ide' ? 5 : 4,
332+
required: $type === 'ide',
333+
hint: empty($detectedClasses) ? null : sprintf('Auto-detected %s for you',
334+
Arr::join(array_map(fn ($className) => $availableEnvironments->first(fn ($env) => get_class($env) === $className)->displayName(), $detectedClasses), ', ', ' & ')
335+
)
392336
))->sort();
393337

394-
return $selectedAgentClasses->map(fn ($agentClass) => new $agentClass);
338+
return $selectedClasses->map(fn ($className) => $availableEnvironments->first(fn ($env) => get_class($env) === $className));
395339
}
396340

397341
protected function enactGuidelines(): void
@@ -424,9 +368,9 @@ protected function enactGuidelines(): void
424368
$failed = [];
425369
$composedAiGuidelines = $composer->compose();
426370

427-
$longestAgentName = max(1, ...$this->selectedTargetAgents->map(fn ($agent) => Str::length(class_basename($agent)))->toArray());
371+
$longestAgentName = max(1, ...$this->selectedTargetAgents->map(fn ($agent) => Str::length($agent->agentName()))->toArray());
428372
foreach ($this->selectedTargetAgents as $agent) {
429-
$agentName = class_basename($agent);
373+
$agentName = $agent->agentName();
430374
$displayAgentName = str_pad($agentName, $longestAgentName);
431375
$this->output->write(" {$displayAgentName}... ");
432376

@@ -483,10 +427,10 @@ private function enactMcpServers(): void
483427
usleep(750000);
484428

485429
$failed = [];
486-
$longestIdeName = max(1, ...$this->selectedTargetIdes->map(fn ($ide) => Str::length(class_basename($ide)))->toArray());
430+
$longestIdeName = max(1, ...$this->selectedTargetIdes->map(fn ($ide) => Str::length($ide->ideName()))->toArray());
487431

488432
foreach ($this->selectedTargetIdes as $ide) {
489-
$ideName = class_basename($ide);
433+
$ideName = $ide->ideName();
490434
$ideDisplay = str_pad($ideName, $longestIdeName);
491435
$this->output->write(" {$ideDisplay}... ");
492436
$results = [];

src/Install/Agents/ClaudeCode.php

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/Install/Agents/Copilot.php

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/Install/Agents/Cursor.php

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)