opencode-raven 2.1.1 → 2.1.2

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.
Files changed (3) hide show
  1. package/README.md +47 -33
  2. package/index.ts +56 -28
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -2,27 +2,27 @@
2
2
 
3
3
  <table>
4
4
  <tr>
5
- <td><img src="Raven.png" alt="Raven" width="768" /></td>
5
+ <td width="40%"><img src="Raven.png" alt="Raven" width="100%" /></td>
6
6
  <td>
7
- <strong>Context firewall for <a href="https://opencode.ai">OpenCode</a></strong><br/>
8
- Raven routes noisy search, docs, web, GitHub, and MCP calls through a free focused agent before they hit your main model, saving cost and context.
7
+ Raven gives OpenCode useful search tools by default and optional on-demand MCPs for any remote or local MCP you want to add. It keeps noisy tool work behind a focused Raven agent, so your main model gets compact answers instead of raw search results, docs pages, GitHub examples, MCP output, or large MCP schemas.
8
+
9
+ ## Why Use Raven?
10
+
11
+ * Routed search, fetch, and bash discovery work through Raven.
12
+ * Context7, Exa, and Grep.app are bundled as on-demand MCP defaults.
13
+ * Any extra MCP can be added under `onDemandMcpServers` when you want it.
14
+ * Configured noisy tools and MCPs are blocked for non-Raven agents and forced through `raven_seek`.
9
15
  </td>
10
16
  </tr>
11
17
  </table>
12
18
 
13
- ## What Raven Does
14
-
15
- Tool-heavy work floods context. Search, docs, web, GitHub examples, and verbose MCP calls can dump raw results into your expensive main model. Raven is a hard blocker-rerouter: configured tools fail closed for non-Raven agents and must be delegated through `raven_seek`, where a focused Raven agent performs the work and returns a compact answer.
16
-
17
- Raven fixes three problems:
18
-
19
- 1. **Context flooding** - Keep noisy tool results out of the main session. Raven summarizes tool/MCP output before returning it.
20
- 2. **Cost** - Use a cheaper model like `opencode/deepseek-v4-flash-free` for tool-heavy work while saving your main model's context for decisions and edits.
21
- 3. **Enforcement** - This is not soft nudging. Raven blocks configured tools/MCP prefixes for non-Raven agents and gives them the exact `raven_seek` retry path.
19
+ ## Example
22
20
 
23
- Raven defaults to routing noisy search/fetch tools and bundles Context7, Exa, and Grep.app as on-demand MCPs that are always handled behind Raven.
21
+ <p align="center">
22
+ <img src="example.gif" alt="Raven example: searching today's top 30 AI news items while saving 71K context" />
23
+ </p>
24
24
 
25
- Important limitation: MCPs enabled directly in OpenCode config still load their tool schemas into the main session. Raven saves context from tool calls and raw results for those global MCPs, but schema hiding requires putting the MCP in Raven's `onDemandMcpServers`. **Strongly recommended:** configure noisy/search/docs/web MCPs as on-demand MCPs unless you need direct main-agent access.
25
+ In this example, the main model asks for the top 30 AI news items from today. Raven handles the noisy search/web work in a focused agent and returns the compact result, saving about 71K context that would otherwise have landed in the main session.
26
26
 
27
27
  ## Install
28
28
 
@@ -36,6 +36,8 @@ Add Raven to your `opencode.jsonc` plugins:
36
36
 
37
37
  Restart opencode. It will resolve Raven from npm and cache the plugin automatically.
38
38
 
39
+ That is enough to start using Raven. The default config already includes routed search/fetch tools and bundled on-demand MCPs for Context7, Exa, and Grep.app. Add any other MCP under `onDemandMcpServers` only if you want more capabilities.
40
+
39
41
  Raven checks for package updates and notifies you when a newer version is available. Run `/raven update`, then restart opencode.
40
42
 
41
43
  ## Quick Use
@@ -78,17 +80,11 @@ The main agent does not see Raven's internal tool calls or raw tool output, just
78
80
 
79
81
  Config persists across restarts in `~/.config/opencode/opencode-raven/raven-config.json`. It is global and shared across all projects.
80
82
 
