diff --git a/.claude/skills/fastcrw-search-api/SKILL.md b/.claude/skills/fastcrw-search-api/SKILL.md index d4b752e..4acc207 100644 --- a/.claude/skills/fastcrw-search-api/SKILL.md +++ b/.claude/skills/fastcrw-search-api/SKILL.md @@ -35,16 +35,16 @@ Result: `url`, `title`, `description`, `snippet` (always-populated alias of `des Body `{ "success": false, "error": string, "error_code": string }`. -| HTTP | error_code | -|---|---| -| 400 | `invalid_request` | -| 401 | auth middleware message | -| 422 | `target_unreachable` | -| 429 | `rate_limited` | -| 500 | `internal_error` | -| 502 | `http_error` | -| 503 | `search_disabled` / `search_degraded` | -| 504 | `timeout` | +| HTTP | error_code | +| ---- | ------------------------------------- | +| 400 | `invalid_request` | +| 401 | auth middleware message | +| 422 | `target_unreachable` | +| 429 | `rate_limited` | +| 500 | `internal_error` | +| 502 | `http_error` | +| 503 | `search_disabled` / `search_degraded` | +| 504 | `timeout` | Self-hosted search needs a SearXNG-style backend (`CRW_SEARCH__SEARCH_BACKEND_URL`); without one expect 503 `search_disabled`. diff --git a/.claude/skills/opencode-v2-plugin/SKILL.md b/.claude/skills/opencode-v2-plugin/SKILL.md index b48cc66..97430f0 100644 --- a/.claude/skills/opencode-v2-plugin/SKILL.md +++ b/.claude/skills/opencode-v2-plugin/SKILL.md @@ -43,8 +43,13 @@ await ctx.websearch.transform((editor) => { ```jsonc { - "plugins": [{ "package": "~/.config/opencode/plugins/fastcrw", "options": { "baseURL": "http://crw.home:3000", "limit": 8 } }], - "websearch": { "provider": "fastcrw" } + "plugins": [ + { + "package": "~/.config/opencode/plugins/fastcrw", + "options": { "baseURL": "http://crw.home:3000", "limit": 8 }, + }, + ], + "websearch": { "provider": "fastcrw" }, } ``` diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..16acd49 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules +dist +package-lock.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..7acf4f0 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "semi": false, + "printWidth": 100 +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ffb1aba --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [Unreleased] diff --git a/docs/research/fastcrw-search-api.md b/docs/research/fastcrw-search-api.md index c551b21..ad47e28 100644 --- a/docs/research/fastcrw-search-api.md +++ b/docs/research/fastcrw-search-api.md @@ -18,14 +18,14 @@ Resolves issue #3. Researched 2026-10-03 against `main` of github.com/us/crw (`f ## Env var names -| Var | Used by | Meaning | -|---|---|---| -| `CRW_API_URL` | crw-mcp, crw CLI | Remote server base URL. Default `https://api.fastcrw.com`; `http://localhost:3000` for self-hosted. | -| `CRW_API_KEY` | crw-mcp, crw CLI | Bearer token for the remote server. | -| `CRW_LOCAL` | crw-mcp | Truthy forces embedded mode, ignoring `CRW_API_URL`. | -| `CRW_AUTH__API_KEYS` | server | Accepted keys (server side). | -| `CRW_SEARCH__*` | server | Search config (`enabled`, `search_backend_url`, `timeout_ms`, `default_limit`, `max_limit`). | -| `CRW_SEARXNG_URL` | CLI | Legacy local search backend URL. | +| Var | Used by | Meaning | +| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------- | +| `CRW_API_URL` | crw-mcp, crw CLI | Remote server base URL. Default `https://api.fastcrw.com`; `http://localhost:3000` for self-hosted. | +| `CRW_API_KEY` | crw-mcp, crw CLI | Bearer token for the remote server. | +| `CRW_LOCAL` | crw-mcp | Truthy forces embedded mode, ignoring `CRW_API_URL`. | +| `CRW_AUTH__API_KEYS` | server | Accepted keys (server side). | +| `CRW_SEARCH__*` | server | Search config (`enabled`, `search_backend_url`, `timeout_ms`, `default_limit`, `max_limit`). | +| `CRW_SEARXNG_URL` | CLI | Legacy local search backend URL. | Sources: docs.fastcrw.com (REST API section), crates/crw-core/src/config.rs, crw-mcp README. Plugin should use `CRW_API_URL` / `CRW_API_KEY` to match upstream naming. @@ -34,16 +34,16 @@ Plugin should use `CRW_API_URL` / `CRW_API_KEY` to match upstream naming. Source: `SearchRequest` in crates/crw-core/src/types.rs (camelCase serde); blog/crw-search-api-release.md. -| Field | Type | Notes | -|---|---|---| -| `query` | string, required | Max 2000 chars (handler validation). | -| `limit` | int, optional | Default 5 (`default_limit`); clamped server-side to `1..=max_limit` (max 20). | -| `lang` | string | SearXNG language, e.g. `en`, `de`, `auto`; validated as a language tag. | -| `tbs` | enum | `qdr:h`, `qdr:d`, `qdr:w`, `qdr:m`, `qdr:y`. | -| `sources` | array | `web`, `news`, `images`. Presence switches response to grouped form. | -| `categories` | array, max 5 | `github`, `research`, `pdf`, or passthrough string. | -| `scrapeOptions` | object | Scrapes each web result (timeout bounds 1..60000 ms). Adds `markdown` etc. to results. | -| `answer`, `answerTopN`, `summarizeResults`, `maxCharsPerSource`, `llmApiKey`, `llmProvider`, `llmModel` | | LLM synthesis; need `scrapeOptions`. Not needed by this plugin. | +| Field | Type | Notes | +| ------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------- | +| `query` | string, required | Max 2000 chars (handler validation). | +| `limit` | int, optional | Default 5 (`default_limit`); clamped server-side to `1..=max_limit` (max 20). | +| `lang` | string | SearXNG language, e.g. `en`, `de`, `auto`; validated as a language tag. | +| `tbs` | enum | `qdr:h`, `qdr:d`, `qdr:w`, `qdr:m`, `qdr:y`. | +| `sources` | array | `web`, `news`, `images`. Presence switches response to grouped form. | +| `categories` | array, max 5 | `github`, `research`, `pdf`, or passthrough string. | +| `scrapeOptions` | object | Scrapes each web result (timeout bounds 1..60000 ms). Adds `markdown` etc. to results. | +| `answer`, `answerTopN`, `summarizeResults`, `maxCharsPerSource`, `llmApiKey`, `llmProvider`, `llmModel` | | LLM synthesis; need `scrapeOptions`. Not needed by this plugin. | Minimal request is `{"query","limit"}`; `lang`/`tbs` are the only extras worth exposing. @@ -56,9 +56,19 @@ Envelope `ApiResponse` (camelCase, absent fields omitted): `success`, `data`, `e Hosted example (flat, per docs): ```json -{"success": true, "data": [ - {"url": "https://...", "title": "...", "description": "...", "snippet": "...", "position": 1, "score": 9.5} -]} +{ + "success": true, + "data": [ + { + "url": "https://...", + "title": "...", + "description": "...", + "snippet": "...", + "position": 1, + "score": 9.5 + } + ] +} ``` Self-hosted (CLI parser handles `{"success": true, "data": {"results": [ ... ]}}`). @@ -71,16 +81,16 @@ Conflict note: the docs example and CLI show flat `data[]` for hosted; the struc JSON body `{"success": false, "error": "", "error_code": ""}` (crates/crw-server/src/error.rs). -| HTTP | error_code | Cause | -|---|---|---| -| 400 | `invalid_request` | bad query/limit/lang | -| 401 | (auth middleware message) | missing/invalid key | -| 429 | `rate_limited` | rate limit | -| 502 | `http_error` | search backend HTTP/JSON failure | -| 504 | `timeout` | search timeout (default 15 s) | -| 503 | `search_disabled` / `search_degraded` | search off or backend unhealthy | -| 422 | `target_unreachable` | backend unreachable | -| 500 | `internal_error` | other | +| HTTP | error_code | Cause | +| ---- | ------------------------------------- | -------------------------------- | +| 400 | `invalid_request` | bad query/limit/lang | +| 401 | (auth middleware message) | missing/invalid key | +| 429 | `rate_limited` | rate limit | +| 502 | `http_error` | search backend HTTP/JSON failure | +| 504 | `timeout` | search timeout (default 15 s) | +| 503 | `search_disabled` / `search_degraded` | search off or backend unhealthy | +| 422 | `target_unreachable` | backend unreachable | +| 500 | `internal_error` | other | Backend hostnames/credentials are never leaked in errors. diff --git a/docs/research/opencode-v2-plugin-api.md b/docs/research/opencode-v2-plugin-api.md index ed2cb6b..e546be9 100644 --- a/docs/research/opencode-v2-plugin-api.md +++ b/docs/research/opencode-v2-plugin-api.md @@ -13,15 +13,16 @@ Caveat: pages were read through a fetch-and-summarise tool, so quotes are as ret ## Verdict on the user's sketch -| Sketch element | Verdict | Evidence | -|---|---|---| -| `Plugin.define` from `"@opencode/plugin"` | RIGHT | [S1] `import { Plugin } from "@opencode/plugin"` / `export default Plugin.define({ id: "example", async setup(ctx) {...} })`. V1 package was `@opencode-ai/plugin` [S5]. | -| `ctx.websearch.transform` | RIGHT | [S1] `await ctx.websearch.transform((editor) => { ... })` | -| `editor.add` | RIGHT | [S1] `editor.add({ id: "internal", name: "Internal search", execute: async ({ query }, { signal }) => {...} })` | -| `plugins[].package` / `options` in opencode.json | RIGHT | [S2][S3] key is `"plugins"` (V1 was `"plugin"`, tuples replaced by objects [S5]); object form `{ "package": "...", "options": {...} }` | -| `websearch.provider` | RIGHT (config key) | [S3][S4] `{ "websearch": { "provider": "tavily" } }`; also `"random"`. Built-in ids: exa, firecrawl, parallel, tavily, tinyfish [S4]. | +| Sketch element | Verdict | Evidence | +| ------------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Plugin.define` from `"@opencode/plugin"` | RIGHT | [S1] `import { Plugin } from "@opencode/plugin"` / `export default Plugin.define({ id: "example", async setup(ctx) {...} })`. V1 package was `@opencode-ai/plugin` [S5]. | +| `ctx.websearch.transform` | RIGHT | [S1] `await ctx.websearch.transform((editor) => { ... })` | +| `editor.add` | RIGHT | [S1] `editor.add({ id: "internal", name: "Internal search", execute: async ({ query }, { signal }) => {...} })` | +| `plugins[].package` / `options` in opencode.json | RIGHT | [S2][S3] key is `"plugins"` (V1 was `"plugin"`, tuples replaced by objects [S5]); object form `{ "package": "...", "options": {...} }` | +| `websearch.provider` | RIGHT (config key) | [S3][S4] `{ "websearch": { "provider": "tavily" } }`; also `"random"`. Built-in ids: exa, firecrawl, parallel, tavily, tinyfish [S4]. | Things the sketch likely missed / must add: + - `id` is required on `Plugin.define` (stable, used for storage scoping/diagnostics) [S5]. - A plugin-added provider is made active with `editor.default.set("")` [S1]; `websearch.provider` in config selects by provider id, so `"provider": ""` is the expected pairing (inference from [S1]+[S4]; docs do not state it explicitly -> verify at runtime). - Options are read as `ctx.options` (not passed as a setup arg) [S1]. @@ -29,47 +30,64 @@ Things the sketch likely missed / must add: ## Sub-questions ### 1. Plugin entry / definition shape + Each module has one default export with a unique id and `setup`: + ```ts import { Plugin } from "@opencode/plugin" export default Plugin.define({ id: "example", - async setup(ctx) { /* may return a cleanup function */ }, + async setup(ctx) { + /* may return a cleanup function */ + }, }) ``` + [S1][S5]. Context: `ctx.app.version`, `ctx.location` (`directory`, optional `workspaceID`, project), `ctx.options`, `ctx.storage` (durable JSON, plugin-scoped), plus domains `ctx.session/permission/shell/tool/websearch/...` [S1][S5]. Return cleanup from `setup` for timers/sockets [S5]. V1 plugins do not run in V2; dual-support is possible via spreading `Plugin.define(...)` and adding a V1 `server()` [S1][S5]. ### 2. Registration/loading (local path vs npm) + `"plugins"` array in `opencode.json(c)`; entries are strings or objects [S2]: npm `"opencode-acme-plugin"`, `"opencode-acme-plugin@1.2.0"`, `"@acme/opencode-plugin"`; local `"./plugins/local"`, `"../shared/plugin.ts"`, `"/abs/plugin.ts"`, `"file:///..."`. Also auto-discovery of `.ts/.js` files and packages in `.opencode/plugins/` or `~/.config/opencode/plugins/` [S2]. CLI: `opencode plugin add|list|check|update|remove` [S2]. Disable via ids: `["*", "-opencode.provider.*", ...]` [S2]. Published package needs `"type":"module"`, `exports["."]` -> entry, dependency `@opencode/plugin` [S1]. ### 3. Passing options + ```jsonc -{ "plugins": [ { "package": "./plugins/company", "options": { "strict": true } } ] } +{ "plugins": [{ "package": "./plugins/company", "options": { "strict": true } }] } ``` + read via `ctx.options.strict` in `setup` [S1][S3]. Options type is untyped key/value (validate yourself). ### 4. Can a plugin provide/replace the websearch provider? + Yes. [S1]: + ```ts await ctx.websearch.transform((editor) => { - editor.add({ id: "internal", name: "Internal search", + editor.add({ + id: "internal", + name: "Internal search", execute: async ({ query }, { signal }) => { const response = await fetch(`https://search.example.com?q=${query}`, { signal }) return [{ url: "", title: "", content: "", time: {} }] - } }) + }, + }) editor.default.set("internal") }) ``` + Disable: `editor.default.set(false)`; re-evaluate: `await ctx.websearch.reload()` [S1]. The API reference lists add/default/remove operations [S6] (remove not shown in S1 example; exact editor type unverified). ### 5. How `websearch.provider` is selected + Config `{ "websearch": { "provider": "" | "random" } }`; `"random"` picks an available provider; `"websearch": false` disables search entirely [S3][S4]. Provider on HTTP 429 enters cooldown and search retries another provider [S4]. Provider API keys via `/connect` or env (e.g. `TAVILY_API_KEY`) [S4]. For a plugin provider, `editor.default.set(id)` is the plugin-side selection [S1]; interplay/precedence with the config `provider` key is not documented -> verify. ### 6. Required search result shape + From [S1] the `execute` return is an array of `{ url, title, content, time }`; the example uses `time: {}` (object, inner shape undocumented). [S6] lists the same four fields. Unverified: whether `time` fields are optional, exact `time` keys (e.g. published/updated), and whether extra fields are allowed. `execute` receives `({ query }, { signal })`; `signal` is an AbortSignal and must be passed to fetch [S1]. ## Open items (need source or runtime check) + - Exact TS types for editor, `time`, and execute args (read anomalyco/opencode source or `@opencode/plugin` typings after install). - Precedence of config `websearch.provider` vs plugin `editor.default.set`. - [S6] suggested `POST /api/websearch/query` with `{query, provider?}` -> `{results, provider}`; this came from the summariser and is lower confidence. diff --git a/docs/spec.md b/docs/spec.md index 1b0247e..6bcce00 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -8,12 +8,12 @@ An opencode v2 plugin that makes a self-hosted fastCRW server opencode's only we ## Identifiers -| What | Value | -|---|---| -| npm package | `@haylan/opencode-fastcrw` | -| Plugin id (`Plugin.define`) | `fastcrw.websearch` | -| Provider id / display name | `fastcrw` / "fastCRW" | -| Registry | `https://git.arthurerlich.de/api/packages/haylan/npm/` | +| What | Value | +| --------------------------- | ------------------------------------------------------ | +| npm package | `@haylan/opencode-fastcrw` | +| Plugin id (`Plugin.define`) | `fastcrw.websearch` | +| Provider id / display name | `fastcrw` / "fastCRW" | +| Registry | `https://git.arthurerlich.de/api/packages/haylan/npm/` | ## Plugin behaviour @@ -25,7 +25,11 @@ export default Plugin.define({ async setup(ctx) { const config = resolveConfig(ctx.options, process.env) // throws on bad values await ctx.websearch.transform((editor) => { - editor.add({ id: "fastcrw", name: "fastCRW", execute: ({ query }, { signal }) => search(config, query, signal, fetch) }) + editor.add({ + id: "fastcrw", + name: "fastCRW", + execute: ({ query }, { signal }) => search(config, query, signal, fetch), + }) editor.default.set("fastcrw") }) }, @@ -38,20 +42,25 @@ fastCRW is the default and only provider; there is no fallback to built-ins. Precedence: plugin option > env var > default. -| Option | Env var | Default | Rules | -|---|---|---|---| -| `baseURL` | `CRW_API_URL` | `http://localhost:3000` | trim, strip trailing `/`, must be `http(s)` URL | -| `apiKey` | `CRW_API_KEY` | none | optional; Bearer header only when set; never logged or echoed | -| `limit` | none | `8` | clamp to 1..20; invalid falls back to 8, never throws | -| `lang` | none | unset | string; omitted from request when unset | -| `tbs` | none | unset | one of `qdr:h`, `qdr:d`, `qdr:w`, `qdr:m`, `qdr:y`; omitted when unset | +| Option | Env var | Default | Rules | +| --------- | ------------- | ----------------------- | ---------------------------------------------------------------------- | +| `baseURL` | `CRW_API_URL` | `http://localhost:3000` | trim, strip trailing `/`, must be `http(s)` URL | +| `apiKey` | `CRW_API_KEY` | none | optional; Bearer header only when set; never logged or echoed | +| `limit` | none | `8` | clamp to 1..20; invalid falls back to 8, never throws | +| `lang` | none | unset | string; omitted from request when unset | +| `tbs` | none | unset | one of `qdr:h`, `qdr:d`, `qdr:w`, `qdr:m`, `qdr:y`; omitted when unset | Validation runs in `setup`. A malformed `baseURL`, bad `tbs` or non-string `lang` makes `setup` throw, with a message naming the option and the bad value. Example `opencode.json`: ```jsonc { - "plugins": [{ "package": "@haylan/opencode-fastcrw", "options": { "baseURL": "http://crw.home:3000", "limit": 8 } }], - "websearch": { "provider": "fastcrw" } + "plugins": [ + { + "package": "@haylan/opencode-fastcrw", + "options": { "baseURL": "http://crw.home:3000", "limit": 8 }, + }, + ], + "websearch": { "provider": "fastcrw" }, } ``` diff --git a/package-lock.json b/package-lock.json index 92fbd28..a6e23f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,11 @@ "dependencies": { "@opencode/plugin": "^2.0.22" }, + "devDependencies": { + "@types/node": "^26.6.4", + "prettier": "^3.9.9", + "typescript": "^7.0.2" + }, "engines": { "node": ">=20" } @@ -1591,6 +1596,346 @@ "@types/node": "*" } }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/abbrev": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", @@ -3080,6 +3425,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prettier": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -3576,6 +3937,41 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, "node_modules/undici": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", diff --git a/package.json b/package.json index 4727e6d..e83bd87 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,10 @@ "license": "MIT", "dependencies": { "@opencode/plugin": "^2.0.22" + }, + "devDependencies": { + "@types/node": "^26.6.4", + "prettier": "^3.9.9", + "typescript": "^7.0.2" } } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..39d58de --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +// ponytail: placeholder so `tsc` has an input; replaced by the plugin entry. +export {} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d8c61b5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "types": ["node"], + "skipLibCheck": true + }, + "include": ["src"] +}