From 0050a3c3563f122e32363f7f5bb9a0bf6c8ba8f1 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 29 Sep 2025 16:56:41 +0530 Subject: [PATCH 01/15] Add hooks for registering third party code environments Signed-off-by: Pushpak Chhajed --- src/Boost.php | 21 ++ src/BoostManager.php | 46 +++ src/BoostServiceProvider.php | 2 + .../CodeEnvironment/CodeEnvironment.php | 5 - src/Install/CodeEnvironmentsDetector.php | 23 +- tests/Feature/BoostFacadeTest.php | 34 ++ tests/Feature/BoostServiceProviderTest.php | 23 ++ tests/Unit/BoostManagerTest.php | 62 ++++ .../Install/CodeEnvironmentsDetectorTest.php | 310 ++++++------------ tests/Unit/Install/ExampleCodeEnvironment.php | 43 +++ 10 files changed, 329 insertions(+), 240 deletions(-) create mode 100644 src/Boost.php create mode 100644 src/BoostManager.php create mode 100644 tests/Feature/BoostFacadeTest.php create mode 100644 tests/Unit/BoostManagerTest.php create mode 100644 tests/Unit/Install/ExampleCodeEnvironment.php diff --git a/src/Boost.php b/src/Boost.php new file mode 100644 index 00000000..58424896 --- /dev/null +++ b/src/Boost.php @@ -0,0 +1,21 @@ +> */ + private array $codeEnvironments = [ + 'phpstorm' => PhpStorm::class, + 'vscode' => VSCode::class, + 'cursor' => Cursor::class, + 'claudecode' => ClaudeCode::class, + 'codex' => Codex::class, + 'copilot' => Copilot::class, + ]; + + /** + * @param class-string $className + */ + public function registerCodeEnvironment(string $key, string $className): void + { + if (array_key_exists($key, $this->codeEnvironments)) { + return; + } + + $this->codeEnvironments[$key] = $className; + } + + /** + * @return array> + */ + public function getCodeEnvironments(): array + { + return $this->codeEnvironments; + } +} diff --git a/src/BoostServiceProvider.php b/src/BoostServiceProvider.php index 4baa5c2d..4cdf0b4a 100644 --- a/src/BoostServiceProvider.php +++ b/src/BoostServiceProvider.php @@ -26,6 +26,8 @@ public function register(): void 'boost' ); + $this->app->singleton(BoostManager::class, fn (): BoostManager => new BoostManager); + if (! $this->shouldRun()) { return; } diff --git a/src/Install/CodeEnvironment/CodeEnvironment.php b/src/Install/CodeEnvironment/CodeEnvironment.php index 9a96b052..8fd166d7 100644 --- a/src/Install/CodeEnvironment/CodeEnvironment.php +++ b/src/Install/CodeEnvironment/CodeEnvironment.php @@ -4,7 +4,6 @@ namespace Laravel\Boost\Install\CodeEnvironment; -use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Support\Facades\Process; use Laravel\Boost\Contracts\Agent; use Laravel\Boost\Contracts\McpClient; @@ -118,8 +117,6 @@ public function mcpConfigKey(): string * * @param array $args * @param array $env - * - * @throws FileNotFoundException */ public function installMcp(string $key, string $command, array $args = [], array $env = []): bool { @@ -176,8 +173,6 @@ protected function installShellMcp(string $key, string $command, array $args = [ * * @param array $args * @param array $env - * - * @throws FileNotFoundException */ protected function installFileMcp(string $key, string $command, array $args = [], array $env = []): bool { diff --git a/src/Install/CodeEnvironmentsDetector.php b/src/Install/CodeEnvironmentsDetector.php index ad446e61..99ca65a7 100644 --- a/src/Install/CodeEnvironmentsDetector.php +++ b/src/Install/CodeEnvironmentsDetector.php @@ -6,29 +6,15 @@ use Illuminate\Container\Container; use Illuminate\Support\Collection; -use Laravel\Boost\Install\CodeEnvironment\ClaudeCode; +use Laravel\Boost\BoostManager; use Laravel\Boost\Install\CodeEnvironment\CodeEnvironment; -use Laravel\Boost\Install\CodeEnvironment\Codex; -use Laravel\Boost\Install\CodeEnvironment\Copilot; -use Laravel\Boost\Install\CodeEnvironment\Cursor; -use Laravel\Boost\Install\CodeEnvironment\PhpStorm; -use Laravel\Boost\Install\CodeEnvironment\VSCode; use Laravel\Boost\Install\Enums\Platform; class CodeEnvironmentsDetector { - /** @var array> */ - private array $programs = [ - 'phpstorm' => PhpStorm::class, - 'vscode' => VSCode::class, - 'cursor' => Cursor::class, - 'claudecode' => ClaudeCode::class, - 'codex' => Codex::class, - 'copilot' => Copilot::class, - ]; - public function __construct( - private readonly Container $container + private readonly Container $container, + private readonly BoostManager $boostManager ) {} /** @@ -68,6 +54,7 @@ public function discoverProjectInstalledCodeEnvironments(string $basePath): arra */ public function getCodeEnvironments(): Collection { - return collect($this->programs)->map(fn (string $className) => $this->container->make($className)); + return collect($this->boostManager->getCodeEnvironments()) + ->map(fn (string $className) => $this->container->make($className)); } } diff --git a/tests/Feature/BoostFacadeTest.php b/tests/Feature/BoostFacadeTest.php new file mode 100644 index 00000000..23453ed2 --- /dev/null +++ b/tests/Feature/BoostFacadeTest.php @@ -0,0 +1,34 @@ +toBeInstanceOf(BoostManager::class); +}); + +it('Boost Facade registers code environments via facade', function (): void { + Boost::registerCodeEnvironment('example1', ExampleCodeEnvironment::class); + Boost::registerCodeEnvironment('example2', ExampleCodeEnvironment::class); + $registered = Boost::getFacadeRoot()->getCodeEnvironments(); + + expect($registered)->toHaveKey('example1') + ->and($registered['example1'])->toBe(ExampleCodeEnvironment::class) + ->and($registered)->toHaveKey('example2') + ->and($registered['example2'])->toBe(ExampleCodeEnvironment::class) + ->and($registered)->toHaveKey('phpstorm'); +}); + +it('Boost Facade maintains registration state across facade calls', function (): void { + Boost::registerCodeEnvironment('persistent', 'Test\Persistent'); + + $registered = Boost::getFacadeRoot()->getCodeEnvironments(); + + expect($registered)->toHaveKey('persistent') + ->and($registered['persistent'])->toBe('Test\Persistent'); +}); diff --git a/tests/Feature/BoostServiceProviderTest.php b/tests/Feature/BoostServiceProviderTest.php index c852de12..7ad4e8bc 100644 --- a/tests/Feature/BoostServiceProviderTest.php +++ b/tests/Feature/BoostServiceProviderTest.php @@ -3,6 +3,8 @@ declare(strict_types=1); use Illuminate\Support\Facades\Config; +use Laravel\Boost\Boost; +use Laravel\Boost\BoostManager; use Laravel\Boost\BoostServiceProvider; beforeEach(function (): void { @@ -74,3 +76,24 @@ }); }); }); + +describe('BoostManager registration', function (): void { + it('registers BoostManager in the container', function (): void { + expect(app()->bound(BoostManager::class))->toBeTrue() + ->and(app(BoostManager::class))->toBeInstanceOf(BoostManager::class); + }); + + it('registers BoostManager as a singleton', function (): void { + $instance1 = app(BoostManager::class); + $instance2 = app(BoostManager::class); + + expect($instance1)->toBe($instance2); + }); + + it('binds Boost facade to the same BoostManager instance', function (): void { + $containerInstance = app(BoostManager::class); + $facadeInstance = Boost::getFacadeRoot(); + + expect($facadeInstance)->toBe($containerInstance); + }); +}); diff --git a/tests/Unit/BoostManagerTest.php b/tests/Unit/BoostManagerTest.php new file mode 100644 index 00000000..80e972a6 --- /dev/null +++ b/tests/Unit/BoostManagerTest.php @@ -0,0 +1,62 @@ +getCodeEnvironments(); + + expect($registered)->toMatchArray([ + 'phpstorm' => PhpStorm::class, + 'vscode' => VSCode::class, + 'cursor' => Cursor::class, + 'claudecode' => ClaudeCode::class, + 'codex' => Codex::class, + 'copilot' => Copilot::class, + ]); +}); + +it('can register a single code environment', function (): void { + $manager = new BoostManager; + $manager->registerCodeEnvironment('example', ExampleCodeEnvironment::class); + + $registered = $manager->getCodeEnvironments(); + + expect($registered)->toHaveKey('example') + ->and($registered['example'])->toBe(ExampleCodeEnvironment::class) + ->and($registered)->toHaveKey('phpstorm'); +}); + +it('can register multiple code environments', function (): void { + $manager = new BoostManager; + $manager->registerCodeEnvironment('example1', ExampleCodeEnvironment::class); + $manager->registerCodeEnvironment('example2', ExampleCodeEnvironment::class); + + $registered = $manager->getCodeEnvironments(); + + expect($registered)->toHaveKey('example1')->toHaveKey('example2') + ->and($registered['example1'])->toBe(ExampleCodeEnvironment::class) + ->and($registered['example2'])->toBe(ExampleCodeEnvironment::class) + ->and($registered)->toHaveKey('phpstorm'); +}); + +it('does not overwrite default code environments', function (): void { + $manager = new BoostManager; + $manager->registerCodeEnvironment('phpstorm', PHPStorm::class); + $manager->registerCodeEnvironment('phpstorm', ExampleCodeEnvironment::class); + + $registered = $manager->getCodeEnvironments(); + + expect($registered)->toHaveKey('phpstorm') + ->and($registered['phpstorm'])->toBe(PHPStorm::class) + ->and($registered['phpstorm'])->not()->toBe(ExampleCodeEnvironment::class); +}); diff --git a/tests/Unit/Install/CodeEnvironmentsDetectorTest.php b/tests/Unit/Install/CodeEnvironmentsDetectorTest.php index dc46503c..2e11b26e 100644 --- a/tests/Unit/Install/CodeEnvironmentsDetectorTest.php +++ b/tests/Unit/Install/CodeEnvironmentsDetectorTest.php @@ -2,263 +2,139 @@ declare(strict_types=1); +use Illuminate\Container\Container; +use Illuminate\Support\Collection; +use Laravel\Boost\BoostManager; +use Laravel\Boost\Install\CodeEnvironment\ClaudeCode; use Laravel\Boost\Install\CodeEnvironment\CodeEnvironment; +use Laravel\Boost\Install\CodeEnvironment\Codex; +use Laravel\Boost\Install\CodeEnvironment\Copilot; +use Laravel\Boost\Install\CodeEnvironment\Cursor; +use Laravel\Boost\Install\CodeEnvironment\PhpStorm; +use Laravel\Boost\Install\CodeEnvironment\VSCode; use Laravel\Boost\Install\CodeEnvironmentsDetector; use Laravel\Boost\Install\Enums\Platform; beforeEach(function (): void { - $this->container = new \Illuminate\Container\Container; - $this->detector = new CodeEnvironmentsDetector($this->container); + $this->container = new Container; + $this->boostManager = new BoostManager; + $this->detector = new CodeEnvironmentsDetector($this->container, $this->boostManager); }); afterEach(function (): void { Mockery::close(); }); -test('discoverSystemInstalledCodeEnvironments returns detected programs', function (): void { - // Create mock programs - $program1 = Mockery::mock(CodeEnvironment::class); - $program1->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(true); - $program1->shouldReceive('name')->andReturn('phpstorm'); +it('returns collection of all registered code environments', function (): void { + $codeEnvironments = $this->detector->getCodeEnvironments(); - $program2 = Mockery::mock(CodeEnvironment::class); - $program2->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(false); - $program2->shouldReceive('name')->andReturn('vscode'); + expect($codeEnvironments)->toBeInstanceOf(Collection::class) + ->and($codeEnvironments->count())->toBe(6) + ->and($codeEnvironments->keys()->toArray())->toBe([ + 'phpstorm', 'vscode', 'cursor', 'claudecode', 'codex', 'copilot', + ]); - $program3 = Mockery::mock(CodeEnvironment::class); - $program3->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(true); - $program3->shouldReceive('name')->andReturn('cursor'); + $codeEnvironments->each(function ($environment): void { + expect($environment)->toBeInstanceOf(CodeEnvironment::class); + }); +}); + +it('returns an array of detected environment names for system discovery', function (): void { + $mockPhpStorm = Mockery::mock(CodeEnvironment::class); + $mockPhpStorm->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(true); + $mockPhpStorm->shouldReceive('name')->andReturn('phpstorm'); + + $mockVSCode = Mockery::mock(CodeEnvironment::class); + $mockVSCode->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(false); + $mockVSCode->shouldReceive('name')->andReturn('vscode'); + + $mockCursor = Mockery::mock(CodeEnvironment::class); + $mockCursor->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(true); + $mockCursor->shouldReceive('name')->andReturn('cursor'); - // Mock all other programs that might be instantiated - $otherProgram = Mockery::mock(CodeEnvironment::class); - $otherProgram->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(false); - $otherProgram->shouldReceive('name')->andReturn('other'); + $mockOther = Mockery::mock(CodeEnvironment::class); + $mockOther->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(false); + $mockOther->shouldReceive('name')->andReturn('other'); - // Bind mocked programs to container - $container = new \Illuminate\Container\Container; - $container->bind(\Laravel\Boost\Install\CodeEnvironment\PhpStorm::class, fn () => $program1); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\VSCode::class, fn () => $program2); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\Cursor::class, fn () => $program3); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\ClaudeCode::class, fn () => $otherProgram); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\Codex::class, fn () => $otherProgram); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\Copilot::class, fn () => $otherProgram); + $this->container->bind(PhpStorm::class, fn () => $mockPhpStorm); + $this->container->bind(VSCode::class, fn () => $mockVSCode); + $this->container->bind(Cursor::class, fn () => $mockCursor); + $this->container->bind(ClaudeCode::class, fn () => $mockOther); + $this->container->bind(Codex::class, fn () => $mockOther); + $this->container->bind(Copilot::class, fn () => $mockOther); - $detector = new CodeEnvironmentsDetector($container); + $detector = new CodeEnvironmentsDetector($this->container, $this->boostManager); $detected = $detector->discoverSystemInstalledCodeEnvironments(); expect($detected)->toBe(['phpstorm', 'cursor']); }); -test('discoverSystemInstalledCodeEnvironments returns empty array when no programs detected', function (): void { - $program1 = Mockery::mock(CodeEnvironment::class); - $program1->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(false); - $program1->shouldReceive('name')->andReturn('phpstorm'); +it('returns an empty array when no environments are detected for system discovery', function (): void { + $mockEnvironment = Mockery::mock(CodeEnvironment::class); + $mockEnvironment->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(false); + $mockEnvironment->shouldReceive('name')->andReturn('mock'); - // Mock all other programs that might be instantiated - $otherProgram = Mockery::mock(CodeEnvironment::class); - $otherProgram->shouldReceive('detectOnSystem')->with(Mockery::type(Platform::class))->andReturn(false); - $otherProgram->shouldReceive('name')->andReturn('other'); + $this->container->bind(PhpStorm::class, fn () => $mockEnvironment); + $this->container->bind(VSCode::class, fn () => $mockEnvironment); + $this->container->bind(Cursor::class, fn () => $mockEnvironment); + $this->container->bind(ClaudeCode::class, fn () => $mockEnvironment); + $this->container->bind(Codex::class, fn () => $mockEnvironment); + $this->container->bind(Copilot::class, fn () => $mockEnvironment); - // Bind mocked program to container - $container = new \Illuminate\Container\Container; - $container->bind(\Laravel\Boost\Install\CodeEnvironment\PhpStorm::class, fn () => $program1); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\VSCode::class, fn () => $otherProgram); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\Cursor::class, fn () => $otherProgram); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\ClaudeCode::class, fn () => $otherProgram); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\Codex::class, fn () => $otherProgram); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\Copilot::class, fn () => $otherProgram); - - $detector = new CodeEnvironmentsDetector($container); + $detector = new CodeEnvironmentsDetector($this->container, $this->boostManager); $detected = $detector->discoverSystemInstalledCodeEnvironments(); - expect($detected)->toBeEmpty(); + expect($detected)->toBe([]); }); -test('discoverProjectInstalledCodeEnvironments detects programs in project', function (): void { - $basePath = '/path/to/project'; +it('returns an array of detected environment names for project discovery', function (): void { + $basePath = '/test/project'; + + $mockVSCode = Mockery::mock(CodeEnvironment::class); + $mockVSCode->shouldReceive('detectInProject')->with($basePath)->andReturn(true); + $mockVSCode->shouldReceive('name')->andReturn('vscode'); - $program1 = Mockery::mock(CodeEnvironment::class); - $program1->shouldReceive('detectInProject')->with($basePath)->andReturn(true); - $program1->shouldReceive('name')->andReturn('vscode'); + $mockPhpStorm = Mockery::mock(CodeEnvironment::class); + $mockPhpStorm->shouldReceive('detectInProject')->with($basePath)->andReturn(false); + $mockPhpStorm->shouldReceive('name')->andReturn('phpstorm'); - $program2 = Mockery::mock(CodeEnvironment::class); - $program2->shouldReceive('detectInProject')->with($basePath)->andReturn(false); - $program2->shouldReceive('name')->andReturn('phpstorm'); + $mockClaudeCode = Mockery::mock(CodeEnvironment::class); + $mockClaudeCode->shouldReceive('detectInProject')->with($basePath)->andReturn(true); + $mockClaudeCode->shouldReceive('name')->andReturn('claudecode'); - $program3 = Mockery::mock(CodeEnvironment::class); - $program3->shouldReceive('detectInProject')->with($basePath)->andReturn(true); - $program3->shouldReceive('name')->andReturn('claudecode'); + $mockOther = Mockery::mock(CodeEnvironment::class); + $mockOther->shouldReceive('detectInProject')->with($basePath)->andReturn(false); + $mockOther->shouldReceive('name')->andReturn('other'); - // Bind mocked programs to container - $container = new \Illuminate\Container\Container; - $container->bind(\Laravel\Boost\Install\CodeEnvironment\VSCode::class, fn () => $program1); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\PhpStorm::class, fn () => $program2); - $container->bind(\Laravel\Boost\Install\CodeEnvironment\ClaudeCode::class, fn () => $program3); + $this->container->bind(PhpStorm::class, fn () => $mockPhpStorm); + $this->container->bind(VSCode::class, fn () => $mockVSCode); + $this->container->bind(Cursor::class, fn () => $mockOther); + $this->container->bind(ClaudeCode::class, fn () => $mockClaudeCode); + $this->container->bind(Codex::class, fn () => $mockOther); + $this->container->bind(Copilot::class, fn () => $mockOther); - $detector = new CodeEnvironmentsDetector($container); + $detector = new CodeEnvironmentsDetector($this->container, $this->boostManager); $detected = $detector->discoverProjectInstalledCodeEnvironments($basePath); expect($detected)->toBe(['vscode', 'claudecode']); }); -test('discoverProjectInstalledCodeEnvironments returns empty array when no programs detected in project', function (): void { - $basePath = '/path/to/project'; +it('returns an empty array when no environments are detected for project discovery', function (): void { + $basePath = '/empty/project'; - $program1 = Mockery::mock(CodeEnvironment::class); - $program1->shouldReceive('detectInProject')->with($basePath)->andReturn(false); - $program1->shouldReceive('name')->andReturn('vscode'); + $mockEnvironment = Mockery::mock(CodeEnvironment::class); + $mockEnvironment->shouldReceive('detectInProject')->with($basePath)->andReturn(false); + $mockEnvironment->shouldReceive('name')->andReturn('mock'); - // Bind mocked program to container - $container = new \Illuminate\Container\Container; - $container->bind(\Laravel\Boost\Install\CodeEnvironment\VSCode::class, fn () => $program1); + $this->container->bind(PhpStorm::class, fn () => $mockEnvironment); + $this->container->bind(VSCode::class, fn () => $mockEnvironment); + $this->container->bind(Cursor::class, fn () => $mockEnvironment); + $this->container->bind(ClaudeCode::class, fn () => $mockEnvironment); + $this->container->bind(Codex::class, fn () => $mockEnvironment); + $this->container->bind(Copilot::class, fn () => $mockEnvironment); - $detector = new CodeEnvironmentsDetector($container); + $detector = new CodeEnvironmentsDetector($this->container, $this->boostManager); $detected = $detector->discoverProjectInstalledCodeEnvironments($basePath); - expect($detected)->toBeEmpty(); -}); - -test('discoverProjectInstalledCodeEnvironments detects applications by directory', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.vscode'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('vscode'); - - // Cleanup - rmdir($tempDir.'/.vscode'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects applications with mixed type', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - file_put_contents($tempDir.'/CLAUDE.md', 'test'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('claudecode'); - - unlink($tempDir.'/CLAUDE.md'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects copilot with nested file path', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.github'); - file_put_contents($tempDir.'/.github/copilot-instructions.md', 'test'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('copilot'); - - // Cleanup - unlink($tempDir.'/.github/copilot-instructions.md'); - rmdir($tempDir.'/.github'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects claude code with directory', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.claude'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('claudecode'); - - rmdir($tempDir.'/.claude'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects phpstorm with idea directory', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.idea'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('phpstorm'); - - // Cleanup - rmdir($tempDir.'/.idea'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects phpstorm with junie directory', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.junie'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('phpstorm'); - - // Cleanup - rmdir($tempDir.'/.junie'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects cursor with cursor directory', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.cursor'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('cursor'); - - // Cleanup - rmdir($tempDir.'/.cursor'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects codex with codex directory', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.codex'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('codex'); - - rmdir($tempDir.'/.codex'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments detects codex with AGENTS.md file', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - file_put_contents($tempDir.'/AGENTS.md', 'test'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('codex'); - - unlink($tempDir.'/AGENTS.md'); - rmdir($tempDir); -}); - -test('discoverProjectInstalledCodeEnvironments handles multiple detections', function (): void { - $tempDir = sys_get_temp_dir().'/boost_test_'.uniqid(); - mkdir($tempDir); - mkdir($tempDir.'/.vscode'); - mkdir($tempDir.'/.cursor'); - file_put_contents($tempDir.'/CLAUDE.md', 'test'); - - $detected = $this->detector->discoverProjectInstalledCodeEnvironments($tempDir); - - expect($detected)->toContain('vscode') - ->and($detected)->toContain('cursor') - ->and($detected)->toContain('claudecode') - ->and(count($detected))->toBeGreaterThanOrEqual(3); - - // Cleanup - rmdir($tempDir.'/.vscode'); - rmdir($tempDir.'/.cursor'); - unlink($tempDir.'/CLAUDE.md'); - rmdir($tempDir); + expect($detected)->toBe([]); }); diff --git a/tests/Unit/Install/ExampleCodeEnvironment.php b/tests/Unit/Install/ExampleCodeEnvironment.php new file mode 100644 index 00000000..34a64f06 --- /dev/null +++ b/tests/Unit/Install/ExampleCodeEnvironment.php @@ -0,0 +1,43 @@ + 'which example']; + } + + public function projectDetectionConfig(): array + { + return ['paths' => ['.example']]; + } + + public function mcpConfigPath(): string + { + return '.example/config.json'; + } + + public function guidelinesPath(): string + { + return 'EXAMPLE.md'; + } +} From e70c2e4d5a9e62922a2fe5f8173e57b004c49d9f Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 29 Sep 2025 17:15:14 +0530 Subject: [PATCH 02/15] Dynamically set scroll Signed-off-by: Pushpak Chhajed --- src/Console/InstallCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 4a621772..1cdaf5c3 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -302,13 +302,13 @@ protected function selectTargetAgents(): Collection /** * Get configuration settings for contract-specific selection behavior. * - * @return array{scroll: int, required: bool, displayMethod: string} + * @return array{required: bool, displayMethod: string} */ protected function getSelectionConfig(string $contractClass): array { return match ($contractClass) { - Agent::class => ['scroll' => 5, 'required' => false, 'displayMethod' => 'agentName'], - McpClient::class => ['scroll' => 5, 'required' => true, 'displayMethod' => 'displayName'], + Agent::class => ['required' => false, 'displayMethod' => 'agentName'], + McpClient::class => ['required' => true, 'displayMethod' => 'displayName'], default => throw new InvalidArgumentException("Unsupported contract class: {$contractClass}"), }; } @@ -351,7 +351,7 @@ protected function selectCodeEnvironments(string $contractClass, string $label): label: $label, options: $options->toArray(), default: array_unique($detectedClasses), - scroll: $config['scroll'], + scroll: count($options->toArray()), required: $config['required'], hint: $detectedClasses === [] ? '' : sprintf('Auto-detected %s for you', Arr::join(array_map(function ($className) use ($availableEnvironments, $config) { From e32ee7bc3196389a49acc4ca43a7ee3a9404482d Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 29 Sep 2025 17:18:28 +0530 Subject: [PATCH 03/15] Fix PHPStan errors Signed-off-by: Pushpak Chhajed --- src/Mcp/Methods/CallToolWithExecutor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mcp/Methods/CallToolWithExecutor.php b/src/Mcp/Methods/CallToolWithExecutor.php index 9db398a5..51b6a858 100644 --- a/src/Mcp/Methods/CallToolWithExecutor.php +++ b/src/Mcp/Methods/CallToolWithExecutor.php @@ -38,7 +38,7 @@ public function handle(JsonRpcRequest $request, ServerContext $context): JsonRpc } $tool = $context - ->tools($request->toRequest()) + ->tools() ->first( fn ($tool): bool => $tool->name() === $request->params['name'], fn () => throw new JsonRpcException( From 06674db79ec534e3a7c49faeda328320c11e412c Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 29 Sep 2025 17:36:08 +0530 Subject: [PATCH 04/15] Add Docs Signed-off-by: Pushpak Chhajed --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 9c4bfcdf..50c5e026 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,50 @@ JSON Example: } ``` +## Adding Support for Custom IDE/AI Agents + +### Writing the Code Environment for an IDE/AI Agent + +To create a custom code environment for an IDE, you first need to define a class that extends +`\Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implements the appropriate contracts: + +- `Laravel\Boost\Contracts\Agent` +- `Laravel\Boost\Contracts\McpClient` + +### Example Implementation + +```php + Date: Mon, 29 Sep 2025 19:10:28 +0530 Subject: [PATCH 05/15] Fix Issues Signed-off-by: Pushpak Chhajed --- README.md | 2 +- src/BoostManager.php | 3 ++- tests/Unit/BoostManagerTest.php | 19 +++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 50c5e026..eeea9bf9 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ class OpenCode extends CodeEnvironment implements Agent, McpClient { // Your implementation goes here } -```` +``` ### Registering the Custom Code Environment diff --git a/src/BoostManager.php b/src/BoostManager.php index 6c3fa9df..bcf70773 100644 --- a/src/BoostManager.php +++ b/src/BoostManager.php @@ -4,6 +4,7 @@ namespace Laravel\Boost; +use InvalidArgumentException; use Laravel\Boost\Install\CodeEnvironment\ClaudeCode; use Laravel\Boost\Install\CodeEnvironment\CodeEnvironment; use Laravel\Boost\Install\CodeEnvironment\Codex; @@ -30,7 +31,7 @@ class BoostManager public function registerCodeEnvironment(string $key, string $className): void { if (array_key_exists($key, $this->codeEnvironments)) { - return; + throw new InvalidArgumentException("Code environment '{$key}' is already registered"); } $this->codeEnvironments[$key] = $className; diff --git a/tests/Unit/BoostManagerTest.php b/tests/Unit/BoostManagerTest.php index 80e972a6..6a6071ca 100644 --- a/tests/Unit/BoostManagerTest.php +++ b/tests/Unit/BoostManagerTest.php @@ -11,7 +11,7 @@ use Laravel\Boost\Install\CodeEnvironment\VSCode; use Tests\Unit\Install\ExampleCodeEnvironment; -it('return default code environments', function (): void { +it('returns default code environments', function (): void { $manager = new BoostManager; $registered = $manager->getCodeEnvironments(); @@ -49,14 +49,17 @@ ->and($registered)->toHaveKey('phpstorm'); }); -it('does not overwrite default code environments', function (): void { +it('throws an exception when registering a duplicate key', function (): void { $manager = new BoostManager; - $manager->registerCodeEnvironment('phpstorm', PHPStorm::class); - $manager->registerCodeEnvironment('phpstorm', ExampleCodeEnvironment::class); - $registered = $manager->getCodeEnvironments(); + expect(fn () => $manager->registerCodeEnvironment('phpstorm', ExampleCodeEnvironment::class)) + ->toThrow(InvalidArgumentException::class, "Code environment 'phpstorm' is already registered"); +}); + +it('throws exception when registering custom environment with a duplicate key', function (): void { + $manager = new BoostManager; + $manager->registerCodeEnvironment('custom', ExampleCodeEnvironment::class); - expect($registered)->toHaveKey('phpstorm') - ->and($registered['phpstorm'])->toBe(PHPStorm::class) - ->and($registered['phpstorm'])->not()->toBe(ExampleCodeEnvironment::class); + expect(fn () => $manager->registerCodeEnvironment('custom', ExampleCodeEnvironment::class)) + ->toThrow(InvalidArgumentException::class, "Code environment 'custom' is already registered"); }); From 314498b9057d7838e0856d4144a9cb9cb93786ab Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 29 Sep 2025 19:19:35 +0530 Subject: [PATCH 06/15] Fix Issues Signed-off-by: Pushpak Chhajed --- src/BoostServiceProvider.php | 4 ++-- src/Install/CodeEnvironmentsDetector.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BoostServiceProvider.php b/src/BoostServiceProvider.php index 4cdf0b4a..c54670df 100644 --- a/src/BoostServiceProvider.php +++ b/src/BoostServiceProvider.php @@ -26,12 +26,12 @@ public function register(): void 'boost' ); - $this->app->singleton(BoostManager::class, fn (): BoostManager => new BoostManager); - if (! $this->shouldRun()) { return; } + $this->app->singleton(BoostManager::class, fn (): BoostManager => new BoostManager); + $this->app->singleton(Roster::class, function () { $lockFiles = [ base_path('composer.lock'), diff --git a/src/Install/CodeEnvironmentsDetector.php b/src/Install/CodeEnvironmentsDetector.php index 99ca65a7..c88d962c 100644 --- a/src/Install/CodeEnvironmentsDetector.php +++ b/src/Install/CodeEnvironmentsDetector.php @@ -41,8 +41,8 @@ public function discoverSystemInstalledCodeEnvironments(): array public function discoverProjectInstalledCodeEnvironments(string $basePath): array { return $this->getCodeEnvironments() - ->filter(fn ($program): bool => $program->detectInProject($basePath)) - ->map(fn ($program): string => $program->name()) + ->filter(fn (CodeEnvironment $program): bool => $program->detectInProject($basePath)) + ->map(fn (CodeEnvironment $program): string => $program->name()) ->values() ->toArray(); } From a6d27cce6e2e6773f897fd9a4e6614773cfb8692 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 29 Sep 2025 19:33:49 +0530 Subject: [PATCH 07/15] Fix Test Signed-off-by: Pushpak Chhajed --- tests/Feature/BoostServiceProviderTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Feature/BoostServiceProviderTest.php b/tests/Feature/BoostServiceProviderTest.php index 7ad4e8bc..cd2e5bb4 100644 --- a/tests/Feature/BoostServiceProviderTest.php +++ b/tests/Feature/BoostServiceProviderTest.php @@ -78,12 +78,21 @@ }); describe('BoostManager registration', function (): void { + beforeEach(function (): void { + Config::set('boost.enabled', true); + app()->detectEnvironment(fn (): string => 'local'); + $provider = new BoostServiceProvider(app()); + $provider->register(); + $provider->boot(app('router')); + }); + it('registers BoostManager in the container', function (): void { expect(app()->bound(BoostManager::class))->toBeTrue() ->and(app(BoostManager::class))->toBeInstanceOf(BoostManager::class); }); it('registers BoostManager as a singleton', function (): void { + Config::set('boost.enabled', true); $instance1 = app(BoostManager::class); $instance2 = app(BoostManager::class); @@ -91,6 +100,7 @@ }); it('binds Boost facade to the same BoostManager instance', function (): void { + Config::set('boost.enabled', true); $containerInstance = app(BoostManager::class); $facadeInstance = Boost::getFacadeRoot(); From f4ad1501291e1dad329d06714aaebb67345b43c9 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Tue, 30 Sep 2025 09:27:17 +0530 Subject: [PATCH 08/15] Fix scroll option calculation in InstallCommand Signed-off-by: Pushpak Chhajed --- src/Console/InstallCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 1cdaf5c3..e934175d 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -351,7 +351,7 @@ protected function selectCodeEnvironments(string $contractClass, string $label): label: $label, options: $options->toArray(), default: array_unique($detectedClasses), - scroll: count($options->toArray()), + scroll: $options->count(), required: $config['required'], hint: $detectedClasses === [] ? '' : sprintf('Auto-detected %s for you', Arr::join(array_map(function ($className) use ($availableEnvironments, $config) { From 589076ee8fdf8650995910aa7b147bc0ecb5ec5a Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Wed, 1 Oct 2025 18:01:07 +0530 Subject: [PATCH 09/15] Replace a hardcoded IDE list Signed-off-by: Pushpak Chhajed --- src/Console/InstallCommand.php | 10 +++++----- src/Install/CodeEnvironment/CodeEnvironment.php | 15 +++++---------- src/Support/Composer.php | 10 +++++----- tests/Unit/Support/ComposerTest.php | 12 ++++++------ tests/Unit/Support/ConfigTest.php | 2 +- 5 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index c456db04..84d42cf5 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -261,7 +261,7 @@ protected function selectBoostFeatures(): Collection protected function selectAiGuidelines(): Collection { $options = app(GuidelineComposer::class)->guidelines() - ->reject(fn (array $guideline) => $guideline['third_party'] === false); + ->reject(fn (array $guideline): bool => $guideline['third_party'] === false); if ($options->isEmpty()) { return collect(); @@ -270,7 +270,7 @@ protected function selectAiGuidelines(): Collection return collect(multiselect( label: 'Which third-party AI guidelines do you want to install?', // @phpstan-ignore-next-line - options: $options->mapWithKeys(function (array $guideline, string $name) { + options: $options->mapWithKeys(function (array $guideline, string $name): array { $humanName = str_replace('/core', '', $name); return [$name => "{$humanName} (~{$guideline['tokens']} tokens) {$guideline['description']}"]; @@ -360,8 +360,8 @@ protected function selectCodeEnvironments(string $contractClass, string $label, $selectedCodeEnvironments = collect(multiselect( label: $label, options: $options->toArray(), - scroll: $options->count(), default: $defaults === [] ? $detectedDefaults : $defaults, + scroll: $options->count(), required: $config['required'], hint: $defaults === [] || $detectedDefaults === [] ? '' : sprintf('Auto-detected %s for you', Arr::join(array_map(function ($className) use ($availableEnvironments, $config) { @@ -447,11 +447,11 @@ protected function installGuidelines(): void ); $this->config->setEditors( - $this->selectedTargetMcpClient->map(fn (McpClient $mcpClient) => $mcpClient->name())->values()->toArray() + $this->selectedTargetMcpClient->map(fn (McpClient $mcpClient): string => $mcpClient->name())->values()->toArray() ); $this->config->setAgents( - $this->selectedTargetAgents->map(fn (Agent $agent) => $agent->name())->values()->toArray() + $this->selectedTargetAgents->map(fn (Agent $agent): string => $agent->name())->values()->toArray() ); $this->config->setGuidelines( diff --git a/src/Install/CodeEnvironment/CodeEnvironment.php b/src/Install/CodeEnvironment/CodeEnvironment.php index e6010e9c..ed66ee29 100644 --- a/src/Install/CodeEnvironment/CodeEnvironment.php +++ b/src/Install/CodeEnvironment/CodeEnvironment.php @@ -5,6 +5,7 @@ namespace Laravel\Boost\Install\CodeEnvironment; use Illuminate\Support\Facades\Process; +use Laravel\Boost\BoostManager; use Laravel\Boost\Contracts\Agent; use Laravel\Boost\Contracts\McpClient; use Laravel\Boost\Install\Detection\DetectionStrategyFactory; @@ -92,19 +93,13 @@ public function mcpInstallationStrategy(): McpInstallationStrategy return McpInstallationStrategy::FILE; } - public static function fromName(string $name): ?static + public static function fromName(string $name): ?CodeEnvironment { $detectionFactory = app(DetectionStrategyFactory::class); + $boostManager = app(BoostManager::class); - foreach ([ - ClaudeCode::class, - Codex::class, - Copilot::class, - Cursor::class, - PhpStorm::class, - VSCode::class, - ] as $class) { - /** @var class-string $class */ + foreach ($boostManager->getCodeEnvironments() as $class) { + /** @var class-string $class */ $instance = new $class($detectionFactory); if ($instance->name() === $name) { return $instance; diff --git a/src/Support/Composer.php b/src/Support/Composer.php index 25994027..57dcafd4 100644 --- a/src/Support/Composer.php +++ b/src/Support/Composer.php @@ -9,11 +9,11 @@ class Composer public static function packagesDirectories(): array { return collect(static::packages()) - ->mapWithKeys(fn (string $key, string $package) => [$package => implode(DIRECTORY_SEPARATOR, [ + ->mapWithKeys(fn (string $key, string $package): array => [$package => implode(DIRECTORY_SEPARATOR, [ base_path('vendor'), str_replace('/', DIRECTORY_SEPARATOR, $package), ])]) - ->filter(fn (string $path) => is_dir($path)) + ->filter(fn (string $path): bool => is_dir($path)) ->toArray(); } @@ -33,19 +33,19 @@ public static function packages(): array return collect($composerData['require'] ?? []) ->merge($composerData['require-dev'] ?? []) - ->mapWithKeys(fn (string $key, string $package) => [$package => $key]) + ->mapWithKeys(fn (string $key, string $package): array => [$package => $key]) ->toArray(); } public static function packagesDirectoriesWithBoostGuidelines(): array { return collect(Composer::packagesDirectories()) - ->map(fn (string $path) => implode(DIRECTORY_SEPARATOR, [ + ->map(fn (string $path): string => implode(DIRECTORY_SEPARATOR, [ $path, 'resources', 'boost', 'guidelines', - ]))->filter(fn (string $path) => is_dir($path)) + ]))->filter(fn (string $path): bool => is_dir($path)) ->toArray(); } } diff --git a/tests/Unit/Support/ComposerTest.php b/tests/Unit/Support/ComposerTest.php index 04def20b..69f86fac 100644 --- a/tests/Unit/Support/ComposerTest.php +++ b/tests/Unit/Support/ComposerTest.php @@ -2,12 +2,12 @@ use Laravel\Boost\Support\Config; -afterEach(function () { - (new Config(__DIR__))->flush(); +afterEach(function (): void { + (new Config)->flush(); }); it('may store and retrieve guidelines', function (): void { - $config = new Config(__DIR__); + $config = new Config; expect($config->getGuidelines())->toBeEmpty(); @@ -22,7 +22,7 @@ }); it('may store and retrieve agents', function (): void { - $config = new Config(__DIR__); + $config = new Config; expect($config->getAgents())->toBeEmpty(); @@ -37,7 +37,7 @@ }); it('may store and retrieve editors', function (): void { - $config = new Config(__DIR__); + $config = new Config; expect($config->getEditors())->toBeEmpty(); @@ -52,7 +52,7 @@ }); it('may store and retrieve herd mcp installation status', function (): void { - $config = new Config(__DIR__); + $config = new Config; expect($config->getHerdMcp())->toBeFalse(); diff --git a/tests/Unit/Support/ConfigTest.php b/tests/Unit/Support/ConfigTest.php index 4c979077..69f86fac 100644 --- a/tests/Unit/Support/ConfigTest.php +++ b/tests/Unit/Support/ConfigTest.php @@ -2,7 +2,7 @@ use Laravel\Boost\Support\Config; -afterEach(function () { +afterEach(function (): void { (new Config)->flush(); }); From 1384d88290fe7cd620b7622afc5ab5fdd6f2b390 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Thu, 2 Oct 2025 22:20:58 +0530 Subject: [PATCH 10/15] Fix Readme Signed-off-by: Pushpak Chhajed --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5dbbeda2..543c18f1 100644 --- a/README.md +++ b/README.md @@ -172,13 +172,7 @@ JSON Example: ## Adding Support for Custom IDE/AI Agents -### Writing the Code Environment for an IDE/AI Agent - -To create a custom code environment for an IDE, you first need to define a class that extends -`\Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implements the appropriate contracts: - -- `Laravel\Boost\Contracts\Agent` -- `Laravel\Boost\Contracts\McpClient` +To create a custom code environment for an IDE, you first need to define a class that extends `\Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implements the contracts: `Laravel\Boost\Contracts\Agent` and `Laravel\Boost\Contracts\McpClient` ### Example Implementation @@ -201,8 +195,7 @@ class OpenCode extends CodeEnvironment implements Agent, McpClient ### Registering the Custom Code Environment -Once your implementation is complete, register the custom environment using the `Boost` facade’s -`registerCodeEnvironment` method: +Once your implementation is complete, register the custom environment using the `Boost` facade’s `registerCodeEnvironment` method: ```php use Laravel\Boost\Boost; @@ -213,7 +206,6 @@ public function boot(): void } ``` - ## Contributing Thank you for considering contributing to Boost! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). From 7e0a411f3e5cef4baf6e8bcec7554714e4cd6236 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Thu, 9 Oct 2025 17:52:23 +0530 Subject: [PATCH 11/15] Update README.md Signed-off-by: Pushpak Chhajed --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 543c18f1..fd9a9fb1 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,11 @@ JSON Example: ## Adding Support for Custom IDE/AI Agents -To create a custom code environment for an IDE, you first need to define a class that extends `\Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implements the contracts: `Laravel\Boost\Contracts\Agent` and `Laravel\Boost\Contracts\McpClient` +Boost works with many popular IDEs and AI agents out of the box. If your coding tool isn’t supported yet, you can create your own code environment and integrate it with Boost. + +To do this, create a class that extends `Laravel\Boost\Install\CodeEnvironment\CodeEnvironment`and implement one or both of these contracts depending on what you need: +- `Laravel\Boost\Contracts\Agent` adds support for Guidelines +- `Laravel\Boost\Contracts\McpClient` adds support for MCP ### Example Implementation @@ -195,8 +199,7 @@ class OpenCode extends CodeEnvironment implements Agent, McpClient ### Registering the Custom Code Environment -Once your implementation is complete, register the custom environment using the `Boost` facade’s `registerCodeEnvironment` method: - +After implementing your class, register it using the `Boost` facade’s`registerCodeEnvironment` method. ```php use Laravel\Boost\Boost; From 4fab8be01eb9ae23de372672fd53f3694417732b Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Thu, 9 Oct 2025 18:24:04 +0530 Subject: [PATCH 12/15] Update README.md Signed-off-by: Pushpak Chhajed --- README.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fd9a9fb1..f5ebba67 100644 --- a/README.md +++ b/README.md @@ -172,13 +172,13 @@ JSON Example: ## Adding Support for Custom IDE/AI Agents -Boost works with many popular IDEs and AI agents out of the box. If your coding tool isn’t supported yet, you can create your own code environment and integrate it with Boost. +Boost works with many popular IDEs and AI agents out of the box. If your coding tool isn't supported yet, you can create your own code environment and integrate it with Boost. -To do this, create a class that extends `Laravel\Boost\Install\CodeEnvironment\CodeEnvironment`and implement one or both of these contracts depending on what you need: -- `Laravel\Boost\Contracts\Agent` adds support for Guidelines -- `Laravel\Boost\Contracts\McpClient` adds support for MCP +To do this, create a class that extends `Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implement one or both of these contracts depending on what you need: +- `Laravel\Boost\Contracts\Agent` - Adds support for AI guidelines +- `Laravel\Boost\Contracts\McpClient` - Adds support for MCP -### Example Implementation +### Writing the Code Environment ```php Date: Thu, 9 Oct 2025 18:30:20 +0530 Subject: [PATCH 13/15] Update README.md Signed-off-by: Pushpak Chhajed --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index d90e66a0..f9290ee2 100644 --- a/README.md +++ b/README.md @@ -218,8 +218,6 @@ namespace App; use Laravel\Boost\Contracts\Agent; use Laravel\Boost\Contracts\McpClient; use Laravel\Boost\Install\CodeEnvironment\CodeEnvironment; -use Laravel\Boost\Install\Enums\McpInstallationStrategy; -use Laravel\Boost\Install\Enums\Platform; class OpenCode extends CodeEnvironment implements Agent, McpClient { @@ -238,7 +236,7 @@ use Laravel\Boost\Boost; public function boot(): void { - Boost::registerCodeEnvironment('opencode', \App\Extensions\OpenCode::class); + Boost::registerCodeEnvironment('opencode', OpenCode::class); } ``` From 33af7ba3027364a3dbd7abbff4fc0091838f0b7b Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Thu, 9 Oct 2025 18:35:15 +0530 Subject: [PATCH 14/15] Update README.md Signed-off-by: Pushpak Chhajed --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9290ee2..d7a4df79 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ JSON Example: Boost works with many popular IDEs and AI agents out of the box. If your coding tool isn't supported yet, you can create your own code environment and integrate it with Boost. -To do this, create a class that extends `Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implement one or both of these contracts depending on what you need: +To do this, create a class that extends `Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implement one or both of the following contracts depending on what you need: - `Laravel\Boost\Contracts\Agent` - Adds support for AI guidelines - `Laravel\Boost\Contracts\McpClient` - Adds support for MCP @@ -240,7 +240,7 @@ public function boot(): void } ``` -Once registered, your code environment will be available when running `php artisan boost:install`. +Once registered, your code environment will be available for selection when running `php artisan boost:install`. ## Contributing From 9e4beaf7a0501f44882e4991b91d63b51a9e249c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 9 Oct 2025 09:57:45 -0500 Subject: [PATCH 15/15] Update README.md --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d7a4df79..17eeca21 100644 --- a/README.md +++ b/README.md @@ -198,13 +198,12 @@ JSON Example: } ``` -## Adding Support for Custom IDE/AI Agents +## Adding Support for Other IDEs / AI Agents -Boost works with many popular IDEs and AI agents out of the box. If your coding tool isn't supported yet, you can create your own code environment and integrate it with Boost. +Boost works with many popular IDEs and AI agents out of the box. If your coding tool isn't supported yet, you can create your own code environment and integrate it with Boost. To do this, create a class that extends `Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implement one or both of the following contracts depending on what you need: -To do this, create a class that extends `Laravel\Boost\Install\CodeEnvironment\CodeEnvironment` and implement one or both of the following contracts depending on what you need: -- `Laravel\Boost\Contracts\Agent` - Adds support for AI guidelines -- `Laravel\Boost\Contracts\McpClient` - Adds support for MCP +- `Laravel\Boost\Contracts\Agent` - Adds support for AI guidelines. +- `Laravel\Boost\Contracts\McpClient` - Adds support for MCP. ### Writing the Code Environment @@ -221,15 +220,15 @@ use Laravel\Boost\Install\CodeEnvironment\CodeEnvironment; class OpenCode extends CodeEnvironment implements Agent, McpClient { - // Your implementation goes here + // Your implementation... } ``` -For implementation examples, see [ClaudeCode.php](https://github.com/laravel/boost/blob/main/src/Install/CodeEnvironment/ClaudeCode.php). +For an example implementation, see [ClaudeCode.php](https://github.com/laravel/boost/blob/main/src/Install/CodeEnvironment/ClaudeCode.php). ### Registering the Code Environment -Register your custom code environment in the `boot` method of `App\Providers\AppServiceProvider`: +Register your custom code environment in the `boot` method of your application's `App\Providers\AppServiceProvider`: ```php use Laravel\Boost\Boost;