diff --git a/.github/workflows/deploy-mcp.yml b/.github/workflows/deploy-mcp.yml new file mode 100644 index 00000000..6aca56e6 --- /dev/null +++ b/.github/workflows/deploy-mcp.yml @@ -0,0 +1,53 @@ +name: Deploy MCP Worker + +on: + push: + branches: + - main + paths: + - "worker/src/mcp.ts" + - "worker/src/env.ts" + - "worker/mcp-entry.ts" + - "worker/wrangler-mcp.toml" + - "worker/package.json" + - "worker/package-lock.json" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy-mcp-production + cancel-in-progress: false + +jobs: + deploy: + name: Deploy PS-Wiki MCP Worker + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: worker/package-lock.json + + - name: Install and verify + working-directory: worker + run: | + npm ci + npm run check + npx wrangler deploy --config wrangler-mcp.toml --dry-run + + - name: Deploy + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: deploy --config wrangler-mcp.toml + workingDirectory: worker + wranglerVersion: "4" diff --git a/.github/workflows/deploy-worker.yml b/.github/workflows/deploy-worker.yml index 383da3ca..6092b2fb 100644 --- a/.github/workflows/deploy-worker.yml +++ b/.github/workflows/deploy-worker.yml @@ -5,7 +5,8 @@ on: branches: - main paths: - - "worker/src/**" + - "worker/src/index.ts" + - "worker/src/env.ts" - "worker/wrangler.toml" - "worker/package.json" - "worker/package-lock.json" @@ -27,6 +28,20 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: worker/package-lock.json + + - name: Install and verify + working-directory: worker + run: | + npm ci + npm run check + npx wrangler deploy --dry-run + - name: Deploy uses: cloudflare/wrangler-action@v4 with: diff --git a/README.md b/README.md index 4a68e635..b609d2f8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Built with [MkDocs Material](https://squidfunk.github.io/mkdocs-material/), the ## REST API The PS-Wiki REST API provides read-only access to term data in JSON format. -It is deployed via [Cloudflare Workers](./worker) and documented using the OpenAPI 3.1.0 specification at +It is deployed via [Cloudflare Workers](./worker) and documented using the OpenAPI 3.1.0 specification at ### Developer Notes diff --git a/database/openapi/pswiki.v1.yaml b/database/openapi/pswiki.v1.yaml index 71bd0dee..b0145631 100644 --- a/database/openapi/pswiki.v1.yaml +++ b/database/openapi/pswiki.v1.yaml @@ -7,7 +7,7 @@ info: name: CC BY-NC 4.0 url: https://creativecommons.org/licenses/by-nc/4.0/ servers: - - url: https://api.ning.guru + - url: https://api.ps-wiki.ning.guru description: Production server - url: http://localhost:8787 description: Local development server diff --git a/database/pyscripts/test_api.py b/database/pyscripts/test_api.py index bf68c497..04500d79 100644 --- a/database/pyscripts/test_api.py +++ b/database/pyscripts/test_api.py @@ -381,8 +381,8 @@ def main(argv=None) -> int: parser = argparse.ArgumentParser(description="PS-Wiki API Contract Test Suite") parser.add_argument( "--base-url", - default="https://api.ning.guru", - help="Base URL (default: https://api.ning.guru). Examples: http://localhost:8787 or https://api.ning.guru", + default="https://api.ps-wiki.ning.guru", + help="Base URL (default: https://api.ps-wiki.ning.guru). Examples: http://localhost:8787 or https://api.ps-wiki.ning.guru", ) parser.add_argument("--query", help="Optional free-text query for /v1/terms") parser.add_argument("--tag", help="Optional tag filter for /v1/terms") diff --git a/docs/changelog.md b/docs/changelog.md index 7b24a9c7..d2888010 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,10 @@ This page records major changes in this wiki. ## 2026 +### 09-14 + +- Added the public, read-only remote MCP service at `https://mcp.ps-wiki.ning.guru/mcp`, backed by the REST API at `https://api.ps-wiki.ning.guru`; documented local stdio and remote MCP usage, deployment, verification, and attribution + ### 09-13 - Prepared the REST API contract for the upcoming `1.1.0` release while retaining the `/v1` routes; added canonical term URLs and structured PS-Wiki attribution and license metadata to API responses diff --git a/docs/mcp.md b/docs/mcp.md index 9e5cd2e8..b8bd7762 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1,150 +1,254 @@ --- title: MCP Server -description: AI-native access to PS-Wiki terminology via Model Context Protocol +description: Local and remote AI-agent access to PS-Wiki terminology --- -The **PS-Wiki MCP Server** provides AI assistants with direct access to power systems terminology through the [Model Context Protocol](https://modelcontextprotocol.io). This enables natural language queries with accurate, cited responses. +# PS-Wiki MCP Server -## Features +PS-Wiki exposes the same read-only terminology through two MCP transports: -- Search 180+ power systems terms by keyword or concept -- Retrieve detailed definitions with equations and citations -- Explore related concepts and build knowledge graphs -- Browse by tags (stability, control, protection, market, etc.) -- Works with Claude Desktop, Ollama, and other MCP clients +- **Local MCP:** a Python package launched over stdio. +- **Remote MCP:** a dedicated Cloudflare Worker endpoint at `https://mcp.ps-wiki.ning.guru/mcp`. -## Quick Start +The REST API remains the canonical programmatic data interface. MCP is a thin, +agent-facing semantic adapter: -### Installation +```text +LLM / AI agent + ↓ +PS-Wiki MCP + ↓ +PS-Wiki REST API + ↓ +PS-Wiki data +``` + +All tools are read-only. Responses include concise structured JSON and, where +available, canonical PS-Wiki URLs and attribution metadata. + +## Local MCP (stdio) + +Install from source: ```bash -# Install from source -git clone https://github.com/ps-wiki/pswiki.git -cd pswiki/mcp -pip install -e . +git clone https://github.com/ps-wiki/ps-wiki.github.io.git +cd ps-wiki.github.io/mcp +python -m venv .venv +source .venv/bin/activate +pip install -e ".[dev]" ``` -### Configuration +Run the server directly: -=== "Claude Desktop" +```bash +pswiki-mcp +# equivalent: +python -m pswiki_mcp +``` - Add to `claude_desktop_config.json`: +For a live smoke test against the REST API: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` +```bash +python tests/test_server_manual.py +``` - ```json - { - "mcpServers": { - "pswiki": { - "command": "python", - "args": ["-m", "pswiki_mcp"] - } - } - } - ``` - - Restart Claude Desktop, then ask: - - - _"Search for power systems stability terms"_ - - _"Explain voltage stability in detail"_ - - _"What are related concepts to automatic generation control?"_ - -=== "Ollama (Continue.dev)" - - 1. Install the [Continue](https://continue.dev) extension for VS Code. - 2. Configure `~/.continue/config.json`: - - ```json - { - "models": [ - { - "title": "Llama 3.2", - "provider": "ollama", - "model": "llama3.2" - } - ], - "mcpServers": { - "pswiki": { - "command": "python", - "args": ["-m", "pswiki_mcp"] - } - } - } - ``` +Example Claude Desktop configuration: - 3. Open the Continue sidebar (`⌘+L`) and start asking questions. +```json +{ + "mcpServers": { + "pswiki": { + "command": "/absolute/path/to/ps-wiki.github.io/mcp/.venv/bin/pswiki-mcp" + } + } +} +``` -## Available Tools +The same stdio configuration shape works for other local MCP clients that +launch a child process. The automated Python tests use mocked HTTP responses: -The MCP server exposes five tools to AI assistants: +```bash +cd mcp +PYTHONPATH=src python -m pytest -q +``` -### search_terms +## Remote MCP (Streamable HTTP) -Search terminology by keyword, phrase, or concept. +No installation is required for a client that supports remote MCP: -```python -search_terms(query="frequency control", limit=10) +```text +https://mcp.ps-wiki.ning.guru/mcp ``` -### get_term +The endpoint is stateless, public, and read-only. It supports MCP initialization, +tool discovery, and tool calls over the current Streamable HTTP handler, with +compatibility for ordinary stateless 2025-era clients. Clients should send +`Accept: application/json, text/event-stream` and `Content-Type: application/json`. -Retrieve full details for a specific term. +Generic client configuration: -```python -get_term(term_id="voltage-stability") +```json +{ + "mcpServers": { + "pswiki": { + "url": "https://mcp.ps-wiki.ning.guru/mcp" + } + } +} ``` -### get_related_terms +Claude Desktop versions that do not accept a remote URL directly can use the +standard local proxy: + +```json +{ + "mcpServers": { + "pswiki": { + "command": "npx", + "args": ["-y", "mcp-remote", "https://mcp.ps-wiki.ning.guru/mcp"] + } + } +} +``` -Find related concepts with configurable depth. +In ChatGPT or another client with remote MCP/connectors support, add the server +URL `https://mcp.ps-wiki.ning.guru/mcp` in that client’s MCP/connector settings. UI +availability and approval controls depend on the account or workspace. For the +OpenAI Responses API, the protocol-level shape is: ```python -get_related_terms(term_id="power-flow", depth=2) +response = client.responses.create( + model="gpt-5", + input="Explain voltage stability using PS-Wiki.", + tools=[{ + "type": "mcp", + "server_label": "pswiki", + "server_url": "https://mcp.ps-wiki.ning.guru/mcp", + "allowed_tools": {"read_only": True}, + "require_approval": "never", + }], +) ``` -### list_tags +## Tools -List all available tags with usage counts. +### `search_terms` -```python -list_tags() +Search by keyword, phrase, or concept. Use this to discover candidate terms; +results are bounded summaries with IDs, tags, update times, and canonical URLs. + +Arguments: `query` (required string), `limit` (optional integer, 1–50, +default 10). + +### `get_term` + +Retrieve the complete definition for a known term ID, including description, +equations, citations, related terms, canonical URL, and attribution. + +Argument: `term_id` (required kebab-case string, for example +`voltage-stability`). + +### `get_related_terms` + +Explore direct relationships for a known term. Use `depth: 1` for direct +relationships or `depth: 2` for a bounded second-level exploration. + +Arguments: `term_id` (required string), `depth` (optional integer, 1–2, +default 1). + +### `list_tags` + +List the PS-Wiki taxonomy and usage counts. Use this to browse categories. + +### `get_terms_by_tag` + +List terms in a specific category/tag. Use this for taxonomy-based browsing +rather than free-text concept search. + +Argument: `tag` (required string, case-insensitive). + +## Local development and verification + +Run the remote Worker locally: + +```bash +cd worker +npm ci +npm run mcp:dev +# endpoint: http://localhost:8788/mcp ``` -### get_terms_by_tag +Run deterministic checks: + +```bash +npm run check +npx wrangler deploy --config wrangler-mcp.toml --dry-run +``` -Filter terms by category. +For interactive verification, start the [MCP Inspector](https://github.com/modelcontextprotocol/inspector): -```python -get_terms_by_tag(tag="stability") +```bash +npx @modelcontextprotocol/inspector@latest ``` -## Programmatic Usage +Connect it to `http://localhost:8788/mcp`, select **List Tools**, and invoke +`search_terms` with `{"query":"stability","limit":3}`. -```python -from mcp import ClientSession, StdioServerParameters -from mcp.client.stdio import stdio_client +## Deployment and rollback -server_params = StdioServerParameters( - command="python", - args=["-m", "pswiki_mcp"] -) +From the Worker directory: -async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write) as session: - await session.initialize() - result = await session.call_tool( - "search_terms", - {"query": "stability", "limit": 5} - ) +```bash +cd worker +npm ci +npm run check +npm run deploy:mcp ``` -## Data Source +The MCP Worker deployment uses the Cloudflare account configured in +Wrangler or the repository’s `CLOUDFLARE_API_TOKEN` and +`CLOUDFLARE_ACCOUNT_ID` Actions secrets. No new secret, OAuth provider, +or Durable Object is required. + +After deployment, verify the endpoint with MCP Inspector or by sending the +following JSON-RPC requests: + +```bash +MCP_URL=https://mcp.ps-wiki.ning.guru/mcp +curl -sS "$MCP_URL" \ + -X POST \ + -H 'Accept: application/json, text/event-stream' \ + -H 'Content-Type: application/json' \ + --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' + +curl -sS "$MCP_URL" \ + -X POST \ + -H 'Accept: application/json, text/event-stream' \ + -H 'Content-Type: application/json' \ + --data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' + +curl -sS "$MCP_URL" \ + -X POST \ + -H 'Accept: application/json, text/event-stream' \ + -H 'Content-Type: application/json' \ + --data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_terms","arguments":{"query":"stability","limit":3}}}' +``` + +For a failed deployment, inspect `npx wrangler versions list` and use +`npx wrangler rollback ` from `worker/` after confirming the +target version. The existing REST routes should be probed after rollback. + +The MCP Worker config declares the dedicated `pswiki-mcp` Worker at the +`mcp.ps-wiki.ning.guru` custom hostname. If Cloudflare asks for dashboard +confirmation on the first deployment, approve only that hostname. The REST +Worker uses `api.ps-wiki.ning.guru`. A rate-limit rule scoped to `/mcp` can be +added later if traffic warrants it. Do not change nameservers or unrelated DNS +records. -The MCP server fetches data from the [PS-Wiki REST API](rest-api.md). All data is read-only and licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). +## Resources and attribution -## Resources +The local stdio server also retains its existing read-only resources: +`tags://all`, `index://terms`, and `term:///`. -- **GitHub**: [ps-wiki/ps-wiki.github.io/mcp](https://github.com/ps-wiki/ps-wiki.github.io/tree/main/mcp) -- **MCP Specification**: [modelcontextprotocol.io](https://modelcontextprotocol.io) -- **Issues**: [GitHub Issues](https://github.com/ps-wiki/ps-wiki.github.io/issues) +PS-Wiki content is licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). +The remote MCP layer delegates data retrieval to the [PS-Wiki REST API](rest-api.md). diff --git a/docs/rest-api.md b/docs/rest-api.md index 7d0403d2..865a5b79 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -7,24 +7,23 @@ The **PS-Wiki REST API** provides programmatic access to 173+ power systems term !!! note "Production API URL" - The PS-Wiki API is available at `https://api.ning.guru`. - The original `pswiki-api.jinning.workers.dev` hostname remains available as a fallback. + The PS-Wiki API is available at `https://api.ps-wiki.ning.guru`. ## Base URL ``` -https://api.ning.guru +https://api.ps-wiki.ning.guru ``` -**OpenAPI Specification**: [/openapi.json](https://api.ning.guru/openapi.json) +**OpenAPI Specification**: [/openapi.json](https://api.ps-wiki.ning.guru/openapi.json) ## Quick Start ### Search for Terms ```bash -curl "https://api.ning.guru/v1/terms?query=stability&limit=5" +curl "https://api.ps-wiki.ning.guru/v1/terms?query=stability&limit=5" ``` **Response**: @@ -56,7 +55,7 @@ curl "https://api.ning.guru/v1/terms?query=stability&limit=5" ### Get a Specific Term ```bash -curl "https://api.ning.guru/v1/terms/voltage-stability" +curl "https://api.ps-wiki.ning.guru/v1/terms/voltage-stability" ``` Returns the complete term data including definition, equations, citations, metadata, @@ -80,16 +79,16 @@ Search or list term summaries. ```bash # List all terms (paginated) -curl "https://api.ning.guru/v1/terms?limit=10" +curl "https://api.ps-wiki.ning.guru/v1/terms?limit=10" # Search by keyword -curl "https://api.ning.guru/v1/terms?query=frequency+control" +curl "https://api.ps-wiki.ning.guru/v1/terms?query=frequency+control" # Filter by tag -curl "https://api.ning.guru/v1/terms?tag=stability" +curl "https://api.ps-wiki.ning.guru/v1/terms?tag=stability" # Combine search and limit -curl "https://api.ning.guru/v1/terms?query=power+flow&limit=5" +curl "https://api.ps-wiki.ning.guru/v1/terms?query=power+flow&limit=5" ``` ### GET /v1/terms/{id} @@ -103,7 +102,7 @@ Retrieve full details for a specific term. **Example**: ```bash -curl "https://api.ning.guru/v1/terms/automatic-generation-control" +curl "https://api.ps-wiki.ning.guru/v1/terms/automatic-generation-control" ``` **Response**: Complete term JSON including full definition and description, mathematical equations (LaTeX), citations and references, related terms, tags and metadata. @@ -113,7 +112,7 @@ curl "https://api.ning.guru/v1/terms/automatic-generation-control" List all available tags with usage counts. ```bash -curl "https://api.ning.guru/v1/tags" +curl "https://api.ps-wiki.ning.guru/v1/tags" ``` **Response**: @@ -145,7 +144,7 @@ Get terms updated since a specific date. - `since` (string, required) — ISO 8601 date or datetime (e.g., `2025-11-01`) ```bash -curl "https://api.ning.guru/v1/changes?since=2025-11-01" +curl "https://api.ps-wiki.ning.guru/v1/changes?since=2025-11-01" ``` **Response**: @@ -184,14 +183,14 @@ curl "https://api.ning.guru/v1/changes?since=2025-11-01" # Search for terms response = requests.get( - "https://api.ning.guru/v1/terms", + "https://api.ps-wiki.ning.guru/v1/terms", params={"query": "stability", "limit": 10} ) terms = response.json() # Get specific term term = requests.get( - "https://api.ning.guru/v1/terms/voltage-stability" + "https://api.ps-wiki.ning.guru/v1/terms/voltage-stability" ).json() print(f"Title: {term['title']}") @@ -203,13 +202,13 @@ curl "https://api.ning.guru/v1/changes?since=2025-11-01" ```javascript // Search for terms const response = await fetch( - "https://api.ning.guru/v1/terms?query=stability&limit=10" + "https://api.ps-wiki.ning.guru/v1/terms?query=stability&limit=10" ); const data = await response.json(); // Get specific term const term = await fetch( - "https://api.ning.guru/v1/terms/voltage-stability" + "https://api.ps-wiki.ning.guru/v1/terms/voltage-stability" ).then((r) => r.json()); console.log(`Title: ${term.title}`); @@ -220,15 +219,15 @@ curl "https://api.ning.guru/v1/changes?since=2025-11-01" ```bash # Pretty-print search results - curl -s "https://api.ning.guru/v1/terms?query=stability" \ + curl -s "https://api.ps-wiki.ning.guru/v1/terms?query=stability" \ | jq '.items[] | {id, title}' # Extract just the description - curl -s "https://api.ning.guru/v1/terms/voltage-stability" \ + curl -s "https://api.ps-wiki.ning.guru/v1/terms/voltage-stability" \ | jq -r '.description' # List all tags - curl -s "https://api.ning.guru/v1/tags" \ + curl -s "https://api.ps-wiki.ning.guru/v1/tags" \ | jq '.tags[] | "\(.tag): \(.count)"' ``` diff --git a/mcp/DEVELOPMENT.md b/mcp/DEVELOPMENT.md index 16001ea9..c810a60f 100644 --- a/mcp/DEVELOPMENT.md +++ b/mcp/DEVELOPMENT.md @@ -1,356 +1,53 @@ -# Development Guide +# MCP development -This document provides guidance for developing and maintaining the PS-Wiki MCP server. +The Python package is the local stdio compatibility layer. The public remote +MCP endpoint is implemented by the dedicated `pswiki-mcp` Worker in `worker/` +and calls the existing `api.ps-wiki.ning.guru` REST API; it is not deployed from this +directory. -## Project Structure - -``` -mcp/ -├── pyproject.toml # Package configuration -├── README.md # User documentation -├── DEVELOPMENT.md # This file -├── .gitignore -├── src/ -│ └── pswiki_mcp/ -│ ├── __init__.py # Package exports -│ ├── __main__.py # Module entry point -│ ├── server.py # MCP server implementation -│ ├── api_client.py # REST API wrapper -│ └── tools.py # Tool implementations -├── tests/ -│ ├── test_api_client.py # API client tests -│ └── test_server_manual.py # Manual server test -└── examples/ - ├── claude_desktop_config.json - └── usage_example.py -``` - -## Development Setup - -### 1. Install in Development Mode +## Setup and tests ```bash cd mcp +python -m venv .venv +source .venv/bin/activate pip install -e ".[dev]" +PYTHONPATH=src python -m pytest -q +ruff check src tests +python -m py_compile src/pswiki_mcp/*.py ``` -This installs the package in editable mode with development dependencies: - -- `pytest` - Testing framework -- `pytest-asyncio` - Async test support -- `black` - Code formatter -- `ruff` - Linter - -### 2. Verify Installation +The API-client tests use `httpx.MockTransport` and do not contact production. +The explicit live stdio smoke test is: ```bash -# Run tests -pytest tests/ -v - -# Test server manually python tests/test_server_manual.py - -# Check if command works -pswiki-mcp --help # Should show error (no --help implemented yet) ``` -## Testing - -### Unit Tests +## Remote Worker development ```bash -# Run all tests -pytest - -# Run with verbose output -pytest -v - -# Run specific test file -pytest tests/test_api_client.py - -# Run with coverage -pytest --cov=pswiki_mcp tests/ +cd worker +npm ci +npm run mcp:dev ``` -### Manual Server Testing +The local remote endpoint is `http://localhost:8788/mcp`. Run `npm run check` +for type checking and protocol tests. Use MCP Inspector for interactive calls. -```bash -# Test server initialization and basic functionality -python tests/test_server_manual.py -``` - -### Integration Testing with Claude Desktop - -1. Install the package: - - ```bash - pip install -e . - ``` +## Remote deployment -2. Configure Claude Desktop (see `examples/claude_desktop_config.json`): - - ```json - { - "mcpServers": { - "pswiki": { - "command": "python", - "args": ["-m", "pswiki_mcp"] - } - } - } - ``` - -3. Restart Claude Desktop - -4. Test by asking Claude: - - "Search for power systems stability terms" - - "What is automatic generation control?" - - "Show me terms related to voltage stability" - -## Code Quality - -### Formatting +From `worker/`, after review: ```bash -# Format code with black -black src/ tests/ - -# Check formatting without changes -black --check src/ tests/ -``` - -### Linting - -```bash -# Run ruff linter -ruff check src/ tests/ - -# Auto-fix issues -ruff check --fix src/ tests/ -``` - -### Type Checking (Optional) - -```bash -# Install mypy -pip install mypy - -# Run type checker -mypy src/pswiki_mcp -``` - -## Making Changes - -### Adding a New Tool - -1. Define the tool in `server.py` (`list_tools()` method) -2. Implement the handler in `tools.py` -3. Register the handler in `server.py` (`call_tool()` method) -4. Add tests in `tests/` -5. Update `README.md` documentation - -Example: - -```python -# In tools.py -async def my_new_tool(client: APIClient, args: dict[str, Any]) -> dict[str, Any]: - """Implementation of new tool.""" - # ... implementation - return result - -# In server.py list_tools() -Tool( - name="my_new_tool", - description="Description of what it does", - inputSchema={...} -) - -# In server.py call_tool() -elif name == "my_new_tool": - result = await my_new_tool(api_client, arguments) -``` - -### Adding a New Resource - -1. Define the resource in `server.py` (`list_resources()` method) -2. Handle reading in `read_resource()` method -3. Add tests -4. Update documentation - -### Modifying API Client - -1. Update methods in `api_client.py` -2. Update `TermSummary` model if needed -3. Add/update tests in `test_api_client.py` - -## Building and Publishing - -### Build Package - -```bash -# Install build tools -pip install build twine - -# Build distribution -python -m build - -# This creates: -# - dist/pswiki_mcp-0.1.0-py3-none-any.whl -# - dist/pswiki_mcp-0.1.0.tar.gz -``` - -### Test Package Locally - -```bash -# Install from wheel -pip install dist/pswiki_mcp-0.1.0-py3-none-any.whl - -# Test it works -pswiki-mcp # Should start server -python -m pswiki_mcp # Alternative way -``` - -### Publish to PyPI - -```bash -# Test on TestPyPI first -twine upload --repository testpypi dist/* - -# Install from TestPyPI to verify -pip install --index-url https://test.pypi.org/simple/ pswiki-mcp - -# Publish to real PyPI -twine upload dist/* -``` - -### Version Bumping - -1. Update version in `pyproject.toml` -2. Update version in `src/pswiki_mcp/__init__.py` -3. Commit changes -4. Tag release: `git tag mcp-v0.2.0` -5. Push tag: `git push origin mcp-v0.2.0` - -## Debugging - -### Enable Debug Logging - -```python -# In server.py, add at the top -import logging -logging.basicConfig(level=logging.DEBUG) -``` - -### Test with MCP Inspector - -The MCP project provides an inspector tool: - -```bash -# Install MCP inspector -npm install -g @modelcontextprotocol/inspector - -# Run inspector -mcp-inspector python -m pswiki_mcp -``` - -### Common Issues - -**Server won't start:** - -- Check Python version (requires 3.10+) -- Verify dependencies: `pip list | grep mcp` -- Check for syntax errors: `python -m py_compile src/pswiki_mcp/*.py` - -**API calls failing:** - -- Verify API is accessible: `curl https://api.ning.guru/v1/terms` -- Check network connectivity -- Look for rate limiting issues - -**Claude Desktop not seeing server:** - -- Verify config file location -- Check JSON syntax in config -- Restart Claude Desktop completely -- Check Claude Desktop logs (Help → View Logs) - -## CI/CD - -### GitHub Actions Workflow - -Create `.github/workflows/test-mcp.yml`: - -```yaml -name: Test MCP Server - -on: - push: - paths: - - "mcp/**" - pull_request: - paths: - - "mcp/**" - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.12" - - name: Install dependencies - working-directory: ./mcp - run: | - pip install -e ".[dev]" - - name: Run tests - working-directory: ./mcp - run: pytest -v - - name: Check formatting - working-directory: ./mcp - run: black --check src/ tests/ - - name: Run linter - working-directory: ./mcp - run: ruff check src/ tests/ -``` - -### Publishing Workflow - -Create `.github/workflows/publish-mcp.yml`: - -```yaml -name: Publish MCP to PyPI - -on: - push: - tags: - - "mcp-v*" - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.12" - - name: Install build tools - run: pip install build twine - - name: Build package - working-directory: ./mcp - run: python -m build - - name: Publish to PyPI - working-directory: ./mcp - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: twine upload dist/* +npm run check +npx wrangler deploy --config wrangler-mcp.toml --dry-run +npm run deploy:mcp ``` -## Resources +The Worker’s MCP configuration is in `worker/wrangler-mcp.toml`. It contains no +secrets. `MCP_API_BASE`, `MCP_ALLOWED_HOSTS`, `MCP_ALLOWED_ORIGINS`, +`MCP_TIMEOUT_MS`, and `MCP_MAX_RESPONSE_BYTES` are configurable variables. -- [MCP Specification](https://modelcontextprotocol.io) -- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) -- [PS-Wiki REST API](https://api.ning.guru/openapi.json) -- [Pydantic Documentation](https://docs.pydantic.dev/) -- [pytest Documentation](https://docs.pytest.org/) +See [the site MCP documentation](https://ps-wiki.ning.guru/mcp/) for client +configuration, curl verification, rollback, and Cloudflare follow-up. diff --git a/mcp/OLLAMA_GUIDE.md b/mcp/OLLAMA_GUIDE.md deleted file mode 100644 index 2fd7cf29..00000000 --- a/mcp/OLLAMA_GUIDE.md +++ /dev/null @@ -1,404 +0,0 @@ -# Using PS-Wiki MCP Server with Ollama on Mac - -Complete step-by-step guide to use your PS-Wiki MCP server with Ollama for local, private AI-powered power systems research. - -## Prerequisites - -✅ You have: - -- Mac with Ollama installed -- PS-Wiki MCP server installed (`cd mcp && pip install -e .`) - -## Step 1: Start Ollama - -### 1.1 Check if Ollama is Running - -```bash -# Check if Ollama service is running -ollama list -``` - -If you see a list of models (or "No models found"), Ollama is running. If you get an error, start it: - -```bash -# Ollama should auto-start on Mac, but if not: -# Just run any ollama command and it will start automatically -ollama --version -``` - -### 1.2 Pull a Model (if you haven't already) - -```bash -# Pull a good model for technical content (recommended) -ollama pull llama3.2 - -# Or try a larger model for better quality (if you have RAM) -ollama pull llama3.1:8b - -# Or a smaller, faster model -ollama pull phi3 -``` - -**Recommended**: `llama3.2` (good balance of speed and quality) - -### 1.3 Test Ollama - -```bash -# Quick test -ollama run llama3.2 "What is voltage stability?" -``` - -Press `Ctrl+D` or type `/bye` to exit the chat. - -## Step 2: Choose Your MCP Client - -You have **3 options** to use MCP with Ollama: - -### Option A: Continue.dev in VS Code (Easiest) ⭐ **RECOMMENDED** - -### Option B: Direct Python Script (Most Flexible) - -### Option C: Custom Integration (Advanced) - ---- - -## Option A: Continue.dev (Easiest Setup) - -### A.1 Install Continue Extension - -1. Open VS Code -2. Go to Extensions (⌘+Shift+X) -3. Search for "Continue" -4. Click Install - -### A.2 Configure Continue - -1. Open Command Palette (⌘+Shift+P) -2. Type "Continue: Open config.json" -3. Replace contents with: - -```json -{ - "models": [ - { - "title": "Llama 3.2 (Local)", - "provider": "ollama", - "model": "llama3.2", - "apiBase": "http://localhost:11434" - } - ], - "mcpServers": { - "pswiki": { - "command": "python", - "args": ["-m", "pswiki_mcp"] - } - }, - "tabAutocompleteModel": { - "title": "Starcoder", - "provider": "ollama", - "model": "starcoder2:3b" - } -} -``` - -### A.3 Restart VS Code - -Close and reopen VS Code for changes to take effect. - -### A.4 Test It! - -1. Open Continue sidebar (click Continue icon or ⌘+L) -2. Ask: **"Search for power systems stability terms"** -3. Continue will use your MCP server to search PS-Wiki! -4. Ask: **"Explain voltage stability in detail"** -5. It will fetch the term and explain it! - -**Example conversation:** - -``` -You: "What is automatic generation control?" - -Continue: [Uses get_term tool] -Based on PS-Wiki, Automatic Generation Control (AGC) is... -[Provides detailed explanation with citations] - -You: "What are related concepts?" - -Continue: [Uses get_related_terms tool] -Related concepts include: -- Load Frequency Control -- Governor Response -- Area Control Error -... -``` - ---- - -## Option B: Direct Python Script (Most Flexible) - -This gives you full control and doesn't require VS Code. - -### B.1 Create a Test Script - -Create `test_ollama_mcp.py`: - -```python -#!/usr/bin/env python3 -""" -Simple script to test PS-Wiki MCP server with Ollama. -This demonstrates the data flow without needing a full MCP client. -""" - -import asyncio -import json -from mcp import ClientSession, StdioServerParameters -from mcp.client.stdio import stdio_client - - -async def search_and_explain(topic: str): - """Search PS-Wiki and get data for Ollama to explain.""" - - # Start MCP server - server_params = StdioServerParameters( - command="python", - args=["-m", "pswiki_mcp"], - ) - - async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write) as session: - await session.initialize() - - print(f"\n🔍 Searching PS-Wiki for: {topic}") - - # Search for terms - result = await session.call_tool( - "search_terms", - arguments={"query": topic, "limit": 3} - ) - - data = json.loads(result.content[0].text) - - if not data['results']: - print("No results found!") - return - - # Get first term details - term_id = data['results'][0]['id'] - print(f"\n📖 Fetching details for: {term_id}") - - result = await session.call_tool( - "get_term", - arguments={"term_id": term_id} - ) - - term = json.loads(result.content[0].text) - - print(f"\n{'='*60}") - print(f"Term: {term['title']}") - print(f"{'='*60}") - print(f"\nDescription:\n{term.get('description', 'N/A')}") - - if term.get('related'): - print(f"\nRelated terms: {', '.join(term['related'][:5])}") - - # Now you can pass this to Ollama - print(f"\n{'='*60}") - print("Now calling Ollama to explain this...") - print(f"{'='*60}\n") - - # Call Ollama (requires ollama Python package) - try: - import subprocess - prompt = f"Based on this definition: {term.get('description', '')}\n\nExplain '{term['title']}' to a graduate student in power systems engineering." - - result = subprocess.run( - ["ollama", "run", "llama3.2", prompt], - capture_output=True, - text=True, - timeout=30 - ) - - print("Ollama's Explanation:") - print(result.stdout) - - except Exception as e: - print(f"Note: Install ollama CLI to see AI explanation") - print(f"You can manually ask Ollama: 'Explain {term['title']}'") - - -if __name__ == "__main__": - import sys - topic = sys.argv[1] if len(sys.argv) > 1 else "voltage stability" - asyncio.run(search_and_explain(topic)) -``` - -### B.2 Run It - -```bash -cd /Users/jinningwang/work/pswiki/mcp - -# Search for voltage stability -python test_ollama_mcp.py "voltage stability" - -# Search for frequency control -python test_ollama_mcp.py "frequency control" - -# Search for any topic -python test_ollama_mcp.py "renewable energy integration" -``` - -This will: - -1. Search PS-Wiki via MCP -2. Get the term definition -3. Pass it to Ollama for explanation - ---- - -## Option C: Interactive Chat (Advanced) - -For a more interactive experience, you can use the MCP Inspector: - -```bash -# Install MCP Inspector -npm install -g @modelcontextprotocol/inspector - -# Run inspector with your server -npx @modelcontextprotocol/inspector python -m pswiki_mcp -``` - -This opens a web UI where you can: - -- See all available tools -- Call tools manually -- Inspect responses -- Test your MCP server - ---- - -## Quick Test Commands - -### Test 1: Search Terms - -```bash -cd /Users/jinningwang/work/pswiki/mcp -python examples/ollama_integration.py -``` - -### Test 2: Manual Ollama + MCP Workflow - -```bash -# Terminal 1: Get data from PS-Wiki -cd /Users/jinningwang/work/pswiki/mcp -python -c " -import asyncio -import json -from mcp import ClientSession, StdioServerParameters -from mcp.client.stdio import stdio_client - -async def search(): - params = StdioServerParameters(command='python', args=['-m', 'pswiki_mcp']) - async with stdio_client(params) as (r, w): - async with ClientSession(r, w) as s: - await s.initialize() - result = await s.call_tool('search_terms', {'query': 'stability', 'limit': 3}) - print(json.loads(result.content[0].text)) - -asyncio.run(search()) -" - -# Terminal 2: Ask Ollama to explain -ollama run llama3.2 -# Then paste the term description and ask for explanation -``` - ---- - -## Troubleshooting - -### Issue: "command not found: ollama" - -**Solution**: Ollama not in PATH. Try: - -```bash -/Applications/Ollama.app/Contents/MacOS/ollama list -``` - -### Issue: "Connection refused" from Ollama - -**Solution**: Start Ollama: - -```bash -# Open Ollama app from Applications -# Or run: ollama serve -``` - -### Issue: MCP server not found - -**Solution**: Make sure you're in the right environment: - -```bash -cd /Users/jinningwang/work/pswiki/mcp -pip install -e . -python -m pswiki_mcp # Should start without error -``` - -### Issue: Tests failing - -**Solution**: Install pytest-asyncio: - -```bash -pip install pytest-asyncio -pytest tests/ -v -``` - ---- - -## Recommended Workflow - -1. **Start with Option B** (Python script) to understand the flow -2. **Move to Option A** (Continue.dev) for daily use -3. **Use Option C** (Inspector) for debugging - ---- - -## Example Research Session - -```bash -# 1. Start VS Code with Continue -code /Users/jinningwang/work/pswiki - -# 2. Open Continue (⌘+L) - -# 3. Ask questions: -"Search for terms related to power system stability" -"Explain the difference between voltage and frequency stability" -"What are the main challenges in renewable energy integration?" -"Show me terms related to automatic generation control" - -# Continue will: -# - Use your MCP server to fetch accurate data from PS-Wiki -# - Use Ollama to provide natural language explanations -# - All processing happens locally on your Mac! -``` - ---- - -## Benefits of This Setup - -✅ **Complete Privacy**: All data stays on your Mac -✅ **No API Costs**: Free to use, no rate limits -✅ **Accurate Data**: PS-Wiki provides cited definitions -✅ **Natural Language**: Ollama makes it conversational -✅ **Offline Capable**: Works without internet (after models are downloaded) - ---- - -## Next Steps - -1. Try Option B script first to see it working -2. Install Continue.dev for better UX -3. Experiment with different Ollama models -4. Customize the prompts for your research needs - -Enjoy your local AI-powered power systems research assistant! 🎉 diff --git a/mcp/README.md b/mcp/README.md index 77b759ac..49c42e46 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -1,312 +1,42 @@ # PS-Wiki MCP Server -MCP (Model Context Protocol) server for [PS-Wiki](https://ps-wiki.ning.guru) - providing AI assistants with direct access to power systems terminology and knowledge. +This package provides the local stdio MCP server for PS-Wiki. The production +remote endpoint is hosted by the dedicated Cloudflare MCP Worker: -## Features - -- 🔍 **Semantic Search**: Search across 173+ power systems terms -- 📖 **Term Lookup**: Get detailed definitions with citations -- 🔗 **Relationship Traversal**: Explore related concepts -- 🏷️ **Tag Browsing**: Filter by categories (stability, control, protection, etc.) -- 📚 **Bibliography Access**: Reference academic papers and standards - -## Installation - -### Prerequisites - -- Python 3.10 or higher -- An MCP-compatible client: - - [Claude Desktop](https://claude.ai/download) (commercial, easy setup) - - [Ollama](https://ollama.com) + MCP frontend (open-source, local) - - Or any other [MCP-compatible client](https://modelcontextprotocol.io) - -### Install from PyPI - -```bash -pip install pswiki-mcp -``` - -### Install from Source - -```bash -git clone https://github.com/ps-wiki/ps-wiki.github.io.git -cd ps-wiki.github.io/mcp -pip install -e . -``` - -## Configuration - -### Claude Desktop - -Add to your `claude_desktop_config.json`: - -**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` -**Windows**: `%APPDATA%\Claude\claude_desktop_config.json` - -```json -{ - "mcpServers": { - "pswiki": { - "command": "python", - "args": ["-m", "pswiki_mcp"] - } - } -} -``` - -Or if installed globally: - -```json -{ - "mcpServers": { - "pswiki": { - "command": "pswiki-mcp" - } - } -} -``` - -### Ollama (Open-Source Alternative) - -For local, open-source AI models, you can use Ollama with MCP-compatible frontends. - -#### Option 1: Using Continue.dev (VS Code Extension) - -1. Install [Continue](https://continue.dev) extension in VS Code -2. Configure `~/.continue/config.json`: - -```json -{ - "models": [ - { - "title": "Llama 3.2", - "provider": "ollama", - "model": "llama3.2" - } - ], - "mcpServers": { - "pswiki": { - "command": "python", - "args": ["-m", "pswiki_mcp"] - } - } -} +```text +https://mcp.ps-wiki.ning.guru/mcp ``` -#### Option 2: Using Ollama MCP Bridge +For the complete local/remote client guide, tool reference, deployment commands, +Inspector workflow, and verification requests, see the canonical site +documentation: [docs/mcp.md](https://ps-wiki.ning.guru/mcp/). -Install an Ollama MCP bridge: +## Local quick start ```bash -# Install ollama-mcp-bridge -npm install -g @modelcontextprotocol/server-ollama - -# Configure your MCP client to use both servers +python -m venv .venv +source .venv/bin/activate +pip install -e ".[dev]" +pswiki-mcp ``` -Example configuration: +Claude Desktop example: ```json { "mcpServers": { - "ollama": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-ollama"] - }, "pswiki": { - "command": "python", - "args": ["-m", "pswiki_mcp"] + "command": "/absolute/path/to/mcp/.venv/bin/pswiki-mcp" } } } ``` -#### Option 3: Direct Integration (Advanced) - -Use the programmatic API (see examples below) to integrate with your own Ollama-based application. - -### Other MCP Clients - -The server uses stdio transport and can be integrated with any MCP-compatible client. See [MCP documentation](https://modelcontextprotocol.io) for details. - -## Available Tools - -### `search_terms` - -Search power systems terminology by keyword, phrase, or concept. - -**Parameters:** - -- `query` (string, required): Search query -- `limit` (integer, optional): Max results (default: 10, max: 50) - -**Example:** - -``` -Search for "frequency control" → Returns terms related to AGC, governor response, inertia, etc. -``` - -### `get_term` - -Retrieve full details for a specific term by ID. - -**Parameters:** - -- `term_id` (string, required): Term identifier (e.g., "automatic-generation-control") - -**Example:** - -``` -Get term "voltage-stability" → Returns definition, equations, citations, related terms -``` - -### `get_related_terms` - -Find terms related to a given term. - -**Parameters:** - -- `term_id` (string, required): Term identifier -- `depth` (integer, optional): Relationship depth (default: 1, max: 2) - -**Example:** - -``` -Get related terms for "power-flow" → Returns load-flow, optimal-power-flow, etc. -``` - -### `list_tags` - -List all available tags with usage counts. - -**Example:** - -``` -List tags → Returns: stability (15), control (23), protection (8), etc. -``` - -### `get_terms_by_tag` - -Get all terms with a specific tag. - -**Parameters:** - -- `tag` (string, required): Tag name (case-insensitive) - -**Example:** - -``` -Get terms by tag "stability" → Returns all stability-related terms -``` - -## Available Resources - -### `term:///{term_id}` - -Access individual term as a resource. - -### `tags://all` - -List of all tags with counts. - -### `index://terms` - -Complete term index with metadata. - -## Usage Examples - -### With Claude Desktop - -Once configured, you can ask Claude: - -> "Explain the difference between voltage stability and frequency stability in power systems" - -Claude will use the MCP server to fetch relevant terms and provide accurate, cited explanations. - -> "What are the main control strategies for automatic generation control?" +The local server exposes five read-only tools: `search_terms`, `get_term`, +`get_related_terms`, `list_tags`, and `get_terms_by_tag`. -Claude will retrieve the AGC term and related control concepts. - -### With Ollama (Open-Source) - -If using Ollama with Continue.dev or another MCP-compatible frontend: - -> **User**: "Search for power systems stability terms" - -> **AI**: _Uses `search_terms` tool to find stability-related terminology_ - -> **User**: "Explain voltage stability in detail" - -> **AI**: _Uses `get_term` tool to fetch the full definition with equations and citations_ - -The advantage of Ollama is complete privacy - all processing happens locally on your machine. - -### Programmatic Usage - -```python -from mcp import ClientSession, StdioServerParameters -from mcp.client.stdio import stdio_client - -server_params = StdioServerParameters( - command="python", - args=["-m", "pswiki_mcp"] -) - -async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write) as session: - await session.initialize() - - # Search for terms - result = await session.call_tool("search_terms", {"query": "stability"}) - print(result) -``` - -## Data Source - -The MCP server fetches data from the [PS-Wiki REST API](https://api.ning.guru) hosted on Cloudflare Workers. All data is read-only and licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). - -> **API URL:** The production API is now available at `https://api.ning.guru`. -> The original `pswiki-api.jinning.workers.dev` hostname remains available as a fallback. -> Update any custom MCP client configuration that uses the old hostname when convenient. - -## Development - -### Setup Development Environment +Run deterministic package tests with: ```bash -cd mcp -pip install -e ".[dev]" -``` - -### Run Tests - -```bash -pytest -``` - -### Code Formatting - -```bash -black src/ tests/ -ruff check src/ tests/ +PYTHONPATH=src python -m pytest -q ``` - -## Contributing - -Contributions are welcome! Please see the [main repository](https://github.com/ps-wiki/ps-wiki.github.io) for contribution guidelines. - -## License - -This project is licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) - see the [LICENSE](../LICENSE) file for details. - -## Related Projects - -- [PS-Wiki Website](https://ps-wiki.ning.guru) - Main terminology wiki -- [PS-Wiki REST API](https://api.ning.guru) - Public API -- [MCP Specification](https://modelcontextprotocol.io) - Model Context Protocol - -## Support - -- 📖 [Documentation](https://ps-wiki.ning.guru) -- 🐛 [Issue Tracker](https://github.com/ps-wiki/ps-wiki.github.io/issues) -- 💬 [Discussions](https://github.com/ps-wiki/ps-wiki.github.io/discussions) diff --git a/mcp/examples/ollama_integration.py b/mcp/examples/ollama_integration.py deleted file mode 100644 index 64d4436d..00000000 --- a/mcp/examples/ollama_integration.py +++ /dev/null @@ -1,134 +0,0 @@ -"""Example of using PS-Wiki MCP server with Ollama models. - -This example shows how to integrate the PS-Wiki MCP server with Ollama -for completely local, private AI-powered power systems research. - -Prerequisites: -- Ollama installed (https://ollama.com) -- A model pulled (e.g., `ollama pull llama3.2`) -- pswiki-mcp installed (`pip install pswiki-mcp`) -""" - -import asyncio -import json -from mcp import ClientSession, StdioServerParameters -from mcp.client.stdio import stdio_client - - -async def query_pswiki_with_ollama(): - """ - Example: Use MCP server to get power systems data, then use Ollama to explain it. - - This demonstrates the separation of concerns: - - MCP server provides accurate, cited data - - Ollama provides natural language understanding and explanation - """ - - # Start PS-Wiki MCP server - server_params = StdioServerParameters( - command="python", - args=["-m", "pswiki_mcp"], - ) - - async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write) as session: - await session.initialize() - - print("=== PS-Wiki + Ollama Integration Example ===\n") - - # Step 1: Search for terms using MCP - print("1. Searching for 'voltage stability' terms...") - result = await session.call_tool( - "search_terms", arguments={"query": "voltage stability", "limit": 3} - ) - - search_data = json.loads(result.content[0].text) - print(f" Found {search_data['count']} terms\n") - - # Step 2: Get detailed term information - if search_data["results"]: - term_id = search_data["results"][0]["id"] - print(f"2. Getting details for '{term_id}'...") - - result = await session.call_tool("get_term", arguments={"term_id": term_id}) - - term_data = json.loads(result.content[0].text) - print(f" Title: {term_data.get('title')}") - print(f" Description: {term_data.get('description', '')[:100]}...\n") - - # Step 3: Get related terms - print(f"3. Finding related terms...") - result = await session.call_tool( - "get_related_terms", arguments={"term_id": term_id, "depth": 1} - ) - - related_data = json.loads(result.content[0].text) - print(f" Found {len(related_data.get('related_terms', []))} related terms") - for rel in related_data.get("related_terms", [])[:3]: - print(f" - {rel['title']}") - - print("\n" + "=" * 50) - print("\nNow you could pass this data to Ollama for explanation:") - print("\nExample Ollama prompt:") - print(f" 'Based on this definition: {term_data.get('description', '')[:100]}...'") - print( - f" 'Explain {term_data.get('title')} to a graduate student in power systems.'" - ) - print("\nOllama would provide the explanation while PS-Wiki provides the facts!") - - -async def demonstrate_workflow(): - """ - Demonstrate a complete research workflow: - 1. Search for a topic - 2. Get related terms - 3. Build a knowledge graph - """ - - server_params = StdioServerParameters( - command="python", - args=["-m", "pswiki_mcp"], - ) - - async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write) as session: - await session.initialize() - - print("\n=== Research Workflow Example ===\n") - - # Research topic: Understanding power system stability - topic = "stability" - - print(f"Research Topic: Power System {topic.title()}\n") - - # Get all stability-related terms - result = await session.call_tool("get_terms_by_tag", arguments={"tag": topic}) - - data = json.loads(result.content[0].text) - print(f"Found {data['count']} terms tagged with '{topic}':") - - for term in data["terms"][:5]: - print(f"\n 📖 {term['title']}") - print(f" ID: {term['id']}") - print(f" Summary: {term['summary'][:80]}...") - - print("\n" + "=" * 50) - print("\nWith Ollama, you could ask:") - print(" 'Compare and contrast these stability concepts'") - print(" 'Which stability type is most critical for renewable integration?'") - print(" 'Create a study guide for these topics'") - - -if __name__ == "__main__": - print("PS-Wiki MCP Server + Ollama Integration\n") - print("This demonstrates using MCP for data access with local AI models.\n") - - # Run examples - asyncio.run(query_pswiki_with_ollama()) - asyncio.run(demonstrate_workflow()) - - print("\n✅ Examples complete!") - print("\nNext steps:") - print("1. Install Continue.dev in VS Code") - print("2. Configure it to use both Ollama and pswiki-mcp") - print("3. Ask questions naturally and get cited answers!") diff --git a/mcp/quick_test.py b/mcp/quick_test.py deleted file mode 100755 index f0bc9406..00000000 --- a/mcp/quick_test.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 -""" -Quick test script for PS-Wiki MCP + Ollama integration. -This is the simplest way to see your MCP server working with local AI. -""" - -import asyncio -import json -import subprocess -from mcp import ClientSession, StdioServerParameters -from mcp.client.stdio import stdio_client - - -async def demo_mcp_ollama(topic: str = "voltage stability"): - """Demonstrate MCP server fetching data for Ollama to explain.""" - - print(f"\n{'='*70}") - print(f" PS-Wiki MCP + Ollama Demo") - print(f"{'='*70}\n") - - # Start MCP server - server_params = StdioServerParameters( - command="python", - args=["-m", "pswiki_mcp"], - ) - - async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write) as session: - await session.initialize() - - print(f"🔍 Step 1: Searching PS-Wiki for '{topic}'...") - - # Search for terms - result = await session.call_tool("search_terms", arguments={"query": topic, "limit": 3}) - - data = json.loads(result.content[0].text) - print(f" ✓ Found {data['count']} results\n") - - if not data["results"]: - print(" No results found. Try a different topic.") - return - - # Show search results - print(" Search Results:") - for i, term in enumerate(data["results"], 1): - print(f" {i}. {term['title']} ({term['id']})") - - # Get first term details - term_id = data["results"][0]["id"] - print(f"\n📖 Step 2: Fetching full details for '{term_id}'...") - - result = await session.call_tool("get_term", arguments={"term_id": term_id}) - - term = json.loads(result.content[0].text) - print(f" ✓ Retrieved term data\n") - - # Display term info - print(f"{'='*70}") - print(f" {term['title']}") - print(f"{'='*70}\n") - - description = term.get("description", "No description available") - print(f"Description:\n{description[:300]}...") - - if term.get("tags"): - print(f"\nTags: {', '.join(term['tags'][:5])}") - - if term.get("related"): - print(f"Related: {', '.join(term['related'][:5])}") - - # Now call Ollama - print(f"\n{'='*70}") - print(f"🤖 Step 3: Asking Ollama to explain (using llama3.2)...") - print(f"{'='*70}\n") - - prompt = f"""Based on this technical definition from PS-Wiki: - -"{description}" - -Please explain "{term['title']}" in simple terms for a graduate student in power systems engineering. Keep it concise (2-3 paragraphs).""" - - try: - # Call Ollama CLI - result = subprocess.run( - ["ollama", "run", "llama3.2", prompt], - capture_output=True, - text=True, - timeout=60, - ) - - if result.returncode == 0: - print("Ollama's Explanation:") - print("-" * 70) - print(result.stdout) - print("-" * 70) - else: - print(f"Error calling Ollama: {result.stderr}") - print("\nTo use Ollama:") - print("1. Make sure Ollama is running") - print("2. Pull a model: ollama pull llama3.2") - print("3. Run this script again") - - except FileNotFoundError: - print("⚠️ Ollama CLI not found!") - print("\nYou can still use the data above. To enable Ollama:") - print("1. Install Ollama from https://ollama.com") - print("2. Run: ollama pull llama3.2") - print("3. Run this script again") - - except subprocess.TimeoutExpired: - print("⚠️ Ollama took too long to respond (>60s)") - print("Try a smaller model or increase timeout") - - except Exception as e: - print(f"⚠️ Error: {e}") - print("\nManual workflow:") - print(f"1. Run: ollama run llama3.2") - print(f"2. Paste: Explain '{term['title']}' based on: {description[:100]}...") - - -if __name__ == "__main__": - import sys - - # Get topic from command line or use default - topic = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "voltage stability" - - print("\nPS-Wiki MCP Server + Ollama Integration Demo") - print("=" * 70) - print(f"Topic: {topic}") - - try: - asyncio.run(demo_mcp_ollama(topic)) - - print(f"\n{'='*70}") - print("✅ Demo complete!") - print(f"{'='*70}\n") - print("Try other topics:") - print(" python quick_test.py 'frequency control'") - print(" python quick_test.py 'renewable energy'") - print(" python quick_test.py 'power flow'") - print() - - except KeyboardInterrupt: - print("\n\nDemo interrupted by user.") - except Exception as e: - print(f"\n\n❌ Error: {e}") - print("\nTroubleshooting:") - print("1. Make sure you're in the mcp/ directory") - print("2. Install the package: pip install -e .") - print("3. Check Ollama is running: ollama list") diff --git a/mcp/src/pswiki_mcp/api_client.py b/mcp/src/pswiki_mcp/api_client.py index 576f6d3a..d5dbf843 100644 --- a/mcp/src/pswiki_mcp/api_client.py +++ b/mcp/src/pswiki_mcp/api_client.py @@ -1,7 +1,8 @@ """REST API client for PS-Wiki.""" -import httpx from typing import Any, Optional + +import httpx from pydantic import BaseModel @@ -18,18 +19,24 @@ class TermSummary(BaseModel): class APIClient: """Client for PS-Wiki REST API.""" - def __init__(self, base_url: str = "https://api.ning.guru"): + def __init__( + self, + base_url: str = "https://api.ps-wiki.ning.guru", + client: httpx.AsyncClient | None = None, + ): """Initialize API client. Args: base_url: Base URL for the API (default: production API) """ self.base_url = base_url.rstrip("/") - self.client = httpx.AsyncClient(timeout=30.0) + self.client = client or httpx.AsyncClient(timeout=10.0) + self._owns_client = client is None async def close(self): """Close the HTTP client.""" - await self.client.aclose() + if self._owns_client: + await self.client.aclose() async def search_terms( self, query: Optional[str] = None, tag: Optional[str] = None, limit: int = 20 diff --git a/mcp/src/pswiki_mcp/server.py b/mcp/src/pswiki_mcp/server.py index 0100f69c..f41dd5a0 100644 --- a/mcp/src/pswiki_mcp/server.py +++ b/mcp/src/pswiki_mcp/server.py @@ -8,22 +8,19 @@ from mcp.server.stdio import stdio_server from mcp.types import ( Resource, - Tool, TextContent, - ImageContent, - EmbeddedResource, + Tool, ) from .api_client import APIClient from .tools import ( - search_terms_tool, - get_term_tool, get_related_terms_tool, - list_tags_tool, + get_term_tool, get_terms_by_tag_tool, + list_tags_tool, + search_terms_tool, ) - # Initialize server app = Server("pswiki-mcp") diff --git a/mcp/src/pswiki_mcp/tools.py b/mcp/src/pswiki_mcp/tools.py index 436dcf7b..cb009f30 100644 --- a/mcp/src/pswiki_mcp/tools.py +++ b/mcp/src/pswiki_mcp/tools.py @@ -1,6 +1,7 @@ """Tool implementations for PS-Wiki MCP server.""" from typing import Any + from .api_client import APIClient diff --git a/mcp/tests/test_api_client.py b/mcp/tests/test_api_client.py index 7a1e0ece..48987b33 100644 --- a/mcp/tests/test_api_client.py +++ b/mcp/tests/test_api_client.py @@ -1,69 +1,94 @@ -"""Tests for API client.""" +"""Deterministic tests for the PS-Wiki REST API client.""" +import httpx import pytest + from pswiki_mcp.api_client import APIClient, TermSummary @pytest.fixture async def client(): - """Create API client for testing.""" - client = APIClient() - yield client - await client.close() + async def handler(request: httpx.Request) -> httpx.Response: + if request.url.path == "/v1/terms" and request.url.params.get("query") == "stability": + return httpx.Response( + 200, + request=request, + json={ + "items": [ + { + "id": "voltage-stability", + "title": "Voltage Stability", + "summary": "Maintaining acceptable voltages.", + "tags": ["stability"], + "updated_at": "2026-09-01", + } + ] + }, + ) + if request.url.path == "/v1/terms" and request.url.params.get("tag") == "stability": + return httpx.Response( + 200, + request=request, + json={ + "items": [ + { + "id": "voltage-stability", + "title": "Voltage Stability", + "tags": ["stability"], + "updated_at": "2026-09-01", + } + ] + }, + ) + if request.url.path == "/v1/terms/voltage-stability": + return httpx.Response( + 200, + request=request, + json={"id": "voltage-stability", "title": "Voltage Stability"}, + ) + if request.url.path == "/v1/terms/missing-term": + return httpx.Response(404, request=request, json={"error": "not_found"}) + if request.url.path == "/v1/tags": + return httpx.Response( + 200, + request=request, + json={"tags": [{"tag": "stability", "count": 12}]}, + ) + return httpx.Response(502, request=request, json={"error": "upstream_error"}) + + transport = httpx.MockTransport(handler) + http_client = httpx.AsyncClient(transport=transport) + api_client = APIClient(client=http_client) + yield api_client + await http_client.aclose() @pytest.mark.asyncio async def test_search_terms(client): - """Test searching for terms.""" results = await client.search_terms(query="stability", limit=5) - assert isinstance(results, list) - assert len(results) <= 5 - if results: - assert isinstance(results[0], TermSummary) - assert hasattr(results[0], "id") - assert hasattr(results[0], "title") + assert isinstance(results[0], TermSummary) + assert results[0].id == "voltage-stability" @pytest.mark.asyncio async def test_get_term(client): - """Test getting a specific term.""" - # First search to get a valid term ID - results = await client.search_terms(query="stability", limit=1) - if not results: - pytest.skip("No terms found to test with") - - term_id = results[0].id - term = await client.get_term(term_id) - - assert isinstance(term, dict) - assert "id" in term - assert "title" in term - assert term["id"] == term_id + term = await client.get_term("voltage-stability") + assert term == {"id": "voltage-stability", "title": "Voltage Stability"} @pytest.mark.asyncio async def test_list_tags(client): - """Test listing tags.""" - tags = await client.list_tags() - assert isinstance(tags, list) - if tags: - assert "tag" in tags[0] - assert "count" in tags[0] - assert isinstance(tags[0]["count"], int) + assert await client.list_tags() == [{"tag": "stability", "count": 12}] @pytest.mark.asyncio async def test_search_by_tag(client): - """Test searching by tag.""" - # First get available tags - tags = await client.list_tags() - if not tags: - pytest.skip("No tags found to test with") + results = await client.search_terms(tag="stability", limit=10) + assert results[0].tags == ["stability"] - tag_name = tags[0]["tag"] - results = await client.search_terms(tag=tag_name, limit=10) - assert isinstance(results, list) - # All results should have the tag - for result in results: - assert tag_name.lower() in [t.lower() for t in result.tags] +@pytest.mark.asyncio +async def test_http_errors_are_preserved(client): + with pytest.raises(httpx.HTTPStatusError) as error: + await client.get_term("missing-term") + assert error.value.response.status_code == 404 diff --git a/mcp/tests/test_server_manual.py b/mcp/tests/test_server_manual.py index e22cd22c..fda84217 100644 --- a/mcp/tests/test_server_manual.py +++ b/mcp/tests/test_server_manual.py @@ -3,11 +3,12 @@ import asyncio import json + from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client -async def test_server(): +async def run_server_smoke_test(): """Test basic server functionality.""" server_params = StdioServerParameters( command="python", @@ -50,4 +51,4 @@ async def test_server(): if __name__ == "__main__": - asyncio.run(test_server()) + asyncio.run(run_server_smoke_test()) diff --git a/worker/README.md b/worker/README.md index c3d42648..49f8097c 100644 --- a/worker/README.md +++ b/worker/README.md @@ -1,172 +1,81 @@ -# PS-Wiki API (Cloudflare Worker) +# PS-Wiki Cloudflare Worker -This directory contains the **Cloudflare Worker** that exposes the PS-Wiki database -as a read-only REST API, suitable for integration with **ChatGPT Actions**, other AI -agents, or web clients. +The root `pswiki-api` Worker serves the existing read-only REST API. A separate +lightweight `pswiki-mcp` Worker serves remote MCP and calls that REST API at +`MCP_API_BASE`; it does not duplicate the PS-Wiki database. +| Surface | Production URL | +| --- | --- | +| REST API | `https://api.ps-wiki.ning.guru` | +| OpenAPI | `https://api.ps-wiki.ning.guru/openapi.json` | +| Remote MCP | `https://mcp.ps-wiki.ning.guru/mcp` | -## 🌐 Overview +## Development -| Component | Description | -|------------|--------------| -| **Platform** | Cloudflare Workers (TypeScript) | -| **Purpose** | Serve Power Systems Wiki term data as JSON | -| **Data Source** | JSON terms stored in the [`database/json/`](../database/json) directory of this repo | -| **Index Source** | [`database/build/index.json`](../database/build/index.json) & [`database/build/tags.json`](../database/build/tags.json) | -| **Schema** | [`database/schema/v1/term.schema.json`](../database/schema/v1/term.schema.json) | -| **Production Endpoint** | `https://api.ning.guru` (custom domain) | - - -## 🗂 Directory Structure - -``` -worker/ -├── src/ -│ └── index.ts # Main Worker script (REST API) -├── wrangler.toml # Worker configuration (entry point, env vars) -├── package.json # Node project metadata -├── tsconfig.json # TypeScript compiler settings -└── README.md # This file -``` - - -## ⚙️ Environment Variables (set in `wrangler.toml`) - -| Name | Description | -|------|--------------| -| `ORIGIN_BASE` | Base URL of raw JSON term files in GitHub, e.g.
`https://raw.githubusercontent.com/ps-wiki/ps-wiki.github.io/main/database/json` | -| `INDEX_URL` | URL of `index.json` built by `build_index.py` | -| `TAGS_URL` | URL of `tags.json` built by `build_index.py` | -| `SITE_BASE` | Canonical human-facing PS-Wiki site URL, e.g. `https://ps-wiki.ning.guru` | - -These variables allow the Worker to always fetch the latest data directly -from GitHub without redeploying the Worker. - - -## 🚀 Deployment - -### One-time setup -```bash -npm install --save-dev wrangler typescript -npx wrangler login -``` - -### Local development ```bash -cd worker -npx wrangler dev +npm ci +npm run mcp:dev ``` -Local development server URL: +The REST Worker listens on `http://localhost:8787`; the MCP Worker listens on +`http://localhost:8788`. Use `http://localhost:8788/mcp` with MCP Inspector. -Example test: -```bash -curl 'http://localhost:8787/v1/terms?query=stability' -``` - -### Deploy to Cloudflare +Checks: -Manually deploy: ```bash -npx wrangler deploy +npm run check +npx wrangler deploy --config wrangler.toml --dry-run +npx wrangler deploy --config wrangler-mcp.toml --dry-run ``` -### Continuous deployment with GitHub Actions - -The repository deploys the production Worker automatically when a commit is -merged to `main` with changes to the root Worker's source, configuration, or -dependencies. The workflow is -`.github/workflows/deploy-worker.yml`; it does not deploy pull requests or the -unrelated `worker/pswiki-api/` starter template. +The root Worker tests are intentionally scoped to `tests/`; the nested +`worker/pswiki-api/` directory is an unrelated starter template and is not the +deployed API. -Before the first automated deployment, add these repository Actions secrets in -GitHub under **Settings → Secrets and variables → Actions**: +## Configuration -| Secret | Description | -|--------|-------------| -| `CLOUDFLARE_API_TOKEN` | Cloudflare API token with the minimum required Workers deployment permission | -| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account containing the `pswiki-api` Worker | +Existing REST variables remain unchanged: -The workflow can also be rerun safely through **Actions → Deploy Worker → Run -workflow**. It uses Cloudflare's official Wrangler Action and deploys from the -root `worker/` directory. +- `ORIGIN_BASE` — raw term JSON base URL. +- `INDEX_URL` — generated term index URL. +- `TAGS_URL` — generated tag index URL. +- `SITE_BASE` — human-facing canonical site URL. -Cloudflare may also expose a free `workers.dev` URL during initial setup. The -custom domain above is the canonical production endpoint and should be used in -client configurations. +MCP variables are: -``` -https://pswiki-api..workers.dev -``` +- `MCP_API_BASE` — REST base used by MCP tools; production uses + `https://api.ps-wiki.ning.guru`. +- `MCP_ALLOWED_HOSTS` — comma-separated MCP Host allowlist. +- `MCP_ALLOWED_ORIGINS` — comma-separated browser Origin allowlist. +- `MCP_TIMEOUT_MS` — upstream REST timeout, bounded by the Worker code. +- `MCP_MAX_RESPONSE_BYTES` — maximum upstream JSON response size. -### Local API contracts testing +The initial deployment is public and read-only. It uses no OAuth, accounts, or +Durable Objects. Cloudflare’s MCP handler validates Host and present Origin +headers; non-browser clients without Origin remain supported. -After local development server is running, you can test the OpenAPI spec with: +## Deployment and rollback ```bash -python database/pyscripts/test_api.py --base-url http://localhost:8787 --validate-schema +npm run check +npx wrangler deploy +npm run deploy:mcp ``` -## 🔍 API Endpoints - -| Endpoint | Description | -|-----------|--------------| -| `GET /v1/terms?query=&tag=&limit=&cursor=` | Search or list term summaries | -| `GET /v1/terms/{id}` | Retrieve a single term (full JSON) | -| `GET /v1/tags` | List all tags and counts | -| `GET /v1/changes?since=YYYY-MM-DD` | List terms modified since a given date | -| `GET /openapi.json` | Return OpenAPI spec for ChatGPT Actions | - -All endpoints return `application/json` and include permissive CORS headers. -Term records include a canonical `url`, and successful responses include -structured PS-Wiki attribution and license metadata. Full term responses retain -the existing `authors` array for term contributors. +Two GitHub Actions workflows deploy these Workers independently on approved +changes to `main`, using `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`. +The MCP config declares `mcp.ps-wiki.ning.guru` as its single Cloudflare custom +domain; the REST config declares `api.ps-wiki.ning.guru` for `pswiki-api`. +If the account requires dashboard confirmation for the first custom-domain +attachment, approve only that hostname. -## 🧪 Quick Local Testing Commands +Inspect or roll back versions with: ```bash -# check OpenAPI spec -curl "http://localhost:8787/openapi.json" - -# List terms -curl "http://localhost:8787/v1/terms?limit=5" - -# Search by keyword -curl "http://localhost:8787/v1/terms?query=stability&limit=5" - -# filter by tag -curl "http://localhost:8787/v1/terms?tag=stability" - -# get single term by ID -curl "http://localhost:8787/v1/terms/ambient-adjusted-ratings" - -# Changes since October 2025 -curl "http://localhost:8787/v1/changes?since=2025-10-01" +npx wrangler versions list +npx wrangler rollback ``` - -## 🧰 Troubleshooting - -| Symptom | Likely Cause / Fix | -|----------|--------------------| -| `Missing entry-point` | Run `wrangler dev` *inside* the `worker` folder or add `--config worker/wrangler.toml` | -| `404 on /v1/terms/{id}` | Check `ORIGIN_BASE` path and ensure the file `.json` exists in `database/json` | -| Empty results | Ensure `database/build/index.json` is committed and `INDEX_URL` matches its GitHub raw URL | -| CORS error | The Worker includes `Access-Control-Allow-Origin: *`; if you modified headers, re-add it | -| OpenAPI invalid | Hit `/openapi.json` in browser; it must return valid JSON for ChatGPT Actions | - - -## 🧾 License - -The PS-Wiki API code follows the same license as the repository: -**CC BY-NC 4.0** for content, and MIT-style for supporting scripts unless otherwise noted. - - -## 🧩 Future Enhancements - -- `/v1/tags/{tag}` endpoint for direct tag lookup -- `/v1/references` to expose citation metadata -- Cache invalidation hooks (GitHub webhook trigger) - - -_Last updated: 2025-11-03_ +After deployment, run the MCP Inspector against +`https://mcp.ps-wiki.ning.guru/mcp`, or follow the initialize/tools-list/tools-call curl +examples in [docs/mcp.md](https://ps-wiki.ning.guru/mcp/). diff --git a/worker/mcp-entry.ts b/worker/mcp-entry.ts new file mode 100644 index 00000000..9d159440 --- /dev/null +++ b/worker/mcp-entry.ts @@ -0,0 +1,8 @@ +import { handleMcp } from "./src/mcp"; +import type { McpEnv } from "./src/env"; + +export default { + fetch(request: Request, env: McpEnv, ctx: ExecutionContext) { + return handleMcp(request, env, ctx); + }, +} satisfies ExportedHandler; diff --git a/worker/package-lock.json b/worker/package-lock.json index 0863b6b3..394d927e 100644 --- a/worker/package-lock.json +++ b/worker/package-lock.json @@ -8,11 +8,387 @@ "name": "worker", "version": "1.0.0", "license": "ISC", + "dependencies": { + "@modelcontextprotocol/server": "^2.0.0", + "agents": "^0.20.1", + "zod": "^4.6.5" + }, "devDependencies": { + "@cloudflare/workers-types": "^5.20260914.1", "typescript": "^5.9.3", + "vitest": "^5.0.0", "wrangler": "^4.131.1" } }, + "node_modules/@babel/code-frame": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz", + "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.0", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.5.tgz", + "integrity": "sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/core": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.5.tgz", + "integrity": "sha512-2/oWkgTbBYoqioCWAE4XJobOrzwxTDa5/XjDP3tJ1BhDr/owcd9qnXBp4xc3/2G5X4bvXM04nrxbRJxFcXAxFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.5", + "@babel/helper-compilation-targets": "^8.0.5", + "@babel/helpers": "^8.0.5", + "@babel/parser": "^8.0.5", + "@babel/template": "^8.0.0", + "@babel/traverse": "^8.0.5", + "@babel/types": "^8.0.5", + "@types/gensync": "^1.0.5", + "convert-source-map": "^2.0.0", + "empathic": "^2.0.1", + "gensync": "^1.0.0-beta.2", + "import-meta-resolve": "^4.2.0", + "json5": "^2.2.3", + "obug": "^2.1.1", + "verkit": "^0.3.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.5.tgz", + "integrity": "sha512-f/TuhuMAxJqhwxEGNsJrswuG9VHmh0oNFoQoo6TbpgtFAz9wYZXcTAcWZMHfp7ljesr0RG04bp3Aos9GI59L7w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.5", + "@babel/types": "^8.0.5", + "@jridgewell/gen-mapping": "0.4.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", + "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.5.tgz", + "integrity": "sha512-Qk8ahMGooH5mz6uuhoDvfZGkUf/Mf3RTBucVVl4MKx4LKMTv872TeW8O92h15iVtlN8wAROBIpI1aV6x1z0LCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^8.0.5", + "@babel/helper-validator-option": "^8.0.0", + "browserslist": "^4.24.0", + "lru-cache": "^11.0.0", + "verkit": "^0.3.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.5.tgz", + "integrity": "sha512-ckUE7tmolBbW4QV02lD874sWSPy8da2A09i/h0qS+IWolR+WiNhpq7C/rHZm00uOIkUb0Cp6+0Cy1ku/wj5uQQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-member-expression-to-functions": "^8.0.5", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/helper-replace-supers": "^8.0.1", + "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", + "@babel/traverse": "^8.0.5", + "verkit": "^0.3.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz", + "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.5.tgz", + "integrity": "sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.5", + "@babel/types": "^8.0.5" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz", + "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz", + "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz", + "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^8.0.0", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/traverse": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz", + "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", + "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helpers": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.5.tgz", + "integrity": "sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.5" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/parser": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.5.tgz", + "integrity": "sha512-51RXvQNFakaS0bTpYiGkxNbUVwkPO4kONv6EVLorZABxsx+KZ6Z7uSYvi/wmKS/+X+rfj9RvOw0/ZNh+cmI0Rw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz", + "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^8.0.1", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-syntax-decorators": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz", + "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.7.tgz", + "integrity": "sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.48.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/traverse": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.5.tgz", + "integrity": "sha512-XFfnuvapSc/vJOcUO7kwORSvpBIvraofKEZ2dhT0PjiF21BRCD7YbAFC8UEeDJNeLoQz82/gVqzgX5hCzkCbdg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.5", + "@babel/helper-globals": "^8.0.0", + "@babel/parser": "^8.0.5", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.5", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/types": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.5.tgz", + "integrity": "sha512-eVdMqi3ej5aHhyQ2Si6yD2cAWeV8FJK9UrhK5aL0Sd8hu5GhT+YswhVNbVheOGVYMg8kuGuMaUpkB3stjj4z8A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, "node_modules/@cloudflare/kv-asset-handler": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", @@ -124,6 +500,12 @@ "node": ">=16" } }, + "node_modules/@cloudflare/workers-types": { + "version": "5.20260914.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-5.20260914.1.tgz", + "integrity": "sha512-9xGgkvmG1lw0QdKJmSR5YNXaA221DfefpDCfPLz5PlzdYU0BXzxONQoWakLXTsFIz8xtsNfeUg+mbspIA5P4Xg==", + "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", @@ -155,7 +537,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -172,7 +553,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -189,7 +569,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -206,7 +585,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -223,7 +601,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -240,7 +617,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -257,7 +633,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -274,7 +649,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -291,7 +665,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -308,7 +681,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -325,7 +697,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -342,7 +713,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -359,7 +729,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -376,7 +745,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -393,7 +761,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -410,7 +777,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -427,7 +793,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -444,7 +809,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -461,7 +825,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -478,7 +841,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -495,7 +857,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -512,7 +873,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -529,7 +889,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -546,7 +905,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -563,7 +921,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -580,7 +937,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -590,6 +946,19 @@ "node": ">=18" } }, + "node_modules/@hono/node-server": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", + "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -1117,11 +1486,30 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz", + "integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "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" @@ -1131,7 +1519,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1145,6 +1532,101 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@modelcontextprotocol/client": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0.tgz", + "integrity": "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@modelcontextprotocol/core": "2.0.0", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz", + "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==", + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz", + "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.149.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", + "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, "node_modules/@poppinss/colors": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", @@ -1174,165 +1656,2412 @@ "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, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", + "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", + "cpu": [ + "arm" + ], "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@speed-highlight/core": { - "version": "1.2.24", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", + "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz", + "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", + "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", + "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", + "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", + "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", + "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", + "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", + "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", + "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", + "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", + "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", + "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", + "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/plugin-babel": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.4.tgz", + "integrity": "sha512-ygmm2j/mN+aCvstHo0x3IOu4uc6LqXT9SJ3q4kpfG/+Yhfz0B+E47FITcC0FljZADwUFARP6E2kiE+Lc26D8wg==", + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.7" + }, + "engines": { + "node": ">=22.12.0 || ^24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.29.0 || ^8.0.0-rc.1", + "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", + "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", + "rolldown": "^1.0.0-rc.5", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@babel/plugin-transform-runtime": { + "optional": true + }, + "@babel/runtime": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT", + "peer": true + }, + "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.24", "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", "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==", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "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==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/gensync": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", + "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "license": "MIT" + }, + "node_modules/@vitest/mocker": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.0.tgz", + "integrity": "sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.0", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.0.tgz", + "integrity": "sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agents": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/agents/-/agents-0.20.1.tgz", + "integrity": "sha512-HQRYMeZpD3k8djYBH7atRPojZMee3NvmXkzsmMWXfdHZ94vMljmWqSsD1XZd70LovHyQrw6/R81AZZIsRiFM6Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-proposal-decorators": "^8.0.2", + "@cfworker/json-schema": "^4.1.1", + "@rolldown/plugin-babel": "^0.2.3", + "cron-schedule": "^6.0.0", + "esbuild": "^0.28.1", + "mimetext": "^3.0.28", + "nanoid": "^5.1.16", + "partyserver": "^0.5.8", + "partysocket": "1.3.0", + "yaml": "^2.9.0", + "yargs": "^18.0.0" + }, + "bin": { + "agents": "dist/cli/index.js" + }, + "peerDependencies": { + "@ai-sdk/react": "^3.0.0 || ^4.0.0", + "@cloudflare/codemode": ">=0.5.0", + "@modelcontextprotocol/client": "2.0.0", + "@modelcontextprotocol/sdk": "1.30.0", + "@modelcontextprotocol/server": "2.0.0", + "@tanstack/ai": ">=0.10.2 <1.0.0", + "@x402/core": "^2.0.0", + "@x402/evm": "^2.0.0", + "ai": "^6.0.0 || ^7.0.0", + "chat": "^4.29.0", + "just-bash": "^3.0.0", + "react": "^19.0.0", + "vite": ">=6.0.0 <9.0.0", + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "@ai-sdk/react": { + "optional": true + }, + "@cloudflare/codemode": { + "optional": true + }, + "@tanstack/ai": { + "optional": true + }, + "@x402/core": { + "optional": true + }, + "@x402/evm": { + "optional": true + }, + "ai": { + "optional": true + }, + "chat": { + "optional": true + }, + "just-bash": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz", + "integrity": "sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.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/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT", + "peer": true + }, + "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/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-js-pure": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.50.0.tgz", + "integrity": "sha512-6GP3Pxz4IKyWjAfa747vIu/jilB5z29JWROLqH/b+pXVcpgh6tM06ZIBwSuglgVqzDYURhOK6oEzTrG0bCHitA==", + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cron-schedule": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-6.0.0.tgz", + "integrity": "sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT", + "peer": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.427", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz", + "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==", + "license": "ISC", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.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/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "peer": true + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-polyfill": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz", + "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==", + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "peer": true, + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "peer": true + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "peer": true + }, + "node_modules/ip-address": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT", + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "peer": true + }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-base64": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.3.tgz", + "integrity": "sha512-uwYQp+VJ38FVvtim6qNbit6e9uT6dwWQ4Y1+H9TxhW5hcHjpHwoxlR0nMpqUmIFOmu4VqMxwdJA88gIVuZJQ/g==", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "devOptional": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.3.1.tgz", + "integrity": "sha512-rm91zr2Ou+XueDTohjQQjdQEcYM6zVi8KVUCG8Ec3vHwUEKrhSdCNyfuIywkA6hcCAteIn0ZOtAHA6eGpiX+Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimetext": { + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/mimetext/-/mimetext-3.0.28.tgz", + "integrity": "sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@babel/runtime-corejs3": "^7.26.0", + "js-base64": "^3.7.7", + "mime-types": "^2.1.35" + }, + "funding": { + "type": "patreon", + "url": "https://patreon.com/muratgozel" + } + }, + "node_modules/mimetext/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimetext/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/miniflare": { + "version": "5.20260911.0-alpha", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260911.0-alpha.tgz", + "integrity": "sha512-CRieJmvHx+7rNqnA5SKdsYsER6rfkUIE/jruIUw+fLhsQ4sORfuMtr3+FQzsQ9/y8lhk061V4Fl1DFdHiyBB6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.4", + "undici": "7.29.0", + "workerd": "1.20260911.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/partyserver": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.5.10.tgz", + "integrity": "sha512-t2B3mhTL1IOxCsj8rZgn4TxTuub7oLhypjc1/fGPNsbgnn9OsHms6uR3QEd5bFJ/7xrFo771j3DdUlll8cxgBg==", + "license": "ISC", + "dependencies": { + "nanoid": "^5.1.9" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260424.1 || ^5.20260703.1" + } + }, + "node_modules/partysocket": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.3.0.tgz", + "integrity": "sha512-1zToNyolZFK/7nuAw/K2bZrNzFqaZyRoCEkS+9vG6WSC5ikrN6qWRe96q6ImU51uptz2r+dAwSkwhJVdQi4LiA==", + "license": "MIT", + "dependencies": { + "event-target-polyfill": "^0.0.4" + }, + "peerDependencies": { + "react": ">=17" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.10" } }, - "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", + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, "engines": { - "node": ">=8" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "peer": true, + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "node": ">= 0.10" } }, - "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, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=0.10.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, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", + "peer": true, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/miniflare": { - "version": "5.20260911.0-alpha", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260911.0-alpha.tgz", - "integrity": "sha512-CRieJmvHx+7rNqnA5SKdsYsER6rfkUIE/jruIUw+fLhsQ4sORfuMtr3+FQzsQ9/y8lhk061V4Fl1DFdHiyBB6g==", - "dev": true, + "node_modules/rolldown": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz", + "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", "license": "MIT", + "peer": true, "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "sharp": "0.35.4", - "undici": "7.29.0", - "workerd": "1.20260911.1", - "ws": "8.21.0", - "youch": "4.1.0-beta.10" + "@oxc-project/types": "=0.149.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=22.0.0" + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.8", + "@rolldown/binding-android-arm64": "1.2.8", + "@rolldown/binding-darwin-arm64": "1.2.8", + "@rolldown/binding-darwin-x64": "1.2.8", + "@rolldown/binding-freebsd-x64": "1.2.8", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", + "@rolldown/binding-linux-arm64-gnu": "1.2.8", + "@rolldown/binding-linux-arm64-musl": "1.2.8", + "@rolldown/binding-linux-ppc64-gnu": "1.2.8", + "@rolldown/binding-linux-s390x-gnu": "1.2.8", + "@rolldown/binding-linux-x64-gnu": "1.2.8", + "@rolldown/binding-linux-x64-musl": "1.2.8", + "@rolldown/binding-openharmony-arm64": "1.2.8", + "@rolldown/binding-win32-arm64-msvc": "1.2.8", + "@rolldown/binding-win32-x64-msvc": "1.2.8" } }, - "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/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } }, - "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/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "peer": true }, "node_modules/semver": { "version": "7.8.5", @@ -1347,6 +4076,60 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "peer": true, + "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.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC", + "peer": true + }, "node_modules/sharp": { "version": "0.35.4", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", @@ -1359,42 +4142,214 @@ "semver": "^7.8.5" }, "engines": { - "node": ">=20.9.0" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "peer": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "devOptional": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.4", - "@img/sharp-darwin-x64": "0.35.4", - "@img/sharp-freebsd-wasm32": "0.35.4", - "@img/sharp-libvips-darwin-arm64": "1.3.3", - "@img/sharp-libvips-darwin-x64": "1.3.3", - "@img/sharp-libvips-linux-arm": "1.3.3", - "@img/sharp-libvips-linux-arm64": "1.3.3", - "@img/sharp-libvips-linux-ppc64": "1.3.3", - "@img/sharp-libvips-linux-riscv64": "1.3.3", - "@img/sharp-libvips-linux-s390x": "1.3.3", - "@img/sharp-libvips-linux-x64": "1.3.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", - "@img/sharp-libvips-linuxmusl-x64": "1.3.3", - "@img/sharp-linux-arm": "0.35.4", - "@img/sharp-linux-arm64": "0.35.4", - "@img/sharp-linux-ppc64": "0.35.4", - "@img/sharp-linux-riscv64": "0.35.4", - "@img/sharp-linux-s390x": "0.35.4", - "@img/sharp-linux-x64": "0.35.4", - "@img/sharp-linuxmusl-arm64": "0.35.4", - "@img/sharp-linuxmusl-x64": "0.35.4", - "@img/sharp-webcontainers-wasm32": "0.35.4", - "@img/sharp-win32-arm64": "0.35.4", - "@img/sharp-win32-ia32": "0.35.4", - "@img/sharp-win32-x64": "0.35.4" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/supports-color": { @@ -1410,6 +4365,53 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/tinybench": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -1418,6 +4420,39 @@ "license": "0BSD", "optional": true }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "peer": true, + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -1452,6 +4487,264 @@ "pathe": "^2.0.3" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verkit": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.3.2.tgz", + "integrity": "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vite": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.0.tgz", + "integrity": "sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.0", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.0", + "@vitest/browser-preview": "5.0.0", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.0", + "@vitest/coverage-v8": "5.0.0", + "@vitest/ui": "5.0.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/workerd": { "version": "1.20260911.1", "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260911.1.tgz", @@ -1509,6 +4802,47 @@ } } }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "peer": true + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", @@ -1531,6 +4865,56 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/youch": { "version": "4.1.0-beta.10", "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", @@ -1555,6 +4939,25 @@ "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peer": true, + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } } } } diff --git a/worker/package.json b/worker/package.json index bd0e9f44..c653ae17 100644 --- a/worker/package.json +++ b/worker/package.json @@ -4,14 +4,27 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "dev": "wrangler dev --port 8787", + "mcp:dev": "wrangler dev --config wrangler-mcp.toml --port 8788", + "typecheck": "tsc --noEmit", + "test": "vitest run tests", + "check": "npm run typecheck && npm test", + "deploy": "wrangler deploy", + "deploy:mcp": "wrangler deploy --config wrangler-mcp.toml" }, "keywords": [], "author": "", "license": "ISC", "type": "commonjs", "devDependencies": { + "@cloudflare/workers-types": "^5.20260914.1", "typescript": "^5.9.3", + "vitest": "^5.0.0", "wrangler": "^4.131.1" + }, + "dependencies": { + "@modelcontextprotocol/server": "^2.0.0", + "agents": "^0.20.1", + "zod": "^4.6.5" } } diff --git a/worker/src/env.ts b/worker/src/env.ts new file mode 100644 index 00000000..234f7c28 --- /dev/null +++ b/worker/src/env.ts @@ -0,0 +1,15 @@ +export interface McpEnv { + OPENAPI_JSON?: string; + MCP_API_BASE?: string; + MCP_ALLOWED_HOSTS?: string; + MCP_ALLOWED_ORIGINS?: string; + MCP_TIMEOUT_MS?: string; + MCP_MAX_RESPONSE_BYTES?: string; +} + +export interface Env extends McpEnv { + ORIGIN_BASE: string; + INDEX_URL: string; + TAGS_URL: string; + SITE_BASE: string; +} diff --git a/worker/src/index.ts b/worker/src/index.ts index 98861a27..67afba0b 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -1,12 +1,6 @@ -export interface Env { - // Point these to your GitHub raw files in main branch. - // If your JSONs live in ./pswiki/json -> append that path. - ORIGIN_BASE: string; // e.g., https://raw.githubusercontent.com/ps-wiki/ps-wiki.github.io/main/pswiki/json - INDEX_URL: string; // e.g., https://raw.githubusercontent.com/ps-wiki/ps-wiki.github.io/main/pswiki/database/build/index.json - TAGS_URL: string; // e.g., https://raw.githubusercontent.com/ps-wiki/ps-wiki.github.io/main/pswiki/database/build/tags.json - SITE_BASE: string; // e.g., https://ps-wiki.ning.guru - OPENAPI_JSON?: string; // optional; can inline or serve static later -} +import type { Env } from "./env"; + +export type { Env } from "./env"; type TermSummary = { id: string; title: string; summary?: string; tags?: string[]; updated_at: string }; type IndexDoc = { items: TermSummary[]; generated_at?: string }; @@ -180,7 +174,7 @@ function text(s: string, status = 200): Response { const DEFAULT_OPENAPI = JSON.stringify({ openapi: "3.1.0", info: { title: "PS-Wiki API", version: "1.1.0", description: "Read-only access to PS-Wiki terms and tags with attribution metadata." }, - servers: [{ url: "https://api.ning.guru" }], + servers: [{ url: "https://api.ps-wiki.ning.guru" }], paths: { "/v1/terms": { get: { diff --git a/worker/src/mcp.ts b/worker/src/mcp.ts new file mode 100644 index 00000000..12dad0b9 --- /dev/null +++ b/worker/src/mcp.ts @@ -0,0 +1,356 @@ +import { createMcpHandler } from "agents/mcp/server"; +import { McpServer } from "@modelcontextprotocol/server"; +import { z } from "zod"; + +import type { McpEnv } from "./env"; + +const DEFAULT_API_BASE = "https://api.ps-wiki.ning.guru"; +const DEFAULT_TIMEOUT_MS = 5_000; +const DEFAULT_MAX_RESPONSE_BYTES = 1_000_000; +const MAX_SEARCH_LIMIT = 50; +const MAX_RELATED_TERMS = 25; + +type TermSummary = { + id: string; + title: string; + summary?: string; + description?: string; + tags?: string[]; + updated_at?: string; + url?: string; +}; + +type ApiAttribution = { + provider?: string; + url?: string; + license?: { name?: string; url?: string }; +}; + +type ApiTermsResponse = { + items?: TermSummary[]; + next_cursor?: string | null; + attribution?: ApiAttribution; +}; + +class ApiError extends Error { + constructor( + readonly code: "not_found" | "upstream_error" | "invalid_response", + readonly status?: number, + ) { + super(code); + } +} + +function parsePositiveInt(value: string | undefined, fallback: number, maximum: number): number { + const parsed = Number.parseInt(value ?? "", 10); + return Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, maximum) : fallback; +} + +function splitHostnames(value: string | undefined, fallback: string[]): string[] { + const hostnames = (value ?? "") + .split(",") + .map((hostname) => hostname.trim().toLowerCase()) + .filter(Boolean); + return hostnames.length > 0 ? hostnames : fallback; +} + +function jsonText(value: unknown): string { + return JSON.stringify(value, null, 2); +} + +function success(value: unknown) { + return { content: [{ type: "text" as const, text: jsonText(value) }] }; +} + +function failure(error: unknown) { + if (error instanceof ApiError) { + return { + isError: true, + content: [ + { + type: "text" as const, + text: jsonText({ + error: error.code, + ...(error.status ? { status: error.status } : {}), + message: error.code === "not_found" ? "The requested PS-Wiki term was not found." : "The PS-Wiki REST API could not complete the request.", + }), + }, + ], + }; + } + + return { + isError: true, + content: [{ type: "text" as const, text: jsonText({ error: "internal_error" }) }], + }; +} + +async function readJson(response: Response, maxBytes: number): Promise { + const contentLength = Number.parseInt(response.headers.get("Content-Length") ?? "", 10); + if (Number.isFinite(contentLength) && contentLength > maxBytes) { + throw new ApiError("upstream_error", 502); + } + + if (!response.body) { + return response.json(); + } + + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let total = 0; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + total += value.byteLength; + if (total > maxBytes) throw new ApiError("upstream_error", 502); + chunks.push(value); + } + } finally { + reader.releaseLock(); + } + + const bytes = new Uint8Array(total); + let offset = 0; + for (const chunk of chunks) { + bytes.set(chunk, offset); + offset += chunk.byteLength; + } + + try { + return JSON.parse(new TextDecoder().decode(bytes)); + } catch { + throw new ApiError("invalid_response", 502); + } +} + +async function fetchJson(path: string, env: McpEnv): Promise { + const base = (env.MCP_API_BASE ?? DEFAULT_API_BASE).replace(/\/+$/, ""); + const timeoutMs = parsePositiveInt(env.MCP_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, 30_000); + const maxBytes = parsePositiveInt( + env.MCP_MAX_RESPONSE_BYTES, + DEFAULT_MAX_RESPONSE_BYTES, + 5_000_000, + ); + const controller = new AbortController(); + let timeout: ReturnType | undefined; + + try { + let response: Response; + try { + const request = fetch(`${base}${path}`, { + headers: { Accept: "application/json" }, + signal: controller.signal, + }); + const deadline = new Promise((_, reject) => { + timeout = setTimeout(() => { + controller.abort(); + reject(new ApiError("upstream_error", 502)); + }, timeoutMs); + }); + response = await Promise.race([request, deadline]); + } catch { + throw new ApiError("upstream_error", 502); + } + + if (response.status === 404) throw new ApiError("not_found", 404); + if (!response.ok) throw new ApiError("upstream_error", 502); + return await readJson(response, maxBytes); + } finally { + if (timeout) clearTimeout(timeout); + } +} + +function asRecord(value: unknown): Record { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + throw new ApiError("invalid_response", 502); + } + return value as Record; +} + +function asTermsResponse(value: unknown): ApiTermsResponse { + const record = asRecord(value); + if (record.items !== undefined && !Array.isArray(record.items)) { + throw new ApiError("invalid_response", 502); + } + return record as ApiTermsResponse; +} + +async function searchTerms(env: McpEnv, query: string, limit: number): Promise { + const params = new URLSearchParams({ query, limit: String(limit) }); + return asTermsResponse(await fetchJson(`/v1/terms?${params}`, env)); +} + +async function getTerm(env: McpEnv, termId: string): Promise> { + return asRecord(await fetchJson(`/v1/terms/${encodeURIComponent(termId)}`, env)); +} + +async function relatedTermSummaries( + env: McpEnv, + relatedIds: string[], +): Promise> { + const results = await Promise.allSettled( + relatedIds.slice(0, MAX_RELATED_TERMS).map(async (id) => { + const term = await getTerm(env, id); + return { + id: term.id ?? id, + title: term.title, + summary: term.description ?? term.summary ?? "", + url: term.url, + }; + }), + ); + return results.flatMap((result) => (result.status === "fulfilled" ? [result.value] : [])); +} + +function createServer(env: McpEnv): McpServer { + const server = new McpServer({ name: "pswiki-mcp", version: "1.0.0" }); + + server.registerTool( + "search_terms", + { + description: "Search PS-Wiki terminology by keyword, phrase, or concept. Use this to discover candidate terms before requesting a complete definition.", + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, + inputSchema: { + query: z.string().trim().min(1).max(200).describe("Keyword, phrase, or concept to search for."), + limit: z.number().int().min(1).max(MAX_SEARCH_LIMIT).optional().default(10).describe("Maximum number of matching term summaries to return; defaults to 10."), + }, + }, + async ({ query, limit }) => { + try { + const data = await searchTerms(env, query, limit); + return success({ query, count: data.items?.length ?? 0, results: data.items ?? [], attribution: data.attribution }); + } catch (error) { + return failure(error); + } + }, + ); + + server.registerTool( + "get_term", + { + description: "Retrieve the complete PS-Wiki definition for one known term ID, including description, equations, citations, related terms, canonical URL, and attribution.", + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, + inputSchema: { + term_id: z.string().trim().min(1).max(200).describe("Kebab-case PS-Wiki term ID, such as voltage-stability."), + }, + }, + async ({ term_id }) => { + try { + return success(await getTerm(env, term_id)); + } catch (error) { + return failure(error); + } + }, + ); + + server.registerTool( + "get_related_terms", + { + description: "Explore concepts related to a known PS-Wiki term. Use depth 1 for direct relationships; depth 2 also includes a bounded set of second-level concepts.", + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, + inputSchema: { + term_id: z.string().trim().min(1).max(200).describe("Kebab-case PS-Wiki term ID."), + depth: z.number().int().min(1).max(2).optional().default(1).describe("Relationship depth: 1 or 2; defaults to 1."), + }, + }, + async ({ term_id, depth }) => { + try { + const term = await getTerm(env, term_id); + const relatedIds = Array.isArray(term.related) + ? term.related.filter((value): value is string => typeof value === "string") + : []; + const relatedTerms = await relatedTermSummaries(env, relatedIds); + const result: Record = { + term_id, + term_title: term.title, + depth, + related_terms: relatedTerms, + attribution: term.attribution, + }; + + if (depth >= 2) { + const secondLevelIds = new Set(); + const relatedFull = await Promise.allSettled( + relatedTerms.map(async (related) => getTerm(env, String(related.id))), + ); + for (const related of relatedFull) { + if (related.status !== "fulfilled" || !Array.isArray(related.value.related)) continue; + for (const secondId of related.value.related) { + if (typeof secondId === "string" && secondId !== term_id && !relatedIds.includes(secondId)) { + secondLevelIds.add(secondId); + } + } + } + result.second_level_terms = await relatedTermSummaries(env, [...secondLevelIds]); + } + + return success(result); + } catch (error) { + return failure(error); + } + }, + ); + + server.registerTool( + "list_tags", + { + description: "List PS-Wiki categories/tags and their usage counts. Use this to browse the taxonomy before filtering terms by a tag.", + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, + inputSchema: {}, + }, + async () => { + try { + const data = asRecord(await fetchJson("/v1/tags", env)); + const tags = Array.isArray(data.tags) ? data.tags : []; + return success({ tags, count: tags.length, attribution: data.attribution }); + } catch (error) { + return failure(error); + } + }, + ); + + server.registerTool( + "get_terms_by_tag", + { + description: "Browse PS-Wiki terms in one category/tag. Use this when the user wants a taxonomy-based list rather than a free-text concept search.", + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }, + inputSchema: { + tag: z.string().trim().min(1).max(100).describe("Case-insensitive PS-Wiki tag, such as stability or control."), + }, + }, + async ({ tag }) => { + try { + const params = new URLSearchParams({ tag, limit: "100" }); + const data = asTermsResponse(await fetchJson(`/v1/terms?${params}`, env)); + return success({ tag, count: data.items?.length ?? 0, terms: data.items ?? [], attribution: data.attribution }); + } catch (error) { + return failure(error); + } + }, + ); + + return server; +} + +export async function handleMcp(request: Request, env: McpEnv, ctx: ExecutionContext): Promise { + const handler = createMcpHandler(() => createServer(env), { + route: "/mcp", + allowedHostnames: splitHostnames(env.MCP_ALLOWED_HOSTS, [ + "mcp.ps-wiki.ning.guru", + "pswiki-mcp.jinning.workers.dev", + "localhost", + "127.0.0.1", + "[::1]", + ]), + allowedOriginHostnames: splitHostnames(env.MCP_ALLOWED_ORIGINS, [ + "mcp.ps-wiki.ning.guru", + "pswiki-mcp.jinning.workers.dev", + "localhost", + "127.0.0.1", + "[::1]", + ]), + }); + return handler(request, env, ctx); +} diff --git a/worker/tests/mcp.test.ts b/worker/tests/mcp.test.ts new file mode 100644 index 00000000..9b8d001e --- /dev/null +++ b/worker/tests/mcp.test.ts @@ -0,0 +1,218 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import worker from "../mcp-entry"; +import type { McpEnv } from "../src/env"; + +const env: McpEnv = { + MCP_API_BASE: "https://api.ps-wiki.ning.guru", + MCP_ALLOWED_HOSTS: "mcp.ps-wiki.ning.guru", + MCP_ALLOWED_ORIGINS: "mcp.ps-wiki.ning.guru", + MCP_TIMEOUT_MS: "20", + MCP_MAX_RESPONSE_BYTES: "100000", +}; + +const context = { + waitUntil: vi.fn(), + passThroughOnException: vi.fn(), +} as unknown as ExecutionContext; + +type McpReply = { + result?: { + serverInfo?: { name?: string }; + tools?: Array<{ name: string }>; + isError?: boolean; + content?: Array<{ text: string }>; + }; + error?: { code: number }; +}; + +function apiResponse(body: unknown, status = 200, headers: Record = {}) { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json", ...headers }, + }); +} + +function initializeRequest(id = 1) { + return { + jsonrpc: "2.0", + id, + method: "initialize", + params: { + protocolVersion: "2025-06-18", + capabilities: {}, + clientInfo: { name: "pswiki-test", version: "1.0.0" }, + }, + }; +} + +async function mcpRequest(body: unknown, origin?: string, requestEnv: McpEnv = env) { + const response = await worker.fetch( + new Request("https://mcp.ps-wiki.ning.guru/mcp", { + method: "POST", + headers: { + Accept: "application/json, text/event-stream", + "Content-Type": "application/json", + Host: "mcp.ps-wiki.ning.guru", + ...(origin ? { Origin: origin } : {}), + }, + body: JSON.stringify(body), + }), + requestEnv, + context, + ); + const text = await response.text(); + const payload = response.headers.get("Content-Type")?.includes("text/event-stream") + ? text + .split("\n") + .find((line) => line.startsWith("data: ")) + ?.slice("data: ".length) ?? "null" + : text; + return { + response, + body: response.status === 204 ? null : JSON.parse(payload) as McpReply, + }; +} + +describe("remote MCP Worker", () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input); + if (url.endsWith("/v1/terms?query=stability&limit=10")) { + return apiResponse({ + items: [ + { + id: "voltage-stability", + title: "Voltage Stability", + summary: "Ability to maintain acceptable bus voltages.", + tags: ["stability"], + updated_at: "2026-09-01", + url: "https://ps-wiki.ning.guru/wiki/voltage-stability/", + }, + ], + next_cursor: null, + attribution: { provider: "PS-Wiki" }, + }); + } + if (url.endsWith("/v1/terms/voltage-stability")) { + return apiResponse({ + id: "voltage-stability", + title: "Voltage Stability", + description: "The ability of a power system to maintain steady voltages.", + related: ["loadability"], + url: "https://ps-wiki.ning.guru/wiki/voltage-stability/", + attribution: { provider: "PS-Wiki" }, + }); + } + if (url.endsWith("/v1/terms/missing-term")) return apiResponse({ error: "not_found" }, 404); + if (url.endsWith("/v1/tags")) { + return apiResponse({ tags: [{ tag: "stability", count: 12 }], attribution: { provider: "PS-Wiki" } }); + } + throw new Error(`Unexpected upstream URL: ${url}`); + }), + ); + }); + + it("initializes and lists the five read-only tools", async () => { + const initialized = await mcpRequest(initializeRequest()); + expect(initialized.response.status).toBe(200); + expect(initialized.body!.result!.serverInfo!.name).toBe("pswiki-mcp"); + + const listed = await mcpRequest({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }); + expect(listed.response.status).toBe(200); + expect(listed.body!.result!.tools!.map((tool) => tool.name)).toEqual([ + "search_terms", + "get_term", + "get_related_terms", + "list_tags", + "get_terms_by_tag", + ]); + }); + + it("calls search_terms through the REST API", async () => { + await mcpRequest(initializeRequest()); + const called = await mcpRequest({ + jsonrpc: "2.0", + id: 3, + method: "tools/call", + params: { name: "search_terms", arguments: { query: "stability" } }, + }); + + expect(called.response.status).toBe(200); + expect(called.body!.result!.isError).toBeUndefined(); + expect(JSON.parse(called.body!.result!.content![0].text).results[0].id).toBe("voltage-stability"); + }); + + it("returns a protocol validation error for invalid arguments", async () => { + const called = await mcpRequest({ + jsonrpc: "2.0", + id: 4, + method: "tools/call", + params: { name: "get_term", arguments: {} }, + }); + + expect(called.response.status).toBe(200); + expect(called.body!.result!.isError).toBe(true); + expect(called.body!.result!.content![0].text).toContain("Invalid arguments"); + }); + + it("returns a safe error for an unknown term", async () => { + const called = await mcpRequest({ + jsonrpc: "2.0", + id: 5, + method: "tools/call", + params: { name: "get_term", arguments: { term_id: "missing-term" } }, + }); + expect(called.body!.result!.isError).toBe(true); + expect(JSON.parse(called.body!.result!.content![0].text)).toMatchObject({ error: "not_found" }); + }); + + it("returns a safe error when the REST API times out", async () => { + vi.mocked(fetch).mockImplementationOnce(() => new Promise(() => {})); + const called = await mcpRequest({ + jsonrpc: "2.0", + id: 6, + method: "tools/call", + params: { name: "list_tags", arguments: {} }, + }); + expect(called.body!.result!.isError).toBe(true); + expect(JSON.parse(called.body!.result!.content![0].text)).toMatchObject({ error: "upstream_error" }); + }); + + it("returns a safe error when the REST API response is too large", async () => { + vi.mocked(fetch).mockImplementationOnce(async () => + apiResponse({ tags: ["x".repeat(100)] }), + ); + const called = await mcpRequest( + { + jsonrpc: "2.0", + id: 7, + method: "tools/call", + params: { name: "list_tags", arguments: {} }, + }, + undefined, + { ...env, MCP_MAX_RESPONSE_BYTES: "32" }, + ); + expect(called.body!.result!.isError).toBe(true); + expect(JSON.parse(called.body!.result!.content![0].text)).toMatchObject({ error: "upstream_error" }); + }); + + it("rejects malformed MCP JSON and disallowed origins", async () => { + const malformed = await worker.fetch( + new Request("https://mcp.ps-wiki.ning.guru/mcp", { + method: "POST", + headers: { Accept: "application/json, text/event-stream", "Content-Type": "application/json", Host: "mcp.ps-wiki.ning.guru" }, + body: "not-json", + }), + env, + context, + ); + expect(malformed.status).toBe(400); + + const disallowed = await mcpRequest(initializeRequest(8), "https://evil.example"); + expect(disallowed.response.status).toBe(403); + }); +}); diff --git a/worker/tsconfig.json b/worker/tsconfig.json new file mode 100644 index 00000000..d663767f --- /dev/null +++ b/worker/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM"], + "types": ["@cloudflare/workers-types", "vitest/globals"], + "strict": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["*.ts", "src/**/*.ts", "tests/**/*.ts"] +} diff --git a/worker/wrangler-mcp.toml b/worker/wrangler-mcp.toml new file mode 100644 index 00000000..5bd99c42 --- /dev/null +++ b/worker/wrangler-mcp.toml @@ -0,0 +1,22 @@ +name = "pswiki-mcp" +main = "mcp-entry.ts" +compatibility_date = "2025-11-02" +compatibility_flags = ["nodejs_compat"] + +[[routes]] +pattern = "mcp.ps-wiki.ning.guru" +custom_domain = true + +[observability] +enabled = true + + [observability.logs] + invocation_logs = true + head_sampling_rate = 1 + +[vars] +MCP_API_BASE = "https://api.ps-wiki.ning.guru" +MCP_ALLOWED_HOSTS = "mcp.ps-wiki.ning.guru,pswiki-mcp.jinning.workers.dev" +MCP_ALLOWED_ORIGINS = "mcp.ps-wiki.ning.guru,pswiki-mcp.jinning.workers.dev" +MCP_TIMEOUT_MS = "5000" +MCP_MAX_RESPONSE_BYTES = "1000000" diff --git a/worker/wrangler.toml b/worker/wrangler.toml index b3f5c857..99f9385c 100644 --- a/worker/wrangler.toml +++ b/worker/wrangler.toml @@ -2,6 +2,10 @@ name = "pswiki-api" main = "src/index.ts" compatibility_date = "2025-11-02" +[[routes]] +pattern = "api.ps-wiki.ning.guru" +custom_domain = true + [observability] enabled = true