diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..a88c051 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,14 @@ +{ + "mcpServers": { + "oddkit": { + "command": "npx", + "args": [ + "--yes", + "--package", + "github:klappy/oddkit", + "oddkit-mcp" + ], + "env": {} + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 011cc67..85d41f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.10.0] - 2026-02-02 + +### Added + +- **Cloudflare Workers deployment** — Remote MCP server for Claude.ai on iOS/iPad/web: + - New `workers/` directory with Cloudflare Worker implementation + - Streamable HTTP transport for MCP communication + - Fetches baseline from GitHub raw content API (no git clone required) + - Full MCP capabilities: tools, resources, and prompts + - CORS enabled for cross-origin requests + - Deploy with `cd workers && npm run deploy` + +- **Three deployment methods** — oddkit now runs everywhere: + - **CLI** — `npx oddkit ` for terminal usage + - **MCP (local)** — `npx oddkit-mcp` for Cursor/Claude Code + - **MCP (remote)** — Cloudflare Worker for Claude.ai mobile/web + +- **MCP resources in Worker** — Same resources as CLI version: + - `oddkit://instructions` — Decision gate + - `oddkit://quickstart` — Agent quick start + - `oddkit://examples` — Usage patterns + +- **MCP prompts in Worker** — Fetched live from baseline registry: + - Agent prompts like odd-epistemic-guide, odd-scribe + - Loaded from `klappy.dev/canon/instructions/REGISTRY.json` + +### Changed + +- **Updated documentation** — All docs now cover CLI, npx, and HTTP deployment methods + ## [0.9.1] - 2026-02-02 ### Fixed diff --git a/README.md b/README.md index 9df651e..8be5616 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,18 @@ Agent-first CLI for ODD-governed repos. Portable Librarian + Validation with bas > > OddKit is epistemic terrain rendering (map), not epistemic authority (compass). +## Deployment Methods + +oddkit runs in three ways: + +| Method | Use Case | Setup | +|--------|----------|-------| +| **CLI** | Terminal usage | `npx oddkit ` | +| **MCP (local)** | Cursor, Claude Code | `npx oddkit init --claude` | +| **MCP (remote)** | Claude.ai iOS/iPad/web | Deploy `workers/` to Cloudflare | + +See [workers/README.md](workers/README.md) for Cloudflare deployment. + ## Documentation | Doc | What It Covers | diff --git a/docs/CLAUDE-CODE.md b/docs/CLAUDE-CODE.md index 3571d24..3c3bf8a 100644 --- a/docs/CLAUDE-CODE.md +++ b/docs/CLAUDE-CODE.md @@ -28,6 +28,22 @@ npx oddkit init --claude --project This creates `.mcp.json` in your repository for project-specific configuration. +### Option 4: Claude.ai Mobile/Web (Remote MCP) + +For Claude.ai on iOS, iPad, or web browsers, deploy oddkit as a Cloudflare Worker: + +```bash +cd workers +npm install +npm run deploy +``` + +Then add the remote MCP server in Claude.ai: +1. Go to Settings → Integrations → MCP +2. Add server URL: `https://oddkit-mcp..workers.dev/mcp` + +See [workers/README.md](../workers/README.md) for full deployment instructions. + ## Verify Setup After init, restart Claude Code. You should see `oddkit_orchestrate` available as a tool. diff --git a/docs/MCP.md b/docs/MCP.md index 317d09d..c4f4830 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -2,6 +2,15 @@ oddkit exposes an MCP (Model Context Protocol) server that allows Cursor, Claude Code, and other MCP-compatible hosts to use oddkit as a tool. +## Deployment Options + +| Method | Transport | Use Case | +|--------|-----------|----------| +| **Local MCP** | stdio | Cursor, Claude Code (desktop) | +| **Remote MCP** | HTTP | Claude.ai (iOS, iPad, web) | + +For remote deployment, see [workers/README.md](../workers/README.md). + ## Zero-config Behavior (recommended) Once oddkit MCP is installed (globally or project-local), agents automatically receive always-on guidance via MCP GetInstructions. This means: diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index a8b4870..4d5c7cc 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -4,13 +4,14 @@ Get running in 60 seconds. ## What's in the Box -oddkit has three layers: - -| Layer | What It Is | Setup | -| ---------- | --------------------------------------------------------- | --------------------------- | -| **CLI** | Command-line tools (`oddkit librarian`, `validate`, etc.) | `npx oddkit ` | -| **MCP** | Model Context Protocol server for IDE integration | `npx oddkit init` | -| **Agents** | Subagent prompts (Epistemic Guide, Scribe) | Copy to `~/.cursor/agents/` | +oddkit has four layers: + +| Layer | What It Is | Setup | +| ---------------- | --------------------------------------------------------- | ------------------------------ | +| **CLI** | Command-line tools (`oddkit librarian`, `validate`, etc.) | `npx oddkit ` | +| **MCP (local)** | Model Context Protocol server for IDE integration | `npx oddkit init` | +| **MCP (remote)** | Cloudflare Worker for Claude.ai iOS/iPad/web | Deploy `workers/` to Cloudflare | +| **Agents** | Subagent prompts (Epistemic Guide, Scribe) | Copy to `~/.cursor/agents/` | **New to ODD?** Start with [ODD Agents](getting-started/agents.md) to understand the system. diff --git a/package-lock.json b/package-lock.json index 69d7068..daa4b5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oddkit", - "version": "0.9.1", + "version": "0.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oddkit", - "version": "0.9.1", + "version": "0.10.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", diff --git a/package.json b/package.json index e262271..0c19764 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oddkit", - "version": "0.9.1", + "version": "0.10.0", "description": "Agent-first CLI for ODD-governed repos. Epistemic terrain rendering with portable baseline.", "type": "module", "bin": { diff --git a/workers/.gitignore b/workers/.gitignore new file mode 100644 index 0000000..a933f10 --- /dev/null +++ b/workers/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.wrangler/ +.dev.vars diff --git a/workers/README.md b/workers/README.md new file mode 100644 index 0000000..c98920d --- /dev/null +++ b/workers/README.md @@ -0,0 +1,93 @@ +# oddkit MCP Worker + +Remote MCP server for oddkit, deployable to Cloudflare Workers. Enables oddkit in Claude.ai on iOS, iPad, and web. + +## Features + +- **oddkit_orchestrate** — Routes messages to librarian/validate/preflight/catalog +- **oddkit_librarian** — Policy Q&A with citations +- **oddkit_validate** — Completion claim validation + +## Setup + +1. **Install dependencies:** + ```bash + cd workers + npm install + ``` + +2. **Configure Cloudflare:** + - Update `wrangler.toml` with your account details + - Optionally add KV namespace for caching + +3. **Deploy:** + ```bash + npm run deploy + ``` + +## Endpoints + +| Path | Method | Description | +|------|--------|-------------| +| `/` | GET | Health check | +| `/health` | GET | Health check | +| `/mcp` | POST | MCP JSON-RPC endpoint | + +## Connecting to Claude.ai + +1. Go to Claude.ai Settings → Integrations → MCP +2. Add new MCP server: + - URL: `https://oddkit-mcp..workers.dev/mcp` + - Name: `oddkit` + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `BASELINE_URL` | GitHub raw content URL for baseline | `https://raw.githubusercontent.com/klappy/klappy.dev/main` | +| `ODDKIT_VERSION` | Version string | `0.9.1` | + +## Optional: KV Caching + +To enable baseline index caching: + +1. Create a KV namespace: + ```bash + npx wrangler kv:namespace create BASELINE_CACHE + ``` + +2. Update `wrangler.toml`: + ```toml + [[kv_namespaces]] + binding = "BASELINE_CACHE" + id = "your-namespace-id" + ``` + +## Local Development + +```bash +npm run dev +``` + +Then test with: +```bash +curl -X POST http://localhost:8787/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' +``` + +## Architecture + +``` +Claude.ai (iOS/iPad/Web) + ↓ HTTPS +Cloudflare Worker (/mcp) + ↓ JSON-RPC +runOrchestrate() + ↓ fetch() +GitHub Raw Content API + ↓ +klappy.dev baseline docs +``` + +The worker fetches the baseline index and documents directly from GitHub, eliminating the need for git clone in the serverless environment. diff --git a/workers/package-lock.json b/workers/package-lock.json new file mode 100644 index 0000000..ca25d77 --- /dev/null +++ b/workers/package-lock.json @@ -0,0 +1,1527 @@ +{ + "name": "oddkit-mcp-worker", + "version": "0.9.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "oddkit-mcp-worker", + "version": "0.9.1", + "devDependencies": { + "@cloudflare/workers-types": "^4.20250124.0", + "typescript": "^5.7.0", + "wrangler": "^4.0.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.12.0.tgz", + "integrity": "sha512-NK4vN+2Z/GbfGS4BamtbbVk1rcu5RmqaYGiyHJQrA09AoxdZPHDF3W/EhgI0YSK8p3vRo/VNCtbSJFPON7FWMQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "^1.20260115.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260128.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260128.0.tgz", + "integrity": "sha512-XJN8zWWNG3JwAUqqwMLNKJ9fZfdlQkx/zTTHW/BB8wHat9LjKD6AzxqCu432YmfjR+NxEKCzUOxMu1YOxlVxmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260128.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260128.0.tgz", + "integrity": "sha512-vKnRcmnm402GQ5DOdfT5H34qeR2m07nhnTtky8mTkNWP+7xmkz32AMdclwMmfO/iX9ncyKwSqmml2wPG32eq/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260128.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260128.0.tgz", + "integrity": "sha512-RiaR+Qugof/c6oI5SagD2J5wJmIfI8wQWaV2Y9905Raj6sAYOFaEKfzkKnoLLLNYb4NlXicBrffJi1j7R/ypUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260128.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260128.0.tgz", + "integrity": "sha512-U39U9vcXLXYDbrJ112Q7D0LDUUnM54oXfAxPgrL2goBwio7Z6RnsM25TRvm+Q06F4+FeDOC4D51JXlFHb9t1OA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260128.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260128.0.tgz", + "integrity": "sha512-fdJwSqRkJsAJFJ7+jy0th2uMO6fwaDA8Ny6+iFCssfzlNkc4dP/twXo+3F66FMLMe/6NIqjzVts0cpiv7ERYbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20260131.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260131.0.tgz", + "integrity": "sha512-ELgvb2mp68Al50p+FmpgCO2hgU5o4tmz8pi7kShN+cRXc0UZoEdxpDIikR0CeT7b3tV7wlnEnsUzd0UoJLS0oQ==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz", + "integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/miniflare": { + "version": "4.20260128.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260128.0.tgz", + "integrity": "sha512-AVCn3vDRY+YXu1sP4mRn81ssno6VUqxo29uY2QVfgxXU2TMLvhRIoGwm7RglJ3Gzfuidit5R86CMQ6AvdFTGAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "^0.34.5", + "undici": "7.18.2", + "workerd": "1.20260128.0", + "ws": "8.18.0", + "youch": "4.1.0-beta.10" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/workerd": { + "version": "1.20260128.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260128.0.tgz", + "integrity": "sha512-EhLJGptSGFi8AEErLiamO3PoGpbRqL+v4Ve36H2B38VxmDgFOSmDhfepBnA14sCQzGf1AEaoZX2DCwZsmO74yQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260128.0", + "@cloudflare/workerd-darwin-arm64": "1.20260128.0", + "@cloudflare/workerd-linux-64": "1.20260128.0", + "@cloudflare/workerd-linux-arm64": "1.20260128.0", + "@cloudflare/workerd-windows-64": "1.20260128.0" + } + }, + "node_modules/wrangler": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.61.1.tgz", + "integrity": "sha512-hfYQ16VLPkNi8xE1/V3052S2stM5e+vq3Idpt83sXoDC3R7R1CLgMkK6M6+Qp3G+9GVDNyHCkvohMPdfFTaD4Q==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.2", + "@cloudflare/unenv-preset": "2.12.0", + "blake3-wasm": "2.1.5", + "esbuild": "0.27.0", + "miniflare": "4.20260128.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260128.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260128.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + } + } +} diff --git a/workers/package.json b/workers/package.json new file mode 100644 index 0000000..07a1203 --- /dev/null +++ b/workers/package.json @@ -0,0 +1,17 @@ +{ + "name": "oddkit-mcp-worker", + "version": "0.10.0", + "private": true, + "type": "module", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "tail": "wrangler tail", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250124.0", + "typescript": "^5.7.0", + "wrangler": "^4.0.0" + } +} diff --git a/workers/src/index.ts b/workers/src/index.ts new file mode 100644 index 0000000..08d36c8 --- /dev/null +++ b/workers/src/index.ts @@ -0,0 +1,609 @@ +/** + * oddkit MCP Worker + * + * Remote MCP server for oddkit, deployable to Cloudflare Workers. + * Provides policy retrieval and completion validation for Claude.ai. + * + * Uses streamable-http transport for MCP communication. + */ + +import { runOrchestrate, type OrchestrateResult } from "./orchestrate"; + +export interface Env { + BASELINE_URL: string; + ODDKIT_VERSION: string; + BASELINE_CACHE?: KVNamespace; +} + +// Tool definitions +const TOOLS = [ + { + name: "oddkit_orchestrate", + description: `Routes a message to librarian/validate/explain and returns tool-grade JSON with ready-to-send assistant_text. + +MUST: Before editing files or implementing a spec, call with your implementation plan. +MUST: Before claiming done/fixed/shipped/merged, call with completion claim + artifact paths. + +Use when: +- Policy/canon questions ("what's the rule?", "is this allowed?") +- Pre-implementation guidance ("preflight: add authentication") +- Completion validation ("done: implemented X with screenshot Y") +- Discovery ("what's in ODD?", "list canon")`, + inputSchema: { + type: "object" as const, + properties: { + message: { type: "string", description: "The message to process" }, + action: { + type: "string", + enum: ["orient", "catalog", "preflight", "librarian", "validate", "explain"], + description: "Explicit action override (optional, auto-detected from message)", + }, + }, + }, + }, + { + name: "oddkit_librarian", + description: "Retrieves governing/operational docs with quotes + citations.", + inputSchema: { + type: "object" as const, + properties: { + query: { type: "string", description: "The policy question to answer" }, + }, + required: ["query"], + }, + }, + { + name: "oddkit_validate", + description: "Validates completion claims against required artifacts.", + inputSchema: { + type: "object" as const, + properties: { + message: { type: "string", description: "The completion claim with artifact references" }, + }, + required: ["message"], + }, + }, +]; + +// Resource definitions +const RESOURCES = [ + { + uri: "oddkit://instructions", + name: "ODDKIT Decision Gate", + description: "When and how to call oddkit_orchestrate", + mimeType: "text/plain", + }, + { + uri: "oddkit://quickstart", + name: "ODDKIT Quick Start for Agents", + description: "Essential oddkit usage patterns for spawned agents", + mimeType: "text/plain", + }, + { + uri: "oddkit://examples", + name: "ODDKIT Usage Examples", + description: "Common oddkit_orchestrate call patterns", + mimeType: "text/plain", + }, +]; + +// Resource content +function getInstructionsResource(): string { + return `ODDKIT DECISION GATE + +You have access to oddkit for epistemic governance. + +CALL oddkit_orchestrate WHEN: +1. About to implement something → preflight first +2. Have a policy/rules question → ask librarian +3. Claiming completion → validate with artifacts +4. Need to understand available docs → catalog + +DO NOT CALL WHEN: +- Simple file operations with no policy implications +- Continuing work already preflighted +- User explicitly says to skip + +The tool returns ready-to-use assistant_text with citations.`; +} + +function getQuickStartResource(): string { + return `ODDKIT QUICK START FOR AGENTS + +You have access to oddkit_orchestrate for policy retrieval and completion validation. + +THREE CRITICAL MOMENTS TO CALL ODDKIT: + +1. BEFORE IMPLEMENTING + Call: oddkit_orchestrate({ message: "preflight: " }) + Returns: Start here / Constraints / Definition of Done / Pitfalls + +2. WHEN YOU HAVE QUESTIONS + Call: oddkit_orchestrate({ message: "" }) + Returns: Answer with citations and evidence quotes + +3. BEFORE CLAIMING DONE + Call: oddkit_orchestrate({ message: "done: " }) + Returns: VERIFIED or NEEDS_ARTIFACTS with missing evidence list + +RESPONSE HANDLING: +- Use the "assistant_text" field from the response directly +- It contains a complete answer with citations +- Don't add extra narration - the text is ready to use + +COMMON PATTERNS: +- Policy question: { "message": "What is the definition of done?" } +- Preflight: { "message": "preflight: add user authentication" } +- Validate: { "message": "done: implemented login. Screenshot: login.png" } +- Discovery: { "message": "What's in ODD?" } + +IMPORTANT: Never pre-inject large documents. Always retrieve on-demand via oddkit.`; +} + +function getExamplesResource(): string { + return `ODDKIT USAGE EXAMPLES + +=== PREFLIGHT (before implementing) === + +Request: +{ + "message": "preflight: implement user authentication with OAuth" +} + +Response includes: +- Start here: files to read first +- Constraints: rules that apply +- Definition of Done: what completion looks like +- Pitfalls: common mistakes to avoid + + +=== POLICY QUESTION === + +Request: +{ + "message": "What evidence is required for UI changes?" +} + +Response includes: +- Answer with 2-4 substantial quotes +- Citations (file#section format) +- Read next suggestions + + +=== COMPLETION VALIDATION === + +Request: +{ + "message": "done: implemented search feature with tests. Screenshot: search.png, Test output: npm test passed" +} + +Response verdict: +- VERIFIED: All required evidence provided +- NEEDS_ARTIFACTS: Lists what's missing + + +=== DISCOVERY (what's available) === + +Request: +{ + "message": "What's in ODD? Show me the canon." +} + +Response includes: +- Start here documents +- Top canon by category +- Playbooks and guides + + +=== EXPLICIT ACTION === + +Sometimes you want to force a specific action: + +Request: +{ + "message": "...", + "action": "preflight" +} + +Valid actions: preflight, catalog, librarian, validate, explain, orient`; +} + +function getResourceContent(uri: string): string | null { + switch (uri) { + case "oddkit://instructions": + return getInstructionsResource(); + case "oddkit://quickstart": + return getQuickStartResource(); + case "oddkit://examples": + return getExamplesResource(); + default: + return null; + } +} + +// Prompt registry interface +interface PromptRegistryEntry { + id: string; + uri: string; + path: string; + audience: string; +} + +interface PromptRegistry { + version: string; + instructions: PromptRegistryEntry[]; +} + +// Fetch prompts registry from GitHub +async function fetchPromptsRegistry(baselineUrl: string): Promise { + try { + const response = await fetch(`${baselineUrl}/canon/instructions/REGISTRY.json`); + if (!response.ok) return null; + return (await response.json()) as PromptRegistry; + } catch { + return null; + } +} + +// Fetch prompt content from GitHub +async function fetchPromptContent(baselineUrl: string, path: string): Promise { + try { + const response = await fetch(`${baselineUrl}/${path}`); + if (!response.ok) return null; + const content = await response.text(); + // Strip YAML frontmatter if present + return content.replace(/^---[\s\S]*?---\n/, "").trim(); + } catch { + return null; + } +} + +// Server info +function getServerInfo(version: string) { + return { + name: "oddkit", + version, + protocolVersion: "2024-11-05", + }; +} + +// Handle MCP JSON-RPC requests +async function handleMcpRequest( + body: unknown, + env: Env +): Promise<{ jsonrpc: string; id?: unknown; result?: unknown; error?: unknown }> { + const request = body as { + jsonrpc: string; + id?: unknown; + method: string; + params?: unknown; + }; + + const { method, params, id } = request; + + try { + switch (method) { + case "initialize": + return { + jsonrpc: "2.0", + id, + result: { + protocolVersion: "2024-11-05", + serverInfo: getServerInfo(env.ODDKIT_VERSION), + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + }, + }; + + case "tools/list": + return { + jsonrpc: "2.0", + id, + result: { tools: TOOLS }, + }; + + case "resources/list": + return { + jsonrpc: "2.0", + id, + result: { resources: RESOURCES }, + }; + + case "resources/read": { + const { uri } = params as { uri: string }; + const content = getResourceContent(uri); + + if (!content) { + return { + jsonrpc: "2.0", + id, + error: { + code: -32602, + message: `Unknown resource: ${uri}`, + }, + }; + } + + return { + jsonrpc: "2.0", + id, + result: { + contents: [ + { + uri, + mimeType: "text/plain", + text: content, + }, + ], + }, + }; + } + + case "prompts/list": { + const registry = await fetchPromptsRegistry(env.BASELINE_URL); + if (!registry) { + return { + jsonrpc: "2.0", + id, + result: { prompts: [] }, + }; + } + + const prompts = registry.instructions + .filter((inst) => inst.audience === "agent") + .map((inst) => ({ + name: inst.id, + description: `Agent: ${inst.id} (${inst.uri})`, + })); + + return { + jsonrpc: "2.0", + id, + result: { prompts }, + }; + } + + case "prompts/get": { + const { name } = params as { name: string }; + const registry = await fetchPromptsRegistry(env.BASELINE_URL); + + if (!registry) { + return { + jsonrpc: "2.0", + id, + error: { + code: -32602, + message: "Failed to load prompts registry", + }, + }; + } + + const instruction = registry.instructions.find((i) => i.id === name); + if (!instruction) { + return { + jsonrpc: "2.0", + id, + error: { + code: -32602, + message: `Unknown prompt: ${name}`, + }, + }; + } + + const content = await fetchPromptContent(env.BASELINE_URL, instruction.path); + if (!content) { + return { + jsonrpc: "2.0", + id, + error: { + code: -32602, + message: `Failed to fetch prompt content: ${instruction.path}`, + }, + }; + } + + return { + jsonrpc: "2.0", + id, + result: { + description: `Agent: ${instruction.id}`, + messages: [ + { + role: "user", + content: { + type: "text", + text: content, + }, + }, + ], + }, + }; + } + + case "tools/call": { + const { name, arguments: args } = params as { + name: string; + arguments?: Record; + }; + + let result: OrchestrateResult; + + switch (name) { + case "oddkit_orchestrate": + result = await runOrchestrate({ + message: (args?.message as string) || "", + action: args?.action as string | undefined, + baselineUrl: env.BASELINE_URL, + cache: env.BASELINE_CACHE, + }); + break; + + case "oddkit_librarian": + result = await runOrchestrate({ + message: (args?.query as string) || "", + action: "librarian", + baselineUrl: env.BASELINE_URL, + cache: env.BASELINE_CACHE, + }); + break; + + case "oddkit_validate": + result = await runOrchestrate({ + message: (args?.message as string) || "", + action: "validate", + baselineUrl: env.BASELINE_URL, + cache: env.BASELINE_CACHE, + }); + break; + + default: + return { + jsonrpc: "2.0", + id, + error: { + code: -32601, + message: `Unknown tool: ${name}`, + }, + }; + } + + return { + jsonrpc: "2.0", + id, + result: { + content: [ + { + type: "text", + text: JSON.stringify(result, null, 2), + }, + ], + }, + }; + } + + case "notifications/initialized": + // Notification, no response needed + return { jsonrpc: "2.0" }; + + default: + return { + jsonrpc: "2.0", + id, + error: { + code: -32601, + message: `Method not found: ${method}`, + }, + }; + } + } catch (err) { + return { + jsonrpc: "2.0", + id, + error: { + code: -32603, + message: err instanceof Error ? err.message : "Internal error", + }, + }; + } +} + +// CORS headers +function corsHeaders(origin: string = "*"): Record { + return { + "Access-Control-Allow-Origin": origin, + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + }; +} + +/** + * Main fetch handler + */ +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + const url = new URL(request.url); + const origin = request.headers.get("Origin") || "*"; + + // Handle CORS preflight + if (request.method === "OPTIONS") { + return new Response(null, { headers: corsHeaders(origin) }); + } + + // Health check + if (url.pathname === "/" || url.pathname === "/health") { + return new Response( + JSON.stringify({ + ok: true, + service: "oddkit-mcp", + version: env.ODDKIT_VERSION, + endpoints: { + mcp: "/mcp", + health: "/health", + }, + capabilities: ["tools", "resources", "prompts"], + }), + { + headers: { + "Content-Type": "application/json", + ...corsHeaders(origin), + }, + } + ); + } + + // MCP endpoint - streamable HTTP + if (url.pathname === "/mcp") { + if (request.method !== "POST") { + return new Response("Method not allowed", { + status: 405, + headers: corsHeaders(origin), + }); + } + + try { + const body = await request.json(); + const response = await handleMcpRequest(body, env); + + // Don't return response for notifications + if (!response.id && !response.error) { + return new Response(null, { + status: 204, + headers: corsHeaders(origin), + }); + } + + return new Response(JSON.stringify(response), { + headers: { + "Content-Type": "application/json", + ...corsHeaders(origin), + }, + }); + } catch (err) { + return new Response( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32700, + message: "Parse error", + }, + }), + { + status: 400, + headers: { + "Content-Type": "application/json", + ...corsHeaders(origin), + }, + } + ); + } + } + + return new Response("Not found", { + status: 404, + headers: corsHeaders(origin), + }); + }, +}; diff --git a/workers/src/orchestrate.ts b/workers/src/orchestrate.ts new file mode 100644 index 0000000..c8c5b03 --- /dev/null +++ b/workers/src/orchestrate.ts @@ -0,0 +1,393 @@ +/** + * Orchestration logic for oddkit MCP Worker + * + * Simplified version of src/mcp/orchestrate.js adapted for Cloudflare Workers. + * Fetches baseline from GitHub raw content instead of git clone. + */ + +export interface OrchestrateOptions { + message: string; + action?: string; + baselineUrl: string; + cache?: KVNamespace; +} + +export interface OrchestrateResult { + action: string; + result: unknown; + assistant_text: string; + debug?: Record; +} + +interface IndexEntry { + path: string; + title: string; + intent?: string; + tags?: string[]; + excerpt?: string; +} + +interface BaselineIndex { + version: string; + entries: IndexEntry[]; +} + +/** + * Detect action from message content + */ +function detectAction(message: string): string { + const lower = message.toLowerCase().trim(); + + // Preflight patterns + if ( + lower.startsWith("preflight:") || + lower.startsWith("before i implement") || + lower.includes("what should i read first") || + /^implement\s+\w+/.test(lower) + ) { + return "preflight"; + } + + // Catalog patterns + if ( + lower.includes("what's in odd") || + lower.includes("whats in odd") || + lower.includes("list the canon") || + lower.includes("show me the docs") + ) { + return "catalog"; + } + + // Validate patterns + if ( + /\b(done|finished|completed|shipped|merged|fixed|implemented)\b/i.test(lower) && + lower.length > 10 + ) { + return "validate"; + } + + // Explain patterns + if ( + lower.startsWith("explain") || + lower.includes("why did you") || + lower.includes("what happened") + ) { + return "explain"; + } + + // Default to librarian + return "librarian"; +} + +/** + * Fetch baseline index from GitHub + */ +async function fetchBaselineIndex( + baselineUrl: string, + cache?: KVNamespace +): Promise { + const indexUrl = `${baselineUrl}/.oddkit/index.json`; + const cacheKey = "baseline-index"; + + // Try cache first + if (cache) { + const cached = await cache.get(cacheKey, "json"); + if (cached) { + return cached as BaselineIndex; + } + } + + // Fetch from GitHub + const response = await fetch(indexUrl); + if (!response.ok) { + // Return minimal index if not available + return { + version: "1.0", + entries: [], + }; + } + + const index = (await response.json()) as BaselineIndex; + + // Cache for 5 minutes + if (cache) { + await cache.put(cacheKey, JSON.stringify(index), { expirationTtl: 300 }); + } + + return index; +} + +/** + * Fetch a document from baseline + */ +async function fetchDoc(baselineUrl: string, path: string): Promise { + const docUrl = `${baselineUrl}/${path}`; + const response = await fetch(docUrl); + if (!response.ok) { + return null; + } + return response.text(); +} + +/** + * Simple text search in index + */ +function searchIndex(index: BaselineIndex, query: string): IndexEntry[] { + const terms = query.toLowerCase().split(/\s+/); + return index.entries + .filter((entry) => { + const searchable = `${entry.title} ${entry.path} ${entry.tags?.join(" ") || ""} ${entry.excerpt || ""}`.toLowerCase(); + return terms.some((term) => searchable.includes(term)); + }) + .slice(0, 5); +} + +/** + * Run librarian action + */ +async function runLibrarian( + message: string, + baselineUrl: string, + cache?: KVNamespace +): Promise { + const index = await fetchBaselineIndex(baselineUrl, cache); + const results = searchIndex(index, message); + + if (results.length === 0) { + return { + action: "librarian", + result: { + status: "NO_MATCH", + answer: "No relevant documents found.", + }, + assistant_text: `I searched the ODD baseline but found no documents matching "${message}". Try rephrasing your question or ask "what's in ODD?" to see available documentation.`, + }; + } + + // Fetch excerpts for top results + const evidence: Array<{ quote: string; citation: string }> = []; + for (const entry of results.slice(0, 3)) { + const content = await fetchDoc(baselineUrl, entry.path); + if (content) { + // Extract first meaningful paragraph + const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#")); + const excerpt = lines.slice(0, 3).join(" ").slice(0, 200); + evidence.push({ + quote: excerpt, + citation: `${entry.path}#${entry.title}`, + }); + } + } + + const assistantText = `### Answer +Found ${results.length} relevant document(s) for: "${message}" + +### Evidence +${evidence.map((e) => `- "${e.quote}..." — \`${e.citation}\``).join("\n")} + +### Read Next +${results.map((r) => `- \`${r.path}\` — ${r.title}`).join("\n")}`; + + return { + action: "librarian", + result: { + status: "SUPPORTED", + answer: `Found ${results.length} relevant document(s)`, + evidence, + }, + assistant_text: assistantText, + }; +} + +/** + * Run validate action + */ +async function runValidate(message: string): Promise { + // Extract claims and artifacts from message + const artifactPatterns = /\b(\w+\.(png|jpg|jpeg|gif|mp4|mov|pdf|log|txt))\b/gi; + const artifacts = [...message.matchAll(artifactPatterns)].map((m) => m[1]); + + // Check for required evidence types + const hasScreenshot = artifacts.some((a) => /\.(png|jpg|jpeg|gif)$/i.test(a)); + const hasVideo = artifacts.some((a) => /\.(mp4|mov)$/i.test(a)); + const hasLog = artifacts.some((a) => /\.(log|txt)$/i.test(a)); + + const gaps: string[] = []; + if (!hasScreenshot && !hasVideo) { + gaps.push("visual proof (screenshot or recording)"); + } + + if (gaps.length > 0) { + return { + action: "validate", + result: { + verdict: "NEEDS_ARTIFACTS", + claims: [message], + provided_artifacts: artifacts, + gaps, + }, + assistant_text: `### Verdict +NEEDS_ARTIFACTS + +### Claims +- ${message} + +### Provided Artifacts +${artifacts.length > 0 ? artifacts.map((a) => `- ${a}`).join("\n") : "- None detected"} + +### Missing Evidence +${gaps.map((g) => `- ${g}`).join("\n")} + +Please provide the missing evidence to validate completion.`, + }; + } + + return { + action: "validate", + result: { + verdict: "VERIFIED", + claims: [message], + provided_artifacts: artifacts, + }, + assistant_text: `### Verdict +VERIFIED + +### Claims +- ${message} + +### Evidence +${artifacts.map((a) => `- ${a}`).join("\n")} + +Completion validated with required artifacts.`, + }; +} + +/** + * Run catalog action + */ +async function runCatalog( + baselineUrl: string, + cache?: KVNamespace +): Promise { + const index = await fetchBaselineIndex(baselineUrl, cache); + + // Group by intent/tags + const byTag: Record = {}; + for (const entry of index.entries) { + for (const tag of entry.tags || ["other"]) { + if (!byTag[tag]) byTag[tag] = []; + byTag[tag].push(entry); + } + } + + const topTags = Object.entries(byTag) + .sort((a, b) => b[1].length - a[1].length) + .slice(0, 5); + + const assistantText = `### ODD Documentation Catalog + +**Start here:** docs/QUICKSTART.md + +**Top categories:** +${topTags.map(([tag, entries]) => `- **${tag}**: ${entries.slice(0, 3).map((e) => e.path).join(", ")}`).join("\n")} + +**Total documents:** ${index.entries.length} + +Ask about any topic to get relevant documents with citations.`; + + return { + action: "catalog", + result: { + total: index.entries.length, + categories: Object.keys(byTag), + }, + assistant_text: assistantText, + }; +} + +/** + * Run preflight action + */ +async function runPreflight( + message: string, + baselineUrl: string, + cache?: KVNamespace +): Promise { + const index = await fetchBaselineIndex(baselineUrl, cache); + + // Find relevant docs for the implementation + const topic = message.replace(/^preflight:\s*/i, "").trim(); + const results = searchIndex(index, topic); + + // Look for definition of done + const dodEntry = index.entries.find((e) => + e.path.toLowerCase().includes("definition-of-done") + ); + + const assistantText = `### Preflight Summary + +**Topic:** ${topic} + +**Start here:** +${results.slice(0, 2).map((r) => `- \`${r.path}\` — ${r.title}`).join("\n") || "- docs/QUICKSTART.md"} + +**Definition of Done:** +${dodEntry ? `- \`${dodEntry.path}\`` : "- Check canon/definition-of-done.md"} + +**Constraints to review:** +- Check for governing docs in canon/constraints/ +- Review operational notes in docs/ + +**Before claiming done:** +- Provide visual proof for UI changes +- Include test output for logic changes +- Reference any decisions made + +Run \`oddkit_orchestrate({ message: "What is the definition of done?" })\` for full DoD details.`; + + return { + action: "preflight", + result: { + topic, + start_here: results.slice(0, 2).map((r) => r.path), + dod: dodEntry?.path, + }, + assistant_text: assistantText, + }; +} + +/** + * Main orchestration entry point + */ +export async function runOrchestrate(options: OrchestrateOptions): Promise { + const { message, action: explicitAction, baselineUrl, cache } = options; + const action = explicitAction || detectAction(message); + + try { + switch (action) { + case "librarian": + return await runLibrarian(message, baselineUrl, cache); + case "validate": + return await runValidate(message); + case "catalog": + return await runCatalog(baselineUrl, cache); + case "preflight": + return await runPreflight(message, baselineUrl, cache); + case "explain": + return { + action: "explain", + result: { note: "Explain requires session state not available in remote mode" }, + assistant_text: + "The explain action requires session state which is not available in remote MCP mode. Please re-run the previous action if you need details.", + }; + default: + return await runLibrarian(message, baselineUrl, cache); + } + } catch (error) { + return { + action: "error", + result: { error: error instanceof Error ? error.message : "Unknown error" }, + assistant_text: `An error occurred: ${error instanceof Error ? error.message : "Unknown error"}. Please try again.`, + }; + } +} diff --git a/workers/tsconfig.json b/workers/tsconfig.json new file mode 100644 index 0000000..9f22c00 --- /dev/null +++ b/workers/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["@cloudflare/workers-types"], + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/workers/wrangler.toml b/workers/wrangler.toml new file mode 100644 index 0000000..6c11888 --- /dev/null +++ b/workers/wrangler.toml @@ -0,0 +1,27 @@ +# oddkit MCP Worker +# Exposes oddkit as a remote MCP server for Claude.ai + +name = "oddkit-mcp" +main = "src/index.ts" +compatibility_date = "2025-01-01" +compatibility_flags = ["nodejs_compat"] + +# Environment variables +[vars] +BASELINE_URL = "https://raw.githubusercontent.com/klappy/klappy.dev/main" +ODDKIT_VERSION = "0.10.0" + +# OAuth configuration (set via wrangler secret) +# OAUTH_CLIENT_ID = "" +# OAUTH_CLIENT_SECRET = "" + +# Optional: Durable Objects for session state +# [durable_objects] +# bindings = [ +# { name = "SESSIONS", class_name = "SessionState" } +# ] + +# Optional: KV for caching baseline index +# [[kv_namespaces]] +# binding = "BASELINE_CACHE" +# id = "your-kv-namespace-id"