A build-ready spec for a custom opencode v2 plugin that replaces opencode's built-in websearch with a self-hosted fastCRW search backend (POST {baseURL}/v1/search). The spec fixes the verified plugin API shape, config surface (endpoint + token), packaging/distribution, and the repo build steps. The build itself is handed off afterwards.
Chosen approach: a native websearch-provider plugin, NOT the official crw-mcp MCP server.
Config surface (decided): endpoint (baseURL) and token (apiKey) as plugin options, with env vars (CRW_API_URL / CRW_API_KEY) as fallback. The token must never be required in opencode.json.
The sample code supplied by the user (@opencode/plugin, Plugin.define, ctx.websearch.transform, plugins[].package/options) is a sketch, unverified against the real v2 docs. Facts come from the research ticket.
Setup items (npm init, Prettier, main branch protection via tea) are build steps that go into the spec, not map tickets. Only setup that unblocks a decision (installing skills) is a task ticket.
Tracker convention (no native parent/child in tea): each ticket's body starts with Map: #<map>; blocking uses Gitea's native issue dependencies.
Decisions so far
Research: verified opencode v2 plugin API — sketch is essentially right (Plugin.define needs a required id; plural plugins key; options via ctx.options; editor.add + editor.default.set(id)); TS types, time shape and provider-vs-default precedence unverified, check at runtime. Detail: docs/research/opencode-v2-plugin-api.md.
Research: fastCRW /v1/search API contract — POST {CRW_API_URL}/v1/search, optional Bearer CRW_API_KEY, query required, limit 1..20; data may be a flat array OR data.results (parse both); errors {success:false,error,error_code}; self-hosted search needs a SearXNG-style backend (else 503 search_disabled). Not tested against a live instance. Detail: docs/research/fastcrw-search-api.md.
Task: install opencode v2 and TypeScript skills — nothing to install: no opencode or TypeScript skill exists on skills.sh; existing skills (tdd, codebase-design, conventional-commits, gitea-tea) suffice, and docs/research/opencode-v2-plugin-api.md is the opencode reference.
Grilling: config surface for endpoint and token — options baseURL/apiKey/limit(8, clamp 1..20)/lang/tbs, env CRW_API_URL/CRW_API_KEY, default http://localhost:3000, token optional, setup throws on bad values, provider id fastcrw as the only default provider; three runtime checks listed in the ticket. Glossary: CONTEXT.md.
Grilling: packaging and distribution — Gitea npm registry, @haylan/opencode-fastcrw, plugin id fastcrw.websearch, tsc to dist/ (ESM), dev version in package.json; manual workflow_dispatch Action publishes the latest CHANGELOG.md version via a script against the registry API (no npm publish, skip if exists); main already protected. Runtime risk: scope-mapped install by name.
Grilling: result mapping and error semantics — parse data[] or data.results, map to {url,title,content,time:{}}, empty → [], throw fastCRW <status> <error_code>: <error> with hints for 401/503, abort untouched, fixed 30 s timeout, no retries, 429 status kept visible.
Grilling: test strategy, runtime checks and CI — node --test with mocked fetch over src/{config,search,index}.ts; four manual runtime checks and a manual npm run smoke recorded in docs/runtime-checks.md; .gitea/workflows/check.yml on PRs (ubuntu-latest, Node 24) becomes a required check on main; no pre-commit hooks.
Not yet specified
Out of scope
The crw-mcp MCP-server approach (mcp.servers.crw, websearch: false) — explicitly rejected in favour of a native plugin.
## Destination
A **build-ready spec** for a custom opencode v2 plugin that replaces opencode's built-in websearch with a self-hosted fastCRW search backend (`POST {baseURL}/v1/search`). The spec fixes the verified plugin API shape, config surface (endpoint + token), packaging/distribution, and the repo build steps. The build itself is handed off afterwards.
## Notes
- Domain: opencode v2 plugin (https://opencode.ai/v2/docs/plugins), fastCRW self-hosted search API, TypeScript, npm, Gitea (git.arthurerlich.de via `tea`).
- Chosen approach: a **native websearch-provider plugin**, NOT the official `crw-mcp` MCP server.
- Config surface (decided): endpoint (`baseURL`) and token (`apiKey`) as plugin options, with env vars (`CRW_API_URL` / `CRW_API_KEY`) as fallback. The token must never be required in `opencode.json`.
- The sample code supplied by the user (`@opencode/plugin`, `Plugin.define`, `ctx.websearch.transform`, `plugins[].package/options`) is a **sketch, unverified** against the real v2 docs. Facts come from the research ticket.
- Setup items (npm init, Prettier, main branch protection via `tea`) are build steps that go into the spec, not map tickets. Only setup that unblocks a decision (installing skills) is a task ticket.
- Consult skills: `/grilling`, `/domain-modeling`, `/research`, `gitea-tea`.
- Tracker convention (no native parent/child in `tea`): each ticket's body starts with `Map: #<map>`; blocking uses Gitea's native issue dependencies.
## Decisions so far
- [Research: verified opencode v2 plugin API](https://git.arthurerlich.de/haylan/fastcrw-opencode-websarch/issues/2) — sketch is essentially right (`Plugin.define` needs a required `id`; plural `plugins` key; options via `ctx.options`; `editor.add` + `editor.default.set(id)`); TS types, `time` shape and provider-vs-default precedence unverified, check at runtime. Detail: `docs/research/opencode-v2-plugin-api.md`.
- [Research: fastCRW /v1/search API contract](https://git.arthurerlich.de/haylan/fastcrw-opencode-websarch/issues/3) — `POST {CRW_API_URL}/v1/search`, optional Bearer `CRW_API_KEY`, `query` required, `limit` 1..20; `data` may be a flat array OR `data.results` (parse both); errors `{success:false,error,error_code}`; self-hosted search needs a SearXNG-style backend (else 503 `search_disabled`). Not tested against a live instance. Detail: `docs/research/fastcrw-search-api.md`.
- [Task: install opencode v2 and TypeScript skills](https://git.arthurerlich.de/haylan/fastcrw-opencode-websarch/issues/4) — nothing to install: no opencode or TypeScript skill exists on skills.sh; existing skills (tdd, codebase-design, conventional-commits, gitea-tea) suffice, and `docs/research/opencode-v2-plugin-api.md` is the opencode reference.
- [Grilling: config surface for endpoint and token](https://git.arthurerlich.de/haylan/fastcrw-opencode-websarch/issues/5) — options `baseURL`/`apiKey`/`limit`(8, clamp 1..20)/`lang`/`tbs`, env `CRW_API_URL`/`CRW_API_KEY`, default `http://localhost:3000`, token optional, `setup` throws on bad values, provider id `fastcrw` as the only default provider; three runtime checks listed in the ticket. Glossary: `CONTEXT.md`.
- [Grilling: packaging and distribution](https://git.arthurerlich.de/haylan/fastcrw-opencode-websarch/issues/6) — Gitea npm registry, `@haylan/opencode-fastcrw`, plugin id `fastcrw.websearch`, tsc to `dist/` (ESM), dev version in `package.json`; manual `workflow_dispatch` Action publishes the latest `CHANGELOG.md` version via a script against the registry API (no `npm publish`, skip if exists); `main` already protected. Runtime risk: scope-mapped install by name.
- [Grilling: result mapping and error semantics](https://git.arthurerlich.de/haylan/fastcrw-opencode-websarch/issues/7) — parse `data[]` or `data.results`, map to `{url,title,content,time:{}}`, empty → `[]`, throw `fastCRW <status> <error_code>: <error>` with hints for 401/503, abort untouched, fixed 30 s timeout, no retries, 429 status kept visible.
- [Grilling: test strategy, runtime checks and CI](https://git.arthurerlich.de/haylan/fastcrw-opencode-websarch/issues/8) — `node --test` with mocked `fetch` over `src/{config,search,index}.ts`; four manual runtime checks and a manual `npm run smoke` recorded in `docs/runtime-checks.md`; `.gitea/workflows/check.yml` on PRs (`ubuntu-latest`, Node 24) becomes a required check on `main`; no pre-commit hooks.
## Not yet specified
<!-- nothing left: all remaining fog graduated to tickets -->
## Out of scope
- The `crw-mcp` MCP-server approach (`mcp.servers.crw`, `websearch: false`) — explicitly rejected in favour of a native plugin.
Map complete. The destination, a build-ready spec, was reached and then built: the spec is docs/spec.md, the plugin is released as @haylan/opencode-fastcrw 0.0.1 (tag v0.0.1, package linked to this repository), and the runtime checks are recorded in docs/runtime-checks.md. All eight decision tickets were closed and nothing was left in the fog.
Map complete. The destination, a build-ready spec, was reached and then built: the spec is `docs/spec.md`, the plugin is released as `@haylan/opencode-fastcrw` 0.0.1 (tag `v0.0.1`, package linked to this repository), and the runtime checks are recorded in `docs/runtime-checks.md`. All eight decision tickets were closed and nothing was left in the fog.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Destination
A build-ready spec for a custom opencode v2 plugin that replaces opencode's built-in websearch with a self-hosted fastCRW search backend (
POST {baseURL}/v1/search). The spec fixes the verified plugin API shape, config surface (endpoint + token), packaging/distribution, and the repo build steps. The build itself is handed off afterwards.Notes
tea).crw-mcpMCP server.baseURL) and token (apiKey) as plugin options, with env vars (CRW_API_URL/CRW_API_KEY) as fallback. The token must never be required inopencode.json.@opencode/plugin,Plugin.define,ctx.websearch.transform,plugins[].package/options) is a sketch, unverified against the real v2 docs. Facts come from the research ticket.tea) are build steps that go into the spec, not map tickets. Only setup that unblocks a decision (installing skills) is a task ticket./grilling,/domain-modeling,/research,gitea-tea.tea): each ticket's body starts withMap: #<map>; blocking uses Gitea's native issue dependencies.Decisions so far
Plugin.defineneeds a requiredid; pluralpluginskey; options viactx.options;editor.add+editor.default.set(id)); TS types,timeshape and provider-vs-default precedence unverified, check at runtime. Detail:docs/research/opencode-v2-plugin-api.md.POST {CRW_API_URL}/v1/search, optional BearerCRW_API_KEY,queryrequired,limit1..20;datamay be a flat array ORdata.results(parse both); errors{success:false,error,error_code}; self-hosted search needs a SearXNG-style backend (else 503search_disabled). Not tested against a live instance. Detail:docs/research/fastcrw-search-api.md.docs/research/opencode-v2-plugin-api.mdis the opencode reference.baseURL/apiKey/limit(8, clamp 1..20)/lang/tbs, envCRW_API_URL/CRW_API_KEY, defaulthttp://localhost:3000, token optional,setupthrows on bad values, provider idfastcrwas the only default provider; three runtime checks listed in the ticket. Glossary:CONTEXT.md.@haylan/opencode-fastcrw, plugin idfastcrw.websearch, tsc todist/(ESM), dev version inpackage.json; manualworkflow_dispatchAction publishes the latestCHANGELOG.mdversion via a script against the registry API (nonpm publish, skip if exists);mainalready protected. Runtime risk: scope-mapped install by name.data[]ordata.results, map to{url,title,content,time:{}}, empty →[], throwfastCRW <status> <error_code>: <error>with hints for 401/503, abort untouched, fixed 30 s timeout, no retries, 429 status kept visible.node --testwith mockedfetchoversrc/{config,search,index}.ts; four manual runtime checks and a manualnpm run smokerecorded indocs/runtime-checks.md;.gitea/workflows/check.ymlon PRs (ubuntu-latest, Node 24) becomes a required check onmain; no pre-commit hooks.Not yet specified
Out of scope
crw-mcpMCP-server approach (mcp.servers.crw,websearch: false) — explicitly rejected in favour of a native plugin.Map complete. The destination, a build-ready spec, was reached and then built: the spec is
docs/spec.md, the plugin is released as@haylan/opencode-fastcrw0.0.1 (tagv0.0.1, package linked to this repository), and the runtime checks are recorded indocs/runtime-checks.md. All eight decision tickets were closed and nothing was left in the fog.