opencode-raven 1.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 +111 -0
- package/Raven.md +61 -0
- package/Raven.png +0 -0
- package/index.ts +237 -0
- package/mcp-guidance.md +5 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# opencode-raven
|
|
2
|
+
|
|
3
|
+
Search-first subagent for [opencode](https://opencode.ai) — intercepts search tool calls from other agents and routes them to a dedicated **@raven** agent with Context7, Exa AI, and Grep.app MCPs.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
Other agents (orchestrator, fixer, etc.) waste tokens and context on search tools. Raven gives them a single delegation target that's purpose-built for search, with the right MCPs wired in and a compact-output prompt.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add opencode-raven
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then add to your `opencode.jsonc`:
|
|
16
|
+
|
|
17
|
+
```jsonc
|
|
18
|
+
{
|
|
19
|
+
"plugin": ["opencode-raven"]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Restart opencode.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | Action |
|
|
28
|
+
|---------|--------|
|
|
29
|
+
| `/raven` | Show status — enabled/disabled, current model |
|
|
30
|
+
| `/raven on` | Enable search tool redirection to @raven (default) |
|
|
31
|
+
| `/raven off` | Disable interception — all agents can use search tools directly |
|
|
32
|
+
| `/raven model <name>` | Change Raven's model (e.g. `/raven model opencode/deepseek-v4-flash-free`) |
|
|
33
|
+
|
|
34
|
+
Config persists across restarts in `raven-config.json` (next to your `opencode.jsonc`).
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
### raven-config.json
|
|
39
|
+
|
|
40
|
+
Created automatically in your project root on first toggle. Edit manually or use `/raven` commands:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"enabled": true,
|
|
45
|
+
"model": "opencode/deepseek-v4-flash-free"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Field | Default | Description |
|
|
50
|
+
|-------|---------|-------------|
|
|
51
|
+
| `enabled` | `true` | Whether search tool interception is active |
|
|
52
|
+
| `model` | *(from Raven.md)* | Override Raven's model without editing package files |
|
|
53
|
+
|
|
54
|
+
### MCP servers
|
|
55
|
+
|
|
56
|
+
All three MCPs are enabled by default:
|
|
57
|
+
|
|
58
|
+
| MCP | URL | Notes |
|
|
59
|
+
|-----|-----|-------|
|
|
60
|
+
| Context7 | `https://mcp.context7.com/mcp` | No API key needed |
|
|
61
|
+
| Exa AI | `https://mcp.exa.ai/mcp` | Requires Exa account |
|
|
62
|
+
| Grep.app | `https://mcp.grep.app` | No API key needed |
|
|
63
|
+
|
|
64
|
+
To disable an MCP, override it in your `opencode.jsonc`:
|
|
65
|
+
|
|
66
|
+
```jsonc
|
|
67
|
+
{
|
|
68
|
+
"mcp": {
|
|
69
|
+
"exa": { "type": "remote", "url": "https://mcp.exa.ai/mcp", "enabled": false }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## How it works
|
|
75
|
+
|
|
76
|
+
| Hook | What it does |
|
|
77
|
+
|------|--------------|
|
|
78
|
+
| `config` | Registers Raven agent, adds Context7/Exa/Grep.app MCPs, loads MCP guidance |
|
|
79
|
+
| `chat.message` | Tracks Raven's session IDs so its own tools aren't blocked |
|
|
80
|
+
| `command.execute.before` | Handles `/raven on\|off\|model\|status` |
|
|
81
|
+
| `tool.execute.before` | Nukes search tool args for non-Raven agents (no wasted API calls) |
|
|
82
|
+
| `tool.execute.after` | Replaces search tool output with redirect to Raven |
|
|
83
|
+
|
|
84
|
+
**Blocked tools** (redirected to Raven for all agents except Raven itself):
|
|
85
|
+
|
|
86
|
+
| Tool | Raven's equivalent |
|
|
87
|
+
|------|-------------------|
|
|
88
|
+
| `websearch_web_search_exa` | Exa AI (web search) |
|
|
89
|
+
| `exa_web_search_exa` | Exa AI (web search via MCP) |
|
|
90
|
+
| `exa_web_fetch_exa` | Exa AI (page fetch via MCP) |
|
|
91
|
+
| `grep_app_searchGitHub` | Grep.app (GitHub examples) |
|
|
92
|
+
| `grep` | grep/rg (local code search) |
|
|
93
|
+
| `glob` | glob (file search) |
|
|
94
|
+
|
|
95
|
+
**Unrestricted**: `webfetch`, `read`, `bash`, `task`, and all other tools.
|
|
96
|
+
|
|
97
|
+
## Agent capabilities
|
|
98
|
+
|
|
99
|
+
| Tool / MCP | Purpose |
|
|
100
|
+
|------------|---------|
|
|
101
|
+
| `read`, `glob`, `grep`, `list` | Local codebase inspection |
|
|
102
|
+
| `bash` (`rg`, `grep`, `git grep`) | Local search commands |
|
|
103
|
+
| Context7 | Library/framework/SDK/API docs |
|
|
104
|
+
| Exa AI | Web search, news, pages, products |
|
|
105
|
+
| Grep.app | Public GitHub examples |
|
|
106
|
+
|
|
107
|
+
Raven returns compact findings: answer, sources, relevant details, recommended next step, and uncertainty.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
package/Raven.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Search-only agent for web, docs, code, examples, and Unity project inspection.
|
|
3
|
+
mode: subagent
|
|
4
|
+
hidden: true
|
|
5
|
+
model: opencode/deepseek-v4-flash-free
|
|
6
|
+
reasoning_effort: low
|
|
7
|
+
permission:
|
|
8
|
+
read: allow
|
|
9
|
+
glob: allow
|
|
10
|
+
grep: allow
|
|
11
|
+
list: allow
|
|
12
|
+
edit: deny
|
|
13
|
+
bash:
|
|
14
|
+
|
|
15
|
+
"rg *": allow
|
|
16
|
+
"grep *": allow
|
|
17
|
+
"git grep *": allow
|
|
18
|
+
"*": deny
|
|
19
|
+
|
|
20
|
+
task: deny
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
You are Raven.
|
|
24
|
+
|
|
25
|
+
You search only.
|
|
26
|
+
You return compact findings only.
|
|
27
|
+
|
|
28
|
+
Use tools/MCPs like this:
|
|
29
|
+
|
|
30
|
+
**Local code search:** use rg, grep, glob, list, and read only small relevant sections.
|
|
31
|
+
|
|
32
|
+
**MCP usage guidance:**
|
|
33
|
+
|
|
34
|
+
*Context7:*
|
|
35
|
+
Use when implementing, configuring, or debugging code that depends on a library, framework, SDK, package, or API.
|
|
36
|
+
Prefer Context7 over memory when docs may be version-specific or recently changed.
|
|
37
|
+
|
|
38
|
+
*Exa AI:*
|
|
39
|
+
Use for live web search, current information, company/product research, reading webpages, comparing tools, and broad external research.
|
|
40
|
+
Use Exa when the answer may depend on recent updates, pricing, docs pages, releases, or online sources.
|
|
41
|
+
|
|
42
|
+
*Grep.app:*
|
|
43
|
+
Use for searching public GitHub code examples, real-world usage patterns, config examples, and how other projects structure similar code.
|
|
44
|
+
Use Grep.app when docs are unclear or when implementation examples would help.
|
|
45
|
+
|
|
46
|
+
Output format:
|
|
47
|
+
|
|
48
|
+
Answer:
|
|
49
|
+
* Short direct finding.
|
|
50
|
+
|
|
51
|
+
Sources / locations:
|
|
52
|
+
* File paths, URLs, docs, examples, or Unity objects checked.
|
|
53
|
+
|
|
54
|
+
Relevant details:
|
|
55
|
+
* Small notes only. No long code dumps.
|
|
56
|
+
|
|
57
|
+
Recommended next step:
|
|
58
|
+
* What the caller should do next.
|
|
59
|
+
|
|
60
|
+
Uncertainty:
|
|
61
|
+
* Anything unclear or not found.
|
package/Raven.png
ADDED
|
Binary file
|
package/index.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { Plugin, PluginInput } from "@opencode-ai/plugin"
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
|
|
5
|
+
// ── Resolve paths relative to this package (works in node_modules/) ──
|
|
6
|
+
const PKG_DIR = import.meta.dirname!
|
|
7
|
+
|
|
8
|
+
const RAVEN_MD = join(PKG_DIR, "Raven.md")
|
|
9
|
+
const MCP_GUIDANCE_MD = join(PKG_DIR, "mcp-guidance.md")
|
|
10
|
+
|
|
11
|
+
// ── Search tools that should be intercepted for non-Raven agents ──
|
|
12
|
+
const SEARCH_TOOLS = [
|
|
13
|
+
"websearch_web_search_exa",
|
|
14
|
+
"exa_web_search_exa",
|
|
15
|
+
"exa_web_fetch_exa",
|
|
16
|
+
"grep_app_searchGitHub",
|
|
17
|
+
"grep",
|
|
18
|
+
"glob",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
// ── Config file shape ──
|
|
22
|
+
interface RavenConfig {
|
|
23
|
+
enabled: boolean
|
|
24
|
+
model?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DEFAULT_CONFIG: RavenConfig = { enabled: true }
|
|
28
|
+
|
|
29
|
+
// ── Parse Raven.md frontmatter ──
|
|
30
|
+
const ravenMd = readFileSync(RAVEN_MD, "utf-8")
|
|
31
|
+
const { frontmatter: fm, prompt: ravenPrompt } = parseRavenMd(ravenMd)
|
|
32
|
+
|
|
33
|
+
function parseRavenMd(raw: string): { frontmatter: Record<string, any>; prompt: string } {
|
|
34
|
+
const parts = raw.split("---")
|
|
35
|
+
if (parts.length < 3) {
|
|
36
|
+
throw new Error("Raven.md missing frontmatter (--- delimiters)")
|
|
37
|
+
}
|
|
38
|
+
return { frontmatter: parseYaml(parts[1]), prompt: parts.slice(2).join("---").trim() }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Minimal YAML parser (handles the structure used in Raven.md) ──
|
|
42
|
+
function parseYaml(yaml: string): Record<string, any> {
|
|
43
|
+
const lines = yaml.split("\n")
|
|
44
|
+
const root: Record<string, any> = {}
|
|
45
|
+
const stack: Array<{ obj: Record<string, any>; indent: number }> = [
|
|
46
|
+
{ obj: root, indent: -1 },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
for (const rawLine of lines) {
|
|
50
|
+
const line = rawLine.trimEnd()
|
|
51
|
+
if (!line.trim() || line.trim().startsWith("#")) continue
|
|
52
|
+
|
|
53
|
+
const indent = line.search(/\S/)
|
|
54
|
+
const colonIdx = line.indexOf(":")
|
|
55
|
+
if (colonIdx === -1) continue
|
|
56
|
+
|
|
57
|
+
const rawKey = line.slice(indent, colonIdx).trim()
|
|
58
|
+
const key =
|
|
59
|
+
(rawKey.startsWith('"') && rawKey.endsWith('"')) ||
|
|
60
|
+
(rawKey.startsWith("'") && rawKey.endsWith("'"))
|
|
61
|
+
? rawKey.slice(1, -1)
|
|
62
|
+
: rawKey
|
|
63
|
+
|
|
64
|
+
const rawValue = line.slice(colonIdx + 1).trim()
|
|
65
|
+
|
|
66
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
67
|
+
stack.pop()
|
|
68
|
+
}
|
|
69
|
+
const current = stack[stack.length - 1].obj
|
|
70
|
+
|
|
71
|
+
if (!rawValue) {
|
|
72
|
+
const nested: Record<string, any> = {}
|
|
73
|
+
current[key] = nested
|
|
74
|
+
stack.push({ obj: nested, indent })
|
|
75
|
+
} else {
|
|
76
|
+
let value: any = rawValue
|
|
77
|
+
if (
|
|
78
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
79
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
80
|
+
) {
|
|
81
|
+
value = value.slice(1, -1)
|
|
82
|
+
} else if (value === "true") {
|
|
83
|
+
value = true
|
|
84
|
+
} else if (value === "false") {
|
|
85
|
+
value = false
|
|
86
|
+
}
|
|
87
|
+
current[key] = value
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return root
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Move unknown frontmatter fields into options ──
|
|
95
|
+
const KNOWN_KEYS = new Set([
|
|
96
|
+
"description", "mode", "hidden", "model", "permission",
|
|
97
|
+
"prompt", "name", "color", "steps", "disable",
|
|
98
|
+
"temperature", "top_p", "variant",
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
function extractOptions(fm: Record<string, any>): Record<string, any> {
|
|
102
|
+
const options: Record<string, any> = {}
|
|
103
|
+
for (const key of Object.keys(fm)) {
|
|
104
|
+
if (!KNOWN_KEYS.has(key)) options[key] = fm[key]
|
|
105
|
+
}
|
|
106
|
+
return options
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Plugin ──
|
|
110
|
+
export default ((input: PluginInput) => {
|
|
111
|
+
// Config file lives in the project directory (next to opencode.jsonc)
|
|
112
|
+
const configFile = join(input.directory, "raven-config.json")
|
|
113
|
+
|
|
114
|
+
function loadConfig(): RavenConfig {
|
|
115
|
+
try {
|
|
116
|
+
if (existsSync(configFile)) {
|
|
117
|
+
const raw = JSON.parse(readFileSync(configFile, "utf-8"))
|
|
118
|
+
return {
|
|
119
|
+
enabled: raw.enabled !== false,
|
|
120
|
+
model: raw.model,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch { /* ignore corruption, use defaults */ }
|
|
124
|
+
return { ...DEFAULT_CONFIG }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function saveConfig(config: RavenConfig) {
|
|
128
|
+
try {
|
|
129
|
+
writeFileSync(configFile, JSON.stringify(config, null, 2) + "\n")
|
|
130
|
+
} catch { /* non-fatal: config won't persist but toggle still works in-session */ }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let config = loadConfig()
|
|
134
|
+
const ravenSessions = new Set<string>()
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
config(configInput: any) {
|
|
138
|
+
// MCP servers
|
|
139
|
+
configInput.mcp = configInput.mcp || {}
|
|
140
|
+
configInput.mcp.context7 = {
|
|
141
|
+
type: "remote", url: "https://mcp.context7.com/mcp", enabled: true,
|
|
142
|
+
}
|
|
143
|
+
configInput.mcp.exa = {
|
|
144
|
+
type: "remote", url: "https://mcp.exa.ai/mcp", enabled: true,
|
|
145
|
+
}
|
|
146
|
+
configInput.mcp.grep_app = {
|
|
147
|
+
type: "remote", url: "https://mcp.grep.app", enabled: true,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Inject MCP guidance as a startup instruction file (absolute path for npm compat)
|
|
151
|
+
configInput.instructions = configInput.instructions || []
|
|
152
|
+
if (!configInput.instructions.includes(MCP_GUIDANCE_MD)) {
|
|
153
|
+
configInput.instructions.push(MCP_GUIDANCE_MD)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Register Raven from Raven.md, with config file overrides
|
|
157
|
+
configInput.agent = configInput.agent || {}
|
|
158
|
+
configInput.agent.raven = {
|
|
159
|
+
description: fm.description || "",
|
|
160
|
+
mode: fm.mode || "subagent",
|
|
161
|
+
hidden: fm.hidden !== undefined ? fm.hidden : false,
|
|
162
|
+
model: config.model || fm.model,
|
|
163
|
+
options: extractOptions(fm),
|
|
164
|
+
permission: fm.permission || {},
|
|
165
|
+
prompt: ravenPrompt,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Register /raven command
|
|
169
|
+
configInput.command = configInput.command || {}
|
|
170
|
+
if (!configInput.command.raven) {
|
|
171
|
+
configInput.command.raven = {
|
|
172
|
+
template: "Manage Raven: /raven on|off|model <name>|status",
|
|
173
|
+
description: "Toggle search interception or change Raven's model",
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Track Raven sessions so we don't block its own tools
|
|
179
|
+
"chat.message"(input: any, _output: any) {
|
|
180
|
+
if (input.agent === "raven") {
|
|
181
|
+
ravenSessions.add(input.sessionID)
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
// /raven on|off|model <name>|status
|
|
186
|
+
"command.execute.before"(input: any, output: any) {
|
|
187
|
+
if (input.command !== "raven") return
|
|
188
|
+
output.parts.length = 0
|
|
189
|
+
const raw = input.arguments.trim()
|
|
190
|
+
const arg = raw.toLowerCase()
|
|
191
|
+
|
|
192
|
+
if (arg === "on") {
|
|
193
|
+
config.enabled = true
|
|
194
|
+
saveConfig(config)
|
|
195
|
+
output.parts.push({ type: "text", text: "Raven search interception enabled. Non-Raven agents will be redirected to @raven for search tools." })
|
|
196
|
+
} else if (arg === "off") {
|
|
197
|
+
config.enabled = false
|
|
198
|
+
saveConfig(config)
|
|
199
|
+
output.parts.push({ type: "text", text: "Raven search interception disabled. All agents can use search tools directly." })
|
|
200
|
+
} else if (arg.startsWith("model ")) {
|
|
201
|
+
const model = raw.slice(6).trim()
|
|
202
|
+
if (!model) {
|
|
203
|
+
output.parts.push({ type: "text", text: `Usage: /raven model <name>\nCurrent model: ${config.model || fm.model || "(default)"}` })
|
|
204
|
+
} else {
|
|
205
|
+
config.model = model
|
|
206
|
+
saveConfig(config)
|
|
207
|
+
output.parts.push({ type: "text", text: `Raven model set to: ${model}\nRestart opencode for the change to take effect.` })
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
const enabled = config.enabled ? "enabled" : "disabled"
|
|
211
|
+
const model = config.model || fm.model || "(default)"
|
|
212
|
+
output.parts.push({ type: "text", text: `Raven is ${enabled}. Model: ${model}\n\nCommands:\n /raven on — enable search interception\n /raven off — disable search interception\n /raven model <name> — change Raven's model (requires restart)` })
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
"tool.execute.before"(input: any, output: any) {
|
|
217
|
+
if (!config.enabled) return
|
|
218
|
+
if (!SEARCH_TOOLS.includes(input.tool)) return
|
|
219
|
+
if (ravenSessions.has(input.sessionID)) return
|
|
220
|
+
// Break args to prevent actual API calls
|
|
221
|
+
const args = output.args || {}
|
|
222
|
+
if ("query" in args) args.query = ""
|
|
223
|
+
output.args = { ...output.args, ...args }
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
"tool.execute.after"(input: any, output: any) {
|
|
227
|
+
if (!config.enabled) return
|
|
228
|
+
if (!SEARCH_TOOLS.includes(input.tool)) return
|
|
229
|
+
if (ravenSessions.has(input.sessionID)) return
|
|
230
|
+
const msg =
|
|
231
|
+
"This tool is disabled. Delegate to Raven instead: use the task tool with subagent_type=\"raven\"."
|
|
232
|
+
output.output = msg
|
|
233
|
+
const raw = output as any
|
|
234
|
+
if (raw.content?.[0]?.text) raw.content[0].text = msg
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
}) satisfies Plugin
|
package/mcp-guidance.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
## MCP usage guidance — delegate to Raven (subagent_type="raven") for these:
|
|
2
|
+
|
|
3
|
+
- Context7: use when implementing, configuring, or debugging code that depends on a library, framework, SDK, package, or API. Prefer over memory when docs may be version-specific or recently changed.
|
|
4
|
+
- Exa AI: use for live web search, current information, company/product research, reading webpages, comparing tools, and broad external research. Use when the answer depends on recent updates, pricing, docs pages, releases, or online sources.
|
|
5
|
+
- Grep.app: use for searching public GitHub code examples, real-world usage patterns, config examples, and how other projects structure similar code. Use when docs are unclear or when implementation examples would help.
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-raven",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Search-first subagent for opencode — intercepts search tools and routes them to a dedicated @raven agent with Context7, Exa AI, and Grep.app MCPs",
|
|
5
|
+
"main": "./index.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.ts"
|
|
8
|
+
},
|
|
9
|
+
"types": "./index.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"Raven.md",
|
|
13
|
+
"Raven.png",
|
|
14
|
+
"mcp-guidance.md",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"opencode",
|
|
19
|
+
"opencode-plugin",
|
|
20
|
+
"search",
|
|
21
|
+
"subagent",
|
|
22
|
+
"mcp",
|
|
23
|
+
"context7",
|
|
24
|
+
"exa",
|
|
25
|
+
"grep.app"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"bun": ">=1.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|