81
- ## Example
82
-
83
- <p align="center">
84
- <img src="example.gif" alt="Raven example: searching today's top 30 AI news items while saving 71K context" />
85
- </p>
86
-
87
- In this example, the main model asks for the top 30 AI news items from today. Raven handles the noisy search/web work in a focused agent and returns the compact result, saving about 71K context that would otherwise have landed in the main session.
88
-
89
83
  ## Recommended MCP Setup
90
84
 
91
- Raven's bundled Context7, Exa, and Grep.app MCPs are on-demand by default. They live in `onDemandMcpServers`, so Raven connects to them internally through `raven_mcp` instead of registering their full tool schemas in OpenCode's global MCP config.
85
+ You do not need to configure MCPs just to try Raven. Raven gives you the search tools up front, supports any MCP as an on-demand MCP, and bundles Context7, Exa, and Grep.app as defaults. Add more MCPs only when you want extra capabilities.
86
+
87
+ On-demand MCPs live in `onDemandMcpServers`, so Raven connects to them internally through `raven_mcp` only when needed instead of registering their full tool schemas in OpenCode's global MCP config. Context7, Exa, and Grep.app are included there by default, but the same setup works for any remote or local MCP you want to add.
92
88
 
93
89
  On-demand MCPs are always behind Raven and do not need routing entries.
94
90
 
@@ -96,6 +92,8 @@ Raven checks configured on-demand MCPs after startup. `/raven` and `/raven mcp`
96
92
 
97
93
  Raven also warns if the same MCP server name is configured both globally in OpenCode and in Raven's `onDemandMcpServers`. Prefer one location: on-demand for schema hiding, or global only when direct main-agent access is required.
98
94
 
95
+ Important limitation: MCPs enabled directly in OpenCode config still load their tool schemas into the main session. Raven saves context from tool calls and raw results for those global MCPs, but schema hiding requires putting the MCP in Raven's `onDemandMcpServers`. **Strongly recommended:** configure noisy/search/docs/web MCPs as on-demand MCPs unless you need direct main-agent access.
96
+
99
97
  All three bundled MCPs work without API keys. Add keys for higher rate limits:
100
98
 
101
99
  | MCP | URL | API key |
@@ -119,23 +117,36 @@ To add an API key to an on-demand MCP, edit Raven config:
119
117
  }
120
118
  ```
121
119
 
122
- To add a local stdio MCP:
120
+ ### Add Your Own On-Demand MCP
121
+
122
+ To add another MCP, put it under `onDemandMcpServers`. Raven will connect to it only when needed, discover its tools, and generate guidance automatically. Your main model does not need to load that MCP's full schema up front.
123
+
124
+ Use `remote` for hosted MCPs and `local` for command-based MCPs.
125
+
126
+ To add an OpenCode-style local MCP:
123
127
 
124
128
  ```jsonc
