@different-ai/opencode-browser 2.0.2 → 3.0.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenCode Browser
2
2
 
3
- Browser automation plugin for [OpenCode](https://github.com/opencode-ai/opencode).
3
+ Browser automation MCP server for [OpenCode](https://github.com/opencode-ai/opencode).
4
4
 
5
5
  Control your real Chrome browser with existing logins, cookies, and bookmarks. No DevTools Protocol, no security prompts.
6
6
 
@@ -8,7 +8,7 @@ Control your real Chrome browser with existing logins, cookies, and bookmarks. N
8
8
 
9
9
  Chrome 136+ blocks `--remote-debugging-port` on your default profile for security reasons. DevTools-based automation (like Playwright) triggers a security prompt every time.
10
10
 
11
- OpenCode Browser uses a simple WebSocket connection between an OpenCode plugin and a Chrome extension. Your automation works with your existing browser session - no prompts, no separate profiles.
11
+ OpenCode Browser uses a simple WebSocket connection between an MCP server and a Chrome extension. Your automation works with your existing browser session - no prompts, no separate profiles.
12
12
 
13
13
  ## Installation
14
14
 
@@ -19,7 +19,7 @@ npx @different-ai/opencode-browser install
19
19
  The installer will:
20
20
  1. Copy the extension to `~/.opencode-browser/extension/`
21
21
  2. Guide you to load the extension in Chrome
22
- 3. Update your `opencode.json` to use the plugin
22
+ 3. Update your `opencode.json` with MCP server config
23
23
 
24
24
  ## Configuration
25
25
 
@@ -27,7 +27,12 @@ Add to your `opencode.json`:
27
27
 
28
28
  ```json
29
29
  {
30
- "plugin": ["@different-ai/opencode-browser"]
30
+ "mcp": {
31
+ "browser": {
32
+ "type": "local",
33
+ "command": ["bunx", "@different-ai/opencode-browser", "serve"]
34
+ }
35
+ }
31
36
  }
32
37
  ```
33
38
 
@@ -40,59 +45,65 @@ Then load the extension in Chrome:
40
45
 
41
46
  | Tool | Description |
42
47
  |------|-------------|
43
- | `browser_status` | Check if browser is available or locked |
44
- | `browser_kill_session` | Take over from another OpenCode session |
48
+ | `browser_status` | Check if browser extension is connected |
45
49
  | `browser_navigate` | Navigate to a URL |
46
50
  | `browser_click` | Click an element by CSS selector |
47
51
  | `browser_type` | Type text into an input field |
48
- | `browser_screenshot` | Capture the visible page |
49
- | `browser_snapshot` | Get accessibility tree with selectors |
52
+ | `browser_screenshot` | Capture the page (returns base64, optionally saves to file) |
53
+ | `browser_snapshot` | Get accessibility tree with selectors + all page links |
50
54
  | `browser_get_tabs` | List all open tabs |
51
55
  | `browser_scroll` | Scroll page or element into view |
52
56
  | `browser_wait` | Wait for a duration |
53
57
  | `browser_execute` | Run JavaScript in page context |
54
58
 
55
- ## Multi-Session Support
59
+ ### Screenshot Tool
56
60
 
57
- Only one OpenCode session can use the browser at a time. This prevents conflicts when you have multiple terminals open.
61
+ The `browser_screenshot` tool returns base64 image data by default, allowing AI to view images directly:
58
62
 
59
- - `browser_status` - Check who has the lock
60
- - `browser_kill_session` - Kill the other session and take over
63
+ ```javascript
64
+ // Returns base64 image (AI can view it)
65
+ browser_screenshot()
61
66
 
62
- In your prompts, you can say:
63
- - "If browser is locked, kill the session and proceed"
64
- - "If browser is locked, skip this task"
67
+ // Save to current working directory
68
+ browser_screenshot({ save: true })
69
+
70
+ // Save to specific path
71
+ browser_screenshot({ path: "my-screenshot.png" })
72
+ ```
65
73
 
66
74
  ## Architecture
67
75
 
68
76
  ```
69
- OpenCode Plugin ◄──WebSocket:19222──► Chrome Extension
70
-
71
- └── Lock file └── chrome.tabs, chrome.scripting
77
+ OpenCode <──STDIO──> MCP Server <──WebSocket:19222──> Chrome Extension
78
+
79
+ └── @modelcontextprotocol/sdk └── chrome.tabs, chrome.scripting
72
80
  ```
73
81
 
74
82
  **Two components:**
75
- 1. OpenCode plugin (runs WebSocket server, defines tools)
76
- 2. Chrome extension (connects to plugin, executes commands)
83
+ 1. MCP Server (runs as separate process, manages WebSocket server)
84
+ 2. Chrome extension (connects to server, executes browser commands)
77
85
 
78
- **No daemon. No MCP server. No native messaging host.**
86
+ **Benefits of MCP architecture:**
87
+ - No session conflicts between OpenCode instances
88
+ - Server runs independently of OpenCode process
89
+ - Clean separation of concerns
90
+ - Standard MCP protocol
79
91
 
80
- ## Upgrading from v1.x
92
+ ## Upgrading from v2.x (Plugin)
81
93
 
82
- v2.0 is a complete rewrite with a simpler architecture:
94
+ v3.0 migrates from plugin to MCP architecture:
83
95
 
84
- 1. Run `npx @different-ai/opencode-browser install` (cleans up old daemon automatically)
85
- 2. Replace MCP config with plugin config in `opencode.json`:
96
+ 1. Run `npx @different-ai/opencode-browser install`
97
+ 2. Replace plugin config with MCP config in `opencode.json`:
86
98
 
87
99
  ```diff
88
- - "mcp": {
89
- - "browser": {
90
- - "type": "local",
91
- - "command": ["npx", "@different-ai/opencode-browser", "start"],
92
- - "enabled": true
93
- - }
94
- - }
95
- + "plugin": ["@different-ai/opencode-browser"]
100
+ - "plugin": ["@different-ai/opencode-browser"]
101
+ + "mcp": {
102
+ + "browser": {
103
+ + "type": "local",
104
+ + "command": ["bunx", "@different-ai/opencode-browser", "serve"]
105
+ + }
106
+ + }
96
107
  ```
97
108
 
98
109
  3. Restart OpenCode
@@ -104,13 +115,13 @@ v2.0 is a complete rewrite with a simpler architecture:
104
115
  - Check that the extension is loaded and enabled
105
116
  - Click the extension icon to see connection status
106
117
 
107
- **"Browser locked by another session"**
108
- - Use `browser_kill_session` to take over
109
- - Or close the other OpenCode session
110
-
111
118
  **"Failed to start WebSocket server"**
112
119
  - Port 19222 may be in use
113
- - Check if another OpenCode session is running
120
+ - Run `lsof -i :19222` to check what's using it
121
+
122
+ **"browser_execute fails on some sites"**
123
+ - Sites with strict CSP block JavaScript execution
124
+ - Use `browser_snapshot` to get page data instead
114
125
 
115
126
  ## Uninstall
116
127
 
@@ -123,7 +134,7 @@ Then remove the extension from Chrome and delete `~/.opencode-browser/` if desir
123
134
  ## Platform Support
124
135
 
125
136
  - macOS ✓
126
- - Linux ✓
137
+ - Linux ✓
127
138
  - Windows (not yet supported)
128
139
 
129
140
  ## License
package/bin/cli.js CHANGED
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * OpenCode Browser - CLI Installer
3
+ * OpenCode Browser - CLI
4
4
  *
5
- * Installs the Chrome extension for browser automation.
6
- * v2.0: Plugin-based architecture (no daemon, no MCP server)
5
+ * Commands:
6
+ * install - Install Chrome extension
7
+ * serve - Run MCP server (used by OpenCode)
8
+ * status - Check connection status
7
9
  */
8
10
 
9
11
  import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync, readdirSync, unlinkSync } from "fs";
10
12
  import { homedir, platform } from "os";
11
13
  import { join, dirname } from "path";
12
14
  import { fileURLToPath } from "url";
13
- import { execSync } from "child_process";
15
+ import { execSync, spawn } from "child_process";
14
16
  import { createInterface } from "readline";
15
17
 
16
18
  const __filename = fileURLToPath(import.meta.url);
@@ -71,33 +73,71 @@ async function confirm(question) {
71
73
  }
72
74
 
73
75
  async function main() {
74
- console.log(`
75
- ${color("cyan", color("bright", "OpenCode Browser v2.0"))}
76
- ${color("cyan", "Browser automation for OpenCode")}
77
- `);
78
-
79
76
  const command = process.argv[2];
80
77
 
81
- if (command === "install") {
78
+ if (command === "serve") {
79
+ // Run MCP server - this is called by OpenCode
80
+ await serve();
81
+ } else if (command === "install") {
82
+ await showHeader();
82
83
  await install();
84
+ rl.close();
83
85
  } else if (command === "uninstall") {
86
+ await showHeader();
84
87
  await uninstall();
88
+ rl.close();
85
89
  } else if (command === "status") {
90
+ await showHeader();
86
91
  await status();
92
+ rl.close();
87
93
  } else {
94
+ await showHeader();
88
95
  log(`
89
96
  ${color("bright", "Usage:")}
90
97
  npx @different-ai/opencode-browser install Install extension
91
98
  npx @different-ai/opencode-browser uninstall Remove installation
92
- npx @different-ai/opencode-browser status Check lock status
93
-
94
- ${color("bright", "v2.0 Changes:")}
95
- - Plugin-based architecture (no daemon needed)
96
- - Add plugin to opencode.json, load extension in Chrome, done
99
+ npx @different-ai/opencode-browser status Check status
100
+ npx @different-ai/opencode-browser serve Run MCP server (internal)
101
+
102
+ ${color("bright", "Quick Start:")}
103
+ 1. Run: npx @different-ai/opencode-browser install
104
+ 2. Add to your opencode.json:
105
+ ${color("cyan", `"mcp": { "browser": { "type": "local", "command": ["bunx", "@different-ai/opencode-browser", "serve"] } }`)}
106
+ 3. Restart OpenCode
97
107
  `);
108
+ rl.close();
98
109
  }
110
+ }
99
111
 
100
- rl.close();
112
+ async function showHeader() {
113
+ console.log(`
114
+ ${color("cyan", color("bright", "OpenCode Browser v2.1"))}
115
+ ${color("cyan", "Browser automation MCP server for OpenCode")}
116
+ `);
117
+ }
118
+
119
+ async function serve() {
120
+ // Launch the MCP server
121
+ const serverPath = join(PACKAGE_ROOT, "src", "mcp-server.ts");
122
+
123
+ // Use bun to run the TypeScript server
124
+ const child = spawn("bun", ["run", serverPath], {
125
+ stdio: "inherit",
126
+ env: process.env,
127
+ });
128
+
129
+ child.on("error", (err) => {
130
+ console.error("[browser-mcp] Failed to start server:", err);
131
+ process.exit(1);
132
+ });
133
+
134
+ child.on("exit", (code) => {
135
+ process.exit(code || 0);
136
+ });
137
+
138
+ // Forward signals to child
139
+ process.on("SIGINT", () => child.kill("SIGINT"));
140
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
101
141
  }
102
142
 
103
143
  async function install() {
@@ -157,45 +197,50 @@ To load the extension:
157
197
 
158
198
  header("Step 4: Configure OpenCode");
159
199
 
160
- const pluginConfig = `{
161
- "$schema": "https://opencode.ai/config.json",
162
- "plugin": ["@different-ai/opencode-browser"]
163
- }`;
200
+ const mcpConfig = {
201
+ browser: {
202
+ type: "local",
203
+ command: ["bunx", "@different-ai/opencode-browser", "serve"],
204
+ },
205
+ };
164
206
 
165
207
  log(`
166
- Add the plugin to your ${color("cyan", "opencode.json")}:
208
+ Add the MCP server to your ${color("cyan", "opencode.json")}:
167
209
 
168
- ${color("bright", pluginConfig)}
210
+ ${color("bright", JSON.stringify({ $schema: "https://opencode.ai/config.json", mcp: mcpConfig }, null, 2))}
169
211
 
170
- Or if you already have an opencode.json, just add to the "plugin" array:
171
- ${color("bright", '"plugin": ["@different-ai/opencode-browser"]')}
212
+ Or if you already have an opencode.json, add to the "mcp" object:
213
+ ${color("bright", JSON.stringify({ mcp: mcpConfig }, null, 2))}
172
214
  `);
173
215
 
174
216
  const opencodeJsonPath = join(process.cwd(), "opencode.json");
175
217
 
176
218
  if (existsSync(opencodeJsonPath)) {
177
- const shouldUpdate = await confirm(`Found opencode.json. Add plugin automatically?`);
219
+ const shouldUpdate = await confirm(`Found opencode.json. Add MCP server automatically?`);
178
220
 
179
221
  if (shouldUpdate) {
180
222
  try {
181
223
  const config = JSON.parse(readFileSync(opencodeJsonPath, "utf-8"));
182
- config.plugin = config.plugin || [];
183
- if (!config.plugin.includes("@different-ai/opencode-browser")) {
184
- config.plugin.push("@different-ai/opencode-browser");
185
- }
186
- // Remove old MCP config if present
187
- if (config.mcp?.browser) {
188
- delete config.mcp.browser;
189
- if (Object.keys(config.mcp).length === 0) {
190
- delete config.mcp;
224
+ config.mcp = config.mcp || {};
225
+ config.mcp.browser = mcpConfig.browser;
226
+
227
+ // Remove old plugin config if present
228
+ if (config.plugin && Array.isArray(config.plugin)) {
229
+ const idx = config.plugin.indexOf("@different-ai/opencode-browser");
230
+ if (idx !== -1) {
231
+ config.plugin.splice(idx, 1);
232
+ warn("Removed old plugin entry (replaced by MCP)");
233
+ }
234
+ if (config.plugin.length === 0) {
235
+ delete config.plugin;
191
236
  }
192
- warn("Removed old MCP browser config (replaced by plugin)");
193
237
  }
238
+
194
239
  writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
195
- success("Updated opencode.json with plugin");
240
+ success("Updated opencode.json with MCP server");
196
241
  } catch (e) {
197
242
  error(`Failed to update opencode.json: ${e.message}`);
198
- log("Please add the plugin manually.");
243
+ log("Please add the MCP config manually.");
199
244
  }
200
245
  }
201
246
  } else {
@@ -205,60 +250,69 @@ ${color("bright", '"plugin": ["@different-ai/opencode-browser"]')}
205
250
  try {
206
251
  const config = {
207
252
  $schema: "https://opencode.ai/config.json",
208
- plugin: ["@different-ai/opencode-browser"],
253
+ mcp: mcpConfig,
209
254
  };
210
255
  writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
211
- success("Created opencode.json with plugin");
256
+ success("Created opencode.json with MCP server");
212
257
  } catch (e) {
213
258
  error(`Failed to create opencode.json: ${e.message}`);
214
259
  }
215
260
  }
216
261
  }
217
262
 
218
- // Clean up old daemon if present
219
- header("Step 5: Cleanup (v1.x migration)");
263
+ // Clean up old daemon/plugin if present
264
+ header("Step 5: Cleanup (migration)");
220
265
 
221
266
  const oldDaemonPlist = join(homedir(), "Library", "LaunchAgents", "com.opencode.browser-daemon.plist");
222
267
  if (existsSync(oldDaemonPlist)) {
223
268
  try {
224
269
  execSync(`launchctl unload "${oldDaemonPlist}" 2>/dev/null || true`, { stdio: "ignore" });
225
270
  unlinkSync(oldDaemonPlist);
226
- success("Removed old daemon (no longer needed in v2.0)");
271
+ success("Removed old daemon (no longer needed)");
227
272
  } catch {
228
273
  warn("Could not remove old daemon plist. Remove manually if needed.");
229
274
  }
230
- } else {
231
- success("No old daemon to clean up");
232
275
  }
233
276
 
277
+ // Remove old lock file
278
+ const oldLockFile = join(homedir(), ".opencode-browser", "lock.json");
279
+ if (existsSync(oldLockFile)) {
280
+ try {
281
+ unlinkSync(oldLockFile);
282
+ success("Removed old lock file (not needed with MCP)");
283
+ } catch {}
284
+ }
285
+
286
+ success("Cleanup complete");
287
+
234
288
  header("Installation Complete!");
235
289
 
236
290
  log(`
237
291
  ${color("green", "")} Extension: ${extensionDir}
238
- ${color("green", "")} Plugin: @different-ai/opencode-browser
292
+ ${color("green", "")} MCP Server: @different-ai/opencode-browser
239
293
 
240
294
  ${color("bright", "How it works:")}
241
- 1. OpenCode loads the plugin on startup
242
- 2. Plugin starts WebSocket server on port 19222
295
+ 1. OpenCode spawns MCP server on demand
296
+ 2. MCP server starts WebSocket server on port 19222
243
297
  3. Chrome extension connects automatically
244
- 4. Browser tools are available!
298
+ 4. Browser tools are available to any OpenCode session!
245
299
 
246
300
  ${color("bright", "Available tools:")}
247
- browser_status - Check if browser is available
248
- browser_kill_session - Take over from another session
301
+ browser_status - Check if browser is connected
249
302
  browser_navigate - Go to a URL
250
303
  browser_click - Click an element
251
304
  browser_type - Type into an input
252
305
  browser_screenshot - Capture the page
253
- browser_snapshot - Get accessibility tree
306
+ browser_snapshot - Get accessibility tree + all links
254
307
  browser_get_tabs - List open tabs
255
308
  browser_scroll - Scroll the page
256
309
  browser_wait - Wait for duration
257
310
  browser_execute - Run JavaScript
258
311
 
259
- ${color("bright", "Multi-session:")}
260
- Only one OpenCode session can use browser at a time.
261
- Use browser_status to check, browser_kill_session to take over.
312
+ ${color("bright", "Benefits of MCP architecture:")}
313
+ - No session conflicts between OpenCode instances
314
+ - Server runs independently of OpenCode process
315
+ - Clean separation of concerns
262
316
 
263
317
  ${color("bright", "Test it:")}
264
318
  Restart OpenCode and try: ${color("cyan", '"Check browser status"')}
@@ -266,35 +320,27 @@ ${color("bright", "Test it:")}
266
320
  }
267
321
 
268
322
  async function status() {
269
- header("Browser Lock Status");
270
-
271
- const lockFile = join(homedir(), ".opencode-browser", "lock.json");
272
-
273
- if (!existsSync(lockFile)) {
274
- success("Browser available (no lock file)");
275
- return;
276
- }
323
+ header("Browser Status");
277
324
 
325
+ // Check if port 19222 is in use
278
326
  try {
279
- const lock = JSON.parse(readFileSync(lockFile, "utf-8"));
280
- log(`
281
- Lock file: ${lockFile}
282
-
283
- PID: ${lock.pid}
284
- Session: ${lock.sessionId}
285
- Started: ${lock.startedAt}
286
- Working directory: ${lock.cwd}
287
- `);
288
-
289
- // Check if process is alive
290
- try {
291
- process.kill(lock.pid, 0);
292
- warn(`Process ${lock.pid} is running. Browser is locked.`);
293
- } catch {
294
- success(`Process ${lock.pid} is dead. Lock is stale and will be auto-cleaned.`);
327
+ const result = execSync("lsof -i :19222 2>/dev/null || true", { encoding: "utf-8" });
328
+ if (result.trim()) {
329
+ success("WebSocket server is running on port 19222");
330
+ log(result);
331
+ } else {
332
+ warn("WebSocket server not running (starts on demand via MCP)");
295
333
  }
296
- } catch (e) {
297
- error(`Could not read lock file: ${e.message}`);
334
+ } catch {
335
+ warn("Could not check port status");
336
+ }
337
+
338
+ // Check extension directory
339
+ const extensionDir = join(homedir(), ".opencode-browser", "extension");
340
+ if (existsSync(extensionDir)) {
341
+ success(`Extension installed at: ${extensionDir}`);
342
+ } else {
343
+ warn("Extension not installed. Run: npx @different-ai/opencode-browser install");
298
344
  }
299
345
  }
300
346
 
@@ -338,7 +384,7 @@ ${color("bright", "Note:")} Extension files at ~/.opencode-browser/ were not rem
338
384
  Remove manually if needed:
339
385
  rm -rf ~/.opencode-browser/
340
386
 
341
- Also remove "@different-ai/opencode-browser" from your opencode.json plugin array.
387
+ Also remove the "browser" entry from your opencode.json mcp section.
342
388
  `);
343
389
  }
344
390
 
@@ -215,7 +215,8 @@ async function toolSnapshot({ tabId }) {
215
215
  if (rect.width > 0 && rect.height > 0 && (isInteractive || el.innerText?.trim())) {
216
216
  const node = { uid: `e${uid}`, role: el.getAttribute("role") || el.tagName.toLowerCase(),
217
217
  name: getName(el).slice(0, 200), tag: el.tagName.toLowerCase() };
218
- if (el.tagName === "A" && el.href) node.href = el.href;
218
+ // Capture href for any element that has one (links, area, base, etc.)
219
+ if (el.href) node.href = el.href;
219
220
  if (el.tagName === "INPUT") { node.type = el.type; node.value = el.value; }
220
221
  if (el.id) node.selector = `#${el.id}`;
221
222
  else if (el.className && typeof el.className === "string") {
@@ -234,7 +235,27 @@ async function toolSnapshot({ tabId }) {
234
235
  return { nodes, nextUid: uid };
235
236
  }
236
237
 
237
- return { url: location.href, title: document.title, nodes: build(document.body).nodes.slice(0, 500) };
238
+ // Collect all links on the page separately for easy access
239
+ function getAllLinks() {
240
+ const links = [];
241
+ const seen = new Set();
242
+ document.querySelectorAll("a[href]").forEach(a => {
243
+ const href = a.href;
244
+ if (href && !seen.has(href) && !href.startsWith("javascript:")) {
245
+ seen.add(href);
246
+ const text = a.innerText?.trim().slice(0, 100) || a.getAttribute("aria-label") || "";
247
+ links.push({ href, text });
248
+ }
249
+ });
250
+ return links.slice(0, 100); // Limit to 100 links
251
+ }
252
+
253
+ return {
254
+ url: location.href,
255
+ title: document.title,
256
+ nodes: build(document.body).nodes.slice(0, 500),
257
+ links: getAllLinks()
258
+ };
238
259
  }
239
260
  });
240
261
 
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@different-ai/opencode-browser",
3
- "version": "2.0.2",
4
- "description": "Browser automation plugin for OpenCode. Control your real Chrome browser with existing logins and cookies.",
3
+ "version": "3.0.0",
4
+ "description": "Browser automation MCP server for OpenCode. Control your real Chrome browser with existing logins and cookies.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "opencode-browser": "./bin/cli.js"
8
8
  },
9
- "main": "./src/plugin.ts",
9
+ "main": "./src/mcp-server.ts",
10
10
  "exports": {
11
- ".": "./src/plugin.ts",
12
- "./plugin": "./src/plugin.ts"
11
+ ".": "./src/mcp-server.ts"
13
12
  },
14
13
  "files": [
15
14
  "bin",
@@ -18,14 +17,16 @@
18
17
  "README.md"
19
18
  ],
20
19
  "scripts": {
21
- "install-extension": "node bin/cli.js install"
20
+ "install-extension": "node bin/cli.js install",
21
+ "serve": "bun run src/mcp-server.ts"
22
22
  },
23
23
  "keywords": [
24
24
  "opencode",
25
25
  "browser",
26
26
  "automation",
27
27
  "chrome",
28
- "plugin"
28
+ "mcp",
29
+ "model-context-protocol"
29
30
  ],
30
31
  "author": "Benjamin Shafii",
31
32
  "license": "MIT",
@@ -37,11 +38,11 @@
37
38
  "url": "https://github.com/different-ai/opencode-browser/issues"
38
39
  },
39
40
  "homepage": "https://github.com/different-ai/opencode-browser#readme",
40
- "peerDependencies": {
41
- "@opencode-ai/plugin": "*"
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.25.2",
43
+ "zod": "^4.3.5"
42
44
  },
43
45
  "devDependencies": {
44
- "@opencode-ai/plugin": "*",
45
46
  "bun-types": "*"
46
47
  }
47
48
  }