Skip to content

Commit 35ad5a9

Browse files
calebdwpushpak1300
andauthored
feat: add opencode support (#88)
* feat: add opencode support * chore: use command -v instead of which The `which` command is not always available whereas `command -v` is a POSIX built-in. * refactor: standardize MCP server configuration methods and tests for consistency Signed-off-by: Pushpak Chhajed <[email protected]> * keep the breaking changes Signed-off-by: Pushpak Chhajed <[email protected]> * fix: update mcpServerConfig visibility to public Signed-off-by: Pushpak Chhajed <[email protected]> * clean up annotations Signed-off-by: Pushpak Chhajed <[email protected]> --------- Signed-off-by: Pushpak Chhajed <[email protected]> Co-authored-by: Pushpak Chhajed <[email protected]> Co-authored-by: Pushpak Chhajed <[email protected]>
1 parent e0c3498 commit 35ad5a9

File tree

8 files changed

+172
-28
lines changed

8 files changed

+172
-28
lines changed

src/BoostManager.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Laravel\Boost\Install\CodeEnvironment\Codex;
1111
use Laravel\Boost\Install\CodeEnvironment\Copilot;
1212
use Laravel\Boost\Install\CodeEnvironment\Cursor;
13+
use Laravel\Boost\Install\CodeEnvironment\OpenCode;
1314
use Laravel\Boost\Install\CodeEnvironment\PhpStorm;
1415
use Laravel\Boost\Install\CodeEnvironment\VSCode;
1516

@@ -23,6 +24,7 @@ class BoostManager
2324
'claudecode' => ClaudeCode::class,
2425
'codex' => Codex::class,
2526
'copilot' => Copilot::class,
27+
'opencode' => OpenCode::class,
2628
];
2729

2830
/**

src/Install/CodeEnvironment/ClaudeCode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function systemDetectionConfig(Platform $platform): array
2525
{
2626
return match ($platform) {
2727
Platform::Darwin, Platform::Linux => [
28-
'command' => 'which claude',
28+
'command' => 'command -v claude',
2929
],
3030
Platform::Windows => [
3131
'command' => 'where claude 2>nul',

src/Install/CodeEnvironment/CodeEnvironment.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ public function mcpConfigKey(): string
129129
return 'mcpServers';
130130
}
131131

132+
/** @return array<string, mixed> */
133+
public function defaultMcpConfig(): array
134+
{
135+
return [];
136+
}
137+
132138
/**
133139
* Install MCP server using the appropriate strategy.
134140
*
@@ -144,6 +150,22 @@ public function installMcp(string $key, string $command, array $args = [], array
144150
};
145151
}
146152

153+
/**
154+
* Build the MCP server configuration payload for file-based installation.
155+
*
156+
* @param array<int, string> $args
157+
* @param array<string, string> $env
158+
* @return array<string, mixed>
159+
*/
160+
public function mcpServerConfig(string $command, array $args = [], array $env = []): array
161+
{
162+
return [
163+
'command' => $command,
164+
'args' => $args,
165+
'env' => $env,
166+
];
167+
}
168+
147169
/**
148170
* Install MCP server using a shell command strategy.
149171
*
@@ -198,9 +220,9 @@ protected function installFileMcp(string $key, string $command, array $args = []
198220
return false;
199221
}
200222

201-
return (new FileWriter($path))
223+
return (new FileWriter($path, $this->defaultMcpConfig()))
202224
->configKey($this->mcpConfigKey())
203-
->addServer($key, $command, $args, $env)
225+
->addServerConfig($key, $this->mcpServerConfig($command, $args, $env))
204226
->save();
205227
}
206228
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laravel\Boost\Install\CodeEnvironment;
6+
7+
use Laravel\Boost\Contracts\Agent;
8+
use Laravel\Boost\Contracts\McpClient;
9+
use Laravel\Boost\Install\Enums\McpInstallationStrategy;
10+
use Laravel\Boost\Install\Enums\Platform;
11+
12+
class OpenCode extends CodeEnvironment implements Agent, McpClient
13+
{
14+
public function name(): string
15+
{
16+
return 'opencode';
17+
}
18+
19+
public function displayName(): string
20+
{
21+
return 'OpenCode';
22+
}
23+
24+
public function systemDetectionConfig(Platform $platform): array
25+
{
26+
return match ($platform) {
27+
Platform::Darwin, Platform::Linux => [
28+
'command' => 'command -v opencode',
29+
],
30+
Platform::Windows => [
31+
'command' => 'where opencode 2>nul',
32+
],
33+
};
34+
}
35+
36+
public function projectDetectionConfig(): array
37+
{
38+
return [
39+
'files' => ['AGENTS.md', 'opencode.json'],
40+
];
41+
}
42+
43+
public function mcpInstallationStrategy(): McpInstallationStrategy
44+
{
45+
return McpInstallationStrategy::FILE;
46+
}
47+
48+
public function mcpConfigPath(): string
49+
{
50+
return 'opencode.json';
51+
}
52+
53+
public function guidelinesPath(): string
54+
{
55+
return 'AGENTS.md';
56+
}
57+
58+
public function mcpConfigKey(): string
59+
{
60+
return 'mcp';
61+
}
62+
63+
/** {@inheritDoc} */
64+
public function defaultMcpConfig(): array
65+
{
66+
return [
67+
'$schema' => 'https://opencode.ai/config.json',
68+
];
69+
}
70+
71+
/** {@inheritDoc} */
72+
public function mcpServerConfig(string $command, array $args = [], array $env = []): array
73+
{
74+
return [
75+
'type' => 'local',
76+
'enabled' => true,
77+
'command' => [$command, ...$args],
78+
'environment' => $env,
79+
];
80+
}
81+
}

