diff --git a/.changeset/olive-pugs-attack.md b/.changeset/olive-pugs-attack.md
new file mode 100644
index 00000000..b5925919
--- /dev/null
+++ b/.changeset/olive-pugs-attack.md
@@ -0,0 +1,9 @@
+---
+"openwiki": minor
+---
+
+feat: add a `claude-code` provider that routes inference through the local Claude Code CLI
+
+Adds a keyless inference provider for users who cannot provision an Anthropic API key — notably Claude Team and Enterprise members whose plan does not grant API key creation. Selecting `Claude Code (local CLI)` during `openwiki --init` reuses the existing `claude auth login` session; OpenWiki never reads or persists a token.
+
+Claude Code ships its own agent loop, so the bridge constrains it to a single model turn: OpenWiki's DeepAgents tools are exposed through an in-process MCP server, Claude Code's built-in tools are disabled, and `canUseTool` captures the resulting tool call and hands it back to the OpenWiki agent loop for execution. This keeps the virtual filesystem backend, OKF middleware, and translation middleware in control of the run.
diff --git a/README.md b/README.md
index 0e42593e..12bd39ba 100644
--- a/README.md
+++ b/README.md
@@ -219,7 +219,29 @@ notes.
## Customizing
-OpenWiki supports OpenAI (with an API key or a ChatGPT login), OpenRouter, Gemini (AI Studio), Gemini Enterprise (Vertex AI), Nebius Token Factory, Fireworks, Baseten, NVIDIA NIM, an OpenAI-compatible provider, AWS Bedrock, Anthropic, and GitHub Copilot out of the box. The onboarding default is OpenAI with `gpt-5.6-terra`, and each inference provider also includes pre-defined model options plus support for custom model IDs.
+OpenWiki supports OpenAI (with an API key or a ChatGPT login), OpenRouter, Gemini (AI Studio), Gemini Enterprise (Vertex AI), Nebius Token Factory, Fireworks, Baseten, NVIDIA NIM, an OpenAI-compatible provider, AWS Bedrock, Anthropic (with an API key or a Claude Code login), and GitHub Copilot out of the box. The onboarding default is OpenAI with `gpt-5.6-terra`, and each inference provider also includes pre-defined model options plus support for custom model IDs.
+
+### Claude Code (local CLI)
+
+The `claude-code` provider routes inference through the [Claude Code](https://claude.com/claude-code) CLI installed on your machine, reusing its existing session instead of an Anthropic API key. This is aimed at Claude Team and Enterprise members whose plan does not let them create API keys, and at anyone who would rather spend an existing Claude Code seat than provision separate API credit.
+
+1. Install Claude Code and run `claude auth login`.
+2. Select `Claude Code (local CLI)` as the provider during `openwiki --init`. OpenWiki detects the existing session automatically; press Tab at the credential prompt to run `claude auth login` if you are not signed in yet.
+3. Choose a model (for example `claude-opus-5`).
+
+The resulting configuration is entirely keyless:
+
+```env
+OPENWIKI_PROVIDER="claude-code"
+OPENWIKI_MODEL_ID="claude-opus-5"
+```
+
+Two things are worth knowing before you pick this provider:
+
+- **The session stays in the CLI.** OpenWiki never reads, copies, or persists a Claude Code token — it spawns the CLI through the Claude Agent SDK and lets the SDK use whatever session is already there. Nothing is written to `~/.openwiki/.env`.
+- **It is interactive-only, and slower.** The Agent SDK accepts user messages but cannot replay prior assistant turns, so each agent step re-sends the transcript rather than resuming a session. Claude Code's prompt cache absorbs most of the repeated cost, but expect a wiki build to be slower than the `anthropic` provider and to draw on your subscription's rate limits. For CI, use `anthropic` with an API key.
+
+Claude Code's own tools are disabled for these runs and its project settings (`CLAUDE.md`, settings files) are not loaded, so the wiki is generated from OpenWiki's prompts and DeepAgents' virtual filesystem backend alone.
### GitHub Copilot
diff --git a/package.json b/package.json
index dfd338ae..6d63325b 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"typecheck": "tsc --noEmit -p tsconfig.json"
},
"dependencies": {
+ "@anthropic-ai/claude-agent-sdk": "0.3.218",
"@anthropic-ai/vertex-sdk": "^0.19.0",
"@aws-sdk/client-bedrock-runtime": "^3.1080.0",
"@langchain/anthropic": "^1.5.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 433311ff..d39541df 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@anthropic-ai/claude-agent-sdk':
+ specifier: 0.3.218
+ version: 0.3.218(@anthropic-ai/sdk@0.103.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.4.3))(zod@4.4.3)
'@anthropic-ai/vertex-sdk':
specifier: ^0.19.0
version: 0.19.0(zod@4.4.3)
@@ -139,6 +142,58 @@ packages:
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
+ '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.218':
+ resolution: {integrity: sha512-foTI71ua2r5lJo7sfcw/3UafBCJYjxPmOXZ98wOz1UWIJxaP/Oq9Xp4sUavBd1efFVRxPnAcRnrZ5yrmXK1xUw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.218':
+ resolution: {integrity: sha512-rAdlR2ukJd01Osfsb7nPPv31MCVJBennBDaL7ZP/bv+dednwNuY6thuPO5PMFJUM+54Gbnziqm+hqoNTgndqDg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.218':
+ resolution: {integrity: sha512-qfFBwOf0uh/mAtNGEkBJlLWt1XIgFqcQeeX966g2NexasCdSJGnwfc0YWA0QOeAAU/5xk6tSCW/Nn0zHGBO/3A==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.218':
+ resolution: {integrity: sha512-Y6vFEnz6wwd+rYpVGsPqgeXZuZP7NvQg6vjkC+N6cs1+I41LdKjfs+F+WG3daIqgt+vJBz/EB5Q0PByABkwufA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.218':
+ resolution: {integrity: sha512-e47oi8dYWnS0wx2/F6FL3YhaD2HedMd2nhI/nududP4PBzytuXeaDE/sJ0eIqtUVkl6/bn5uBhtIEHHWoj37JQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.218':
+ resolution: {integrity: sha512-gCq/i7yRXeuoQXidGQynuiw5AeczQXSIifxH5Vpr9OFDyHCBNotS1mQ8qutuclZeFpLOPkJ2ic/nvEgHJvfXdg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.218':
+ resolution: {integrity: sha512-D0UKI8jOpEsWO1A+i0DlIrgjXFKEZX3RrID2l49S4pShUlLsnSLJGMTPy4nQt6CCl5MzPqWiJDE/lCpImFTDcg==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.218':
+ resolution: {integrity: sha512-pgE39WnKPPSRsiRJ2pm5NETxQgtVhrquqg7UByZxne0xQw2gNS0Qy+9ZumrsNBNlfrrnQCVDU1I2ks3ZLrEa8Q==}
+ cpu: [x64]
+ os: [win32]
+
+ '@anthropic-ai/claude-agent-sdk@0.3.218':
+ resolution: {integrity: sha512-lbVOmgdzjBdSUCTbxElpoTxxP/u9jY8gpG/3cr5l1jSSvSY5FrANdRcze/z6gM330xo32SHH6WPGFd4+K0fMoQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ '@anthropic-ai/sdk': '>=0.93.0'
+ '@modelcontextprotocol/sdk': ^1.29.0
+ zod: ^4.0.0
+
'@anthropic-ai/sdk@0.103.0':
resolution: {integrity: sha512-1uG7RNgoHTUxzOXqSCODKt0UTVlxWiHk/2Tt2/uQJiPW7XzBeKVuJyd3Aw6T3LPyvZV/jDTnPLX7SaM70WLLjA==}
hasBin: true
@@ -598,6 +653,12 @@ packages:
'@noble/hashes':
optional: true
+ '@hono/node-server@1.19.15':
+ resolution: {integrity: sha512-Za2ai6TLdKjUvnur+eenO6nuYYipVAEhyCAdaV8IRvmU9kK8crOZUSYvIXn72E4f8fJqyAbpcJuTsYYmZp9Deg==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
'@humanfs/core@0.19.2':
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
engines: {node: '>=18.18.0'}
@@ -739,6 +800,16 @@ packages:
'@mermaid-js/parser@1.2.0':
resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==}
+ '@modelcontextprotocol/sdk@1.29.0':
+ resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@cfworker/json-schema': ^4.1.1
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ '@cfworker/json-schema':
+ optional: true
+
'@napi-rs/wasm-runtime@1.1.6':
resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
peerDependencies:
@@ -1132,6 +1203,10 @@ packages:
'@vitest/utils@4.1.10':
resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
+ accepts@2.0.0:
+ resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
+ engines: {node: '>= 0.6'}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1146,9 +1221,20 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
ajv@6.15.0:
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
+ ajv@8.20.0:
+ resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
+
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@@ -1217,6 +1303,10 @@ packages:
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+ body-parser@2.3.0:
+ resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
+ engines: {node: '>=18'}
+
bowser@2.14.1:
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
@@ -1234,6 +1324,18 @@ packages:
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
@@ -1276,6 +1378,18 @@ packages:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
+ content-disposition@1.1.0:
+ resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
+ engines: {node: '>=18'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
+ content-type@2.0.0:
+ resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
+ engines: {node: '>=18'}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@@ -1283,6 +1397,18 @@ packages:
resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ cookie-signature@1.2.2:
+ resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
+ engines: {node: '>=6.6.0'}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ cors@2.8.6:
+ resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
+ engines: {node: '>= 0.10'}
+
cose-base@1.0.3:
resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
@@ -1514,6 +1640,10 @@ packages:
delaunator@5.1.0:
resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
@@ -1533,12 +1663,23 @@ packages:
resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==}
engines: {node: '>=10'}
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
@@ -1554,9 +1695,21 @@ packages:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
es-module-lexer@2.3.0:
resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==}
+ es-object-atoms@1.1.2:
+ resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
+ engines: {node: '>= 0.4'}
+
es-toolkit@1.49.0:
resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==}
@@ -1565,6 +1718,9 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
escape-string-regexp@2.0.0:
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
engines: {node: '>=8'}
@@ -1623,6 +1779,10 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
@@ -1633,6 +1793,10 @@ packages:
resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
engines: {node: '>=18.0.0'}
+ eventsource@3.0.7:
+ resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
+ engines: {node: '>=18.0.0'}
+
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
@@ -1641,6 +1805,16 @@ packages:
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
engines: {node: '>=12.0.0'}
+ express-rate-limit@8.6.0:
+ resolution: {integrity: sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ express: '>= 4.11'
+
+ express@5.2.1:
+ resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
+ engines: {node: '>= 18'}
+
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -1663,6 +1837,9 @@ packages:
fast-sha256@1.3.0:
resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
+ fast-uri@3.1.4:
+ resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==}
+
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -1690,6 +1867,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
+ finalhandler@2.1.1:
+ resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
+ engines: {node: '>= 18.0.0'}
+
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@@ -1709,6 +1890,14 @@ packages:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
+ forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+
+ fresh@2.0.0:
+ resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+ engines: {node: '>= 0.8'}
+
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@@ -1725,6 +1914,9 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
gaxios@7.1.6:
resolution: {integrity: sha512-aIQ0QL8Or8vsUhHyXGA6AohOFRrAAiHhrvsAG6myzcSlfhxSXtnwXA/pRuQTilFgjhLe30swK5rg1d7E1f8Izw==}
engines: {node: '>=18'}
@@ -1737,6 +1929,14 @@ packages:
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
@@ -1760,6 +1960,10 @@ packages:
resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==}
engines: {node: '>=14'}
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -1770,6 +1974,18 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
+ hono@4.12.32:
+ resolution: {integrity: sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==}
+ engines: {node: '>=16.9.0'}
+
html-encoding-sniffer@6.0.0:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
@@ -1777,6 +1993,10 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ http-errors@2.0.1:
+ resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
+ engines: {node: '>= 0.8'}
+
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
@@ -1841,6 +2061,14 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
+ ip-address@10.2.0:
+ resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
+ engines: {node: '>= 12'}
+
+ ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -1873,6 +2101,9 @@ packages:
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+ is-promise@4.0.0:
+ resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
+
is-subdir@1.2.0:
resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==}
engines: {node: '>=4'}
@@ -1938,6 +2169,12 @@ packages:
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-schema-typed@8.0.2:
+ resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -2116,9 +2353,21 @@ packages:
engines: {node: '>= 20'}
hasBin: true
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
mdn-data@2.27.1:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
+ media-typer@1.1.1:
+ resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==}
+ engines: {node: '>= 0.8'}
+
+ merge-descriptors@2.0.0:
+ resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
+ engines: {node: '>=18'}
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -2130,6 +2379,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@3.0.2:
+ resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
+ engines: {node: '>=18'}
+
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -2170,6 +2427,10 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
node-abi@3.94.0:
resolution: {integrity: sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==}
engines: {node: '>=10'}
@@ -2192,10 +2453,22 @@ packages:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
obug@2.1.3:
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
engines: {node: '>=12.20.0'}
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -2291,6 +2564,10 @@ packages:
parse5@8.0.1:
resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
patch-console@2.0.0:
resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -2306,6 +2583,9 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
+ path-to-regexp@8.4.2:
+ resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
+
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -2328,6 +2608,10 @@ packages:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
+ pkce-challenge@5.0.1:
+ resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
+ engines: {node: '>=16.20.0'}
+
points-on-curve@0.2.0:
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
@@ -2367,6 +2651,10 @@ packages:
engines: {node: '>=14'}
hasBin: true
+ proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
@@ -2374,12 +2662,24 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ qs@6.15.3:
+ resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
+ engines: {node: '>=0.6'}
+
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ range-parser@1.3.0:
+ resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==}
+ engines: {node: '>= 0.6'}
+
+ raw-body@3.0.2:
+ resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
+ engines: {node: '>= 0.10'}
+
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@@ -2429,6 +2729,10 @@ packages:
roughjs@4.6.6:
resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
+ router@2.2.0:
+ resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
+ engines: {node: '>= 18'}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -2453,6 +2757,17 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ send@1.2.1:
+ resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
+ engines: {node: '>= 18'}
+
+ serve-static@2.2.1:
+ resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
+ engines: {node: '>= 18'}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -2461,6 +2776,22 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ side-channel-list@1.0.1:
+ resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.1:
+ resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
+ engines: {node: '>= 0.4'}
+
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@@ -2509,6 +2840,10 @@ packages:
standardwebhooks@1.0.0:
resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
+ statuses@2.0.2:
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+ engines: {node: '>= 0.8'}
+
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
@@ -2582,6 +2917,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
tough-cookie@6.0.2:
resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==}
engines: {node: '>=16'}
@@ -2625,6 +2964,10 @@ packages:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
+ type-is@2.1.0:
+ resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
+ engines: {node: '>= 18'}
+
typescript-eslint@8.62.1:
resolution: {integrity: sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2651,6 +2994,10 @@ packages:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -2661,6 +3008,10 @@ packages:
resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==}
hasBin: true
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
vite@8.1.3:
resolution: {integrity: sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2827,6 +3178,11 @@ packages:
yoga-layout@3.2.1:
resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==}
+ zod-to-json-schema@3.25.2:
+ resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
+ peerDependencies:
+ zod: ^3.25.28 || ^4
+
zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
@@ -2842,6 +3198,45 @@ snapshots:
package-manager-detector: 1.7.0
tinyexec: 1.2.4
+ '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.218':
+ optional: true
+
+ '@anthropic-ai/claude-agent-sdk@0.3.218(@anthropic-ai/sdk@0.103.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.4.3))(zod@4.4.3)':
+ dependencies:
+ '@anthropic-ai/sdk': 0.103.0(zod@4.4.3)
+ '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.4.3)
+ zod: 4.4.3
+ optionalDependencies:
+ '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.218
+ '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.218
+ '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.218
+ '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.218
+ '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.218
+ '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.218
+ '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.218
+ '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.218
+
'@anthropic-ai/sdk@0.103.0(zod@4.4.3)':
dependencies:
json-schema-to-ts: 3.1.1
@@ -3411,6 +3806,10 @@ snapshots:
'@exodus/bytes@1.15.1': {}
+ '@hono/node-server@1.19.15(hono@4.12.32)':
+ dependencies:
+ hono: 4.12.32
+
'@humanfs/core@0.19.2':
dependencies:
'@humanfs/types': 0.15.0
@@ -3588,6 +3987,30 @@ snapshots:
dependencies:
'@chevrotain/types': 11.1.2
+ '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.4.3)':
+ dependencies:
+ '@hono/node-server': 1.19.15(hono@4.12.32)
+ ajv: 8.20.0
+ ajv-formats: 3.0.1(ajv@8.20.0)
+ content-type: 1.0.5
+ cors: 2.8.6
+ cross-spawn: 7.0.6
+ eventsource: 3.0.7
+ eventsource-parser: 3.1.0
+ express: 5.2.1
+ express-rate-limit: 8.6.0(express@5.2.1)
+ hono: 4.12.32
+ jose: 6.2.3
+ json-schema-typed: 8.0.2
+ pkce-challenge: 5.0.1
+ raw-body: 3.0.2
+ zod: 4.4.3
+ zod-to-json-schema: 3.25.2(zod@4.4.3)
+ optionalDependencies:
+ '@cfworker/json-schema': 4.1.1
+ transitivePeerDependencies:
+ - supports-color
+
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
'@emnapi/core': 1.11.1
@@ -4020,6 +4443,11 @@ snapshots:
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
+ accepts@2.0.0:
+ dependencies:
+ mime-types: 3.0.2
+ negotiator: 1.0.0
+
acorn-jsx@5.3.2(acorn@8.17.0):
dependencies:
acorn: 8.17.0
@@ -4028,6 +4456,10 @@ snapshots:
agent-base@7.1.4: {}
+ ajv-formats@3.0.1(ajv@8.20.0):
+ optionalDependencies:
+ ajv: 8.20.0
+
ajv@6.15.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -4035,6 +4467,13 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
+ ajv@8.20.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.4
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
ansi-colors@4.1.3: {}
ansi-escapes@7.3.0:
@@ -4094,6 +4533,20 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
+ body-parser@2.3.0:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 2.0.0
+ debug: 4.4.3
+ http-errors: 2.0.1
+ iconv-lite: 0.7.3
+ on-finished: 2.4.1
+ qs: 6.15.3
+ raw-body: 3.0.2
+ type-is: 2.1.0
+ transitivePeerDependencies:
+ - supports-color
+
bowser@2.14.1: {}
brace-expansion@5.0.7:
@@ -4111,6 +4564,18 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
+ bytes@3.1.2: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
chai@6.2.2: {}
chalk@5.6.2: {}
@@ -4140,10 +4605,25 @@ snapshots:
commander@8.3.0: {}
+ content-disposition@1.1.0: {}
+
+ content-type@1.0.5: {}
+
+ content-type@2.0.0: {}
+
convert-source-map@2.0.0: {}
convert-to-spaces@2.0.1: {}
+ cookie-signature@1.2.2: {}
+
+ cookie@0.7.2: {}
+
+ cors@2.8.6:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
cose-base@1.0.3:
dependencies:
layout-base: 1.0.2
@@ -4399,6 +4879,8 @@ snapshots:
dependencies:
robust-predicates: 3.0.3
+ depd@2.0.0: {}
+
detect-indent@6.1.0: {}
detect-libc@2.1.2: {}
@@ -4413,12 +4895,22 @@ snapshots:
dotenv@8.6.0: {}
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
ecdsa-sig-formatter@1.0.11:
dependencies:
safe-buffer: 5.2.1
+ ee-first@1.1.1: {}
+
emoji-regex@10.6.0: {}
+ encodeurl@2.0.0: {}
+
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
@@ -4432,8 +4924,16 @@ snapshots:
environment@1.1.0: {}
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
es-module-lexer@2.3.0: {}
+ es-object-atoms@1.1.2:
+ dependencies:
+ es-errors: 1.3.0
+
es-toolkit@1.49.0: {}
esbuild@0.28.1:
@@ -4465,6 +4965,8 @@ snapshots:
'@esbuild/win32-ia32': 0.28.1
'@esbuild/win32-x64': 0.28.1
+ escape-html@1.0.3: {}
+
escape-string-regexp@2.0.0: {}
escape-string-regexp@4.0.0: {}
@@ -4539,16 +5041,63 @@ snapshots:
esutils@2.0.3: {}
+ etag@1.8.1: {}
+
eventemitter3@4.0.7: {}
eventemitter3@5.0.4: {}
eventsource-parser@3.1.0: {}
+ eventsource@3.0.7:
+ dependencies:
+ eventsource-parser: 3.1.0
+
expand-template@2.0.3: {}
expect-type@1.4.0: {}
+ express-rate-limit@8.6.0(express@5.2.1):
+ dependencies:
+ debug: 4.4.3
+ express: 5.2.1
+ ip-address: 10.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ express@5.2.1:
+ dependencies:
+ accepts: 2.0.0
+ body-parser: 2.3.0
+ content-disposition: 1.1.0
+ content-type: 1.0.5
+ cookie: 0.7.2
+ cookie-signature: 1.2.2
+ debug: 4.4.3
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 2.1.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ merge-descriptors: 2.0.0
+ mime-types: 3.0.2
+ on-finished: 2.4.1
+ once: 1.4.0
+ parseurl: 1.3.3
+ proxy-addr: 2.0.7
+ qs: 6.15.3
+ range-parser: 1.3.0
+ router: 2.2.0
+ send: 1.2.1
+ serve-static: 2.2.1
+ statuses: 2.0.2
+ type-is: 2.1.0
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
extend@3.0.2: {}
extendable-error@0.1.7: {}
@@ -4569,6 +5118,8 @@ snapshots:
fast-sha256@1.3.0: {}
+ fast-uri@3.1.4: {}
+
fastq@1.20.1:
dependencies:
reusify: 1.1.0
@@ -4592,6 +5143,17 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
+ finalhandler@2.1.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
@@ -4613,6 +5175,10 @@ snapshots:
dependencies:
fetch-blob: 3.2.0
+ forwarded@0.2.0: {}
+
+ fresh@2.0.0: {}
+
fs-constants@1.0.0: {}
fs-extra@7.0.1:
@@ -4630,6 +5196,8 @@ snapshots:
fsevents@2.3.3:
optional: true
+ function-bind@1.1.2: {}
+
gaxios@7.1.6:
dependencies:
extend: 3.0.2
@@ -4648,6 +5216,24 @@ snapshots:
get-east-asian-width@1.6.0: {}
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.4
+ math-intrinsics: 1.1.0
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.2
+
github-from-package@0.0.0: {}
glob-parent@5.1.2:
@@ -4680,12 +5266,22 @@ snapshots:
google-logging-utils@1.1.3: {}
+ gopd@1.2.0: {}
+
graceful-fs@4.2.11: {}
hachure-fill@0.5.2: {}
has-flag@4.0.0: {}
+ has-symbols@1.1.0: {}
+
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
+ hono@4.12.32: {}
+
html-encoding-sniffer@6.0.0:
dependencies:
'@exodus/bytes': 1.15.1
@@ -4694,6 +5290,14 @@ snapshots:
html-escaper@2.0.2: {}
+ http-errors@2.0.1:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.2
+ toidentifier: 1.0.1
+
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
@@ -4764,6 +5368,10 @@ snapshots:
internmap@2.0.3: {}
+ ip-address@10.2.0: {}
+
+ ipaddr.js@1.9.1: {}
+
is-extglob@2.1.1: {}
is-fullwidth-code-point@4.0.0: {}
@@ -4784,6 +5392,8 @@ snapshots:
is-potential-custom-element-name@1.0.1: {}
+ is-promise@4.0.0: {}
+
is-subdir@1.2.0:
dependencies:
better-path-resolve: 1.0.0
@@ -4863,6 +5473,10 @@ snapshots:
json-schema-traverse@0.4.1: {}
+ json-schema-traverse@1.0.0: {}
+
+ json-schema-typed@8.0.2: {}
+
json-stable-stringify-without-jsonify@1.0.1: {}
jsonfile@4.0.0:
@@ -5011,8 +5625,14 @@ snapshots:
marked@18.0.5: {}
+ math-intrinsics@1.1.0: {}
+
mdn-data@2.27.1: {}
+ media-typer@1.1.1: {}
+
+ merge-descriptors@2.0.0: {}
+
merge2@1.4.1: {}
mermaid@11.16.0:
@@ -5044,6 +5664,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.2
+ mime-db@1.54.0: {}
+
+ mime-types@3.0.2:
+ dependencies:
+ mime-db: 1.54.0
+
mimic-fn@2.1.0: {}
mimic-response@3.1.0: {}
@@ -5068,6 +5694,8 @@ snapshots:
natural-compare@1.4.0: {}
+ negotiator@1.0.0: {}
+
node-abi@3.94.0:
dependencies:
semver: 7.8.5
@@ -5084,8 +5712,16 @@ snapshots:
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.4: {}
+
obug@2.1.3: {}
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -5168,6 +5804,8 @@ snapshots:
dependencies:
entities: 8.0.0
+ parseurl@1.3.3: {}
+
patch-console@2.0.0: {}
path-data-parser@0.1.0: {}
@@ -5176,6 +5814,8 @@ snapshots:
path-key@3.1.1: {}
+ path-to-regexp@8.4.2: {}
+
path-type@4.0.0: {}
pathe@2.0.3: {}
@@ -5188,6 +5828,8 @@ snapshots:
pify@4.0.1: {}
+ pkce-challenge@5.0.1: {}
+
points-on-curve@0.2.0: {}
points-on-path@0.2.1:
@@ -5226,6 +5868,11 @@ snapshots:
prettier@3.9.4: {}
+ proxy-addr@2.0.7:
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+
pump@3.0.4:
dependencies:
end-of-stream: 1.4.5
@@ -5233,10 +5880,24 @@ snapshots:
punycode@2.3.1: {}
+ qs@6.15.3:
+ dependencies:
+ es-define-property: 1.0.1
+ side-channel: 1.1.1
+
quansync@0.2.11: {}
queue-microtask@1.2.3: {}
+ range-parser@1.3.0: {}
+
+ raw-body@3.0.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.1
+ iconv-lite: 0.7.3
+ unpipe: 1.0.0
+
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
@@ -5308,6 +5969,16 @@ snapshots:
points-on-curve: 0.2.0
points-on-path: 0.2.1
+ router@2.2.0:
+ dependencies:
+ debug: 4.4.3
+ depd: 2.0.0
+ is-promise: 4.0.0
+ parseurl: 1.3.3
+ path-to-regexp: 8.4.2
+ transitivePeerDependencies:
+ - supports-color
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -5328,12 +5999,67 @@ snapshots:
semver@7.8.5: {}
+ send@1.2.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ mime-types: 3.0.2
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.3.0
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ serve-static@2.2.1:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 1.2.1
+ transitivePeerDependencies:
+ - supports-color
+
+ setprototypeof@1.2.0: {}
+
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
+ side-channel-list@1.0.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.1
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
siginfo@2.0.0: {}
signal-exit@3.0.7: {}
@@ -5380,6 +6106,8 @@ snapshots:
'@stablelib/base64': 1.0.1
fast-sha256: 1.3.0
+ statuses@2.0.2: {}
+
std-env@4.1.0: {}
string-width@7.2.0:
@@ -5450,6 +6178,8 @@ snapshots:
dependencies:
is-number: 7.0.0
+ toidentifier@1.0.1: {}
+
tough-cookie@6.0.2:
dependencies:
tldts: 7.4.8
@@ -5486,6 +6216,12 @@ snapshots:
type-fest@4.41.0: {}
+ type-is@2.1.0:
+ dependencies:
+ content-type: 2.0.0
+ media-typer: 1.1.1
+ mime-types: 3.0.2
+
typescript-eslint@8.62.1(eslint@10.6.0)(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0)(typescript@5.9.3))(eslint@10.6.0)(typescript@5.9.3)
@@ -5507,6 +6243,8 @@ snapshots:
universalify@0.1.2: {}
+ unpipe@1.0.0: {}
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
@@ -5515,6 +6253,8 @@ snapshots:
uuid@14.0.1: {}
+ vary@1.1.2: {}
+
vite@8.1.3(@types/node@22.20.0)(esbuild@0.28.1)(tsx@4.22.5)(yaml@2.9.0):
dependencies:
lightningcss: 1.33.0
@@ -5618,4 +6358,8 @@ snapshots:
yoga-layout@3.2.1: {}
+ zod-to-json-schema@3.25.2(zod@4.4.3):
+ dependencies:
+ zod: 4.4.3
+
zod@4.4.3: {}
diff --git a/src/agent/claude-code-model.ts b/src/agent/claude-code-model.ts
new file mode 100644
index 00000000..a1dd9da0
--- /dev/null
+++ b/src/agent/claude-code-model.ts
@@ -0,0 +1,663 @@
+import {
+ createSdkMcpServer,
+ query,
+ type Options as ClaudeAgentOptions,
+ type PermissionResult,
+ type SdkMcpToolDefinition,
+} from "@anthropic-ai/claude-agent-sdk";
+import {
+ AIMessage,
+ type BaseMessage,
+ type ToolMessage,
+} from "@langchain/core/messages";
+import {
+ BaseChatModel,
+ type BaseChatModelCallOptions,
+ type BaseChatModelParams,
+ type BindToolsInput,
+} from "@langchain/core/language_models/chat_models";
+import type { ChatResult } from "@langchain/core/outputs";
+import type { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
+import type { ToolDefinition } from "@langchain/core/language_models/base";
+import { toJsonSchema } from "@langchain/core/utils/json_schema";
+import { z } from "zod";
+
+/**
+ * MCP namespaces every tool it serves as `mcp____`. The bridge
+ * has to strip that prefix before handing a tool call back to DeepAgents,
+ * which only knows the bare name it registered.
+ */
+const MCP_SERVER_NAME = "openwiki";
+const MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
+
+/**
+ * Ceiling on Claude Code turns per LangChain generation. One turn is the happy
+ * path; the extra headroom absorbs a refused built-in tool call, after which
+ * the model retries with an OpenWiki tool. Capture still interrupts
+ * immediately, so a healthy step never reaches this.
+ */
+const MAX_TURNS_PER_GENERATION = 6;
+
+/**
+ * Claude Code ships its own agent loop and toolset. The bridge only wants the
+ * raw model turn, so every built-in tool is disabled — a stray `Read` or
+ * `Bash` call would bypass OpenWiki's virtual filesystem backend and touch the
+ * real repository. `ToolSearch` matters most: it defers MCP tools behind a
+ * lookup step, which burns the single turn the bridge allows.
+ */
+const DISABLED_BUILTIN_TOOLS = [
+ "Bash",
+ "BashOutput",
+ "Edit",
+ "ExitPlanMode",
+ "Glob",
+ "Grep",
+ "KillShell",
+ "NotebookEdit",
+ "Read",
+ "SlashCommand",
+ "Task",
+ "TodoWrite",
+ "ToolSearch",
+ "WebFetch",
+ "WebSearch",
+ "Write",
+] as const;
+
+export type ChatClaudeCodeParams = BaseChatModelParams & {
+ model: string;
+ /**
+ * Overrides the `claude` executable the SDK spawns. Only set when the CLI is
+ * not on `PATH`; the SDK resolves it itself otherwise.
+ */
+ pathToClaudeCodeExecutable?: string;
+ maxRetries?: number;
+};
+
+/**
+ * Converts one JSON Schema property into a Zod type.
+ *
+ * `createSdkMcpServer` derives the JSON Schema it advertises to the model from
+ * a Zod raw shape, but LangChain tools arrive carrying JSON Schema, so the
+ * bridge has to round-trip through Zod. Only the subset DeepAgents actually
+ * emits is handled; anything unrecognized degrades to `z.unknown()`, which
+ * keeps the parameter visible to the model rather than dropping it.
+ *
+ * Note this schema is never used to *validate* a call: the MCP handler is
+ * unreachable by construction (see `captureOnlyHandler`). It exists purely so
+ * the advertised parameter list is faithful.
+ */
+export function jsonSchemaToZod(schema: unknown): z.ZodTypeAny {
+ if (typeof schema !== "object" || schema === null) {
+ return z.unknown();
+ }
+
+ const node = schema as Record;
+
+ if (Array.isArray(node.enum) && node.enum.length > 0) {
+ const values = node.enum.filter(
+ (value): value is string => typeof value === "string",
+ );
+ // Zod enums are string-only; a mixed or numeric enum falls back to unknown
+ // rather than silently narrowing the model's options.
+ if (values.length === node.enum.length) {
+ return z.enum(values as [string, ...string[]]);
+ }
+ return z.unknown();
+ }
+
+ // `anyOf`/`oneOf` unions are flattened to unknown: the model still sees the
+ // description, and the bridge never validates the value.
+ if (node.anyOf || node.oneOf || node.allOf) {
+ return z.unknown();
+ }
+
+ switch (node.type) {
+ case "string":
+ return z.string();
+ case "number":
+ return z.number();
+ case "integer":
+ return z.number().int();
+ case "boolean":
+ return z.boolean();
+ case "array":
+ return z.array(
+ node.items === undefined ? z.unknown() : jsonSchemaToZod(node.items),
+ );
+ case "object": {
+ const shape = jsonSchemaToZodShape(node);
+ // An object declaring `additionalProperties: true` (or listing no
+ // properties at all) is an intentional open bag — OpenWiki's
+ // `openwiki_call_mcp_tool.args` forwards arbitrary connector arguments
+ // through one. `z.object` would advertise it as closed, so the model
+ // could not pass anything through it.
+ if (
+ node.additionalProperties === true ||
+ Object.keys(shape).length === 0
+ ) {
+ return z.looseObject(shape);
+ }
+ return z.object(shape);
+ }
+ case "null":
+ return z.null();
+ default:
+ return z.unknown();
+ }
+}
+
+/**
+ * Converts a JSON Schema object node into the raw Zod shape
+ * `createSdkMcpServer` expects, preserving descriptions and optionality.
+ */
+export function jsonSchemaToZodShape(schema: unknown): z.ZodRawShape {
+ if (typeof schema !== "object" || schema === null) {
+ return {};
+ }
+
+ const node = schema as Record;
+ const properties = node.properties;
+
+ if (typeof properties !== "object" || properties === null) {
+ return {};
+ }
+
+ const required = new Set(
+ Array.isArray(node.required)
+ ? node.required.filter((name): name is string => typeof name === "string")
+ : [],
+ );
+
+ const shape: Record = {};
+
+ for (const [name, rawProperty] of Object.entries(
+ properties as Record,
+ )) {
+ let zodType = jsonSchemaToZod(rawProperty);
+
+ const description =
+ typeof rawProperty === "object" &&
+ rawProperty !== null &&
+ typeof (rawProperty as Record).description === "string"
+ ? ((rawProperty as Record).description as string)
+ : undefined;
+
+ if (description) {
+ zodType = zodType.describe(description);
+ }
+
+ shape[name] = required.has(name) ? zodType : zodType.optional();
+ }
+
+ return shape;
+}
+
+/**
+ * Normalizes the several shapes `bindTools` accepts into a flat
+ * name/description/schema triple.
+ */
+type NormalizedTool = {
+ name: string;
+ description: string;
+ parameters: unknown;
+};
+
+export function normalizeTool(tool: BindToolsInput): NormalizedTool | null {
+ const candidate = tool as Record;
+
+ // OpenAI-style `{ type: "function", function: {...} }`.
+ if (candidate.type === "function" && typeof candidate.function === "object") {
+ const fn = (tool as ToolDefinition).function;
+ return {
+ name: fn.name,
+ description: fn.description ?? "",
+ parameters: fn.parameters,
+ };
+ }
+
+ if (typeof candidate.name !== "string") {
+ return null;
+ }
+
+ // StructuredTool instances expose a Zod schema; everything else already
+ // carries JSON Schema on `schema`/`parameters`.
+ const rawSchema = candidate.schema ?? candidate.parameters;
+
+ return {
+ name: candidate.name,
+ description:
+ typeof candidate.description === "string" ? candidate.description : "",
+ parameters: normalizeToJsonSchema(rawSchema),
+ };
+}
+
+/**
+ * DeepAgents tools may carry a Zod v3 schema, a Zod v4 schema, or plain JSON
+ * Schema. LangChain's own converter handles all three; `z.toJSONSchema` throws
+ * on a v3 schema, which would silently degrade the tool to a parameterless
+ * one and strand the model with no way to call it correctly.
+ */
+export function normalizeToJsonSchema(schema: unknown): unknown {
+ if (typeof schema !== "object" || schema === null) {
+ return schema;
+ }
+
+ // Already plain JSON Schema.
+ if (!("_def" in (schema as Record))) {
+ return schema;
+ }
+
+ return toJsonSchema(schema as Parameters[0]);
+}
+
+/**
+ * Anthropic credentials that would override the CLI's own session.
+ *
+ * OpenWiki loads `~/.openwiki/.env` into `process.env` before the agent runs,
+ * so a user who previously configured the `anthropic` provider still has
+ * `ANTHROPIC_API_KEY` set. The spawned CLI prefers that key over its logged-in
+ * session, which silently bills the API — and fails outright with "Credit
+ * balance is too low" when the key is the reason the user switched to this
+ * provider in the first place.
+ */
+const CONFLICTING_CREDENTIAL_ENV_KEYS = [
+ "ANTHROPIC_API_KEY",
+ "ANTHROPIC_AUTH_TOKEN",
+ // Set by the `anthropic` provider to reach a gateway. Inherited by the CLI it
+ // would redirect the session's traffic to an endpoint that has no idea about
+ // this login, so the run fails in a way that looks like a Claude Code bug.
+ "ANTHROPIC_BASE_URL",
+] as const;
+
+/**
+ * Builds the environment for the spawned CLI, stripping any Anthropic
+ * credential that would shadow its session. Everything else (PATH, HOME, proxy
+ * settings) is inherited so the CLI resolves normally.
+ */
+export function claudeCodeSessionEnv(
+ source: NodeJS.ProcessEnv = process.env,
+): Record {
+ const env: Record = { ...source };
+
+ for (const key of CONFLICTING_CREDENTIAL_ENV_KEYS) {
+ delete env[key];
+ }
+
+ return env;
+}
+
+export type CapturedToolCall = { name: string; args: unknown };
+
+/**
+ * Builds the `canUseTool` callback, the bridge's only interception point.
+ *
+ * Nothing is ever added to `allowedTools`: a bare entry there auto-approves the
+ * call and shadows this callback entirely.
+ *
+ * The two branches must behave differently. An OpenWiki tool call is what the
+ * bridge wants, so it is recorded and the turn is interrupted to hand it to
+ * DeepAgents. A Claude Code built-in is refused *without* interrupting —
+ * `disallowedTools` is a deny-list that cannot stay complete as the CLI gains
+ * tools, so this is the real guard, and interrupting here would end the turn
+ * with nothing captured and fail the step outright.
+ */
+export function createToolPermissionHandler(
+ capturedToolCalls: CapturedToolCall[],
+): (
+ toolName: string,
+ input: Record,
+) => Promise {
+ return (toolName, input) => {
+ if (toolName.startsWith(MCP_TOOL_PREFIX)) {
+ capturedToolCalls.push({
+ name: toolName.slice(MCP_TOOL_PREFIX.length),
+ args: input,
+ });
+
+ return Promise.resolve({
+ behavior: "deny",
+ message: "Captured by OpenWiki; executed by the OpenWiki agent loop.",
+ interrupt: true,
+ });
+ }
+
+ return Promise.resolve({
+ behavior: "deny",
+ message:
+ `${toolName} is not available. Use only the OpenWiki tools provided ` +
+ `to you (the mcp__${MCP_SERVER_NAME}__* tools).`,
+ });
+ };
+}
+
+/**
+ * Decides whether a non-success ending is one the bridge deliberately causes.
+ *
+ * Two are expected. Denying a tool call with `interrupt` aborts the turn, which
+ * the SDK reports as `error_during_execution` — that is the capture path, and
+ * it is only legitimate when a tool call actually came back. A text-only answer
+ * can also exhaust the single-turn cap (`error_max_turns`) after the model has
+ * already said everything it intended to.
+ *
+ * Every other subtype — auth failure, billing, transport — is a real error.
+ */
+export function isExpectedTurnEnding(
+ resultSubtype: string | undefined,
+ capturedToolCallCount: number,
+): boolean {
+ if (capturedToolCallCount > 0) {
+ return true;
+ }
+
+ return resultSubtype === "error_max_turns";
+}
+
+/**
+ * The MCP handler must be unreachable: OpenWiki's agent loop owns tool
+ * execution, so a call that reached here would run the tool twice. The bridge
+ * denies every call in `canUseTool` before the handler can fire, and this
+ * throw makes any regression in that interception loud instead of silent.
+ */
+function captureOnlyHandler(): Promise {
+ return Promise.reject(
+ new Error(
+ "OpenWiki tool handler was invoked inside Claude Code. Tool calls must be " +
+ "captured by canUseTool and executed by the OpenWiki agent loop.",
+ ),
+ );
+}
+
+/**
+ * Renders the LangChain transcript as a single user turn.
+ *
+ * The Agent SDK only accepts user messages — there is no way to inject prior
+ * assistant turns — so a resumable session cannot be reconstructed from
+ * LangChain's message list. Replaying the transcript each call keeps the model
+ * stateless, which is what `_generate` promises and what makes OpenWiki's
+ * checkpointer, summarization middleware, and `--update` resume behave. The
+ * rendering is deterministic so the prefix stays byte-stable across steps and
+ * Claude Code's prompt cache absorbs most of the repeated cost.
+ */
+function renderTranscript(messages: BaseMessage[]): string {
+ const lines: string[] = [];
+
+ for (const message of messages) {
+ const type = message.getType();
+
+ if (type === "system") {
+ // Hoisted into the `systemPrompt` option instead.
+ continue;
+ }
+
+ if (type === "tool") {
+ const toolMessage = message as ToolMessage;
+ lines.push(
+ ``,
+ renderContent(toolMessage.content),
+ "",
+ );
+ continue;
+ }
+
+ if (type === "ai") {
+ const aiMessage = message as AIMessage;
+ const text = renderContent(aiMessage.content).trim();
+
+ if (text) {
+ lines.push("", text, "");
+ }
+
+ for (const toolCall of aiMessage.tool_calls ?? []) {
+ lines.push(
+ ``,
+ JSON.stringify(toolCall.args),
+ "",
+ );
+ }
+ continue;
+ }
+
+ lines.push("", renderContent(message.content), "");
+ }
+
+ return lines.join("\n");
+}
+
+function renderContent(content: BaseMessage["content"]): string {
+ if (typeof content === "string") {
+ return content;
+ }
+
+ return content
+ .map((block) => {
+ if (typeof block === "string") {
+ return block;
+ }
+ const record = block as Record;
+ if (record.type === "text" && typeof record.text === "string") {
+ return record.text;
+ }
+ return JSON.stringify(record);
+ })
+ .join("\n");
+}
+
+function extractSystemPrompt(messages: BaseMessage[]): string | undefined {
+ const system = messages
+ .filter((message) => message.getType() === "system")
+ .map((message) => renderContent(message.content).trim())
+ .filter(Boolean);
+
+ return system.length > 0 ? system.join("\n\n") : undefined;
+}
+
+/**
+ * A LangChain chat model backed by the local Claude Code CLI.
+ *
+ * Routes inference through the user's existing Claude Code session instead of
+ * an Anthropic API key, so teams whose plan does not permit creating API keys
+ * — or whose key has no credit — can still run OpenWiki. This mirrors the
+ * `openai-chatgpt` and `copilot` providers, which reuse a ChatGPT and Copilot
+ * subscription respectively.
+ *
+ * Claude Code owns its own agent loop, so the bridge constrains it to a single
+ * model turn: OpenWiki's tools are exposed through an in-process MCP server,
+ * every built-in tool is disabled, and `canUseTool` captures the resulting
+ * `tool_use` and stops the turn before Claude Code can execute it. Execution
+ * stays with DeepAgents, which is what keeps the virtual filesystem backend,
+ * OKF middleware, and translation middleware in the loop.
+ */
+export class ChatClaudeCode extends BaseChatModel {
+ model: string;
+
+ pathToClaudeCodeExecutable?: string;
+
+ private boundTools: NormalizedTool[] = [];
+
+ constructor(params: ChatClaudeCodeParams) {
+ super(params);
+ this.model = params.model;
+ this.pathToClaudeCodeExecutable = params.pathToClaudeCodeExecutable;
+ }
+
+ static lc_name(): string {
+ return "ChatClaudeCode";
+ }
+
+ _llmType(): string {
+ return "claude-code";
+ }
+
+ override bindTools(tools: BindToolsInput[]): this {
+ const normalized = tools
+ .map(normalizeTool)
+ .filter((tool): tool is NormalizedTool => tool !== null);
+
+ // BaseChatModel has no generic clone hook, so copy the instance and swap
+ // the tool list. `bindTools` must not mutate the receiver: DeepAgents
+ // rebinds per node and expects the original to stay tool-free.
+ const bound = Object.create(
+ Object.getPrototypeOf(this) as object,
+ ) as ChatClaudeCode;
+ Object.assign(bound, this);
+ bound.boundTools = normalized;
+ return bound as this;
+ }
+
+ private buildMcpServer() {
+ const tools: Array> =
+ this.boundTools.map((tool) => ({
+ name: tool.name,
+ description: tool.description,
+ inputSchema: jsonSchemaToZodShape(tool.parameters),
+ handler: captureOnlyHandler,
+ }));
+
+ return createSdkMcpServer({
+ name: MCP_SERVER_NAME,
+ version: "1.0.0",
+ tools,
+ // Without this the tools are deferred behind ToolSearch, which consumes
+ // the single turn the bridge allows and yields no tool call.
+ alwaysLoad: true,
+ });
+ }
+
+ async _generate(
+ messages: BaseMessage[],
+ options: this["ParsedCallOptions"],
+ runManager?: CallbackManagerForLLMRun,
+ ): Promise {
+ const systemPrompt = extractSystemPrompt(messages);
+ const prompt = renderTranscript(messages);
+
+ const capturedToolCalls: Array<{ name: string; args: unknown }> = [];
+ let textOutput = "";
+
+ const queryOptions: ClaudeAgentOptions = {
+ model: this.model,
+ // Claude Code's own project instructions (CLAUDE.md, settings files)
+ // would otherwise leak into OpenWiki's prompt and skew the wiki.
+ settingSources: [],
+ // Defense in depth only — the real guard is the `canUseTool` deny below,
+ // because this list cannot stay complete as the CLI gains tools.
+ disallowedTools: [...DISABLED_BUILTIN_TOOLS],
+ // The bridge wants exactly one model turn, but a refused built-in costs a
+ // turn before the model retries with an OpenWiki tool. Allow a few so
+ // that retry can land; `canUseTool` still interrupts the moment a real
+ // tool call is captured, so this is a ceiling rather than a target.
+ maxTurns: MAX_TURNS_PER_GENERATION,
+ env: claudeCodeSessionEnv(),
+ abortController: options.signal
+ ? toAbortController(options.signal)
+ : undefined,
+ };
+
+ if (systemPrompt) {
+ queryOptions.systemPrompt = systemPrompt;
+ }
+
+ if (this.pathToClaudeCodeExecutable) {
+ queryOptions.pathToClaudeCodeExecutable = this.pathToClaudeCodeExecutable;
+ }
+
+ if (this.boundTools.length > 0) {
+ queryOptions.mcpServers = { [MCP_SERVER_NAME]: this.buildMcpServer() };
+ }
+
+ // Registered even with no tools bound, so a built-in call is still refused
+ // rather than executed against the real repository.
+ queryOptions.canUseTool = createToolPermissionHandler(capturedToolCalls);
+
+ const response = query({ prompt, options: queryOptions });
+
+ // The SDK reports failures both as a thrown error and as a `result`
+ // message. Record the subtype so the two expected non-success endings can
+ // be told apart from a genuine failure (auth, billing, transport), which
+ // must never be reported to the agent loop as a usable turn — doing so
+ // would bake a truncated or empty section into the wiki.
+ let resultSubtype: string | undefined;
+
+ try {
+ for await (const message of response) {
+ if (message.type === "result") {
+ resultSubtype = message.subtype;
+ continue;
+ }
+
+ if (message.type !== "assistant") {
+ continue;
+ }
+
+ for (const block of message.message.content ?? []) {
+ if (block.type === "text" && typeof block.text === "string") {
+ textOutput += block.text;
+ await runManager?.handleLLMNewToken(block.text);
+ }
+ }
+ }
+ } catch (error) {
+ if (!isExpectedTurnEnding(resultSubtype, capturedToolCalls.length)) {
+ throw error;
+ }
+ }
+
+ if (
+ resultSubtype !== undefined &&
+ resultSubtype !== "success" &&
+ !isExpectedTurnEnding(resultSubtype, capturedToolCalls.length)
+ ) {
+ throw new Error(
+ `Claude Code ended the turn with "${resultSubtype}" and produced no usable output.`,
+ );
+ }
+
+ // A turn that produced neither a tool call nor text is not a usable model
+ // response, whatever the SDK called it. Returning it would hand DeepAgents
+ // an empty assistant message and quietly drop a wiki section.
+ if (capturedToolCalls.length === 0 && textOutput.trim().length === 0) {
+ throw new Error(
+ `Claude Code returned no output for this turn (result: ${resultSubtype ?? "unknown"}).`,
+ );
+ }
+
+ const toolCalls = capturedToolCalls.map((call, index) => ({
+ id: `claude_code_tool_${index}_${Date.now()}`,
+ name: call.name,
+ args: (call.args ?? {}) as Record,
+ type: "tool_call" as const,
+ }));
+
+ const message = new AIMessage({
+ content: textOutput,
+ tool_calls: toolCalls,
+ });
+
+ return {
+ generations: [
+ {
+ text: textOutput,
+ message,
+ },
+ ],
+ };
+ }
+}
+
+/**
+ * LangChain hands down an `AbortSignal`; the Agent SDK wants an
+ * `AbortController`. Bridge the two so cancelling an OpenWiki run tears down
+ * the spawned CLI process.
+ */
+function toAbortController(signal: AbortSignal): AbortController {
+ const controller = new AbortController();
+
+ if (signal.aborted) {
+ controller.abort();
+ } else {
+ signal.addEventListener("abort", () => controller.abort(), { once: true });
+ }
+
+ return controller;
+}
diff --git a/src/agent/index.ts b/src/agent/index.ts
index 59dd3ff4..d380904c 100644
--- a/src/agent/index.ts
+++ b/src/agent/index.ts
@@ -34,6 +34,7 @@ import {
resolveConceptTypeLabel,
resolveIndexLabels,
} from "../okf/index-labels.js";
+import { ChatClaudeCode } from "./claude-code-model.js";
import { OpenWikiLocalShellBackend } from "./docs-only-backend.js";
import { createOpenWikiIndexMiddleware } from "./okf-middleware.js";
import {
@@ -917,6 +918,15 @@ export function createModel(
);
}
+ if (provider === "claude-code") {
+ // Keyless by design: the local Claude Code CLI carries the session, so
+ // there is no API key to resolve or base URL to override.
+ return new ChatClaudeCode({
+ model: modelId,
+ ...retryOptions,
+ });
+ }
+
if (provider === "anthropic") {
const baseURL = resolveProviderBaseUrl(provider);
diff --git a/src/constants.ts b/src/constants.ts
index d7475193..75257cae 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -88,6 +88,7 @@ export type OpenWikiProvider =
| "anthropic"
| "baseten"
| "bedrock"
+ | "claude-code"
| "copilot"
| "fireworks"
| "gemini"
@@ -113,7 +114,7 @@ export type ProviderAuthMethod =
* login. The provider config only declares the adapter; its implementation
* lives outside this declarative provider registry.
*/
-export type ExternalCliAuthAdapter = "github-cli";
+export type ExternalCliAuthAdapter = "claude-cli" | "github-cli";
export type SelectableOpenWikiProvider = OpenWikiProvider;
@@ -219,6 +220,7 @@ export const SELECTABLE_OPENWIKI_PROVIDERS = [
"openai",
"openai-chatgpt",
"anthropic",
+ "claude-code",
"copilot",
"gemini",
"gemini-enterprise",
@@ -256,6 +258,22 @@ export const PROVIDER_CONFIGS: Record = {
regionFallbackEnvKeys: [AWS_REGION_ENV_KEY, AWS_DEFAULT_REGION_ENV_KEY],
requiresRegion: true,
},
+ "claude-code": {
+ // Deliberately keyless: inference is routed through the local Claude Code
+ // CLI, which carries its own session. This is the point of the provider —
+ // it serves teams whose plan does not permit creating API keys. There is
+ // no CI fallback key because the CLI must be installed and logged in.
+ authMethod: "external-cli",
+ externalCliAuthAdapter: "claude-cli",
+ label: "Claude Code (local CLI)",
+ modelOptions: [
+ { id: "claude-opus-5", label: "Claude Opus 5" },
+ { id: "claude-fable-5", label: "Claude Fable 5" },
+ { id: "claude-sonnet-5", label: "Claude Sonnet 5" },
+ { id: "claude-opus-4-8", label: "Claude Opus 4.8" },
+ { id: "claude-haiku-4-5", label: "Claude Haiku 4.5" },
+ ],
+ },
copilot: {
apiKeyEnvKey: COPILOT_API_KEY_ENV_KEY,
authMethod: "external-cli",
diff --git a/src/external-cli-auth.ts b/src/external-cli-auth.ts
index 46fe0ab4..52a760ec 100644
--- a/src/external-cli-auth.ts
+++ b/src/external-cli-auth.ts
@@ -20,8 +20,34 @@ type ExternalCliAuthAdapterConfig = {
command: string;
commandArgs: readonly string[];
tokenArgs: readonly string[];
+ /**
+ * Interprets the output of {@link ExternalCliAuthAdapterConfig.tokenArgs}.
+ * Defaults to "any non-empty output is the credential", which suits CLIs
+ * that print a bare token. Adapters whose status command always prints
+ * something — even when logged out — must override this, or a signed-out
+ * CLI is reported as authenticated.
+ */
+ parseCredential?: (stdout: string) => string | null;
};
+/**
+ * `claude auth status` prints a JSON document whether or not a session exists,
+ * so presence of output proves nothing. Gate on the `loggedIn` flag instead.
+ *
+ * Unlike the GitHub CLI adapter, the returned value is a marker rather than a
+ * usable secret: the Claude Code session stays inside the CLI and the Agent
+ * SDK reads it directly when OpenWiki spawns it. Nothing is ever extracted or
+ * persisted.
+ */
+function parseClaudeCliStatus(stdout: string): string | null {
+ try {
+ const status = JSON.parse(stdout) as { loggedIn?: unknown };
+ return status.loggedIn === true ? "claude-code-session" : null;
+ } catch {
+ return null;
+ }
+}
+
/**
* `gh`'s `--hostname` flag must match the tenant a provider's base URL
* actually points at (e.g. a GHE.com data-residency host), or the reused
@@ -53,6 +79,17 @@ function buildExternalCliAuthAdapters(
const githubHostname = resolveGithubCliHostname(provider, env);
return {
+ "claude-cli": {
+ command: "claude",
+ commandArgs: ["auth", "login"],
+ credentialDescription: "Claude Code session",
+ installHint:
+ "Install Claude Code (https://claude.com/claude-code), then run `claude auth login`.",
+ loginCommand: "claude auth login",
+ name: "Claude Code CLI",
+ parseCredential: parseClaudeCliStatus,
+ tokenArgs: ["auth", "status"],
+ },
"github-cli": {
command: "gh",
commandArgs: ["auth", "login", "--hostname", githubHostname],
@@ -118,6 +155,10 @@ export async function detectExternalCliCredential(
const { stdout } = await execFileAsync(adapter.command, adapter.tokenArgs, {
timeout: EXTERNAL_CLI_TIMEOUT_MS,
});
+ if (adapter.parseCredential) {
+ return adapter.parseCredential(stdout);
+ }
+
const credential = stdout.trim();
return credential.length > 0 ? credential : null;
diff --git a/test/claude-code-model.test.ts b/test/claude-code-model.test.ts
new file mode 100644
index 00000000..f5e85ca9
--- /dev/null
+++ b/test/claude-code-model.test.ts
@@ -0,0 +1,193 @@
+import { describe, expect, test } from "vitest";
+import { z } from "zod";
+import { z as z3 } from "zod/v3";
+import { ChatClaudeCode } from "../src/agent/claude-code-model.ts";
+import {
+ createToolPermissionHandler,
+ type CapturedToolCall,
+ claudeCodeSessionEnv,
+ isExpectedTurnEnding,
+ jsonSchemaToZod,
+ normalizeTool,
+ normalizeToJsonSchema,
+} from "../src/agent/claude-code-model.ts";
+
+describe("JSON Schema to Zod conversion", () => {
+ test("preserves required vs optional fields", () => {
+ const schema = jsonSchemaToZod({
+ type: "object",
+ properties: { a: { type: "string" }, b: { type: "number" } },
+ required: ["a"],
+ });
+
+ expect(schema.safeParse({ a: "x" }).success).toBe(true);
+ expect(schema.safeParse({ b: 1 }).success).toBe(false);
+ });
+
+ test("keeps an additionalProperties:true object open", () => {
+ // openwiki_call_mcp_tool forwards arbitrary connector arguments through an
+ // open `args` object. Closing it would strip every argument the model
+ // sends, silently breaking connector ingestion.
+ const schema = jsonSchemaToZod({
+ type: "object",
+ properties: {},
+ additionalProperties: true,
+ });
+
+ const parsed = schema.safeParse({ query: "Applied AI", limit: 5 });
+ expect(parsed.success).toBe(true);
+ expect(parsed.data).toEqual({ query: "Applied AI", limit: 5 });
+ });
+
+ test("closes an object that explicitly declares fields", () => {
+ const schema = jsonSchemaToZod({
+ type: "object",
+ properties: { known: { type: "string" } },
+ required: ["known"],
+ additionalProperties: false,
+ });
+
+ const parsed = schema.safeParse({ known: "x", extra: "dropped" });
+ expect(parsed.success).toBe(true);
+ expect(parsed.data).toEqual({ known: "x" });
+ });
+
+ test("converts arrays, enums, and nested objects", () => {
+ const schema = jsonSchemaToZod({
+ type: "object",
+ properties: {
+ items: { type: "array", items: { type: "string" } },
+ status: { type: "string", enum: ["todo", "done"] },
+ nested: {
+ type: "object",
+ properties: { n: { type: "integer" } },
+ required: ["n"],
+ },
+ },
+ required: ["items", "status", "nested"],
+ });
+
+ expect(
+ schema.safeParse({
+ items: ["a"],
+ status: "todo",
+ nested: { n: 1 },
+ }).success,
+ ).toBe(true);
+ expect(
+ schema.safeParse({ items: ["a"], status: "nope", nested: { n: 1 } })
+ .success,
+ ).toBe(false);
+ });
+});
+
+describe("tool schema normalization", () => {
+ test("converts a Zod v4 tool schema without losing parameters", () => {
+ const jsonSchema = normalizeToJsonSchema(
+ z.object({ todos: z.array(z.string()) }),
+ ) as { properties?: Record };
+
+ expect(Object.keys(jsonSchema.properties ?? {})).toContain("todos");
+ });
+
+ test("converts a Zod v3 tool schema without losing parameters", () => {
+ // DeepAgents tools are not uniformly Zod v4. Zod 4's own `toJSONSchema`
+ // throws on a v3 schema, which previously degraded the tool to a
+ // parameterless one — the model could still call it, but never correctly.
+ const jsonSchema = normalizeToJsonSchema(
+ z3.object({ todos: z3.array(z3.string()) }),
+ ) as { properties?: Record };
+
+ expect(Object.keys(jsonSchema.properties ?? {})).toContain("todos");
+ });
+
+ test("reads OpenAI-style function tool definitions", () => {
+ expect(
+ normalizeTool({
+ type: "function",
+ function: {
+ name: "write_file",
+ description: "Write a file",
+ parameters: {
+ type: "object",
+ properties: { path: { type: "string" } },
+ },
+ },
+ }),
+ ).toMatchObject({ name: "write_file", description: "Write a file" });
+ });
+});
+
+describe("bindTools", () => {
+ test("returns a new model without mutating the receiver", () => {
+ const model = new ChatClaudeCode({ model: "claude-sonnet-5" });
+ const bound = model.bindTools([
+ { name: "ls", description: "List", schema: z.object({}) },
+ ]);
+
+ expect(bound).not.toBe(model);
+ // DeepAgents rebinds per node; a mutating bindTools would leak one node's
+ // tools into every later call.
+ expect(model.bindTools([]).constructor).toBe(ChatClaudeCode);
+ });
+});
+
+describe("session environment", () => {
+ test("strips Anthropic credentials that would shadow the CLI session", () => {
+ // OpenWiki loads ~/.openwiki/.env into process.env, so a previously
+ // configured API key would otherwise bill the API — and fail outright when
+ // that dead key is the reason the user chose this provider.
+ const env = claudeCodeSessionEnv({
+ ANTHROPIC_API_KEY: "sk-ant-dead",
+ ANTHROPIC_AUTH_TOKEN: "oauth",
+ ANTHROPIC_BASE_URL: "https://gateway.example.com",
+ PATH: "/usr/bin",
+ });
+
+ expect(env.ANTHROPIC_API_KEY).toBeUndefined();
+ expect(env.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
+ expect(env.ANTHROPIC_BASE_URL).toBeUndefined();
+ expect(env.PATH).toBe("/usr/bin");
+ });
+});
+
+describe("built-in tool refusal", () => {
+ test("refuses a built-in without interrupting so the model can retry", async () => {
+ // Regression: interrupting on a built-in ended the turn with nothing
+ // captured, surfacing as error_max_turns and failing the whole run. Only an
+ // OpenWiki tool call may interrupt; a built-in must be refused in a way the
+ // model can recover from within the same turn.
+ const captured: CapturedToolCall[] = [];
+ const canUseTool = createToolPermissionHandler(captured);
+
+ const builtin = await canUseTool("Read", { file_path: "/etc/passwd" });
+ expect(builtin.behavior).toBe("deny");
+ expect("interrupt" in builtin && builtin.interrupt).toBeFalsy();
+
+ const openWikiTool = await canUseTool("mcp__openwiki__write_file", {
+ file_path: "a.md",
+ });
+ expect(openWikiTool.behavior).toBe("deny");
+ expect("interrupt" in openWikiTool && openWikiTool.interrupt).toBe(true);
+ expect(captured).toEqual([
+ { name: "write_file", args: { file_path: "a.md" } },
+ ]);
+ });
+});
+
+describe("turn ending classification", () => {
+ test("treats a captured tool call as the expected interrupt", () => {
+ expect(isExpectedTurnEnding("error_during_execution", 1)).toBe(true);
+ });
+
+ test("accepts a text-only turn that exhausted the turn cap", () => {
+ expect(isExpectedTurnEnding("error_max_turns", 0)).toBe(true);
+ });
+
+ test("rejects a genuine failure with nothing captured", () => {
+ // Billing/auth/transport failures must not be reported to the agent loop
+ // as a usable turn, or a truncated section is baked into the wiki.
+ expect(isExpectedTurnEnding("error_during_execution", 0)).toBe(false);
+ expect(isExpectedTurnEnding(undefined, 0)).toBe(false);
+ });
+});
diff --git a/test/claude-code-provider.test.ts b/test/claude-code-provider.test.ts
new file mode 100644
index 00000000..6fabd27d
--- /dev/null
+++ b/test/claude-code-provider.test.ts
@@ -0,0 +1,55 @@
+import { describe, expect, test } from "vitest";
+import {
+ getProviderApiKeyEnvKey,
+ getProviderAuthMethod,
+ getProviderExternalCliAuthAdapter,
+ getProviderModelOptions,
+ providerRequiresApiKey,
+ providerUsesExternalCliAuth,
+ providerUsesResponsesApi,
+ SELECTABLE_OPENWIKI_PROVIDERS,
+} from "../src/constants.ts";
+
+describe("Claude Code provider config", () => {
+ test("authenticates through the Claude Code CLI rather than a key", () => {
+ expect(getProviderAuthMethod("claude-code")).toBe("external-cli");
+ expect(providerUsesExternalCliAuth("claude-code")).toBe(true);
+ expect(getProviderExternalCliAuthAdapter("claude-code")).toBe("claude-cli");
+ });
+
+ test("is keyless: no API key env var and none required", () => {
+ // The whole point of the provider is serving users who cannot create an
+ // API key, so introducing one here would defeat it.
+ expect(getProviderApiKeyEnvKey("claude-code")).toBeUndefined();
+ expect(providerRequiresApiKey("claude-code")).toBe(false);
+ });
+
+ test("is selectable during onboarding", () => {
+ expect(SELECTABLE_OPENWIKI_PROVIDERS).toContain("claude-code");
+ });
+
+ test("offers the frontier Claude models the CLI can serve", () => {
+ const ids = getProviderModelOptions("claude-code").map((model) => model.id);
+
+ expect(ids).toContain("claude-opus-5");
+ expect(ids).toContain("claude-fable-5");
+ });
+
+ test("offers Claude model ids in canonical API form", () => {
+ const ids = getProviderModelOptions("claude-code").map((model) => model.id);
+
+ expect(ids.length).toBeGreaterThan(0);
+ // Copilot lists dotted ids ("claude-opus-4.8"); the CLI takes the canonical
+ // dashed form, so a copy/paste between the two would silently 404.
+ for (const id of ids) {
+ expect(id).toMatch(/^claude-[a-z0-9-]+$/u);
+ expect(id).not.toContain(".");
+ }
+ });
+
+ test("never routes through the OpenAI Responses API", () => {
+ expect(providerUsesResponsesApi("claude-code", "claude-opus-5")).toBe(
+ false,
+ );
+ });
+});
diff --git a/test/constants.test.ts b/test/constants.test.ts
index a1f98a96..fb34aea0 100644
--- a/test/constants.test.ts
+++ b/test/constants.test.ts
@@ -630,18 +630,21 @@ describe("getDefaultModelId", () => {
describe("getProvidersForKnownModelId", () => {
test("finds the provider(s) whose known models include the id", () => {
- // claude-opus-4-8 is a known model of both anthropic and the
- // gemini-enterprise gateway, which also serves Claude models.
+ // claude-opus-4-8 is a known model of anthropic, the claude-code CLI
+ // bridge, and the gemini-enterprise gateway, which also serves Claude
+ // models.
expect(getProvidersForKnownModelId("claude-opus-4-8", "openai")).toEqual([
+ "claude-code",
"anthropic",
"gemini-enterprise",
]);
});
test("excludes the provider passed in", () => {
- // Excluding anthropic still leaves gemini-enterprise, which also lists it.
+ // Excluding anthropic still leaves claude-code and gemini-enterprise,
+ // which also list it.
expect(getProvidersForKnownModelId("claude-opus-4-8", "anthropic")).toEqual(
- ["gemini-enterprise"],
+ ["claude-code", "gemini-enterprise"],
);
});