125
129
  {
126
130
  "onDemandMcpServers": {
127
- "localServer": {
128
- "type": "stdio",
129
- "command": "bunx",
130
- "args": ["some-mcp-server"],
131
- "env": { "API_KEY": "{env:API_KEY}" },
132
- "cwd": "/optional/path",
133
- "description": "Local MCP server."
131
+ "unityMCP": {
132
+ "type": "local",
133
+ "command": [
134
+ "C:\\Users\\evila\\.local\\bin\\uvx.exe",
135
+ "--offline",
136
+ "--from",
137
+ "mcpforunityserver==9.7.1",
138
+ "mcp-for-unity",
139
+ "--transport",
140
+ "stdio"
141
+ ],
142
+ "enabled": true
134
143
  }
135
144
  }
136
145
  }
137
146
  ```
138
147
 
148
+ This matches OpenCode's local MCP shape: `type: "local"`, `command` as an array, optional `environment`, optional `timeout`, and optional `enabled`. Raven also supports `cwd` and `description`. `enabled` defaults to `true`; set it to `false` to keep a server in config without loading it.
149
+
139
150
  `description` is user-authored and never overwritten. Raven fills missing generated metadata after startup or when you run `/raven mcp refresh [server]`, storing tool summaries, MCP health, and avoided schema-load estimates separately in `~/.config/opencode/opencode-raven/autogenerated-on-demand-mcp-metadata.json`.
140
151
 
141
152
  Raven also writes generated main-model guidance to `~/.config/opencode/opencode-raven/autogenerated-on-demand-guidance.md` and injects that file into OpenCode instructions at startup.
@@ -157,16 +168,19 @@ Edit config manually or use `/raven` commands:
157
168
  "onDemandMcpDescriptionDetail": "full",
158
169
  "onDemandMcpServers": {
159
170
  "context7": {
171
+ "enabled": true,
160
172
  "type": "remote",
161
173
  "url": "https://mcp.context7.com/mcp",
162
174
  "description": "Library/framework documentation lookup."
163
175
  },
164
176
  "exa": {
177
+ "enabled": true,
165
178
  "type": "remote",
166
179
  "url": "https://mcp.exa.ai/mcp",
167
180
  "description": "Live web search and webpage fetching."
168
181
  },
169
182
  "grep_app": {
183
+ "enabled": true,
170
184
  "type": "remote",
171
185
  "url": "https://mcp.grep.app",
172
186
  "description": "Public GitHub code search and real-world examples."
@@ -187,7 +201,7 @@ Edit config manually or use `/raven` commands:
187
201
  | `routeTools` | built-in search/fetch tools plus `bash` | Exact tool names hard-routed through Raven. `bash` routes only search-like bash commands, not every bash call. |
188
202
  | `routeToolKeywords` | `[]` | Case-insensitive substrings hard-routed through Raven for globally registered tools with non-standard names. Catches suffix-style names like `web_search_exa`. |
189
203
  | `onDemandMcpDescriptionDetail` | `"full"` | On-demand MCP guidance detail. `full` includes tool names/descriptions; `minimized` uses compact capability summaries. |
190
- | `onDemandMcpServers` | Context7, Exa, Grep.app | MCPs Raven connects to internally on demand, without registering full schemas in OpenCode global MCP config. Supports `remote` and `stdio`. |
204
+ | `onDemandMcpServers` | Context7, Exa, Grep.app | MCPs Raven connects to internally on demand, without registering full schemas in OpenCode global MCP config. Supports OpenCode-style `remote` and `local` entries. Each server supports `enabled`, defaulting to `true`. |
191
205
  | `excludeAgents` | `[]` | Agents that bypass Raven routing, case-insensitive. |
192
206
  | `excludeTools` | `[]` | Exact tools that never get blocked, even if matched by auto-routed global MCP prefixes. |
193
207
  | `timeout` | `180` | Max seconds for a `raven_seek` call. On timeout the session is kept for inspection. |
@@ -285,7 +299,7 @@ Raven itself has access to these tools, blocked for other agents when configured
285
299
  | `read`, `glob`, `grep`, `list` | Local codebase inspection |
286
300
  | `bash` | Full local shell access, including `rg`, `grep`, `dir`, `ls`, `Get-ChildItem`, and `find` |
287
301
  | `external_directory` | Allowed, with no permission prompts when accessing paths outside the workspace |
288
- | `raven_mcp` | Raven-only bridge to on-demand remote/stdio MCP servers |
302
+ | `raven_mcp` | Raven-only bridge to on-demand remote/local MCP servers |
289
303
  | Context7 | On-demand library/framework/SDK/API docs |
290
304
  | Exa AI | On-demand web search, news, pages, products |
291
305
  | Grep.app | On-demand public GitHub examples |
package/index.ts CHANGED
@@ -31,16 +31,19 @@ const DEFAULT_ROUTE_TOOL_KEYWORDS: string[] = []
31
31
 
32
32
  const DEFAULT_ON_DEMAND_MCP_SERVERS: Record<string, OnDemandMcpServer> = {
33
33
  context7: {
34
+ enabled: true,
34
35
  type: "remote",
35
36
  url: "https://mcp.context7.com/mcp",
36
37
  description: "Library/framework documentation lookup.",
37
38
  },
38
39
  exa: {
40
+ enabled: true,
39
41
  type: "remote",
40
42
  url: "https://mcp.exa.ai/mcp",
41
43
  description: "Live web search and webpage fetching.",
42
44
  },
43
45
  grep_app: {
46
+ enabled: true,
44
47
  type: "remote",
45
48
  url: "https://mcp.grep.app",
46
49
  description: "Public GitHub code search and real-world examples.",
@@ -138,6 +141,8 @@ interface OnDemandMcpToolInfo {
138
141
  }
139
142
 
140
143
  interface BaseOnDemandMcpServer {
144
+ enabled?: boolean
145
+ timeout?: number
141
146
  description?: string
142
147
  }
143
148
 
@@ -154,17 +159,17 @@ interface RemoteOnDemandMcpServer extends BaseOnDemandMcpServer {
154
159
  type: "remote"
155
160
  url: string
156
161
  headers?: Record<string, string>
162
+ oauth?: Record<string, unknown> | false
157
163
  }
158
164
 
159
- interface StdioOnDemandMcpServer extends BaseOnDemandMcpServer {
160
- type: "stdio"
161
- command: string
162
- args?: string[]
163
- env?: Record<string, string>
165
+ interface LocalOnDemandMcpServer extends BaseOnDemandMcpServer {
166
+ type: "local"
167
+ command: string[]
168
+ environment?: Record<string, string>
164
169
  cwd?: string
165
170
  }
166
171
 
167
- type OnDemandMcpServer = RemoteOnDemandMcpServer | StdioOnDemandMcpServer
172
+ type OnDemandMcpServer = RemoteOnDemandMcpServer | LocalOnDemandMcpServer
168
173
  type OnDemandMcpDescriptionDetail = "full" | "minimized"
169
174
  type RavenMcpOperation = "list_tools" | "call_tool" | "list_resources" | "read_resource" | "list_prompts" | "get_prompt"
170
175
 
@@ -303,32 +308,45 @@ function normalizeToolInfo(value: unknown): OnDemandMcpToolInfo[] | undefined {
303
308
  return tools.length ? tools : undefined
304
309
  }
305
310
 
311
+ function normalizeStringRecord(value: unknown): Record<string, string> | undefined {
312
+ if (!value || typeof value !== "object") return undefined
313
+ const entries = Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === "string")
314
+ return entries.length ? Object.fromEntries(entries) : undefined
315
+ }
316
+
306
317
  function normalizeOnDemandMcpServer(value: unknown): OnDemandMcpServer | undefined {
307
318
  if (!value || typeof value !== "object") return undefined
308
319
  const source = value as Record<string, any>
309
320
  const base = {
321
+ enabled: source.enabled !== false,
322
+ ...(typeof source.timeout === "number" && source.timeout > 0 ? { timeout: Math.floor(source.timeout) } : {}),
310
323
  ...(typeof source.description === "string" && source.description.trim() ? { description: source.description.trim() } : {}),
311
324
  }
312
325
 
313
- if (source.type === "stdio") {
314
- if (typeof source.command !== "string" || !source.command.trim()) return undefined
326
+ if (source.type === "local") {
327
+ const commandParts = Array.isArray(source.command)
328
+ ? source.command.filter((arg: unknown): arg is string => typeof arg === "string" && arg.trim())
329
+ : []
330
+ if (!commandParts.length) return undefined
331
+ const environment = normalizeStringRecord(source.environment)
315
332
  return {
316
333
  ...base,
317
- type: "stdio",
318
- command: source.command.trim(),
319
- ...(Array.isArray(source.args) ? { args: source.args.filter((arg: unknown) => typeof arg === "string") } : {}),
320
- ...(source.env && typeof source.env === "object" ? { env: Object.fromEntries(Object.entries(source.env).filter((entry): entry is [string, string] => typeof entry[1] === "string")) } : {}),
334
+ type: "local",
335
+ command: commandParts,
336
+ ...(environment ? { environment } : {}),
321
337
  ...(typeof source.cwd === "string" && source.cwd.trim() ? { cwd: source.cwd.trim() } : {}),
322
338
  }
323
339
  }
324
340
 
325
341
  if (source.type === "remote" || typeof source.url === "string") {
326
342
  if (typeof source.url !== "string" || !source.url.trim()) return undefined
343
+ const headers = normalizeStringRecord(source.headers)
327
344
  return {
328
345
  ...base,
329
346
  type: "remote",
330
347
  url: source.url.trim(),
331
- ...(source.headers && typeof source.headers === "object" ? { headers: Object.fromEntries(Object.entries(source.headers).filter((entry): entry is [string, string] => typeof entry[1] === "string")) } : {}),
348
+ ...(headers ? { headers } : {}),
349
+ ...(source.oauth === false ? { oauth: false } : source.oauth && typeof source.oauth === "object" ? { oauth: source.oauth } : {}),
332
350
  }
333
351
  }
334
352
 
@@ -491,7 +509,7 @@ export default ((input: PluginInput) => {
491
509
 
492
510
  function resetOnDemandMcpRuntimeStatuses() {
493
511
  let changed = false
494
- for (const name of Object.keys(config.onDemandMcpServers ?? {})) {
512
+ for (const [name] of activeOnDemandMcpEntries()) {
495
513
  const metadata = mcpMetadata[name] ?? {}
496
514
  if (metadata.status !== "pending" || metadata.lastError || metadata.lastCheckedAt) {
497
515
  mcpMetadata[name] = { ...metadata, status: "pending" }
@@ -591,8 +609,10 @@ export default ((input: PluginInput) => {
591
609
 
592
610
  function onDemandMcpGuidance(target: "delegate" | "raven" = "delegate"): string {
593
611
  const servers = config.onDemandMcpServers ?? {}
594
- const entries = Object.entries(servers).filter(([name]) => onDemandMcpRuntimeStatus(name) === "ready")
612
+ const activeEntries = activeOnDemandMcpEntries()
613
+ const entries = activeEntries.filter(([name]) => onDemandMcpRuntimeStatus(name) === "ready")
595
614
  if (!Object.keys(servers).length) return "No on-demand MCPs are configured."
615
+ if (!activeEntries.length) return "No enabled on-demand MCPs are configured."
596
616
  if (!entries.length) return "No loaded on-demand MCPs are available."
597
617
 
598
618
  const detail = config.onDemandMcpDescriptionDetail ?? "full"
@@ -694,22 +714,29 @@ export default ((input: PluginInput) => {
694
714
  }
695
715
 
696
716
  function getOnDemandMcpServer(serverName: string): [string, OnDemandMcpServer] | undefined {
697
- const entries = Object.entries(config.onDemandMcpServers ?? {})
717
+ const entries = activeOnDemandMcpEntries()
698
718
  return entries.find(([name]) => name.toLowerCase() === serverName.toLowerCase())
699
719
  }
700
720
 
721
+ function activeOnDemandMcpEntries(): [string, OnDemandMcpServer][] {
722
+ return Object.entries(config.onDemandMcpServers ?? {}).filter(([, server]) => server.enabled !== false)
723
+ }
724
+
701
725
  async function connectOnDemandMcp(serverName: string, server: OnDemandMcpServer): Promise<McpConnection> {
702
- const timeout = (config.timeout ?? 180) * 1000
726
+ const timeout = server.timeout ?? (config.timeout ?? 180) * 1000
703
727
  const client = new Client({ name: PACKAGE_NAME, version: PACKAGE_VERSION })
704
728
 
705
- if (server.type === "stdio") {
706
- const env = { ...cleanProcessEnv(), ...resolveStringRecord(server.env) }
729
+ if (server.type === "local") {
730
+ const env = { ...cleanProcessEnv(), ...resolveStringRecord(server.environment) }
707
731
  const transport = new StdioClientTransport({
708
- command: resolveEnvPlaceholders(server.command),
709
- args: server.args?.map((arg) => resolveEnvPlaceholders(arg)),
732
+ command: resolveEnvPlaceholders(server.command[0]),
733
+ args: server.command.slice(1).map((arg) => resolveEnvPlaceholders(arg)),
710
734
  env,
735
+ stderr: "pipe",
711
736
  cwd: server.cwd ? resolveEnvPlaceholders(server.cwd) : undefined,
712
737
  })
738
+ transport.stderr?.on("data", () => {})
739
+ transport.stderr?.on("error", () => {})
713
740
  await withTimeout(client.connect(transport), timeout, `Connecting to ${serverName}`)
714
741
  return { client, transport, serverName, lastUsed: Date.now() }
715
742
  }
@@ -800,7 +827,7 @@ export default ((input: PluginInput) => {
800
827
  }
801
828
 
802
829
  async function refreshOnDemandMcpDescriptions(force = false, serverName?: string): Promise<{ refreshed: string[]; failed: string[] }> {
803
- const entries = Object.entries(config.onDemandMcpServers ?? {})
830
+ const entries = activeOnDemandMcpEntries()
804
831
  .filter(([name]) => !serverName || name.toLowerCase() === serverName.toLowerCase())
805
832
  .filter(([name]) => {
806
833
  const metadata = mcpMetadata[name]
@@ -870,7 +897,7 @@ export default ((input: PluginInput) => {
870
897
 
871
898
  function onDemandMcpStatusBuckets(): { loaded: string[]; failed: string[]; pending: string[] } {
872
899
  const buckets = { loaded: [] as string[], failed: [] as string[], pending: [] as string[] }
873
- for (const name of Object.keys(config.onDemandMcpServers ?? {})) {
900
+ for (const [name] of activeOnDemandMcpEntries()) {
874
901
  const status = onDemandMcpRuntimeStatus(name)
875
902
  if (status === "ready") buckets.loaded.push(name)
876
903
  else if (status === "failed") buckets.failed.push(name)
@@ -880,7 +907,7 @@ export default ((input: PluginInput) => {
880
907
  }
881
908
 
882
909
  function failedOnDemandMcpNames(names?: string[]): string[] {
883
- const configured = Object.keys(config.onDemandMcpServers ?? {})
910
+ const configured = activeOnDemandMcpEntries().map(([name]) => name)
884
911
  const wanted = names?.length ? names : configured
885
912
  return wanted.filter((name) => configured.includes(name) && onDemandMcpRuntimeStatus(name) === "failed")
886
913
  }
@@ -903,8 +930,9 @@ export default ((input: PluginInput) => {
903
930
  }
904
931
 
905
932
  function onDemandMcpStatus(): string {
906
- const entries = Object.entries(config.onDemandMcpServers ?? {})
907
- if (!entries.length) return "Raven on-demand MCPs: none configured."
933
+ const entries = activeOnDemandMcpEntries()
934
+ if (!Object.keys(config.onDemandMcpServers ?? {}).length) return "Raven on-demand MCPs: none configured."
935
+ if (!entries.length) return "Raven on-demand MCPs: none enabled."
908
936
  const buckets = onDemandMcpStatusBuckets()
909
937
  const lines = entries.map(([name, server]) => {
910
938
  const metadata = mcpMetadata[name]
@@ -920,7 +948,7 @@ export default ((input: PluginInput) => {
920
948
  }
921
949
 
922
950
  function avoidedMcpSchemaBytes(): number {
923
- return Object.keys(config.onDemandMcpServers ?? {}).reduce((sum, name) => {
951
+ return activeOnDemandMcpEntries().reduce((sum, [name]) => {
924
952
  const metadata = mcpMetadata[name]
925
953
  if (onDemandMcpRuntimeStatus(name) !== "ready") return sum
926
954
  return sum + Math.max(0, metadata?.avoidedSchemaBytes ?? 0)
@@ -1157,7 +1185,7 @@ export default ((input: PluginInput) => {
1157
1185
  globalMcpServers = uniqueStrings(Object.entries(configInput.mcp ?? {})
1158
1186
  .filter(([, value]) => !value || typeof value !== "object" || (value as any).enabled !== false)
1159
1187
  .map(([name]) => name))
1160
- const onDemandNames = new Set(Object.keys(config.onDemandMcpServers ?? {}).map((name) => name.toLowerCase()))
1188
+ const onDemandNames = new Set(activeOnDemandMcpEntries().map(([name]) => name.toLowerCase()))
1161
1189
  duplicateMcpServers = globalMcpServers.filter((name) => onDemandNames.has(name.toLowerCase()))
1162
1190
 
1163
1191
  // Inject MCP guidance as a startup instruction file (absolute path for npm compat).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-raven",
3
- "version": "2.1.1",
4
- "description": "Raven is a context firewall for OpenCode. It routes noisy search, docs, web, GitHub, and MCP calls through a free focused agent before they hit your main model, saving cost and context!",
3
+ "version": "2.1.2",
4
+ "description": "Ready-to-use search tools and optional on-demand MCPs for OpenCode, routed through Raven to save your main model's context and cost.",
5
5
  "main": "./index.ts",
6
6
  "exports": {
7
7
  ".": "./index.ts"