src/Install/CodeEnvironment/VSCode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function systemDetectionConfig(Platform $platform): array
2626
'paths' => ['/Applications/Visual Studio Code.app'],
2727
],
2828
Platform::Linux => [
29-
'command' => 'which code',
29+
'command' => 'command -v code',
3030
],
3131
Platform::Windows => [
3232
'paths' => [

src/Install/Mcp/FileWriter.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class FileWriter
1515

1616
protected int $defaultIndentation = 8;
1717

18-
public function __construct(protected string $filePath) {}
18+
public function __construct(protected string $filePath, protected array $baseConfig = []) {}
1919

2020
public function configKey(string $key): self
2121
{
@@ -25,17 +25,28 @@ public function configKey(string $key): self
2525
}
2626

2727
/**
28-
* @param string $key MCP Server Name
28+
* @deprecated Use addServerConfig() for array-based configuration.
29+
*
2930
* @param array<int, string> $args
3031
* @param array<string, string> $env
3132
*/
3233
public function addServer(string $key, string $command, array $args = [], array $env = []): self
3334
{
34-
$this->serversToAdd[$key] = collect([
35+
return $this->addServerConfig($key, collect([
3536
'command' => $command,
3637
'args' => $args,
3738
'env' => $env,
38-
])->filter()->toArray();
39+
])->filter(fn ($value): bool => ! in_array($value, [[], null, ''], true))->toArray());
40+
}
41+
42+
/**
43+
* @param array<string, mixed> $config
44+
*/
45+
public function addServerConfig(string $key, array $config): self
46+
{
47+
$this->serversToAdd[$key] = collect($config)
48+
->filter(fn ($value): bool => ! in_array($value, [[], null, ''], true))
49+
->toArray();
3950

4051
return $this;
4152
}
@@ -358,7 +369,7 @@ protected function hasUnquotedComments(string $content): bool
358369

359370
protected function createNewFile(): bool
360371
{
361-
$config = [];
372+
$config = $this->baseConfig;
362373
$this->addServersToConfig($config);
363374

364375
return $this->writeJsonConfig($config);

tests/Unit/Install/CodeEnvironmentsDetectorTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Laravel\Boost\Install\CodeEnvironment\Codex;
1111
use Laravel\Boost\Install\CodeEnvironment\Copilot;
1212
use Laravel\Boost\Install\CodeEnvironment\Cursor;
13+
use Laravel\Boost\Install\CodeEnvironment\OpenCode;
1314
use Laravel\Boost\Install\CodeEnvironment\PhpStorm;
1415
use Laravel\Boost\Install\CodeEnvironment\VSCode;
1516
use Laravel\Boost\Install\CodeEnvironmentsDetector;
@@ -29,9 +30,9 @@
2930
$codeEnvironments = $this->detector->getCodeEnvironments();
3031

3132
expect($codeEnvironments)->toBeInstanceOf(Collection::class)
32-
->and($codeEnvironments->count())->toBe(6)
33+
->and($codeEnvironments->count())->toBe(7)
3334
->and($codeEnvironments->keys()->toArray())->toBe([
34-
'phpstorm', 'vscode', 'cursor', 'claudecode', 'codex', 'copilot',
35+
'phpstorm', 'vscode', 'cursor', 'claudecode', 'codex', 'copilot', 'opencode',
3536
]);
3637

3738
$codeEnvironments->each(function ($environment): void {
@@ -62,6 +63,7 @@
6263
$this->container->bind(ClaudeCode::class, fn () => $mockOther);
6364
$this->container->bind(Codex::class, fn () => $mockOther);
6465
$this->container->bind(Copilot::class, fn () => $mockOther);
66+
$this->container->bind(OpenCode::class, fn () => $mockOther);
6567

6668
$detector = new CodeEnvironmentsDetector($this->container, $this->boostManager);
6769
$detected = $detector->discoverSystemInstalledCodeEnvironments();
@@ -80,6 +82,7 @@
8082
$this->container->bind(ClaudeCode::class, fn () => $mockEnvironment);
8183
$this->container->bind(Codex::class, fn () => $mockEnvironment);
8284
$this->container->bind(Copilot::class, fn () => $mockEnvironment);
85+
$this->container->bind(OpenCode::class, fn () => $mockEnvironment);
8386

8487
$detector = new CodeEnvironmentsDetector($this->container, $this->boostManager);
8588
$detected = $detector->discoverSystemInstalledCodeEnvironments();
@@ -112,6 +115,7 @@
112115
$this->container->bind(ClaudeCode::class, fn () => $mockClaudeCode);
113116
$this->container->bind(Codex::class, fn () => $mockOther);
114117
$this->container->bind(Copilot::class, fn () => $mockOther);
118+
$this->container->bind(OpenCode::class, fn () => $mockOther);
115119

116120
$detector = new CodeEnvironmentsDetector($this->container, $this->boostManager);
117121
$detected = $detector->discoverProjectInstalledCodeEnvironments($basePath);
@@ -132,6 +136,7 @@
132136
$this->container->bind(ClaudeCode::class, fn () => $mockEnvironment);
133137
$this->container->bind(Codex::class, fn () => $mockEnvironment);
134138
$this->container->bind(Copilot::class, fn () => $mockEnvironment);
139+
$this->container->bind(OpenCode::class, fn () => $mockEnvironment);
135140

136141
$detector = new CodeEnvironmentsDetector($this->container, $this->boostManager);
137142
$detected = $detector->discoverProjectInstalledCodeEnvironments($basePath);

0 commit comments

Comments
 (0)