mcp: serveur MCP officiel hfmarketdata-mcp (14 outils, 2 ressources, 3 prompts, stdio) + tests vitest + tarball téléchargeable
- TypeScript, @modelcontextprotocol/sdk + zod, fetch natif, Node >= 20
- outils : search_symbols, get_bars, get_futures_contracts, get_futures_chain, get_continuous, get_term_structure, get_options_chain, get_coverage, get_financial_statements, get_ratios, get_ratios_daily, screen_fundamentals, get_filings, subscribe_filings (websocket borné)
- sorties compactes {columns,rows}, résumé tête/queue au-delà de 200 lignes, en-têtes X-RateLimit-* et 429 explicités, erreurs uniformes
- README vitrine (Claude Code / Cursor / Codex / Claude Desktop), guide d'enregistrement de la démo, smoke test live
- hfmarketdata/web/public/downloads/hfmarketdata-mcp-1.0.0.tgz (npm pack) servi par le SPA fallback
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
25 changed files +4,926 −0
added
hfmarketdata/web/public/downloads/.gitignore
+4 −0
@@ -0,0 +1,4 @@ | ||
| 1 | +# Distributables served at https://www.hfmarketdata.io/downloads/ — committed on purpose | |
| 2 | +# (the repository root ignores *.zip for data archives; re-include ours here). | |
| 3 | +!*.zip | |
| 4 | +!*.tgz | |
added
hfmarketdata/web/public/downloads/hfmarketdata-mcp-1.0.0.tgz
+0 −0
Binary file not shown.
added
mcp/.gitignore
+3 −0
@@ -0,0 +1,3 @@ | ||
| 1 | +node_modules/ | |
| 2 | +dist/ | |
| 3 | +*.tgz | |
added
mcp/LICENSE
+21 −0
@@ -0,0 +1,21 @@ | ||
| 1 | +MIT License | |
| 2 | + | |
| 3 | +Copyright (c) 2026 Simon-Pierre Boucher | |
| 4 | + | |
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | +of this software and associated documentation files (the "Software"), to deal | |
| 7 | +in the Software without restriction, including without limitation the rights | |
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | +copies of the Software, and to permit persons to whom the Software is | |
| 10 | +furnished to do so, subject to the following conditions: | |
| 11 | + | |
| 12 | +The above copyright notice and this permission notice shall be included in all | |
| 13 | +copies or substantial portions of the Software. | |
| 14 | + | |
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | +SOFTWARE. | |
added
mcp/README.md
+185 −0
@@ -0,0 +1,185 @@ | ||
| 1 | +<p align="center"> | |
| 2 | + <img src="docs/demo.gif" alt="hfmarketdata-mcp demo — asking Claude Code for the CL term structure" width="820" /> | |
| 3 | +</p> | |
| 4 | + | |
| 5 | +<h1 align="center">hfmarketdata-mcp</h1> | |
| 6 | + | |
| 7 | +<p align="center"> | |
| 8 | + The official <a href="https://modelcontextprotocol.io">MCP</a> server for <a href="https://www.hfmarketdata.io">HF Market Data</a> — | |
| 9 | + 1-minute to daily bars for stocks, ETFs, futures, crypto, indices and FX since 2007, individual futures contracts, custom continuous series, | |
| 10 | + term structures, options chains with Greeks and point-in-time SEC fundamentals, straight into Claude Code, Cursor, Codex or any MCP client. | |
| 11 | +</p> | |
| 12 | + | |
| 13 | +<p align="center"> | |
| 14 | + <a href="https://www.npmjs.com/package/hfmarketdata-mcp"><img alt="npm" src="https://img.shields.io/npm/v/hfmarketdata-mcp?color=5ee7a5&label=npm" /></a> | |
| 15 | + <img alt="node" src="https://img.shields.io/badge/node-%E2%89%A5%2020-5ee7a5" /> | |
| 16 | + <img alt="MCP" src="https://img.shields.io/badge/MCP-stdio-37b3ff" /> | |
| 17 | + <img alt="tools" src="https://img.shields.io/badge/tools-14-37b3ff" /> | |
| 18 | + <img alt="tests" src="https://img.shields.io/badge/vitest-45%20passing-5ee7a5" /> | |
| 19 | + <a href="./LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-lightgrey" /></a> | |
| 20 | +</p> | |
| 21 | + | |
| 22 | +--- | |
| 23 | + | |
| 24 | +## Quickstart (60 seconds) | |
| 25 | + | |
| 26 | +```bash | |
| 27 | +# 1. Install (pick one) | |
| 28 | +npm i -g hfmarketdata-mcp # from npm (once published) | |
| 29 | +npm i -g https://www.hfmarketdata.io/downloads/hfmarketdata-mcp-1.0.0.tgz # direct tarball, works today | |
| 30 | + | |
| 31 | +# 2. Check the connection (keyless works out of the box: 30 requests/hour) | |
| 32 | +hfmarketdata-mcp --selftest | |
| 33 | +# OK https://www.hfmarketdata.io (312 ms, keyless) — datasets: stock, etf, futures, futures_contracts, crypto, index, fx, options | |
| 34 | + | |
| 35 | +# 3. Add it to Claude Code | |
| 36 | +claude mcp add hfmarketdata -e HFMD_API_KEY=hfmd_live_… -- npx -y hfmarketdata-mcp | |
| 37 | +``` | |
| 38 | + | |
| 39 | +Then just ask: | |
| 40 | + | |
| 41 | +> *"Plot me the CL term structure"* · *"Compare ESZ25 and ESH26 over 30 days"* · *"Screen US stocks with PE<15 and FCF yield>6% and show their last 3 quarters"* | |
| 42 | + | |
| 43 | +A free account (`https://www.hfmarketdata.io/signup`) gives you an API key and **120 requests / minute** instead of 30 / hour. Set it as `HFMD_API_KEY`; the server never logs it. | |
| 44 | + | |
| 45 | +## Tools | |
| 46 | + | |
| 47 | +| Tool | What it does | Backed by | | |
| 48 | +|---|---|---| | |
| 49 | +| `search_symbols` | Find tickers (stock/etf/crypto/index/fx), futures roots or optionable underlyings | `/v1/{asset}/tickers`, `/v1/futures/roots`, `/v1/options/tickers` | | |
| 50 | +| `get_bars` | OHLCV bars 1min…1day for one or many symbols; vendor continuous futures; individual contracts (`ESZ25`) | `/v1/bars/{asset}/{ticker}`, `/v1/bars/{asset}?tickers=`, `/v1/futures/contract/{symbol}/bars` | | |
| 51 | +| `get_futures_contracts` | Every expiration of a root with expiry / FND / status / volume / OI | `/v1/futures/{root}/contracts` | | |
| 52 | +| `get_futures_chain` | Listed contracts as of a date, front month first | `/v1/futures/{root}/chain` | | |
| 53 | +| `get_continuous` | Server-built continuous series: `roll=volume\|open_interest\|calendar`, `adjust=none\|back_adjusted\|ratio`, `depth` | `/v1/futures/{root}/continuous` | | |
| 54 | +| `get_term_structure` | The curve on a date (prices, spreads, OI) — contango / backwardation | `/v1/futures/{root}/term-structure` | | |
| 55 | +| `get_options_chain` | EOD chain with IV and Greeks, filters by expiry / strikes / side; `list_expirations` | `/v1/options/chain/{t}`, `/v1/options/expirations/{t}` | | |
| 56 | +| `get_coverage` | What exists for a contract or a ticker's fundamentals, gaps included | `/v1/futures/contract/{s}/coverage`, `/v1/fundamentals/{t}/coverage` | | |
| 57 | +| `get_financial_statements` | Income / balance / cash-flow, annual or quarterly, point-in-time; single XBRL fact history with `concept` | `/v1/fundamentals/{t}/statements`, `/v1/fundamentals/{t}/facts/{concept}` | | |
| 58 | +| `get_ratios` | Latest P/E, P/B, EV/EBITDA, FCF yield, ROE, margins, leverage… | `/v1/fundamentals/{t}/ratios` | | |
| 59 | +| `get_ratios_daily` | Daily ratio history recomputed with the fundamentals known that day | `/v1/fundamentals/{t}/ratios/daily` | | |
| 60 | +| `screen_fundamentals` | `filters="pe<15,roe>0.15,fcf_yield>0.06"` + `sort="fcf_yield:desc"` (+ `as_of`) | `/v1/fundamentals/screener` | | |
| 61 | +| `get_filings` | 10-K / 10-Q / 8-K / 4 / 13F… with URLs | `/v1/fundamentals/{t}/filings` | | |
| 62 | +| `subscribe_filings` | Opens `wss://…/v1/stream`, subscribes to `filings`, returns up to N messages within a bounded time window | websocket | | |
| 63 | + | |
| 64 | +**Resources:** `hfmarketdata://status` (live dataset inventory) · `hfmarketdata://limits` (tiers + your headroom). | |
| 65 | +**Prompts:** `term-structure-analysis(root, as_of?, compare_to?)` · `compare-contracts(symbol_a, symbol_b, days?)` · `fundamentals-snapshot(ticker, quarters?)`. | |
| 66 | + | |
| 67 | +### Behaviour every tool shares | |
| 68 | + | |
| 69 | +- `HFMD_BASE_URL` (default `https://www.hfmarketdata.io`) and `HFMD_API_KEY` (optional) are the only configuration. | |
| 70 | +- `format=json` is always requested; both the v1 envelope `{count,data}` and the v2 envelope `{data,meta}` are understood. | |
| 71 | +- Outputs are compact tables `{"columns":[…],"rows":[[…]]}`. The default `limit` is 500 rows (max 5 000); above **200 rows** the tool returns the count, the first/last 15 rows, per-column numeric ranges and a hint to narrow the request — so a careless "all 1-minute bars since 2010" cannot flood the context. | |
| 72 | +- Rate-limit headroom (`X-RateLimit-Remaining-*`, reset) is appended to every result; a **429** comes back as a clear message with `Retry-After` and the keyless → key upgrade hint. | |
| 73 | +- Errors are uniform: `HF Market Data error <status> <CODE>: <message> [Docs: …]`, whether the API answered with the v2 `{"error":{code,message,docs}}` envelope or the legacy `{"detail"}`. | |
| 74 | +- `subscribe_filings` is bounded by design (`max_messages` ≤ 100, `timeout_seconds` ≤ 120) and closes the socket when it returns; it needs Node ≥ 22 (global `WebSocket`) or Node 20 with `--experimental-websocket`. | |
| 75 | +- Read-only: no tool ever writes anything anywhere. | |
| 76 | + | |
| 77 | +## Configuration snippets | |
| 78 | + | |
| 79 | +### Claude Code | |
| 80 | + | |
| 81 | +```bash | |
| 82 | +claude mcp add hfmarketdata -e HFMD_API_KEY=hfmd_live_… -- npx -y hfmarketdata-mcp | |
| 83 | +# tarball install instead of npm: | |
| 84 | +npm i -g https://www.hfmarketdata.io/downloads/hfmarketdata-mcp-1.0.0.tgz && claude mcp add hfmarketdata -e HFMD_API_KEY=hfmd_live_… -- hfmarketdata-mcp | |
| 85 | +``` | |
| 86 | + | |
| 87 | +Or in `.mcp.json` at the root of a project (shared with your team): | |
| 88 | + | |
| 89 | +```json | |
| 90 | +{ | |
| 91 | + "mcpServers": { | |
| 92 | + "hfmarketdata": { | |
| 93 | + "command": "npx", | |
| 94 | + "args": ["-y", "hfmarketdata-mcp"], | |
| 95 | + "env": { "HFMD_API_KEY": "hfmd_live_…" } | |
| 96 | + } | |
| 97 | + } | |
| 98 | +} | |
| 99 | +``` | |
| 100 | + | |
| 101 | +### Cursor — `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) | |
| 102 | + | |
| 103 | +```json | |
| 104 | +{ | |
| 105 | + "mcpServers": { | |
| 106 | + "hfmarketdata": { | |
| 107 | + "command": "npx", | |
| 108 | + "args": ["-y", "hfmarketdata-mcp"], | |
| 109 | + "env": { "HFMD_API_KEY": "hfmd_live_…" } | |
| 110 | + } | |
| 111 | + } | |
| 112 | +} | |
| 113 | +``` | |
| 114 | + | |
| 115 | +One-click (best effort): `cursor://anysphere.cursor-deeplink/mcp/install?name=hfmarketdata&config=<base64 of the JSON object above>` — generated for you on <https://www.hfmarketdata.io/integrations/cursor>. | |
| 116 | + | |
| 117 | +### Codex — `~/.codex/config.toml` | |
| 118 | + | |
| 119 | +```toml | |
| 120 | +[mcp_servers.hfmarketdata] | |
| 121 | +command = "npx" | |
| 122 | +args = ["-y", "hfmarketdata-mcp"] | |
| 123 | +env = { HFMD_API_KEY = "hfmd_live_…" } | |
| 124 | +``` | |
| 125 | + | |
| 126 | +### Claude Desktop — `claude_desktop_config.json` | |
| 127 | + | |
| 128 | +```json | |
| 129 | +{ "mcpServers": { "hfmarketdata": { "command": "npx", "args": ["-y", "hfmarketdata-mcp"], "env": { "HFMD_API_KEY": "hfmd_live_…" } } } } | |
| 130 | +``` | |
| 131 | + | |
| 132 | +Keyless? Simply drop the `env` block. | |
| 133 | + | |
| 134 | +## Example session | |
| 135 | + | |
| 136 | +``` | |
| 137 | +> Compare ESZ25 and ESH26 over 30 days | |
| 138 | + | |
| 139 | +⏺ get_coverage(kind="futures_contract", symbol="ESZ25") … get_bars(asset="futures_contract", symbol="ESZ25", start="2025-08-05") … | |
| 140 | +⏺ Over the last 30 sessions ESH26 traded at a 61.5 → 58.0 point premium to ESZ25 (calendar spread narrowing 3.5 pts as | |
| 141 | + financing expectations eased). Volume is still 96 % in ESZ25; the roll window usually opens ~8 sessions before the | |
| 142 | + December expiry (2025-12-19). One session (2025-09-01, Labor Day) is missing in both legs — not interpolated. | |
| 143 | +``` | |
| 144 | + | |
| 145 | +## Development | |
| 146 | + | |
| 147 | +```bash | |
| 148 | +npm install | |
| 149 | +npm test # vitest — 45 tests, every tool mocked (fetch + websocket) | |
| 150 | +npm run build # tsc → dist/ | |
| 151 | +node dist/index.js --selftest | |
| 152 | +node scripts/live-smoke.mjs # real stdio round-trip against www.hfmarketdata.io | |
| 153 | +npm run inspect # MCP Inspector UI | |
| 154 | +``` | |
| 155 | + | |
| 156 | +### Release | |
| 157 | + | |
| 158 | +```bash | |
| 159 | +npm version 1.0.x | |
| 160 | +npm run build && npm test | |
| 161 | +npm pack # → hfmarketdata-mcp-1.0.x.tgz (this is what /downloads serves) | |
| 162 | +npm publish --dry-run # inspect the file list | |
| 163 | +npm publish --access public # requires the maintainer's npm login (npm login / NPM_TOKEN) | |
| 164 | +``` | |
| 165 | + | |
| 166 | +Until the package is on the npm registry, `npx -y hfmarketdata-mcp` resolves nothing: use the tarball URL above or `npm i -g ./hfmarketdata-mcp-1.0.0.tgz`. | |
| 167 | + | |
| 168 | +## Project layout | |
| 169 | + | |
| 170 | +``` | |
| 171 | +src/index.ts bin — stdio transport, --help/--version/--selftest | |
| 172 | +src/server.ts createServer(): 14 tools · 2 resources · 3 prompts | |
| 173 | +src/tools.ts tool catalogue (zod schemas + handlers), runTool() | |
| 174 | +src/client.ts fetch wrapper: auth, format=json, rate headers, uniform HfmdError | |
| 175 | +src/format.ts compact tables, head/tail summaries | |
| 176 | +src/stream.ts bounded websocket consumer | |
| 177 | +src/resources.ts hfmarketdata://status · hfmarketdata://limits | |
| 178 | +src/prompts.ts term-structure-analysis · compare-contracts · fundamentals-snapshot | |
| 179 | +test/ vitest (mocked fetch + fake WebSocket + in-memory MCP transport) | |
| 180 | +scripts/ live-smoke.mjs · record-demo.md | |
| 181 | +``` | |
| 182 | + | |
| 183 | +## Licence | |
| 184 | + | |
| 185 | +MIT © Simon-Pierre Boucher — data by [FirstRate Data](https://firstratedata.com) and SEC EDGAR, served by [HF Market Data](https://www.hfmarketdata.io). Questions: contact@spboucher.ai. | |
added
mcp/docs/README.md
+5 −0
@@ -0,0 +1,5 @@ | ||
| 1 | +# docs/ | |
| 2 | + | |
| 3 | +`demo.gif` — the README hero animation. Not recorded yet: follow `../scripts/record-demo.md` | |
| 4 | +(vhs tape included) and commit the result here, then copy it to | |
| 5 | +`hfmarketdata/web/public/downloads/mcp-demo.gif` for the Integrations page. | |
added
mcp/package-lock.json
+2847 −0
@@ -0,0 +1,2847 @@ | ||
| 1 | +{ | |
| 2 | + "name": "hfmarketdata-mcp", | |
| 3 | + "version": "1.0.0", | |
| 4 | + "lockfileVersion": 3, | |
| 5 | + "requires": true, | |
| 6 | + "packages": { | |
| 7 | + "": { | |
| 8 | + "name": "hfmarketdata-mcp", | |
| 9 | + "version": "1.0.0", | |
| 10 | + "license": "MIT", | |
| 11 | + "dependencies": { | |
| 12 | + "@modelcontextprotocol/sdk": "^1.30.0", | |
| 13 | + "zod": "^4.0.0" | |
| 14 | + }, | |
| 15 | + "bin": { | |
| 16 | + "hfmarketdata-mcp": "dist/index.js" | |
| 17 | + }, | |
| 18 | + "devDependencies": { | |
| 19 | + "@types/node": "^22.0.0", | |
| 20 | + "typescript": "^5.9.0", | |
| 21 | + "vitest": "^3.2.0" | |
| 22 | + }, | |
| 23 | + "engines": { | |
| 24 | + "node": ">=20" | |
| 25 | + } | |
| 26 | + }, | |
| 27 | + "node_modules/@esbuild/aix-ppc64": { | |
| 28 | + "version": "0.28.2", | |
| 29 | + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", | |
| 30 | + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", | |
| 31 | + "cpu": [ | |
| 32 | + "ppc64" | |
| 33 | + ], | |
| 34 | + "dev": true, | |
| 35 | + "license": "MIT", | |
| 36 | + "optional": true, | |
| 37 | + "os": [ | |
| 38 | + "aix" | |
| 39 | + ], | |
| 40 | + "engines": { | |
| 41 | + "node": ">=18" | |
| 42 | + } | |
| 43 | + }, | |
| 44 | + "node_modules/@esbuild/android-arm": { | |
| 45 | + "version": "0.28.2", | |
| 46 | + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", | |
| 47 | + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", | |
| 48 | + "cpu": [ | |
| 49 | + "arm" | |
| 50 | + ], | |
| 51 | + "dev": true, | |
| 52 | + "license": "MIT", | |
| 53 | + "optional": true, | |
| 54 | + "os": [ | |
| 55 | + "android" | |
| 56 | + ], | |
| 57 | + "engines": { | |
| 58 | + "node": ">=18" | |
| 59 | + } | |
| 60 | + }, | |
| 61 | + "node_modules/@esbuild/android-arm64": { | |
| 62 | + "version": "0.28.2", | |
| 63 | + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", | |
| 64 | + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", | |
| 65 | + "cpu": [ | |
| 66 | + "arm64" | |
| 67 | + ], | |
| 68 | + "dev": true, | |
| 69 | + "license": "MIT", | |
| 70 | + "optional": true, | |
| 71 | + "os": [ | |
| 72 | + "android" | |
| 73 | + ], | |
| 74 | + "engines": { | |
| 75 | + "node": ">=18" | |
| 76 | + } | |
| 77 | + }, | |
| 78 | + "node_modules/@esbuild/android-x64": { | |
| 79 | + "version": "0.28.2", | |
| 80 | + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", | |
| 81 | + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", | |
| 82 | + "cpu": [ | |
| 83 | + "x64" | |
| 84 | + ], | |
| 85 | + "dev": true, | |
| 86 | + "license": "MIT", | |
| 87 | + "optional": true, | |
| 88 | + "os": [ | |
| 89 | + "android" | |
| 90 | + ], | |
| 91 | + "engines": { | |
| 92 | + "node": ">=18" | |
| 93 | + } | |
| 94 | + }, | |
| 95 | + "node_modules/@esbuild/darwin-arm64": { | |
| 96 | + "version": "0.28.2", | |
| 97 | + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", | |
| 98 | + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", | |
| 99 | + "cpu": [ | |
| 100 | + "arm64" | |
| 101 | + ], | |
| 102 | + "dev": true, | |
| 103 | + "license": "MIT", | |
| 104 | + "optional": true, | |
| 105 | + "os": [ | |
| 106 | + "darwin" | |
| 107 | + ], | |
| 108 | + "engines": { | |
| 109 | + "node": ">=18" | |
| 110 | + } | |
| 111 | + }, | |
| 112 | + "node_modules/@esbuild/darwin-x64": { | |
| 113 | + "version": "0.28.2", | |
| 114 | + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", | |
| 115 | + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", | |
| 116 | + "cpu": [ | |
| 117 | + "x64" | |
| 118 | + ], | |
| 119 | + "dev": true, | |
| 120 | + "license": "MIT", | |
| 121 | + "optional": true, | |
| 122 | + "os": [ | |
| 123 | + "darwin" | |
| 124 | + ], | |
| 125 | + "engines": { | |
| 126 | + "node": ">=18" | |
| 127 | + } | |
| 128 | + }, | |
| 129 | + "node_modules/@esbuild/freebsd-arm64": { | |
| 130 | + "version": "0.28.2", | |
| 131 | + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", | |
| 132 | + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", | |
| 133 | + "cpu": [ | |
| 134 | + "arm64" | |
| 135 | + ], | |
| 136 | + "dev": true, | |
| 137 | + "license": "MIT", | |
| 138 | + "optional": true, | |
| 139 | + "os": [ | |
| 140 | + "freebsd" | |
| 141 | + ], | |
| 142 | + "engines": { | |
| 143 | + "node": ">=18" | |
| 144 | + } | |
| 145 | + }, | |
| 146 | + "node_modules/@esbuild/freebsd-x64": { | |
| 147 | + "version": "0.28.2", | |
| 148 | + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", | |
| 149 | + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", | |
| 150 | + "cpu": [ | |
| 151 | + "x64" | |
| 152 | + ], | |
| 153 | + "dev": true, | |
| 154 | + "license": "MIT", | |
| 155 | + "optional": true, | |
| 156 | + "os": [ | |
| 157 | + "freebsd" | |
| 158 | + ], | |
| 159 | + "engines": { | |
| 160 | + "node": ">=18" | |
| 161 | + } | |
| 162 | + }, | |
| 163 | + "node_modules/@esbuild/linux-arm": { | |
| 164 | + "version": "0.28.2", | |
| 165 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", | |
| 166 | + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", | |
| 167 | + "cpu": [ | |
| 168 | + "arm" | |
| 169 | + ], | |
| 170 | + "dev": true, | |
| 171 | + "license": "MIT", | |
| 172 | + "optional": true, | |
| 173 | + "os": [ | |
| 174 | + "linux" | |
| 175 | + ], | |
| 176 | + "engines": { | |
| 177 | + "node": ">=18" | |
| 178 | + } | |
| 179 | + }, | |
| 180 | + "node_modules/@esbuild/linux-arm64": { | |
| 181 | + "version": "0.28.2", | |
| 182 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", | |
| 183 | + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", | |
| 184 | + "cpu": [ | |
| 185 | + "arm64" | |
| 186 | + ], | |
| 187 | + "dev": true, | |
| 188 | + "license": "MIT", | |
| 189 | + "optional": true, | |
| 190 | + "os": [ | |
| 191 | + "linux" | |
| 192 | + ], | |
| 193 | + "engines": { | |
| 194 | + "node": ">=18" | |
| 195 | + } | |
| 196 | + }, | |
| 197 | + "node_modules/@esbuild/linux-ia32": { | |
| 198 | + "version": "0.28.2", | |
| 199 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", | |
| 200 | + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", | |
| 201 | + "cpu": [ | |
| 202 | + "ia32" | |
| 203 | + ], | |
| 204 | + "dev": true, | |
| 205 | + "license": "MIT", | |
| 206 | + "optional": true, | |
| 207 | + "os": [ | |
| 208 | + "linux" | |
| 209 | + ], | |
| 210 | + "engines": { | |
| 211 | + "node": ">=18" | |
| 212 | + } | |
| 213 | + }, | |
| 214 | + "node_modules/@esbuild/linux-loong64": { | |
| 215 | + "version": "0.28.2", | |
| 216 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", | |
| 217 | + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", | |
| 218 | + "cpu": [ | |
| 219 | + "loong64" | |
| 220 | + ], | |
| 221 | + "dev": true, | |
| 222 | + "license": "MIT", | |
| 223 | + "optional": true, | |
| 224 | + "os": [ | |
| 225 | + "linux" | |
| 226 | + ], | |
| 227 | + "engines": { | |
| 228 | + "node": ">=18" | |
| 229 | + } | |
| 230 | + }, | |
| 231 | + "node_modules/@esbuild/linux-mips64el": { | |
| 232 | + "version": "0.28.2", | |
| 233 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", | |
| 234 | + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", | |
| 235 | + "cpu": [ | |
| 236 | + "mips64el" | |
| 237 | + ], | |
| 238 | + "dev": true, | |
| 239 | + "license": "MIT", | |
| 240 | + "optional": true, | |
| 241 | + "os": [ | |
| 242 | + "linux" | |
| 243 | + ], | |
| 244 | + "engines": { | |
| 245 | + "node": ">=18" | |
| 246 | + } | |
| 247 | + }, | |
| 248 | + "node_modules/@esbuild/linux-ppc64": { | |
| 249 | + "version": "0.28.2", | |
| 250 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", | |
| 251 | + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", | |
| 252 | + "cpu": [ | |
| 253 | + "ppc64" | |
| 254 | + ], | |
| 255 | + "dev": true, | |
| 256 | + "license": "MIT", | |
| 257 | + "optional": true, | |
| 258 | + "os": [ | |
| 259 | + "linux" | |
| 260 | + ], | |
| 261 | + "engines": { | |
| 262 | + "node": ">=18" | |
| 263 | + } | |
| 264 | + }, | |
| 265 | + "node_modules/@esbuild/linux-riscv64": { | |
| 266 | + "version": "0.28.2", | |
| 267 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", | |
| 268 | + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", | |
| 269 | + "cpu": [ | |
| 270 | + "riscv64" | |
| 271 | + ], | |
| 272 | + "dev": true, | |
| 273 | + "license": "MIT", | |
| 274 | + "optional": true, | |
| 275 | + "os": [ | |
| 276 | + "linux" | |
| 277 | + ], | |
| 278 | + "engines": { | |
| 279 | + "node": ">=18" | |
| 280 | + } | |
| 281 | + }, | |
| 282 | + "node_modules/@esbuild/linux-s390x": { | |
| 283 | + "version": "0.28.2", | |
| 284 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", | |
| 285 | + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", | |
| 286 | + "cpu": [ | |
| 287 | + "s390x" | |
| 288 | + ], | |
| 289 | + "dev": true, | |
| 290 | + "license": "MIT", | |
| 291 | + "optional": true, | |
| 292 | + "os": [ | |
| 293 | + "linux" | |
| 294 | + ], | |
| 295 | + "engines": { | |
| 296 | + "node": ">=18" | |
| 297 | + } | |
| 298 | + }, | |
| 299 | + "node_modules/@esbuild/linux-x64": { | |
| 300 | + "version": "0.28.2", | |
| 301 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", | |
| 302 | + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", | |
| 303 | + "cpu": [ | |
| 304 | + "x64" | |
| 305 | + ], | |
| 306 | + "dev": true, | |
| 307 | + "license": "MIT", | |
| 308 | + "optional": true, | |
| 309 | + "os": [ | |
| 310 | + "linux" | |
| 311 | + ], | |
| 312 | + "engines": { | |
| 313 | + "node": ">=18" | |
| 314 | + } | |
| 315 | + }, | |
| 316 | + "node_modules/@esbuild/netbsd-arm64": { | |
| 317 | + "version": "0.28.2", | |
| 318 | + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", | |
| 319 | + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", | |
| 320 | + "cpu": [ | |
| 321 | + "arm64" | |
| 322 | + ], | |
| 323 | + "dev": true, | |
| 324 | + "license": "MIT", | |
| 325 | + "optional": true, | |
| 326 | + "os": [ | |
| 327 | + "netbsd" | |
| 328 | + ], | |
| 329 | + "engines": { | |
| 330 | + "node": ">=18" | |
| 331 | + } | |
| 332 | + }, | |
| 333 | + "node_modules/@esbuild/netbsd-x64": { | |
| 334 | + "version": "0.28.2", | |
| 335 | + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", | |
| 336 | + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", | |
| 337 | + "cpu": [ | |
| 338 | + "x64" | |
| 339 | + ], | |
| 340 | + "dev": true, | |
| 341 | + "license": "MIT", | |
| 342 | + "optional": true, | |
| 343 | + "os": [ | |
| 344 | + "netbsd" | |
| 345 | + ], | |
| 346 | + "engines": { | |
| 347 | + "node": ">=18" | |
| 348 | + } | |
| 349 | + }, | |
| 350 | + "node_modules/@esbuild/openbsd-arm64": { | |
| 351 | + "version": "0.28.2", | |
| 352 | + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", | |
| 353 | + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", | |
| 354 | + "cpu": [ | |
| 355 | + "arm64" | |
| 356 | + ], | |
| 357 | + "dev": true, | |
| 358 | + "license": "MIT", | |
| 359 | + "optional": true, | |
| 360 | + "os": [ | |
| 361 | + "openbsd" | |
| 362 | + ], | |
| 363 | + "engines": { | |
| 364 | + "node": ">=18" | |
| 365 | + } | |
| 366 | + }, | |
| 367 | + "node_modules/@esbuild/openbsd-x64": { | |
| 368 | + "version": "0.28.2", | |
| 369 | + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", | |
| 370 | + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", | |
| 371 | + "cpu": [ | |
| 372 | + "x64" | |
| 373 | + ], | |
| 374 | + "dev": true, | |
| 375 | + "license": "MIT", | |
| 376 | + "optional": true, | |
| 377 | + "os": [ | |
| 378 | + "openbsd" | |
| 379 | + ], | |
| 380 | + "engines": { | |
| 381 | + "node": ">=18" | |
| 382 | + } | |
| 383 | + }, | |
| 384 | + "node_modules/@esbuild/openharmony-arm64": { | |
| 385 | + "version": "0.28.2", | |
| 386 | + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", | |
| 387 | + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", | |
| 388 | + "cpu": [ | |
| 389 | + "arm64" | |
| 390 | + ], | |
| 391 | + "dev": true, | |
| 392 | + "license": "MIT", | |
| 393 | + "optional": true, | |
| 394 | + "os": [ | |
| 395 | + "openharmony" | |
| 396 | + ], | |
| 397 | + "engines": { | |
| 398 | + "node": ">=18" | |
| 399 | + } | |
| 400 | + }, | |
| 401 | + "node_modules/@esbuild/sunos-x64": { | |
| 402 | + "version": "0.28.2", | |
| 403 | + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", | |
| 404 | + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", | |
| 405 | + "cpu": [ | |
| 406 | + "x64" | |
| 407 | + ], | |
| 408 | + "dev": true, | |
| 409 | + "license": "MIT", | |
| 410 | + "optional": true, | |
| 411 | + "os": [ | |
| 412 | + "sunos" | |
| 413 | + ], | |
| 414 | + "engines": { | |
| 415 | + "node": ">=18" | |
| 416 | + } | |
| 417 | + }, | |
| 418 | + "node_modules/@esbuild/win32-arm64": { | |
| 419 | + "version": "0.28.2", | |
| 420 | + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", | |
| 421 | + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", | |
| 422 | + "cpu": [ | |
| 423 | + "arm64" | |
| 424 | + ], | |
| 425 | + "dev": true, | |
| 426 | + "license": "MIT", | |
| 427 | + "optional": true, | |
| 428 | + "os": [ | |
| 429 | + "win32" | |
| 430 | + ], | |
| 431 | + "engines": { | |
| 432 | + "node": ">=18" | |
| 433 | + } | |
| 434 | + }, | |
| 435 | + "node_modules/@esbuild/win32-ia32": { | |
| 436 | + "version": "0.28.2", | |
| 437 | + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", | |
| 438 | + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", | |
| 439 | + "cpu": [ | |
| 440 | + "ia32" | |
| 441 | + ], | |
| 442 | + "dev": true, | |
| 443 | + "license": "MIT", | |
| 444 | + "optional": true, | |
| 445 | + "os": [ | |
| 446 | + "win32" | |
| 447 | + ], | |
| 448 | + "engines": { | |
| 449 | + "node": ">=18" | |
| 450 | + } | |
| 451 | + }, | |
| 452 | + "node_modules/@esbuild/win32-x64": { | |
| 453 | + "version": "0.28.2", | |
| 454 | + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", | |
| 455 | + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", | |
| 456 | + "cpu": [ | |
| 457 | + "x64" | |
| 458 | + ], | |
| 459 | + "dev": true, | |
| 460 | + "license": "MIT", | |
| 461 | + "optional": true, | |
| 462 | + "os": [ | |
| 463 | + "win32" | |
| 464 | + ], | |
| 465 | + "engines": { | |
| 466 | + "node": ">=18" | |
| 467 | + } | |
| 468 | + }, | |
| 469 | + "node_modules/@hono/node-server": { | |
| 470 | + "version": "2.1.1", | |
| 471 | + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", | |
| 472 | + "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", | |
| 473 | + "license": "MIT", | |
| 474 | + "engines": { | |
| 475 | + "node": ">=20" | |
| 476 | + }, | |
| 477 | + "peerDependencies": { | |
| 478 | + "hono": "^4" | |
| 479 | + } | |
| 480 | + }, | |
| 481 | + "node_modules/@jridgewell/sourcemap-codec": { | |
| 482 | + "version": "1.6.0", | |
| 483 | + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", | |
| 484 | + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", | |
| 485 | + "dev": true, | |
| 486 | + "license": "MIT" | |
| 487 | + }, | |
| 488 | + "node_modules/@modelcontextprotocol/sdk": { | |
| 489 | + "version": "1.30.0", | |
| 490 | + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", | |
| 491 | + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", | |
| 492 | + "license": "MIT", | |
| 493 | + "dependencies": { | |
| 494 | + "@hono/node-server": "^1.19.9 || ^2.0.5", | |
| 495 | + "ajv": "^8.17.1", | |
| 496 | + "ajv-formats": "^3.0.1", | |
| 497 | + "content-type": "^1.0.5", | |
| 498 | + "cors": "^2.8.5", | |
| 499 | + "cross-spawn": "^7.0.5", | |
| 500 | + "eventsource": "^3.0.2", | |
| 501 | + "eventsource-parser": "^3.0.0", | |
| 502 | + "express": "^5.2.1", | |
| 503 | + "express-rate-limit": "^8.2.1", | |
| 504 | + "hono": "^4.11.4", | |
| 505 | + "jose": "^6.1.3", | |
| 506 | + "json-schema-typed": "^8.0.2", | |
| 507 | + "pkce-challenge": "^5.0.0", | |
| 508 | + "raw-body": "^3.0.0", | |
| 509 | + "zod": "^3.25 || ^4.0", | |
| 510 | + "zod-to-json-schema": "^3.25.1" | |
| 511 | + }, | |
| 512 | + "engines": { | |
| 513 | + "node": ">=18" | |
| 514 | + }, | |
| 515 | + "peerDependencies": { | |
| 516 | + "@cfworker/json-schema": "^4.1.1", | |
| 517 | + "zod": "^3.25 || ^4.0" | |
| 518 | + }, | |
| 519 | + "peerDependenciesMeta": { | |
| 520 | + "@cfworker/json-schema": { | |
| 521 | + "optional": true | |
| 522 | + }, | |
| 523 | + "zod": { | |
| 524 | + "optional": false | |
| 525 | + } | |
| 526 | + } | |
| 527 | + }, | |
| 528 | + "node_modules/@napi-rs/lzma-linux-x64-gnu": { | |
| 529 | + "version": "1.5.1", | |
| 530 | + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", | |
| 531 | + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", | |
| 532 | + "cpu": [ | |
| 533 | + "x64" | |
| 534 | + ], | |
| 535 | + "dev": true, | |
| 536 | + "libc": [ | |
| 537 | + "glibc" | |
| 538 | + ], | |
| 539 | + "license": "MIT", | |
| 540 | + "optional": true, | |
| 541 | + "os": [ | |
| 542 | + "linux" | |
| 543 | + ], | |
| 544 | + "engines": { | |
| 545 | + "node": "^22.20 || ^24.12 || >=25" | |
| 546 | + } | |
| 547 | + }, | |
| 548 | + "node_modules/@rollup/rollup-android-arm-eabi": { | |
| 549 | + "version": "4.63.1", | |
| 550 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", | |
| 551 | + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", | |
| 552 | + "cpu": [ | |
| 553 | + "arm" | |
| 554 | + ], | |
| 555 | + "dev": true, | |
| 556 | + "license": "MIT", | |
| 557 | + "optional": true, | |
| 558 | + "os": [ | |
| 559 | + "android" | |
| 560 | + ] | |
| 561 | + }, | |
| 562 | + "node_modules/@rollup/rollup-android-arm64": { | |
| 563 | + "version": "4.63.1", | |
| 564 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", | |
| 565 | + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", | |
| 566 | + "cpu": [ | |
| 567 | + "arm64" | |
| 568 | + ], | |
| 569 | + "dev": true, | |
| 570 | + "license": "MIT", | |
| 571 | + "optional": true, | |
| 572 | + "os": [ | |
| 573 | + "android" | |
| 574 | + ] | |
| 575 | + }, | |
| 576 | + "node_modules/@rollup/rollup-darwin-arm64": { | |
| 577 | + "version": "4.63.1", | |
| 578 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", | |
| 579 | + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", | |
| 580 | + "cpu": [ | |
| 581 | + "arm64" | |
| 582 | + ], | |
| 583 | + "dev": true, | |
| 584 | + "license": "MIT", | |
| 585 | + "optional": true, | |
| 586 | + "os": [ | |
| 587 | + "darwin" | |
| 588 | + ] | |
| 589 | + }, | |
| 590 | + "node_modules/@rollup/rollup-darwin-x64": { | |
| 591 | + "version": "4.63.1", | |
| 592 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", | |
| 593 | + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", | |
| 594 | + "cpu": [ | |
| 595 | + "x64" | |
| 596 | + ], | |
| 597 | + "dev": true, | |
| 598 | + "license": "MIT", | |
| 599 | + "optional": true, | |
| 600 | + "os": [ | |
| 601 | + "darwin" | |
| 602 | + ] | |
| 603 | + }, | |
| 604 | + "node_modules/@rollup/rollup-freebsd-arm64": { | |
| 605 | + "version": "4.63.1", | |
| 606 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", | |
| 607 | + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", | |
| 608 | + "cpu": [ | |
| 609 | + "arm64" | |
| 610 | + ], | |
| 611 | + "dev": true, | |
| 612 | + "license": "MIT", | |
| 613 | + "optional": true, | |
| 614 | + "os": [ | |
| 615 | + "freebsd" | |
| 616 | + ] | |
| 617 | + }, | |
| 618 | + "node_modules/@rollup/rollup-freebsd-x64": { | |
| 619 | + "version": "4.63.1", | |
| 620 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", | |
| 621 | + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", | |
| 622 | + "cpu": [ | |
| 623 | + "x64" | |
| 624 | + ], | |
| 625 | + "dev": true, | |
| 626 | + "license": "MIT", | |
| 627 | + "optional": true, | |
| 628 | + "os": [ | |
| 629 | + "freebsd" | |
| 630 | + ] | |
| 631 | + }, | |
| 632 | + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { | |
| 633 | + "version": "4.63.1", | |
| 634 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", | |
| 635 | + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", | |
| 636 | + "cpu": [ | |
| 637 | + "arm" | |
| 638 | + ], | |
| 639 | + "dev": true, | |
| 640 | + "libc": [ | |
| 641 | + "glibc" | |
| 642 | + ], | |
| 643 | + "license": "MIT", | |
| 644 | + "optional": true, | |
| 645 | + "os": [ | |
| 646 | + "linux" | |
| 647 | + ] | |
| 648 | + }, | |
| 649 | + "node_modules/@rollup/rollup-linux-arm-musleabihf": { | |
| 650 | + "version": "4.63.1", | |
| 651 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", | |
| 652 | + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", | |
| 653 | + "cpu": [ | |
| 654 | + "arm" | |
| 655 | + ], | |
| 656 | + "dev": true, | |
| 657 | + "libc": [ | |
| 658 | + "musl" | |
| 659 | + ], | |
| 660 | + "license": "MIT", | |
| 661 | + "optional": true, | |
| 662 | + "os": [ | |
| 663 | + "linux" | |
| 664 | + ] | |
| 665 | + }, | |
| 666 | + "node_modules/@rollup/rollup-linux-arm64-gnu": { | |
| 667 | + "version": "4.63.1", | |
| 668 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", | |
| 669 | + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", | |
| 670 | + "cpu": [ | |
| 671 | + "arm64" | |
| 672 | + ], | |
| 673 | + "dev": true, | |
| 674 | + "libc": [ | |
| 675 | + "glibc" | |
| 676 | + ], | |
| 677 | + "license": "MIT", | |
| 678 | + "optional": true, | |
| 679 | + "os": [ | |
| 680 | + "linux" | |
| 681 | + ] | |
| 682 | + }, | |
| 683 | + "node_modules/@rollup/rollup-linux-arm64-musl": { | |
| 684 | + "version": "4.63.1", | |
| 685 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", | |
| 686 | + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", | |
| 687 | + "cpu": [ | |
| 688 | + "arm64" | |
| 689 | + ], | |
| 690 | + "dev": true, | |
| 691 | + "libc": [ | |
| 692 | + "musl" | |
| 693 | + ], | |
| 694 | + "license": "MIT", | |
| 695 | + "optional": true, | |
| 696 | + "os": [ | |
| 697 | + "linux" | |
| 698 | + ] | |
| 699 | + }, | |
| 700 | + "node_modules/@rollup/rollup-linux-loong64-gnu": { | |
| 701 | + "version": "4.63.1", | |
| 702 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", | |
| 703 | + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", | |
| 704 | + "cpu": [ | |
| 705 | + "loong64" | |
| 706 | + ], | |
| 707 | + "dev": true, | |
| 708 | + "libc": [ | |
| 709 | + "glibc" | |
| 710 | + ], | |
| 711 | + "license": "MIT", | |
| 712 | + "optional": true, | |
| 713 | + "os": [ | |
| 714 | + "linux" | |
| 715 | + ] | |
| 716 | + }, | |
| 717 | + "node_modules/@rollup/rollup-linux-loong64-musl": { | |
| 718 | + "version": "4.63.1", | |
| 719 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", | |
| 720 | + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", | |
| 721 | + "cpu": [ | |
| 722 | + "loong64" | |
| 723 | + ], | |
| 724 | + "dev": true, | |
| 725 | + "libc": [ | |
| 726 | + "musl" | |
| 727 | + ], | |
| 728 | + "license": "MIT", | |
| 729 | + "optional": true, | |
| 730 | + "os": [ | |
| 731 | + "linux" | |
| 732 | + ] | |
| 733 | + }, | |
| 734 | + "node_modules/@rollup/rollup-linux-ppc64-gnu": { | |
| 735 | + "version": "4.63.1", | |
| 736 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", | |
| 737 | + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", | |
| 738 | + "cpu": [ | |
| 739 | + "ppc64" | |
| 740 | + ], | |
| 741 | + "dev": true, | |
| 742 | + "libc": [ | |
| 743 | + "glibc" | |
| 744 | + ], | |
| 745 | + "license": "MIT", | |
| 746 | + "optional": true, | |
| 747 | + "os": [ | |
| 748 | + "linux" | |
| 749 | + ] | |
| 750 | + }, | |
| 751 | + "node_modules/@rollup/rollup-linux-ppc64-musl": { | |
| 752 | + "version": "4.63.1", | |
| 753 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", | |
| 754 | + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", | |
| 755 | + "cpu": [ | |
| 756 | + "ppc64" | |
| 757 | + ], | |
| 758 | + "dev": true, | |
| 759 | + "libc": [ | |
| 760 | + "musl" | |
| 761 | + ], | |
| 762 | + "license": "MIT", | |
| 763 | + "optional": true, | |
| 764 | + "os": [ | |
| 765 | + "linux" | |
| 766 | + ] | |
| 767 | + }, | |
| 768 | + "node_modules/@rollup/rollup-linux-riscv64-gnu": { | |
| 769 | + "version": "4.63.1", | |
| 770 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", | |
| 771 | + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", | |
| 772 | + "cpu": [ | |
| 773 | + "riscv64" | |
| 774 | + ], | |
| 775 | + "dev": true, | |
| 776 | + "libc": [ | |
| 777 | + "glibc" | |
| 778 | + ], | |
| 779 | + "license": "MIT", | |
| 780 | + "optional": true, | |
| 781 | + "os": [ | |
| 782 | + "linux" | |
| 783 | + ] | |
| 784 | + }, | |
| 785 | + "node_modules/@rollup/rollup-linux-riscv64-musl": { | |
| 786 | + "version": "4.63.1", | |
| 787 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", | |
| 788 | + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", | |
| 789 | + "cpu": [ | |
| 790 | + "riscv64" | |
| 791 | + ], | |
| 792 | + "dev": true, | |
| 793 | + "libc": [ | |
| 794 | + "musl" | |
| 795 | + ], | |
| 796 | + "license": "MIT", | |
| 797 | + "optional": true, | |
| 798 | + "os": [ | |
| 799 | + "linux" | |
| 800 | + ] | |
| 801 | + }, | |
| 802 | + "node_modules/@rollup/rollup-linux-s390x-gnu": { | |
| 803 | + "version": "4.63.1", | |
| 804 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", | |
| 805 | + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", | |
| 806 | + "cpu": [ | |
| 807 | + "s390x" | |
| 808 | + ], | |
| 809 | + "dev": true, | |
| 810 | + "libc": [ | |
| 811 | + "glibc" | |
| 812 | + ], | |
| 813 | + "license": "MIT", | |
| 814 | + "optional": true, | |
| 815 | + "os": [ | |
| 816 | + "linux" | |
| 817 | + ] | |
| 818 | + }, | |
| 819 | + "node_modules/@rollup/rollup-linux-x64-gnu": { | |
| 820 | + "version": "4.63.1", | |
| 821 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", | |
| 822 | + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", | |
| 823 | + "cpu": [ | |
| 824 | + "x64" | |
| 825 | + ], | |
| 826 | + "dev": true, | |
| 827 | + "libc": [ | |
| 828 | + "glibc" | |
| 829 | + ], | |
| 830 | + "license": "MIT", | |
| 831 | + "optional": true, | |
| 832 | + "os": [ | |
| 833 | + "linux" | |
| 834 | + ] | |
| 835 | + }, | |
| 836 | + "node_modules/@rollup/rollup-linux-x64-musl": { | |
| 837 | + "version": "4.63.1", | |
| 838 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", | |
| 839 | + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", | |
| 840 | + "cpu": [ | |
| 841 | + "x64" | |
| 842 | + ], | |
| 843 | + "dev": true, | |
| 844 | + "libc": [ | |
| 845 | + "musl" | |
| 846 | + ], | |
| 847 | + "license": "MIT", | |
| 848 | + "optional": true, | |
| 849 | + "os": [ | |
| 850 | + "linux" | |
| 851 | + ] | |
| 852 | + }, | |
| 853 | + "node_modules/@rollup/rollup-openbsd-x64": { | |
| 854 | + "version": "4.63.1", | |
| 855 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", | |
| 856 | + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", | |
| 857 | + "cpu": [ | |
| 858 | + "x64" | |
| 859 | + ], | |
| 860 | + "dev": true, | |
| 861 | + "license": "MIT", | |
| 862 | + "optional": true, | |
| 863 | + "os": [ | |
| 864 | + "openbsd" | |
| 865 | + ] | |
| 866 | + }, | |
| 867 | + "node_modules/@rollup/rollup-openharmony-arm64": { | |
| 868 | + "version": "4.63.1", | |
| 869 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", | |
| 870 | + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", | |
| 871 | + "cpu": [ | |
| 872 | + "arm64" | |
| 873 | + ], | |
| 874 | + "dev": true, | |
| 875 | + "license": "MIT", | |
| 876 | + "optional": true, | |
| 877 | + "os": [ | |
| 878 | + "openharmony" | |
| 879 | + ] | |
| 880 | + }, | |
| 881 | + "node_modules/@rollup/rollup-win32-arm64-msvc": { | |
| 882 | + "version": "4.63.1", | |
| 883 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", | |
| 884 | + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", | |
| 885 | + "cpu": [ | |
| 886 | + "arm64" | |
| 887 | + ], | |
| 888 | + "dev": true, | |
| 889 | + "license": "MIT", | |
| 890 | + "optional": true, | |
| 891 | + "os": [ | |
| 892 | + "win32" | |
| 893 | + ] | |
| 894 | + }, | |
| 895 | + "node_modules/@rollup/rollup-win32-ia32-msvc": { | |
| 896 | + "version": "4.63.1", | |
| 897 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", | |
| 898 | + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", | |
| 899 | + "cpu": [ | |
| 900 | + "ia32" | |
| 901 | + ], | |
| 902 | + "dev": true, | |
| 903 | + "license": "MIT", | |
| 904 | + "optional": true, | |
| 905 | + "os": [ | |
| 906 | + "win32" | |
| 907 | + ] | |
| 908 | + }, | |
| 909 | + "node_modules/@rollup/rollup-win32-x64-gnu": { | |
| 910 | + "version": "4.63.1", | |
| 911 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", | |
| 912 | + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", | |
| 913 | + "cpu": [ | |
| 914 | + "x64" | |
| 915 | + ], | |
| 916 | + "dev": true, | |
| 917 | + "license": "MIT", | |
| 918 | + "optional": true, | |
| 919 | + "os": [ | |
| 920 | + "win32" | |
| 921 | + ] | |
| 922 | + }, | |
| 923 | + "node_modules/@rollup/rollup-win32-x64-msvc": { | |
| 924 | + "version": "4.63.1", | |
| 925 | + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", | |
| 926 | + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", | |
| 927 | + "cpu": [ | |
| 928 | + "x64" | |
| 929 | + ], | |
| 930 | + "dev": true, | |
| 931 | + "license": "MIT", | |
| 932 | + "optional": true, | |
| 933 | + "os": [ | |
| 934 | + "win32" | |
| 935 | + ] | |
| 936 | + }, | |
| 937 | + "node_modules/@types/chai": { | |
| 938 | + "version": "5.2.3", | |
| 939 | + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", | |
| 940 | + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", | |
| 941 | + "dev": true, | |
| 942 | + "license": "MIT", | |
| 943 | + "dependencies": { | |
| 944 | + "@types/deep-eql": "*", | |
| 945 | + "assertion-error": "^2.0.1" | |
| 946 | + } | |
| 947 | + }, | |
| 948 | + "node_modules/@types/deep-eql": { | |
| 949 | + "version": "4.0.2", | |
| 950 | + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", | |
| 951 | + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", | |
| 952 | + "dev": true, | |
| 953 | + "license": "MIT" | |
| 954 | + }, | |
| 955 | + "node_modules/@types/estree": { | |
| 956 | + "version": "1.0.9", | |
| 957 | + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", | |
| 958 | + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", | |
| 959 | + "dev": true, | |
| 960 | + "license": "MIT" | |
| 961 | + }, | |
| 962 | + "node_modules/@types/node": { | |
| 963 | + "version": "22.20.1", | |
| 964 | + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", | |
| 965 | + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", | |
| 966 | + "dev": true, | |
| 967 | + "license": "MIT", | |
| 968 | + "dependencies": { | |
| 969 | + "undici-types": "~6.21.0" | |
| 970 | + } | |
| 971 | + }, | |
| 972 | + "node_modules/@vitest/expect": { | |
| 973 | + "version": "3.2.7", | |
| 974 | + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", | |
| 975 | + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", | |
| 976 | + "dev": true, | |
| 977 | + "license": "MIT", | |
| 978 | + "dependencies": { | |
| 979 | + "@types/chai": "^5.2.2", | |
| 980 | + "@vitest/spy": "3.2.7", | |
| 981 | + "@vitest/utils": "3.2.7", | |
| 982 | + "chai": "^5.2.0", | |
| 983 | + "tinyrainbow": "^2.0.0" | |
| 984 | + }, | |
| 985 | + "funding": { | |
| 986 | + "url": "https://opencollective.com/vitest" | |
| 987 | + } | |
| 988 | + }, | |
| 989 | + "node_modules/@vitest/mocker": { | |
| 990 | + "version": "3.2.7", | |
| 991 | + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", | |
| 992 | + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", | |
| 993 | + "dev": true, | |
| 994 | + "license": "MIT", | |
| 995 | + "dependencies": { | |
| 996 | + "@vitest/spy": "3.2.7", | |
| 997 | + "estree-walker": "^3.0.3", | |
| 998 | + "magic-string": "^0.30.17" | |
| 999 | + }, | |
| 1000 | + "funding": { | |
| 1001 | + "url": "https://opencollective.com/vitest" | |
| 1002 | + }, | |
| 1003 | + "peerDependencies": { | |
| 1004 | + "msw": "^2.4.9", | |
| 1005 | + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" | |
| 1006 | + }, | |
| 1007 | + "peerDependenciesMeta": { | |
| 1008 | + "msw": { | |
| 1009 | + "optional": true | |
| 1010 | + }, | |
| 1011 | + "vite": { | |
| 1012 | + "optional": true | |
| 1013 | + } | |
| 1014 | + } | |
| 1015 | + }, | |
| 1016 | + "node_modules/@vitest/pretty-format": { | |
| 1017 | + "version": "3.2.7", | |
| 1018 | + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", | |
| 1019 | + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", | |
| 1020 | + "dev": true, | |
| 1021 | + "license": "MIT", | |
| 1022 | + "dependencies": { | |
| 1023 | + "tinyrainbow": "^2.0.0" | |
| 1024 | + }, | |
| 1025 | + "funding": { | |
| 1026 | + "url": "https://opencollective.com/vitest" | |
| 1027 | + } | |
| 1028 | + }, | |
| 1029 | + "node_modules/@vitest/runner": { | |
| 1030 | + "version": "3.2.7", | |
| 1031 | + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", | |
| 1032 | + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", | |
| 1033 | + "dev": true, | |
| 1034 | + "license": "MIT", | |
| 1035 | + "dependencies": { | |
| 1036 | + "@vitest/utils": "3.2.7", | |
| 1037 | + "pathe": "^2.0.3", | |
| 1038 | + "strip-literal": "^3.0.0" | |
| 1039 | + }, | |
| 1040 | + "funding": { | |
| 1041 | + "url": "https://opencollective.com/vitest" | |
| 1042 | + } | |
| 1043 | + }, | |
| 1044 | + "node_modules/@vitest/snapshot": { | |
| 1045 | + "version": "3.2.7", | |
| 1046 | + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", | |
| 1047 | + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", | |
| 1048 | + "dev": true, | |
| 1049 | + "license": "MIT", | |
| 1050 | + "dependencies": { | |
| 1051 | + "@vitest/pretty-format": "3.2.7", | |
| 1052 | + "magic-string": "^0.30.17", | |
| 1053 | + "pathe": "^2.0.3" | |
| 1054 | + }, | |
| 1055 | + "funding": { | |
| 1056 | + "url": "https://opencollective.com/vitest" | |
| 1057 | + } | |
| 1058 | + }, | |
| 1059 | + "node_modules/@vitest/spy": { | |
| 1060 | + "version": "3.2.7", | |
| 1061 | + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", | |
| 1062 | + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", | |
| 1063 | + "dev": true, | |
| 1064 | + "license": "MIT", | |
| 1065 | + "dependencies": { | |
| 1066 | + "tinyspy": "^4.0.3" | |
| 1067 | + }, | |
| 1068 | + "funding": { | |
| 1069 | + "url": "https://opencollective.com/vitest" | |
| 1070 | + } | |
| 1071 | + }, | |
| 1072 | + "node_modules/@vitest/utils": { | |
| 1073 | + "version": "3.2.7", | |
| 1074 | + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", | |
| 1075 | + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", | |
| 1076 | + "dev": true, | |
| 1077 | + "license": "MIT", | |
| 1078 | + "dependencies": { | |
| 1079 | + "@vitest/pretty-format": "3.2.7", | |
| 1080 | + "loupe": "^3.1.4", | |
| 1081 | + "tinyrainbow": "^2.0.0" | |
| 1082 | + }, | |
| 1083 | + "funding": { | |
| 1084 | + "url": "https://opencollective.com/vitest" | |
| 1085 | + } | |
| 1086 | + }, | |
| 1087 | + "node_modules/accepts": { | |
| 1088 | + "version": "2.0.0", | |
| 1089 | + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", | |
| 1090 | + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", | |
| 1091 | + "license": "MIT", | |
| 1092 | + "dependencies": { | |
| 1093 | + "mime-types": "^3.0.0", | |
| 1094 | + "negotiator": "^1.0.0" | |
| 1095 | + }, | |
| 1096 | + "engines": { | |
| 1097 | + "node": ">= 0.6" | |
| 1098 | + } | |
| 1099 | + }, | |
| 1100 | + "node_modules/ajv": { | |
| 1101 | + "version": "8.20.0", | |
| 1102 | + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", | |
| 1103 | + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", | |
| 1104 | + "license": "MIT", | |
| 1105 | + "dependencies": { | |
| 1106 | + "fast-deep-equal": "^3.1.3", | |
| 1107 | + "fast-uri": "^3.0.1", | |
| 1108 | + "json-schema-traverse": "^1.0.0", | |
| 1109 | + "require-from-string": "^2.0.2" | |
| 1110 | + }, | |
| 1111 | + "funding": { | |
| 1112 | + "type": "github", | |
| 1113 | + "url": "https://github.com/sponsors/epoberezkin" | |
| 1114 | + } | |
| 1115 | + }, | |
| 1116 | + "node_modules/ajv-formats": { | |
| 1117 | + "version": "3.0.1", | |
| 1118 | + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", | |
| 1119 | + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", | |
| 1120 | + "license": "MIT", | |
| 1121 | + "dependencies": { | |
| 1122 | + "ajv": "^8.0.0" | |
| 1123 | + }, | |
| 1124 | + "peerDependencies": { | |
| 1125 | + "ajv": "^8.0.0" | |
| 1126 | + }, | |
| 1127 | + "peerDependenciesMeta": { | |
| 1128 | + "ajv": { | |
| 1129 | + "optional": true | |
| 1130 | + } | |
| 1131 | + } | |
| 1132 | + }, | |
| 1133 | + "node_modules/assertion-error": { | |
| 1134 | + "version": "2.0.1", | |
| 1135 | + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", | |
| 1136 | + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", | |
| 1137 | + "dev": true, | |
| 1138 | + "license": "MIT", | |
| 1139 | + "engines": { | |
| 1140 | + "node": ">=12" | |
| 1141 | + } | |
| 1142 | + }, | |
| 1143 | + "node_modules/body-parser": { | |
| 1144 | + "version": "2.3.0", | |
| 1145 | + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", | |
| 1146 | + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", | |
| 1147 | + "license": "MIT", | |
| 1148 | + "dependencies": { | |
| 1149 | + "bytes": "^3.1.2", | |
| 1150 | + "content-type": "^2.0.0", | |
| 1151 | + "debug": "^4.4.3", | |
| 1152 | + "http-errors": "^2.0.1", | |
| 1153 | + "iconv-lite": "^0.7.2", | |
| 1154 | + "on-finished": "^2.4.1", | |
| 1155 | + "qs": "^6.15.2", | |
| 1156 | + "raw-body": "^3.0.2", | |
| 1157 | + "type-is": "^2.1.0" | |
| 1158 | + }, | |
| 1159 | + "engines": { | |
| 1160 | + "node": ">=18" | |
| 1161 | + }, | |
| 1162 | + "funding": { | |
| 1163 | + "type": "opencollective", | |
| 1164 | + "url": "https://opencollective.com/express" | |
| 1165 | + } | |
| 1166 | + }, | |
| 1167 | + "node_modules/body-parser/node_modules/content-type": { | |
| 1168 | + "version": "2.1.0", | |
| 1169 | + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", | |
| 1170 | + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", | |
| 1171 | + "license": "MIT", | |
| 1172 | + "engines": { | |
| 1173 | + "node": ">=18" | |
| 1174 | + }, | |
| 1175 | + "funding": { | |
| 1176 | + "type": "opencollective", | |
| 1177 | + "url": "https://opencollective.com/express" | |
| 1178 | + } | |
| 1179 | + }, | |
| 1180 | + "node_modules/bytes": { | |
| 1181 | + "version": "3.1.2", | |
| 1182 | + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", | |
| 1183 | + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", | |
| 1184 | + "license": "MIT", | |
| 1185 | + "engines": { | |
| 1186 | + "node": ">= 0.8" | |
| 1187 | + } | |
| 1188 | + }, | |
| 1189 | + "node_modules/cac": { | |
| 1190 | + "version": "6.7.14", | |
| 1191 | + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", | |
| 1192 | + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", | |
| 1193 | + "dev": true, | |
| 1194 | + "license": "MIT", | |
| 1195 | + "engines": { | |
| 1196 | + "node": ">=8" | |
| 1197 | + } | |
| 1198 | + }, | |
| 1199 | + "node_modules/call-bind-apply-helpers": { | |
| 1200 | + "version": "1.0.2", | |
| 1201 | + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", | |
| 1202 | + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", | |
| 1203 | + "license": "MIT", | |
| 1204 | + "dependencies": { | |
| 1205 | + "es-errors": "^1.3.0", | |
| 1206 | + "function-bind": "^1.1.2" | |
| 1207 | + }, | |
| 1208 | + "engines": { | |
| 1209 | + "node": ">= 0.4" | |
| 1210 | + } | |
| 1211 | + }, | |
| 1212 | + "node_modules/call-bound": { | |
| 1213 | + "version": "1.0.4", | |
| 1214 | + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", | |
| 1215 | + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", | |
| 1216 | + "license": "MIT", | |
| 1217 | + "dependencies": { | |
| 1218 | + "call-bind-apply-helpers": "^1.0.2", | |
| 1219 | + "get-intrinsic": "^1.3.0" | |
| 1220 | + }, | |
| 1221 | + "engines": { | |
| 1222 | + "node": ">= 0.4" | |
| 1223 | + }, | |
| 1224 | + "funding": { | |
| 1225 | + "url": "https://github.com/sponsors/ljharb" | |
| 1226 | + } | |
| 1227 | + }, | |
| 1228 | + "node_modules/chai": { | |
| 1229 | + "version": "5.3.3", | |
| 1230 | + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", | |
| 1231 | + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", | |
| 1232 | + "dev": true, | |
| 1233 | + "license": "MIT", | |
| 1234 | + "dependencies": { | |
| 1235 | + "assertion-error": "^2.0.1", | |
| 1236 | + "check-error": "^2.1.1", | |
| 1237 | + "deep-eql": "^5.0.1", | |
| 1238 | + "loupe": "^3.1.0", | |
| 1239 | + "pathval": "^2.0.0" | |
| 1240 | + }, | |
| 1241 | + "engines": { | |
| 1242 | + "node": ">=18" | |
| 1243 | + } | |
| 1244 | + }, | |
| 1245 | + "node_modules/check-error": { | |
| 1246 | + "version": "2.1.3", | |
| 1247 | + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", | |
| 1248 | + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", | |
| 1249 | + "dev": true, | |
| 1250 | + "license": "MIT", | |
| 1251 | + "engines": { | |
| 1252 | + "node": ">= 16" | |
| 1253 | + } | |
| 1254 | + }, | |
| 1255 | + "node_modules/content-disposition": { | |
| 1256 | + "version": "1.1.0", | |
| 1257 | + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", | |
| 1258 | + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", | |
| 1259 | + "license": "MIT", | |
| 1260 | + "engines": { | |
| 1261 | + "node": ">=18" | |
| 1262 | + }, | |
| 1263 | + "funding": { | |
| 1264 | + "type": "opencollective", | |
| 1265 | + "url": "https://opencollective.com/express" | |
| 1266 | + } | |
| 1267 | + }, | |
| 1268 | + "node_modules/content-type": { | |
| 1269 | + "version": "1.0.5", | |
| 1270 | + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", | |
| 1271 | + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", | |
| 1272 | + "license": "MIT", | |
| 1273 | + "engines": { | |
| 1274 | + "node": ">= 0.6" | |
| 1275 | + } | |
| 1276 | + }, | |
| 1277 | + "node_modules/cookie": { | |
| 1278 | + "version": "0.7.2", | |
| 1279 | + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", | |
| 1280 | + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", | |
| 1281 | + "license": "MIT", | |
| 1282 | + "engines": { | |
| 1283 | + "node": ">= 0.6" | |
| 1284 | + } | |
| 1285 | + }, | |
| 1286 | + "node_modules/cookie-signature": { | |
| 1287 | + "version": "1.2.2", | |
| 1288 | + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", | |
| 1289 | + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", | |
| 1290 | + "license": "MIT", | |
| 1291 | + "engines": { | |
| 1292 | + "node": ">=6.6.0" | |
| 1293 | + } | |
| 1294 | + }, | |
| 1295 | + "node_modules/cors": { | |
| 1296 | + "version": "2.8.6", | |
| 1297 | + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", | |
| 1298 | + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", | |
| 1299 | + "license": "MIT", | |
| 1300 | + "dependencies": { | |
| 1301 | + "object-assign": "^4", | |
| 1302 | + "vary": "^1" | |
| 1303 | + }, | |
| 1304 | + "engines": { | |
| 1305 | + "node": ">= 0.10" | |
| 1306 | + }, | |
| 1307 | + "funding": { | |
| 1308 | + "type": "opencollective", | |
| 1309 | + "url": "https://opencollective.com/express" | |
| 1310 | + } | |
| 1311 | + }, | |
| 1312 | + "node_modules/cross-spawn": { | |
| 1313 | + "version": "7.0.6", | |
| 1314 | + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", | |
| 1315 | + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", | |
| 1316 | + "license": "MIT", | |
| 1317 | + "dependencies": { | |
| 1318 | + "path-key": "^3.1.0", | |
| 1319 | + "shebang-command": "^2.0.0", | |
| 1320 | + "which": "^2.0.1" | |
| 1321 | + }, | |
| 1322 | + "engines": { | |
| 1323 | + "node": ">= 8" | |
| 1324 | + } | |
| 1325 | + }, | |
| 1326 | + "node_modules/debug": { | |
| 1327 | + "version": "4.4.3", | |
| 1328 | + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", | |
| 1329 | + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", | |
| 1330 | + "license": "MIT", | |
| 1331 | + "dependencies": { | |
| 1332 | + "ms": "^2.1.3" | |
| 1333 | + }, | |
| 1334 | + "engines": { | |
| 1335 | + "node": ">=6.0" | |
| 1336 | + }, | |
| 1337 | + "peerDependenciesMeta": { | |
| 1338 | + "supports-color": { | |
| 1339 | + "optional": true | |
| 1340 | + } | |
| 1341 | + } | |
| 1342 | + }, | |
| 1343 | + "node_modules/deep-eql": { | |
| 1344 | + "version": "5.0.2", | |
| 1345 | + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", | |
| 1346 | + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", | |
| 1347 | + "dev": true, | |
| 1348 | + "license": "MIT", | |
| 1349 | + "engines": { | |
| 1350 | + "node": ">=6" | |
| 1351 | + } | |
| 1352 | + }, | |
| 1353 | + "node_modules/depd": { | |
| 1354 | + "version": "2.0.0", | |
| 1355 | + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", | |
| 1356 | + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", | |
| 1357 | + "license": "MIT", | |
| 1358 | + "engines": { | |
| 1359 | + "node": ">= 0.8" | |
| 1360 | + } | |
| 1361 | + }, | |
| 1362 | + "node_modules/dunder-proto": { | |
| 1363 | + "version": "1.0.1", | |
| 1364 | + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", | |
| 1365 | + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", | |
| 1366 | + "license": "MIT", | |
| 1367 | + "dependencies": { | |
| 1368 | + "call-bind-apply-helpers": "^1.0.1", | |
| 1369 | + "es-errors": "^1.3.0", | |
| 1370 | + "gopd": "^1.2.0" | |
| 1371 | + }, | |
| 1372 | + "engines": { | |
| 1373 | + "node": ">= 0.4" | |
| 1374 | + } | |
| 1375 | + }, | |
| 1376 | + "node_modules/ee-first": { | |
| 1377 | + "version": "1.1.1", | |
| 1378 | + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | |
| 1379 | + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", | |
| 1380 | + "license": "MIT" | |
| 1381 | + }, | |
| 1382 | + "node_modules/encodeurl": { | |
| 1383 | + "version": "2.0.0", | |
| 1384 | + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", | |
| 1385 | + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", | |
| 1386 | + "license": "MIT", | |
| 1387 | + "engines": { | |
| 1388 | + "node": ">= 0.8" | |
| 1389 | + } | |
| 1390 | + }, | |
| 1391 | + "node_modules/es-define-property": { | |
| 1392 | + "version": "1.0.1", | |
| 1393 | + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", | |
| 1394 | + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", | |
| 1395 | + "license": "MIT", | |
| 1396 | + "engines": { | |
| 1397 | + "node": ">= 0.4" | |
| 1398 | + } | |
| 1399 | + }, | |
| 1400 | + "node_modules/es-errors": { | |
| 1401 | + "version": "1.3.0", | |
| 1402 | + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", | |
| 1403 | + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", | |
| 1404 | + "license": "MIT", | |
| 1405 | + "engines": { | |
| 1406 | + "node": ">= 0.4" | |
| 1407 | + } | |
| 1408 | + }, | |
| 1409 | + "node_modules/es-module-lexer": { | |
| 1410 | + "version": "1.7.0", | |
| 1411 | + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", | |
| 1412 | + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", | |
| 1413 | + "dev": true, | |
| 1414 | + "license": "MIT" | |
| 1415 | + }, | |
| 1416 | + "node_modules/es-object-atoms": { | |
| 1417 | + "version": "1.1.2", | |
| 1418 | + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", | |
| 1419 | + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", | |
| 1420 | + "license": "MIT", | |
| 1421 | + "dependencies": { | |
| 1422 | + "es-errors": "^1.3.0" | |
| 1423 | + }, | |
| 1424 | + "engines": { | |
| 1425 | + "node": ">= 0.4" | |
| 1426 | + } | |
| 1427 | + }, | |
| 1428 | + "node_modules/esbuild": { | |
| 1429 | + "version": "0.28.2", | |
| 1430 | + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", | |
| 1431 | + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", | |
| 1432 | + "dev": true, | |
| 1433 | + "hasInstallScript": true, | |
| 1434 | + "license": "MIT", | |
| 1435 | + "bin": { | |
| 1436 | + "esbuild": "bin/esbuild" | |
| 1437 | + }, | |
| 1438 | + "engines": { | |
| 1439 | + "node": ">=18" | |
| 1440 | + }, | |
| 1441 | + "optionalDependencies": { | |
| 1442 | + "@esbuild/aix-ppc64": "0.28.2", | |
| 1443 | + "@esbuild/android-arm": "0.28.2", | |
| 1444 | + "@esbuild/android-arm64": "0.28.2", | |
| 1445 | + "@esbuild/android-x64": "0.28.2", | |
| 1446 | + "@esbuild/darwin-arm64": "0.28.2", | |
| 1447 | + "@esbuild/darwin-x64": "0.28.2", | |
| 1448 | + "@esbuild/freebsd-arm64": "0.28.2", | |
| 1449 | + "@esbuild/freebsd-x64": "0.28.2", | |
| 1450 | + "@esbuild/linux-arm": "0.28.2", | |
| 1451 | + "@esbuild/linux-arm64": "0.28.2", | |
| 1452 | + "@esbuild/linux-ia32": "0.28.2", | |
| 1453 | + "@esbuild/linux-loong64": "0.28.2", | |
| 1454 | + "@esbuild/linux-mips64el": "0.28.2", | |
| 1455 | + "@esbuild/linux-ppc64": "0.28.2", | |
| 1456 | + "@esbuild/linux-riscv64": "0.28.2", | |
| 1457 | + "@esbuild/linux-s390x": "0.28.2", | |
| 1458 | + "@esbuild/linux-x64": "0.28.2", | |
| 1459 | + "@esbuild/netbsd-arm64": "0.28.2", | |
| 1460 | + "@esbuild/netbsd-x64": "0.28.2", | |
| 1461 | + "@esbuild/openbsd-arm64": "0.28.2", | |
| 1462 | + "@esbuild/openbsd-x64": "0.28.2", | |
| 1463 | + "@esbuild/openharmony-arm64": "0.28.2", | |
| 1464 | + "@esbuild/sunos-x64": "0.28.2", | |
| 1465 | + "@esbuild/win32-arm64": "0.28.2", | |
| 1466 | + "@esbuild/win32-ia32": "0.28.2", | |
| 1467 | + "@esbuild/win32-x64": "0.28.2" | |
| 1468 | + } | |
| 1469 | + }, | |
| 1470 | + "node_modules/escape-html": { | |
| 1471 | + "version": "1.0.3", | |
| 1472 | + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", | |
| 1473 | + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", | |
| 1474 | + "license": "MIT" | |
| 1475 | + }, | |
| 1476 | + "node_modules/estree-walker": { | |
| 1477 | + "version": "3.0.3", | |
| 1478 | + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", | |
| 1479 | + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", | |
| 1480 | + "dev": true, | |
| 1481 | + "license": "MIT", | |
| 1482 | + "dependencies": { | |
| 1483 | + "@types/estree": "^1.0.0" | |
| 1484 | + } | |
| 1485 | + }, | |
| 1486 | + "node_modules/etag": { | |
| 1487 | + "version": "1.8.1", | |
| 1488 | + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", | |
| 1489 | + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", | |
| 1490 | + "license": "MIT", | |
| 1491 | + "engines": { | |
| 1492 | + "node": ">= 0.6" | |
| 1493 | + } | |
| 1494 | + }, | |
| 1495 | + "node_modules/eventsource": { | |
| 1496 | + "version": "3.0.7", | |
| 1497 | + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", | |
| 1498 | + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", | |
| 1499 | + "license": "MIT", | |
| 1500 | + "dependencies": { | |
| 1501 | + "eventsource-parser": "^3.0.1" | |
| 1502 | + }, | |
| 1503 | + "engines": { | |
| 1504 | + "node": ">=18.0.0" | |
| 1505 | + } | |
| 1506 | + }, | |
| 1507 | + "node_modules/eventsource-parser": { | |
| 1508 | + "version": "3.1.1", | |
| 1509 | + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", | |
| 1510 | + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", | |
| 1511 | + "license": "MIT", | |
| 1512 | + "engines": { | |
| 1513 | + "node": ">=18.0.0" | |
| 1514 | + } | |
| 1515 | + }, | |
| 1516 | + "node_modules/expect-type": { | |
| 1517 | + "version": "1.4.0", | |
| 1518 | + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", | |
| 1519 | + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", | |
| 1520 | + "dev": true, | |
| 1521 | + "license": "Apache-2.0", | |
| 1522 | + "engines": { | |
| 1523 | + "node": ">=12.0.0" | |
| 1524 | + } | |
| 1525 | + }, | |
| 1526 | + "node_modules/express": { | |
| 1527 | + "version": "5.2.1", | |
| 1528 | + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", | |
| 1529 | + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", | |
| 1530 | + "license": "MIT", | |
| 1531 | + "dependencies": { | |
| 1532 | + "accepts": "^2.0.0", | |
| 1533 | + "body-parser": "^2.2.1", | |
| 1534 | + "content-disposition": "^1.0.0", | |
| 1535 | + "content-type": "^1.0.5", | |
| 1536 | + "cookie": "^0.7.1", | |
| 1537 | + "cookie-signature": "^1.2.1", | |
| 1538 | + "debug": "^4.4.0", | |
| 1539 | + "depd": "^2.0.0", | |
| 1540 | + "encodeurl": "^2.0.0", | |
| 1541 | + "escape-html": "^1.0.3", | |
| 1542 | + "etag": "^1.8.1", | |
| 1543 | + "finalhandler": "^2.1.0", | |
| 1544 | + "fresh": "^2.0.0", | |
| 1545 | + "http-errors": "^2.0.0", | |
| 1546 | + "merge-descriptors": "^2.0.0", | |
| 1547 | + "mime-types": "^3.0.0", | |
| 1548 | + "on-finished": "^2.4.1", | |
| 1549 | + "once": "^1.4.0", | |
| 1550 | + "parseurl": "^1.3.3", | |
| 1551 | + "proxy-addr": "^2.0.7", | |
| 1552 | + "qs": "^6.14.0", | |
| 1553 | + "range-parser": "^1.2.1", | |
| 1554 | + "router": "^2.2.0", | |
| 1555 | + "send": "^1.1.0", | |
| 1556 | + "serve-static": "^2.2.0", | |
| 1557 | + "statuses": "^2.0.1", | |
| 1558 | + "type-is": "^2.0.1", | |
| 1559 | + "vary": "^1.1.2" | |
| 1560 | + }, | |
| 1561 | + "engines": { | |
| 1562 | + "node": ">= 18" | |
| 1563 | + }, | |
| 1564 | + "funding": { | |
| 1565 | + "type": "opencollective", | |
| 1566 | + "url": "https://opencollective.com/express" | |
| 1567 | + } | |
| 1568 | + }, | |
| 1569 | + "node_modules/express-rate-limit": { | |
| 1570 | + "version": "8.7.0", | |
| 1571 | + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", | |
| 1572 | + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", | |
| 1573 | + "license": "MIT", | |
| 1574 | + "dependencies": { | |
| 1575 | + "debug": "^4.4.3", | |
| 1576 | + "ip-address": "^10.2.0" | |
| 1577 | + }, | |
| 1578 | + "engines": { | |
| 1579 | + "node": ">= 16" | |
| 1580 | + }, | |
| 1581 | + "funding": { | |
| 1582 | + "url": "https://github.com/sponsors/express-rate-limit" | |
| 1583 | + }, | |
| 1584 | + "peerDependencies": { | |
| 1585 | + "express": ">= 4.11" | |
| 1586 | + } | |
| 1587 | + }, | |
| 1588 | + "node_modules/fast-deep-equal": { | |
| 1589 | + "version": "3.1.3", | |
| 1590 | + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | |
| 1591 | + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", | |
| 1592 | + "license": "MIT" | |
| 1593 | + }, | |
| 1594 | + "node_modules/fast-uri": { | |
| 1595 | + "version": "3.1.7", | |
| 1596 | + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", | |
| 1597 | + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", | |
| 1598 | + "funding": [ | |
| 1599 | + { | |
| 1600 | + "type": "github", | |
| 1601 | + "url": "https://github.com/sponsors/fastify" | |
| 1602 | + }, | |
| 1603 | + { | |
| 1604 | + "type": "opencollective", | |
| 1605 | + "url": "https://opencollective.com/fastify" | |
| 1606 | + } | |
| 1607 | + ], | |
| 1608 | + "license": "BSD-3-Clause" | |
| 1609 | + }, | |
| 1610 | + "node_modules/fdir": { | |
| 1611 | + "version": "6.5.0", | |
| 1612 | + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", | |
| 1613 | + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", | |
| 1614 | + "dev": true, | |
| 1615 | + "license": "MIT", | |
| 1616 | + "engines": { | |
| 1617 | + "node": ">=12.0.0" | |
| 1618 | + }, | |
| 1619 | + "peerDependencies": { | |
| 1620 | + "picomatch": "^3 || ^4" | |
| 1621 | + }, | |
| 1622 | + "peerDependenciesMeta": { | |
| 1623 | + "picomatch": { | |
| 1624 | + "optional": true | |
| 1625 | + } | |
| 1626 | + } | |
| 1627 | + }, | |
| 1628 | + "node_modules/finalhandler": { | |
| 1629 | + "version": "2.1.1", | |
| 1630 | + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", | |
| 1631 | + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", | |
| 1632 | + "license": "MIT", | |
| 1633 | + "dependencies": { | |
| 1634 | + "debug": "^4.4.0", | |
| 1635 | + "encodeurl": "^2.0.0", | |
| 1636 | + "escape-html": "^1.0.3", | |
| 1637 | + "on-finished": "^2.4.1", | |
| 1638 | + "parseurl": "^1.3.3", | |
| 1639 | + "statuses": "^2.0.1" | |
| 1640 | + }, | |
| 1641 | + "engines": { | |
| 1642 | + "node": ">= 18.0.0" | |
| 1643 | + }, | |
| 1644 | + "funding": { | |
| 1645 | + "type": "opencollective", | |
| 1646 | + "url": "https://opencollective.com/express" | |
| 1647 | + } | |
| 1648 | + }, | |
| 1649 | + "node_modules/forwarded": { | |
| 1650 | + "version": "0.2.0", | |
| 1651 | + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", | |
| 1652 | + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", | |
| 1653 | + "license": "MIT", | |
| 1654 | + "engines": { | |
| 1655 | + "node": ">= 0.6" | |
| 1656 | + } | |
| 1657 | + }, | |
| 1658 | + "node_modules/fresh": { | |
| 1659 | + "version": "2.0.0", | |
| 1660 | + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", | |
| 1661 | + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", | |
| 1662 | + "license": "MIT", | |
| 1663 | + "engines": { | |
| 1664 | + "node": ">= 0.8" | |
| 1665 | + } | |
| 1666 | + }, | |
| 1667 | + "node_modules/fsevents": { | |
| 1668 | + "version": "2.3.3", | |
| 1669 | + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", | |
| 1670 | + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", | |
| 1671 | + "dev": true, | |
| 1672 | + "hasInstallScript": true, | |
| 1673 | + "license": "MIT", | |
| 1674 | + "optional": true, | |
| 1675 | + "os": [ | |
| 1676 | + "darwin" | |
| 1677 | + ], | |
| 1678 | + "engines": { | |
| 1679 | + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" | |
| 1680 | + } | |
| 1681 | + }, | |
| 1682 | + "node_modules/function-bind": { | |
| 1683 | + "version": "1.1.2", | |
| 1684 | + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", | |
| 1685 | + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", | |
| 1686 | + "license": "MIT", | |
| 1687 | + "funding": { | |
| 1688 | + "url": "https://github.com/sponsors/ljharb" | |
| 1689 | + } | |
| 1690 | + }, | |
| 1691 | + "node_modules/get-intrinsic": { | |
| 1692 | + "version": "1.3.0", | |
| 1693 | + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", | |
| 1694 | + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", | |
| 1695 | + "license": "MIT", | |
| 1696 | + "dependencies": { | |
| 1697 | + "call-bind-apply-helpers": "^1.0.2", | |
| 1698 | + "es-define-property": "^1.0.1", | |
| 1699 | + "es-errors": "^1.3.0", | |
| 1700 | + "es-object-atoms": "^1.1.1", | |
| 1701 | + "function-bind": "^1.1.2", | |
| 1702 | + "get-proto": "^1.0.1", | |
| 1703 | + "gopd": "^1.2.0", | |
| 1704 | + "has-symbols": "^1.1.0", | |
| 1705 | + "hasown": "^2.0.2", | |
| 1706 | + "math-intrinsics": "^1.1.0" | |
| 1707 | + }, | |
| 1708 | + "engines": { | |
| 1709 | + "node": ">= 0.4" | |
| 1710 | + }, | |
| 1711 | + "funding": { | |
| 1712 | + "url": "https://github.com/sponsors/ljharb" | |
| 1713 | + } | |
| 1714 | + }, | |
| 1715 | + "node_modules/get-proto": { | |
| 1716 | + "version": "1.0.1", | |
| 1717 | + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", | |
| 1718 | + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", | |
| 1719 | + "license": "MIT", | |
| 1720 | + "dependencies": { | |
| 1721 | + "dunder-proto": "^1.0.1", | |
| 1722 | + "es-object-atoms": "^1.0.0" | |
| 1723 | + }, | |
| 1724 | + "engines": { | |
| 1725 | + "node": ">= 0.4" | |
| 1726 | + } | |
| 1727 | + }, | |
| 1728 | + "node_modules/gopd": { | |
| 1729 | + "version": "1.2.0", | |
| 1730 | + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", | |
| 1731 | + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", | |
| 1732 | + "license": "MIT", | |
| 1733 | + "engines": { | |
| 1734 | + "node": ">= 0.4" | |
| 1735 | + }, | |
| 1736 | + "funding": { | |
| 1737 | + "url": "https://github.com/sponsors/ljharb" | |
| 1738 | + } | |
| 1739 | + }, | |
| 1740 | + "node_modules/has-symbols": { | |
| 1741 | + "version": "1.1.0", | |
| 1742 | + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", | |
| 1743 | + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", | |
| 1744 | + "license": "MIT", | |
| 1745 | + "engines": { | |
| 1746 | + "node": ">= 0.4" | |
| 1747 | + }, | |
| 1748 | + "funding": { | |
| 1749 | + "url": "https://github.com/sponsors/ljharb" | |
| 1750 | + } | |
| 1751 | + }, | |
| 1752 | + "node_modules/hasown": { | |
| 1753 | + "version": "2.0.4", | |
| 1754 | + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", | |
| 1755 | + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", | |
| 1756 | + "license": "MIT", | |
| 1757 | + "dependencies": { | |
| 1758 | + "function-bind": "^1.1.2" | |
| 1759 | + }, | |
| 1760 | + "engines": { | |
| 1761 | + "node": ">= 0.4" | |
| 1762 | + } | |
| 1763 | + }, | |
| 1764 | + "node_modules/hono": { | |
| 1765 | + "version": "4.13.7", | |
| 1766 | + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", | |
| 1767 | + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", | |
| 1768 | + "license": "MIT", | |
| 1769 | + "engines": { | |
| 1770 | + "node": ">=16.9.0" | |
| 1771 | + } | |
| 1772 | + }, | |
| 1773 | + "node_modules/http-errors": { | |
| 1774 | + "version": "2.0.1", | |
| 1775 | + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", | |
| 1776 | + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", | |
| 1777 | + "license": "MIT", | |
| 1778 | + "dependencies": { | |
| 1779 | + "depd": "~2.0.0", | |
| 1780 | + "inherits": "~2.0.4", | |
| 1781 | + "setprototypeof": "~1.2.0", | |
| 1782 | + "statuses": "~2.0.2", | |
| 1783 | + "toidentifier": "~1.0.1" | |
| 1784 | + }, | |
| 1785 | + "engines": { | |
| 1786 | + "node": ">= 0.8" | |
| 1787 | + }, | |
| 1788 | + "funding": { | |
| 1789 | + "type": "opencollective", | |
| 1790 | + "url": "https://opencollective.com/express" | |
| 1791 | + } | |
| 1792 | + }, | |
| 1793 | + "node_modules/iconv-lite": { | |
| 1794 | + "version": "0.7.3", | |
| 1795 | + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", | |
| 1796 | + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", | |
| 1797 | + "license": "MIT", | |
| 1798 | + "dependencies": { | |
| 1799 | + "safer-buffer": ">= 2.1.2 < 3.0.0" | |
| 1800 | + }, | |
| 1801 | + "engines": { | |
| 1802 | + "node": ">=0.10.0" | |
| 1803 | + }, | |
| 1804 | + "funding": { | |
| 1805 | + "type": "opencollective", | |
| 1806 | + "url": "https://opencollective.com/express" | |
| 1807 | + } | |
| 1808 | + }, | |
| 1809 | + "node_modules/inherits": { | |
| 1810 | + "version": "2.0.4", | |
| 1811 | + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", | |
| 1812 | + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", | |
| 1813 | + "license": "ISC" | |
| 1814 | + }, | |
| 1815 | + "node_modules/ip-address": { | |
| 1816 | + "version": "10.7.0", | |
| 1817 | + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", | |
| 1818 | + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", | |
| 1819 | + "license": "MIT", | |
| 1820 | + "engines": { | |
| 1821 | + "node": ">= 12" | |
| 1822 | + } | |
| 1823 | + }, | |
| 1824 | + "node_modules/ipaddr.js": { | |
| 1825 | + "version": "1.9.1", | |
| 1826 | + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", | |
| 1827 | + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", | |
| 1828 | + "license": "MIT", | |
| 1829 | + "engines": { | |
| 1830 | + "node": ">= 0.10" | |
| 1831 | + } | |
| 1832 | + }, | |
| 1833 | + "node_modules/is-promise": { | |
| 1834 | + "version": "4.0.0", | |
| 1835 | + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", | |
| 1836 | + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", | |
| 1837 | + "license": "MIT" | |
| 1838 | + }, | |
| 1839 | + "node_modules/isexe": { | |
| 1840 | + "version": "2.0.0", | |
| 1841 | + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | |
| 1842 | + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", | |
| 1843 | + "license": "ISC" | |
| 1844 | + }, | |
| 1845 | + "node_modules/jose": { | |
| 1846 | + "version": "6.2.11", | |
| 1847 | + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.11.tgz", | |
| 1848 | + "integrity": "sha512-A5NPn7g8EAzGU3IzRs+Yiq8K5n3ypYS75M5+KKiVHdUexfpWK1kP4ZMq7QnTGDoMj6TJ1dtcEJjW60yZDXS4hg==", | |
| 1849 | + "license": "MIT", | |
| 1850 | + "funding": { | |
| 1851 | + "url": "https://github.com/sponsors/panva" | |
| 1852 | + } | |
| 1853 | + }, | |
| 1854 | + "node_modules/js-tokens": { | |
| 1855 | + "version": "9.0.1", | |
| 1856 | + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", | |
| 1857 | + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", | |
| 1858 | + "dev": true, | |
| 1859 | + "license": "MIT" | |
| 1860 | + }, | |
| 1861 | + "node_modules/json-schema-traverse": { | |
| 1862 | + "version": "1.0.0", | |
| 1863 | + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", | |
| 1864 | + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", | |
| 1865 | + "license": "MIT" | |
| 1866 | + }, | |
| 1867 | + "node_modules/json-schema-typed": { | |
| 1868 | + "version": "8.0.2", | |
| 1869 | + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", | |
| 1870 | + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", | |
| 1871 | + "license": "BSD-2-Clause" | |
| 1872 | + }, | |
| 1873 | + "node_modules/loupe": { | |
| 1874 | + "version": "3.2.1", | |
| 1875 | + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", | |
| 1876 | + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", | |
| 1877 | + "dev": true, | |
| 1878 | + "license": "MIT" | |
| 1879 | + }, | |
| 1880 | + "node_modules/magic-string": { | |
| 1881 | + "version": "0.30.21", | |
| 1882 | + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", | |
| 1883 | + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", | |
| 1884 | + "dev": true, | |
| 1885 | + "license": "MIT", | |
| 1886 | + "dependencies": { | |
| 1887 | + "@jridgewell/sourcemap-codec": "^1.5.5" | |
| 1888 | + } | |
| 1889 | + }, | |
| 1890 | + "node_modules/math-intrinsics": { | |
| 1891 | + "version": "1.1.0", | |
| 1892 | + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", | |
| 1893 | + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", | |
| 1894 | + "license": "MIT", | |
| 1895 | + "engines": { | |
| 1896 | + "node": ">= 0.4" | |
| 1897 | + } | |
| 1898 | + }, | |
| 1899 | + "node_modules/media-typer": { | |
| 1900 | + "version": "1.1.1", | |
| 1901 | + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", | |
| 1902 | + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", | |
| 1903 | + "license": "MIT", | |
| 1904 | + "engines": { | |
| 1905 | + "node": ">= 0.8" | |
| 1906 | + }, | |
| 1907 | + "funding": { | |
| 1908 | + "type": "opencollective", | |
| 1909 | + "url": "https://opencollective.com/express" | |
| 1910 | + } | |
| 1911 | + }, | |
| 1912 | + "node_modules/merge-descriptors": { | |
| 1913 | + "version": "2.0.0", | |
| 1914 | + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", | |
| 1915 | + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", | |
| 1916 | + "license": "MIT", | |
| 1917 | + "engines": { | |
| 1918 | + "node": ">=18" | |
| 1919 | + }, | |
| 1920 | + "funding": { | |
| 1921 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 1922 | + } | |
| 1923 | + }, | |
| 1924 | + "node_modules/mime-db": { | |
| 1925 | + "version": "1.54.0", | |
| 1926 | + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", | |
| 1927 | + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", | |
| 1928 | + "license": "MIT", | |
| 1929 | + "engines": { | |
| 1930 | + "node": ">= 0.6" | |
| 1931 | + } | |
| 1932 | + }, | |
| 1933 | + "node_modules/mime-types": { | |
| 1934 | + "version": "3.0.2", | |
| 1935 | + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", | |
| 1936 | + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", | |
| 1937 | + "license": "MIT", | |
| 1938 | + "dependencies": { | |
| 1939 | + "mime-db": "^1.54.0" | |
| 1940 | + }, | |
| 1941 | + "engines": { | |
| 1942 | + "node": ">=18" | |
| 1943 | + }, | |
| 1944 | + "funding": { | |
| 1945 | + "type": "opencollective", | |
| 1946 | + "url": "https://opencollective.com/express" | |
| 1947 | + } | |
| 1948 | + }, | |
| 1949 | + "node_modules/ms": { | |
| 1950 | + "version": "2.1.3", | |
| 1951 | + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", | |
| 1952 | + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", | |
| 1953 | + "license": "MIT" | |
| 1954 | + }, | |
| 1955 | + "node_modules/nanoid": { | |
| 1956 | + "version": "3.3.18", | |
| 1957 | + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", | |
| 1958 | + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", | |
| 1959 | + "dev": true, | |
| 1960 | + "funding": [ | |
| 1961 | + { | |
| 1962 | + "type": "github", | |
| 1963 | + "url": "https://github.com/sponsors/ai" | |
| 1964 | + } | |
| 1965 | + ], | |
| 1966 | + "license": "MIT", | |
| 1967 | + "bin": { | |
| 1968 | + "nanoid": "bin/nanoid.cjs" | |
| 1969 | + }, | |
| 1970 | + "engines": { | |
| 1971 | + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" | |
| 1972 | + } | |
| 1973 | + }, | |
| 1974 | + "node_modules/negotiator": { | |
| 1975 | + "version": "1.1.0", | |
| 1976 | + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", | |
| 1977 | + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", | |
| 1978 | + "license": "MIT", | |
| 1979 | + "dependencies": { | |
| 1980 | + "content-type": "^2.1.0" | |
| 1981 | + }, | |
| 1982 | + "engines": { | |
| 1983 | + "node": ">=18" | |
| 1984 | + }, | |
| 1985 | + "funding": { | |
| 1986 | + "type": "opencollective", | |
| 1987 | + "url": "https://opencollective.com/express" | |
| 1988 | + } | |
| 1989 | + }, | |
| 1990 | + "node_modules/negotiator/node_modules/content-type": { | |
| 1991 | + "version": "2.1.0", | |
| 1992 | + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", | |
| 1993 | + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", | |
| 1994 | + "license": "MIT", | |
| 1995 | + "engines": { | |
| 1996 | + "node": ">=18" | |
| 1997 | + }, | |
| 1998 | + "funding": { | |
| 1999 | + "type": "opencollective", | |
| 2000 | + "url": "https://opencollective.com/express" | |
| 2001 | + } | |
| 2002 | + }, | |
| 2003 | + "node_modules/object-assign": { | |
| 2004 | + "version": "4.1.1", | |
| 2005 | + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", | |
| 2006 | + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", | |
| 2007 | + "license": "MIT", | |
| 2008 | + "engines": { | |
| 2009 | + "node": ">=0.10.0" | |
| 2010 | + } | |
| 2011 | + }, | |
| 2012 | + "node_modules/object-inspect": { | |
| 2013 | + "version": "1.13.4", | |
| 2014 | + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", | |
| 2015 | + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", | |
| 2016 | + "license": "MIT", | |
| 2017 | + "engines": { | |
| 2018 | + "node": ">= 0.4" | |
| 2019 | + }, | |
| 2020 | + "funding": { | |
| 2021 | + "url": "https://github.com/sponsors/ljharb" | |
| 2022 | + } | |
| 2023 | + }, | |
| 2024 | + "node_modules/on-finished": { | |
| 2025 | + "version": "2.4.1", | |
| 2026 | + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", | |
| 2027 | + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", | |
| 2028 | + "license": "MIT", | |
| 2029 | + "dependencies": { | |
| 2030 | + "ee-first": "1.1.1" | |
| 2031 | + }, | |
| 2032 | + "engines": { | |
| 2033 | + "node": ">= 0.8" | |
| 2034 | + } | |
| 2035 | + }, | |
| 2036 | + "node_modules/once": { | |
| 2037 | + "version": "1.4.0", | |
| 2038 | + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | |
| 2039 | + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", | |
| 2040 | + "license": "ISC", | |
| 2041 | + "dependencies": { | |
| 2042 | + "wrappy": "1" | |
| 2043 | + } | |
| 2044 | + }, | |
| 2045 | + "node_modules/parseurl": { | |
| 2046 | + "version": "1.3.3", | |
| 2047 | + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", | |
| 2048 | + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", | |
| 2049 | + "license": "MIT", | |
| 2050 | + "engines": { | |
| 2051 | + "node": ">= 0.8" | |
| 2052 | + } | |
| 2053 | + }, | |
| 2054 | + "node_modules/path-key": { | |
| 2055 | + "version": "3.1.1", | |
| 2056 | + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", | |
| 2057 | + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", | |
| 2058 | + "license": "MIT", | |
| 2059 | + "engines": { | |
| 2060 | + "node": ">=8" | |
| 2061 | + } | |
| 2062 | + }, | |
| 2063 | + "node_modules/path-to-regexp": { | |
| 2064 | + "version": "8.4.2", | |
| 2065 | + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", | |
| 2066 | + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", | |
| 2067 | + "license": "MIT", | |
| 2068 | + "funding": { | |
| 2069 | + "type": "opencollective", | |
| 2070 | + "url": "https://opencollective.com/express" | |
| 2071 | + } | |
| 2072 | + }, | |
| 2073 | + "node_modules/pathe": { | |
| 2074 | + "version": "2.0.3", | |
| 2075 | + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", | |
| 2076 | + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", | |
| 2077 | + "dev": true, | |
| 2078 | + "license": "MIT" | |
| 2079 | + }, | |
| 2080 | + "node_modules/pathval": { | |
| 2081 | + "version": "2.0.1", | |
| 2082 | + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", | |
| 2083 | + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", | |
| 2084 | + "dev": true, | |
| 2085 | + "license": "MIT", | |
| 2086 | + "engines": { | |
| 2087 | + "node": ">= 14.16" | |
| 2088 | + } | |
| 2089 | + }, | |
| 2090 | + "node_modules/picocolors": { | |
| 2091 | + "version": "1.1.1", | |
| 2092 | + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", | |
| 2093 | + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", | |
| 2094 | + "dev": true, | |
| 2095 | + "license": "ISC" | |
| 2096 | + }, | |
| 2097 | + "node_modules/picomatch": { | |
| 2098 | + "version": "4.0.7", | |
| 2099 | + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", | |
| 2100 | + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", | |
| 2101 | + "dev": true, | |
| 2102 | + "license": "MIT", | |
| 2103 | + "engines": { | |
| 2104 | + "node": ">=12" | |
| 2105 | + }, | |
| 2106 | + "funding": { | |
| 2107 | + "url": "https://github.com/sponsors/jonschlinkert" | |
| 2108 | + } | |
| 2109 | + }, | |
| 2110 | + "node_modules/pkce-challenge": { | |
| 2111 | + "version": "5.0.1", | |
| 2112 | + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", | |
| 2113 | + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", | |
| 2114 | + "license": "MIT", | |
| 2115 | + "engines": { | |
| 2116 | + "node": ">=16.20.0" | |
| 2117 | + } | |
| 2118 | + }, | |
| 2119 | + "node_modules/postcss": { | |
| 2120 | + "version": "8.5.28", | |
| 2121 | + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", | |
| 2122 | + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", | |
| 2123 | + "dev": true, | |
| 2124 | + "funding": [ | |
| 2125 | + { | |
| 2126 | + "type": "opencollective", | |
| 2127 | + "url": "https://opencollective.com/postcss/" | |
| 2128 | + }, | |
| 2129 | + { | |
| 2130 | + "type": "tidelift", | |
| 2131 | + "url": "https://tidelift.com/funding/github/npm/postcss" | |
| 2132 | + }, | |
| 2133 | + { | |
| 2134 | + "type": "github", | |
| 2135 | + "url": "https://github.com/sponsors/ai" | |
| 2136 | + } | |
| 2137 | + ], | |
| 2138 | + "license": "MIT", | |
| 2139 | + "dependencies": { | |
| 2140 | + "nanoid": "^3.3.18", | |
| 2141 | + "picocolors": "^1.1.1", | |
| 2142 | + "source-map-js": "^1.2.1" | |
| 2143 | + }, | |
| 2144 | + "engines": { | |
| 2145 | + "node": "^10 || ^12 || >=14" | |
| 2146 | + } | |
| 2147 | + }, | |
| 2148 | + "node_modules/proxy-addr": { | |
| 2149 | + "version": "2.0.7", | |
| 2150 | + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", | |
| 2151 | + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", | |
| 2152 | + "license": "MIT", | |
| 2153 | + "dependencies": { | |
| 2154 | + "forwarded": "0.2.0", | |
| 2155 | + "ipaddr.js": "1.9.1" | |
| 2156 | + }, | |
| 2157 | + "engines": { | |
| 2158 | + "node": ">= 0.10" | |
| 2159 | + } | |
| 2160 | + }, | |
| 2161 | + "node_modules/qs": { | |
| 2162 | + "version": "6.16.0", | |
| 2163 | + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", | |
| 2164 | + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", | |
| 2165 | + "license": "BSD-3-Clause", | |
| 2166 | + "dependencies": { | |
| 2167 | + "es-define-property": "^1.0.1", | |
| 2168 | + "side-channel": "^1.1.1" | |
| 2169 | + }, | |
| 2170 | + "engines": { | |
| 2171 | + "node": ">=0.6" | |
| 2172 | + }, | |
| 2173 | + "funding": { | |
| 2174 | + "url": "https://github.com/sponsors/ljharb" | |
| 2175 | + } | |
| 2176 | + }, | |
| 2177 | + "node_modules/range-parser": { | |
| 2178 | + "version": "1.3.0", | |
| 2179 | + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", | |
| 2180 | + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", | |
| 2181 | + "license": "MIT", | |
| 2182 | + "engines": { | |
| 2183 | + "node": ">= 0.6" | |
| 2184 | + }, | |
| 2185 | + "funding": { | |
| 2186 | + "type": "opencollective", | |
| 2187 | + "url": "https://opencollective.com/express" | |
| 2188 | + } | |
| 2189 | + }, | |
| 2190 | + "node_modules/raw-body": { | |
| 2191 | + "version": "3.0.2", | |
| 2192 | + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", | |
| 2193 | + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", | |
| 2194 | + "license": "MIT", | |
| 2195 | + "dependencies": { | |
| 2196 | + "bytes": "~3.1.2", | |
| 2197 | + "http-errors": "~2.0.1", | |
| 2198 | + "iconv-lite": "~0.7.0", | |
| 2199 | + "unpipe": "~1.0.0" | |
| 2200 | + }, | |
| 2201 | + "engines": { | |
| 2202 | + "node": ">= 0.10" | |
| 2203 | + } | |
| 2204 | + }, | |
| 2205 | + "node_modules/require-from-string": { | |
| 2206 | + "version": "2.0.2", | |
| 2207 | + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", | |
| 2208 | + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", | |
| 2209 | + "license": "MIT", | |
| 2210 | + "engines": { | |
| 2211 | + "node": ">=0.10.0" | |
| 2212 | + } | |
| 2213 | + }, | |
| 2214 | + "node_modules/rollup": { | |
| 2215 | + "version": "4.63.1", | |
| 2216 | + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", | |
| 2217 | + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", | |
| 2218 | + "dev": true, | |
| 2219 | + "license": "MIT", | |
| 2220 | + "dependencies": { | |
| 2221 | + "@types/estree": "1.0.9" | |
| 2222 | + }, | |
| 2223 | + "bin": { | |
| 2224 | + "rollup": "dist/bin/rollup" | |
| 2225 | + }, | |
| 2226 | + "engines": { | |
| 2227 | + "node": ">=18.0.0", | |
| 2228 | + "npm": ">=8.0.0" | |
| 2229 | + }, | |
| 2230 | + "optionalDependencies": { | |
| 2231 | + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", | |
| 2232 | + "@rollup/rollup-android-arm-eabi": "4.63.1", | |
| 2233 | + "@rollup/rollup-android-arm64": "4.63.1", | |
| 2234 | + "@rollup/rollup-darwin-arm64": "4.63.1", | |
| 2235 | + "@rollup/rollup-darwin-x64": "4.63.1", | |
| 2236 | + "@rollup/rollup-freebsd-arm64": "4.63.1", | |
| 2237 | + "@rollup/rollup-freebsd-x64": "4.63.1", | |
| 2238 | + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", | |
| 2239 | + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", | |
| 2240 | + "@rollup/rollup-linux-arm64-gnu": "4.63.1", | |
| 2241 | + "@rollup/rollup-linux-arm64-musl": "4.63.1", | |
| 2242 | + "@rollup/rollup-linux-loong64-gnu": "4.63.1", | |
| 2243 | + "@rollup/rollup-linux-loong64-musl": "4.63.1", | |
| 2244 | + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", | |
| 2245 | + "@rollup/rollup-linux-ppc64-musl": "4.63.1", | |
| 2246 | + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", | |
| 2247 | + "@rollup/rollup-linux-riscv64-musl": "4.63.1", | |
| 2248 | + "@rollup/rollup-linux-s390x-gnu": "4.63.1", | |
| 2249 | + "@rollup/rollup-linux-x64-gnu": "4.63.1", | |
| 2250 | + "@rollup/rollup-linux-x64-musl": "4.63.1", | |
| 2251 | + "@rollup/rollup-openbsd-x64": "4.63.1", | |
| 2252 | + "@rollup/rollup-openharmony-arm64": "4.63.1", | |
| 2253 | + "@rollup/rollup-win32-arm64-msvc": "4.63.1", | |
| 2254 | + "@rollup/rollup-win32-ia32-msvc": "4.63.1", | |
| 2255 | + "@rollup/rollup-win32-x64-gnu": "4.63.1", | |
| 2256 | + "@rollup/rollup-win32-x64-msvc": "4.63.1", | |
| 2257 | + "fsevents": "~2.3.2" | |
| 2258 | + } | |
| 2259 | + }, | |
| 2260 | + "node_modules/router": { | |
| 2261 | + "version": "2.2.0", | |
| 2262 | + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", | |
| 2263 | + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", | |
| 2264 | + "license": "MIT", | |
| 2265 | + "dependencies": { | |
| 2266 | + "debug": "^4.4.0", | |
| 2267 | + "depd": "^2.0.0", | |
| 2268 | + "is-promise": "^4.0.0", | |
| 2269 | + "parseurl": "^1.3.3", | |
| 2270 | + "path-to-regexp": "^8.0.0" | |
| 2271 | + }, | |
| 2272 | + "engines": { | |
| 2273 | + "node": ">= 18" | |
| 2274 | + } | |
| 2275 | + }, | |
| 2276 | + "node_modules/safer-buffer": { | |
| 2277 | + "version": "2.1.2", | |
| 2278 | + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", | |
| 2279 | + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", | |
| 2280 | + "license": "MIT" | |
| 2281 | + }, | |
| 2282 | + "node_modules/send": { | |
| 2283 | + "version": "1.2.1", | |
| 2284 | + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", | |
| 2285 | + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", | |
| 2286 | + "license": "MIT", | |
| 2287 | + "dependencies": { | |
| 2288 | + "debug": "^4.4.3", | |
| 2289 | + "encodeurl": "^2.0.0", | |
| 2290 | + "escape-html": "^1.0.3", | |
| 2291 | + "etag": "^1.8.1", | |
| 2292 | + "fresh": "^2.0.0", | |
| 2293 | + "http-errors": "^2.0.1", | |
| 2294 | + "mime-types": "^3.0.2", | |
| 2295 | + "ms": "^2.1.3", | |
| 2296 | + "on-finished": "^2.4.1", | |
| 2297 | + "range-parser": "^1.2.1", | |
| 2298 | + "statuses": "^2.0.2" | |
| 2299 | + }, | |
| 2300 | + "engines": { | |
| 2301 | + "node": ">= 18" | |
| 2302 | + }, | |
| 2303 | + "funding": { | |
| 2304 | + "type": "opencollective", | |
| 2305 | + "url": "https://opencollective.com/express" | |
| 2306 | + } | |
| 2307 | + }, | |
| 2308 | + "node_modules/serve-static": { | |
| 2309 | + "version": "2.2.1", | |
| 2310 | + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", | |
| 2311 | + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", | |
| 2312 | + "license": "MIT", | |
| 2313 | + "dependencies": { | |
| 2314 | + "encodeurl": "^2.0.0", | |
| 2315 | + "escape-html": "^1.0.3", | |
| 2316 | + "parseurl": "^1.3.3", | |
| 2317 | + "send": "^1.2.0" | |
| 2318 | + }, | |
| 2319 | + "engines": { | |
| 2320 | + "node": ">= 18" | |
| 2321 | + }, | |
| 2322 | + "funding": { | |
| 2323 | + "type": "opencollective", | |
| 2324 | + "url": "https://opencollective.com/express" | |
| 2325 | + } | |
| 2326 | + }, | |
| 2327 | + "node_modules/setprototypeof": { | |
| 2328 | + "version": "1.2.0", | |
| 2329 | + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", | |
| 2330 | + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", | |
| 2331 | + "license": "ISC" | |
| 2332 | + }, | |
| 2333 | + "node_modules/shebang-command": { | |
| 2334 | + "version": "2.0.0", | |
| 2335 | + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", | |
| 2336 | + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", | |
| 2337 | + "license": "MIT", | |
| 2338 | + "dependencies": { | |
| 2339 | + "shebang-regex": "^3.0.0" | |
| 2340 | + }, | |
| 2341 | + "engines": { | |
| 2342 | + "node": ">=8" | |
| 2343 | + } | |
| 2344 | + }, | |
| 2345 | + "node_modules/shebang-regex": { | |
| 2346 | + "version": "3.0.0", | |
| 2347 | + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", | |
| 2348 | + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", | |
| 2349 | + "license": "MIT", | |
| 2350 | + "engines": { | |
| 2351 | + "node": ">=8" | |
| 2352 | + } | |
| 2353 | + }, | |
| 2354 | + "node_modules/side-channel": { | |
| 2355 | + "version": "1.1.1", | |
| 2356 | + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", | |
| 2357 | + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", | |
| 2358 | + "license": "MIT", | |
| 2359 | + "dependencies": { | |
| 2360 | + "es-errors": "^1.3.0", | |
| 2361 | + "object-inspect": "^1.13.4", | |
| 2362 | + "side-channel-list": "^1.0.1", | |
| 2363 | + "side-channel-map": "^1.0.1", | |
| 2364 | + "side-channel-weakmap": "^1.0.2" | |
| 2365 | + }, | |
| 2366 | + "engines": { | |
| 2367 | + "node": ">= 0.4" | |
| 2368 | + }, | |
| 2369 | + "funding": { | |
| 2370 | + "url": "https://github.com/sponsors/ljharb" | |
| 2371 | + } | |
| 2372 | + }, | |
| 2373 | + "node_modules/side-channel-list": { | |
| 2374 | + "version": "1.0.1", | |
| 2375 | + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", | |
| 2376 | + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", | |
| 2377 | + "license": "MIT", | |
| 2378 | + "dependencies": { | |
| 2379 | + "es-errors": "^1.3.0", | |
| 2380 | + "object-inspect": "^1.13.4" | |
| 2381 | + }, | |
| 2382 | + "engines": { | |
| 2383 | + "node": ">= 0.4" | |
| 2384 | + }, | |
| 2385 | + "funding": { | |
| 2386 | + "url": "https://github.com/sponsors/ljharb" | |
| 2387 | + } | |
| 2388 | + }, | |
| 2389 | + "node_modules/side-channel-map": { | |
| 2390 | + "version": "1.0.1", | |
| 2391 | + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", | |
| 2392 | + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", | |
| 2393 | + "license": "MIT", | |
| 2394 | + "dependencies": { | |
| 2395 | + "call-bound": "^1.0.2", | |
| 2396 | + "es-errors": "^1.3.0", | |
| 2397 | + "get-intrinsic": "^1.2.5", | |
| 2398 | + "object-inspect": "^1.13.3" | |
| 2399 | + }, | |
| 2400 | + "engines": { | |
| 2401 | + "node": ">= 0.4" | |
| 2402 | + }, | |
| 2403 | + "funding": { | |
| 2404 | + "url": "https://github.com/sponsors/ljharb" | |
| 2405 | + } | |
| 2406 | + }, | |
| 2407 | + "node_modules/side-channel-weakmap": { | |
| 2408 | + "version": "1.0.2", | |
| 2409 | + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", | |
| 2410 | + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", | |
| 2411 | + "license": "MIT", | |
| 2412 | + "dependencies": { | |
| 2413 | + "call-bound": "^1.0.2", | |
| 2414 | + "es-errors": "^1.3.0", | |
| 2415 | + "get-intrinsic": "^1.2.5", | |
| 2416 | + "object-inspect": "^1.13.3", | |
| 2417 | + "side-channel-map": "^1.0.1" | |
| 2418 | + }, | |
| 2419 | + "engines": { | |
| 2420 | + "node": ">= 0.4" | |
| 2421 | + }, | |
| 2422 | + "funding": { | |
| 2423 | + "url": "https://github.com/sponsors/ljharb" | |
| 2424 | + } | |
| 2425 | + }, | |
| 2426 | + "node_modules/siginfo": { | |
| 2427 | + "version": "2.0.0", | |
| 2428 | + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", | |
| 2429 | + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", | |
| 2430 | + "dev": true, | |
| 2431 | + "license": "ISC" | |
| 2432 | + }, | |
| 2433 | + "node_modules/source-map-js": { | |
| 2434 | + "version": "1.2.1", | |
| 2435 | + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", | |
| 2436 | + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", | |
| 2437 | + "dev": true, | |
| 2438 | + "license": "BSD-3-Clause", | |
| 2439 | + "engines": { | |
| 2440 | + "node": ">=0.10.0" | |
| 2441 | + } | |
| 2442 | + }, | |
| 2443 | + "node_modules/stackback": { | |
| 2444 | + "version": "0.0.2", | |
| 2445 | + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", | |
| 2446 | + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", | |
| 2447 | + "dev": true, | |
| 2448 | + "license": "MIT" | |
| 2449 | + }, | |
| 2450 | + "node_modules/statuses": { | |
| 2451 | + "version": "2.0.2", | |
| 2452 | + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", | |
| 2453 | + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", | |
| 2454 | + "license": "MIT", | |
| 2455 | + "engines": { | |
| 2456 | + "node": ">= 0.8" | |
| 2457 | + } | |
| 2458 | + }, | |
| 2459 | + "node_modules/std-env": { | |
| 2460 | + "version": "3.10.0", | |
| 2461 | + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", | |
| 2462 | + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", | |
| 2463 | + "dev": true, | |
| 2464 | + "license": "MIT" | |
| 2465 | + }, | |
| 2466 | + "node_modules/strip-literal": { | |
| 2467 | + "version": "3.1.0", | |
| 2468 | + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", | |
| 2469 | + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", | |
| 2470 | + "dev": true, | |
| 2471 | + "license": "MIT", | |
| 2472 | + "dependencies": { | |
| 2473 | + "js-tokens": "^9.0.1" | |
| 2474 | + }, | |
| 2475 | + "funding": { | |
| 2476 | + "url": "https://github.com/sponsors/antfu" | |
| 2477 | + } | |
| 2478 | + }, | |
| 2479 | + "node_modules/tinybench": { | |
| 2480 | + "version": "2.9.0", | |
| 2481 | + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", | |
| 2482 | + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", | |
| 2483 | + "dev": true, | |
| 2484 | + "license": "MIT" | |
| 2485 | + }, | |
| 2486 | + "node_modules/tinyexec": { | |
| 2487 | + "version": "0.3.2", | |
| 2488 | + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", | |
| 2489 | + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", | |
| 2490 | + "dev": true, | |
| 2491 | + "license": "MIT" | |
| 2492 | + }, | |
| 2493 | + "node_modules/tinyglobby": { | |
| 2494 | + "version": "0.2.17", | |
| 2495 | + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", | |
| 2496 | + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", | |
| 2497 | + "dev": true, | |
| 2498 | + "license": "MIT", | |
| 2499 | + "dependencies": { | |
| 2500 | + "fdir": "^6.5.0", | |
| 2501 | + "picomatch": "^4.0.4" | |
| 2502 | + }, | |
| 2503 | + "engines": { | |
| 2504 | + "node": ">=12.0.0" | |
| 2505 | + }, | |
| 2506 | + "funding": { | |
| 2507 | + "url": "https://github.com/sponsors/SuperchupuDev" | |
| 2508 | + } | |
| 2509 | + }, | |
| 2510 | + "node_modules/tinypool": { | |
| 2511 | + "version": "1.1.1", | |
| 2512 | + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", | |
| 2513 | + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", | |
| 2514 | + "dev": true, | |
| 2515 | + "license": "MIT", | |
| 2516 | + "engines": { | |
| 2517 | + "node": "^18.0.0 || >=20.0.0" | |
| 2518 | + } | |
| 2519 | + }, | |
| 2520 | + "node_modules/tinyrainbow": { | |
| 2521 | + "version": "2.0.0", | |
| 2522 | + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", | |
| 2523 | + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", | |
| 2524 | + "dev": true, | |
| 2525 | + "license": "MIT", | |
| 2526 | + "engines": { | |
| 2527 | + "node": ">=14.0.0" | |
| 2528 | + } | |
| 2529 | + }, | |
| 2530 | + "node_modules/tinyspy": { | |
| 2531 | + "version": "4.0.6", | |
| 2532 | + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz", | |
| 2533 | + "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==", | |
| 2534 | + "dev": true, | |
| 2535 | + "license": "MIT", | |
| 2536 | + "engines": { | |
| 2537 | + "node": ">=14.0.0" | |
| 2538 | + } | |
| 2539 | + }, | |
| 2540 | + "node_modules/toidentifier": { | |
| 2541 | + "version": "1.0.1", | |
| 2542 | + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", | |
| 2543 | + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", | |
| 2544 | + "license": "MIT", | |
| 2545 | + "engines": { | |
| 2546 | + "node": ">=0.6" | |
| 2547 | + } | |
| 2548 | + }, | |
| 2549 | + "node_modules/type-is": { | |
| 2550 | + "version": "2.1.0", | |
| 2551 | + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", | |
| 2552 | + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", | |
| 2553 | + "license": "MIT", | |
| 2554 | + "dependencies": { | |
| 2555 | + "content-type": "^2.0.0", | |
| 2556 | + "media-typer": "^1.1.0", | |
| 2557 | + "mime-types": "^3.0.0" | |
| 2558 | + }, | |
| 2559 | + "engines": { | |
| 2560 | + "node": ">= 18" | |
| 2561 | + }, | |
| 2562 | + "funding": { | |
| 2563 | + "type": "opencollective", | |
| 2564 | + "url": "https://opencollective.com/express" | |
| 2565 | + } | |
| 2566 | + }, | |
| 2567 | + "node_modules/type-is/node_modules/content-type": { | |
| 2568 | + "version": "2.1.0", | |
| 2569 | + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", | |
| 2570 | + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", | |
| 2571 | + "license": "MIT", | |
| 2572 | + "engines": { | |
| 2573 | + "node": ">=18" | |
| 2574 | + }, | |
| 2575 | + "funding": { | |
| 2576 | + "type": "opencollective", | |
| 2577 | + "url": "https://opencollective.com/express" | |
| 2578 | + } | |
| 2579 | + }, | |
| 2580 | + "node_modules/typescript": { | |
| 2581 | + "version": "5.9.3", | |
| 2582 | + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", | |
| 2583 | + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", | |
| 2584 | + "dev": true, | |
| 2585 | + "license": "Apache-2.0", | |
| 2586 | + "bin": { | |
| 2587 | + "tsc": "bin/tsc", | |
| 2588 | + "tsserver": "bin/tsserver" | |
| 2589 | + }, | |
| 2590 | + "engines": { | |
| 2591 | + "node": ">=14.17" | |
| 2592 | + } | |
| 2593 | + }, | |
| 2594 | + "node_modules/undici-types": { | |
| 2595 | + "version": "6.21.0", | |
| 2596 | + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", | |
| 2597 | + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", | |
| 2598 | + "dev": true, | |
| 2599 | + "license": "MIT" | |
| 2600 | + }, | |
| 2601 | + "node_modules/unpipe": { | |
| 2602 | + "version": "1.0.0", | |
| 2603 | + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", | |
| 2604 | + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", | |
| 2605 | + "license": "MIT", | |
| 2606 | + "engines": { | |
| 2607 | + "node": ">= 0.8" | |
| 2608 | + } | |
| 2609 | + }, | |
| 2610 | + "node_modules/vary": { | |
| 2611 | + "version": "1.1.2", | |
| 2612 | + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", | |
| 2613 | + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", | |
| 2614 | + "license": "MIT", | |
| 2615 | + "engines": { | |
| 2616 | + "node": ">= 0.8" | |
| 2617 | + } | |
| 2618 | + }, | |
| 2619 | + "node_modules/vite": { | |
| 2620 | + "version": "7.3.6", | |
| 2621 | + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", | |
| 2622 | + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", | |
| 2623 | + "dev": true, | |
| 2624 | + "license": "MIT", | |
| 2625 | + "dependencies": { | |
| 2626 | + "esbuild": "^0.27.0 || ^0.28.0", | |
| 2627 | + "fdir": "^6.5.0", | |
| 2628 | + "picomatch": "^4.0.3", | |
| 2629 | + "postcss": "^8.5.6", | |
| 2630 | + "rollup": "^4.43.0", | |
| 2631 | + "tinyglobby": "^0.2.15" | |
| 2632 | + }, | |
| 2633 | + "bin": { | |
| 2634 | + "vite": "bin/vite.js" | |
| 2635 | + }, | |
| 2636 | + "engines": { | |
| 2637 | + "node": "^20.19.0 || >=22.12.0" | |
| 2638 | + }, | |
| 2639 | + "funding": { | |
| 2640 | + "url": "https://github.com/vitejs/vite?sponsor=1" | |
| 2641 | + }, | |
| 2642 | + "optionalDependencies": { | |
| 2643 | + "fsevents": "~2.3.3" | |
| 2644 | + }, | |
| 2645 | + "peerDependencies": { | |
| 2646 | + "@types/node": "^20.19.0 || >=22.12.0", | |
| 2647 | + "jiti": ">=1.21.0", | |
| 2648 | + "less": "^4.0.0", | |
| 2649 | + "lightningcss": "^1.21.0", | |
| 2650 | + "sass": "^1.70.0", | |
| 2651 | + "sass-embedded": "^1.70.0", | |
| 2652 | + "stylus": ">=0.54.8", | |
| 2653 | + "sugarss": "^5.0.0", | |
| 2654 | + "terser": "^5.16.0", | |
| 2655 | + "tsx": "^4.8.1", | |
| 2656 | + "yaml": "^2.4.2" | |
| 2657 | + }, | |
| 2658 | + "peerDependenciesMeta": { | |
| 2659 | + "@types/node": { | |
| 2660 | + "optional": true | |
| 2661 | + }, | |
| 2662 | + "jiti": { | |
| 2663 | + "optional": true | |
| 2664 | + }, | |
| 2665 | + "less": { | |
| 2666 | + "optional": true | |
| 2667 | + }, | |
| 2668 | + "lightningcss": { | |
| 2669 | + "optional": true | |
| 2670 | + }, | |
| 2671 | + "sass": { | |
| 2672 | + "optional": true | |
| 2673 | + }, | |
| 2674 | + "sass-embedded": { | |
| 2675 | + "optional": true | |
| 2676 | + }, | |
| 2677 | + "stylus": { | |
| 2678 | + "optional": true | |
| 2679 | + }, | |
| 2680 | + "sugarss": { | |
| 2681 | + "optional": true | |
| 2682 | + }, | |
| 2683 | + "terser": { | |
| 2684 | + "optional": true | |
| 2685 | + }, | |
| 2686 | + "tsx": { | |
| 2687 | + "optional": true | |
| 2688 | + }, | |
| 2689 | + "yaml": { | |
| 2690 | + "optional": true | |
| 2691 | + } | |
| 2692 | + } | |
| 2693 | + }, | |
| 2694 | + "node_modules/vite-node": { | |
| 2695 | + "version": "3.2.4", | |
| 2696 | + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", | |
| 2697 | + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", | |
| 2698 | + "dev": true, | |
| 2699 | + "license": "MIT", | |
| 2700 | + "dependencies": { | |
| 2701 | + "cac": "^6.7.14", | |
| 2702 | + "debug": "^4.4.1", | |
| 2703 | + "es-module-lexer": "^1.7.0", | |
| 2704 | + "pathe": "^2.0.3", | |
| 2705 | + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" | |
| 2706 | + }, | |
| 2707 | + "bin": { | |
| 2708 | + "vite-node": "vite-node.mjs" | |
| 2709 | + }, | |
| 2710 | + "engines": { | |
| 2711 | + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" | |
| 2712 | + }, | |
| 2713 | + "funding": { | |
| 2714 | + "url": "https://opencollective.com/vitest" | |
| 2715 | + } | |
| 2716 | + }, | |
| 2717 | + "node_modules/vitest": { | |
| 2718 | + "version": "3.2.7", | |
| 2719 | + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", | |
| 2720 | + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", | |
| 2721 | + "dev": true, | |
| 2722 | + "license": "MIT", | |
| 2723 | + "dependencies": { | |
| 2724 | + "@types/chai": "^5.2.2", | |
| 2725 | + "@vitest/expect": "3.2.7", | |
| 2726 | + "@vitest/mocker": "3.2.7", | |
| 2727 | + "@vitest/pretty-format": "^3.2.7", | |
| 2728 | + "@vitest/runner": "3.2.7", | |
| 2729 | + "@vitest/snapshot": "3.2.7", | |
| 2730 | + "@vitest/spy": "3.2.7", | |
| 2731 | + "@vitest/utils": "3.2.7", | |
| 2732 | + "chai": "^5.2.0", | |
| 2733 | + "debug": "^4.4.1", | |
| 2734 | + "expect-type": "^1.2.1", | |
| 2735 | + "magic-string": "^0.30.17", | |
| 2736 | + "pathe": "^2.0.3", | |
| 2737 | + "picomatch": "^4.0.2", | |
| 2738 | + "std-env": "^3.9.0", | |
| 2739 | + "tinybench": "^2.9.0", | |
| 2740 | + "tinyexec": "^0.3.2", | |
| 2741 | + "tinyglobby": "^0.2.14", | |
| 2742 | + "tinypool": "^1.1.1", | |
| 2743 | + "tinyrainbow": "^2.0.0", | |
| 2744 | + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", | |
| 2745 | + "vite-node": "3.2.4", | |
| 2746 | + "why-is-node-running": "^2.3.0" | |
| 2747 | + }, | |
| 2748 | + "bin": { | |
| 2749 | + "vitest": "vitest.mjs" | |
| 2750 | + }, | |
| 2751 | + "engines": { | |
| 2752 | + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" | |
| 2753 | + }, | |
| 2754 | + "funding": { | |
| 2755 | + "url": "https://opencollective.com/vitest" | |
| 2756 | + }, | |
| 2757 | + "peerDependencies": { | |
| 2758 | + "@edge-runtime/vm": "*", | |
| 2759 | + "@types/debug": "^4.1.12", | |
| 2760 | + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", | |
| 2761 | + "@vitest/browser": "3.2.7", | |
| 2762 | + "@vitest/ui": "3.2.7", | |
| 2763 | + "happy-dom": "*", | |
| 2764 | + "jsdom": "*" | |
| 2765 | + }, | |
| 2766 | + "peerDependenciesMeta": { | |
| 2767 | + "@edge-runtime/vm": { | |
| 2768 | + "optional": true | |
| 2769 | + }, | |
| 2770 | + "@types/debug": { | |
| 2771 | + "optional": true | |
| 2772 | + }, | |
| 2773 | + "@types/node": { | |
| 2774 | + "optional": true | |
| 2775 | + }, | |
| 2776 | + "@vitest/browser": { | |
| 2777 | + "optional": true | |
| 2778 | + }, | |
| 2779 | + "@vitest/ui": { | |
| 2780 | + "optional": true | |
| 2781 | + }, | |
| 2782 | + "happy-dom": { | |
| 2783 | + "optional": true | |
| 2784 | + }, | |
| 2785 | + "jsdom": { | |
| 2786 | + "optional": true | |
| 2787 | + } | |
| 2788 | + } | |
| 2789 | + }, | |
| 2790 | + "node_modules/which": { | |
| 2791 | + "version": "2.0.2", | |
| 2792 | + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | |
| 2793 | + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", | |
| 2794 | + "license": "ISC", | |
| 2795 | + "dependencies": { | |
| 2796 | + "isexe": "^2.0.0" | |
| 2797 | + }, | |
| 2798 | + "bin": { | |
| 2799 | + "node-which": "bin/node-which" | |
| 2800 | + }, | |
| 2801 | + "engines": { | |
| 2802 | + "node": ">= 8" | |
| 2803 | + } | |
| 2804 | + }, | |
| 2805 | + "node_modules/why-is-node-running": { | |
| 2806 | + "version": "2.3.0", | |
| 2807 | + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", | |
| 2808 | + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", | |
| 2809 | + "dev": true, | |
| 2810 | + "license": "MIT", | |
| 2811 | + "dependencies": { | |
| 2812 | + "siginfo": "^2.0.0", | |
| 2813 | + "stackback": "0.0.2" | |
| 2814 | + }, | |
| 2815 | + "bin": { | |
| 2816 | + "why-is-node-running": "cli.js" | |
| 2817 | + }, | |
| 2818 | + "engines": { | |
| 2819 | + "node": ">=8" | |
| 2820 | + } | |
| 2821 | + }, | |
| 2822 | + "node_modules/wrappy": { | |
| 2823 | + "version": "1.0.2", | |
| 2824 | + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | |
| 2825 | + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", | |
| 2826 | + "license": "ISC" | |
| 2827 | + }, | |
| 2828 | + "node_modules/zod": { | |
| 2829 | + "version": "4.5.4", | |
| 2830 | + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", | |
| 2831 | + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", | |
| 2832 | + "license": "MIT", | |
| 2833 | + "funding": { | |
| 2834 | + "url": "https://github.com/sponsors/colinhacks" | |
| 2835 | + } | |
| 2836 | + }, | |
| 2837 | + "node_modules/zod-to-json-schema": { | |
| 2838 | + "version": "3.25.2", | |
| 2839 | + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", | |
| 2840 | + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", | |
| 2841 | + "license": "ISC", | |
| 2842 | + "peerDependencies": { | |
| 2843 | + "zod": "^3.25.28 || ^4" | |
| 2844 | + } | |
| 2845 | + } | |
| 2846 | + } | |
| 2847 | +} | |
added
mcp/package.json
+65 −0
@@ -0,0 +1,65 @@ | ||
| 1 | +{ | |
| 2 | + "name": "hfmarketdata-mcp", | |
| 3 | + "version": "1.0.0", | |
| 4 | + "description": "Official MCP server for HF Market Data (www.hfmarketdata.io): stocks, ETFs, futures (contracts, continuous, term structure), options chains, SEC fundamentals and a live filings stream — for Claude Code, Cursor, Codex and any MCP client.", | |
| 5 | + "license": "MIT", | |
| 6 | + "author": "Simon-Pierre Boucher <contact@spboucher.ai>", | |
| 7 | + "homepage": "https://www.hfmarketdata.io/integrations/mcp", | |
| 8 | + "repository": { | |
| 9 | + "type": "git", | |
| 10 | + "url": "https://git.spboucher.ai/hfmarketdata.git", | |
| 11 | + "directory": "mcp" | |
| 12 | + }, | |
| 13 | + "keywords": [ | |
| 14 | + "mcp", | |
| 15 | + "model-context-protocol", | |
| 16 | + "market-data", | |
| 17 | + "futures", | |
| 18 | + "options", | |
| 19 | + "fundamentals", | |
| 20 | + "sec", | |
| 21 | + "edgar", | |
| 22 | + "claude", | |
| 23 | + "cursor", | |
| 24 | + "codex", | |
| 25 | + "finance" | |
| 26 | + ], | |
| 27 | + "type": "module", | |
| 28 | + "bin": { | |
| 29 | + "hfmarketdata-mcp": "dist/index.js" | |
| 30 | + }, | |
| 31 | + "main": "dist/server.js", | |
| 32 | + "types": "dist/server.d.ts", | |
| 33 | + "exports": { | |
| 34 | + ".": { | |
| 35 | + "types": "./dist/server.d.ts", | |
| 36 | + "import": "./dist/server.js" | |
| 37 | + } | |
| 38 | + }, | |
| 39 | + "files": [ | |
| 40 | + "dist", | |
| 41 | + "README.md", | |
| 42 | + "LICENSE" | |
| 43 | + ], | |
| 44 | + "engines": { | |
| 45 | + "node": ">=20" | |
| 46 | + }, | |
| 47 | + "scripts": { | |
| 48 | + "build": "tsc -p tsconfig.json", | |
| 49 | + "prepack": "npm run build", | |
| 50 | + "test": "vitest run", | |
| 51 | + "test:watch": "vitest", | |
| 52 | + "typecheck": "tsc -p tsconfig.json --noEmit", | |
| 53 | + "start": "node dist/index.js", | |
| 54 | + "inspect": "npx -y @modelcontextprotocol/inspector node dist/index.js" | |
| 55 | + }, | |
| 56 | + "dependencies": { | |
| 57 | + "@modelcontextprotocol/sdk": "^1.30.0", | |
| 58 | + "zod": "^4.0.0" | |
| 59 | + }, | |
| 60 | + "devDependencies": { | |
| 61 | + "@types/node": "^22.0.0", | |
| 62 | + "typescript": "^5.9.0", | |
| 63 | + "vitest": "^3.2.0" | |
| 64 | + } | |
| 65 | +} | |
added
mcp/scripts/live-smoke.mjs
+21 −0
@@ -0,0 +1,21 @@ | ||
| 1 | +#!/usr/bin/env node | |
| 2 | +// Live smoke test: spawn the built server over stdio, list tools, call get_bars against the real API. | |
| 3 | +// Usage: node scripts/live-smoke.mjs (requires `npm run build`; respects HFMD_API_KEY / HFMD_BASE_URL) | |
| 4 | +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; | |
| 5 | +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; | |
| 6 | +import { fileURLToPath } from 'node:url'; | |
| 7 | +import path from 'node:path'; | |
| 8 | + | |
| 9 | +const here = path.dirname(fileURLToPath(import.meta.url)); | |
| 10 | +const transport = new StdioClientTransport({ command: process.execPath, args: [path.join(here, '..', 'dist', 'index.js')], env: { ...process.env } }); | |
| 11 | +const client = new Client({ name: 'live-smoke', version: '1.0.0' }); | |
| 12 | +await client.connect(transport); | |
| 13 | +const tools = await client.listTools(); | |
| 14 | +console.log(`tools: ${tools.tools.length} → ${tools.tools.map((t) => t.name).join(', ')}`); | |
| 15 | +const t0 = Date.now(); | |
| 16 | +const res = await client.callTool({ name: 'get_bars', arguments: { asset: 'stock', symbol: 'AAPL', timeframe: '1day', start: '2025-01-02', end: '2025-01-10' } }); | |
| 17 | +console.log(`get_bars AAPL (${Date.now() - t0} ms, isError=${!!res.isError}):`); | |
| 18 | +console.log(res.content[0].text.slice(0, 700)); | |
| 19 | +const status = await client.readResource({ uri: 'hfmarketdata://limits' }); | |
| 20 | +console.log(status.contents[0].text.slice(0, 300)); | |
| 21 | +await client.close(); | |
added
mcp/scripts/record-demo.md
+54 −0
@@ -0,0 +1,54 @@ | ||
| 1 | +# Recording `docs/demo.gif` | |
| 2 | + | |
| 3 | +The README and the Integrations page reference `mcp/docs/demo.gif` (also copied to | |
| 4 | +`hfmarketdata/web/public/downloads/mcp-demo.gif` when it exists). It is not committed yet — record it like this. | |
| 5 | + | |
| 6 | +## Setup (once) | |
| 7 | + | |
| 8 | +```bash | |
| 9 | +brew install vhs # charmbracelet/vhs: scripted terminal recordings → GIF | |
| 10 | +cd mcp && npm run build && npm i -g ./$(npm pack --silent) | |
| 11 | +claude mcp add hfmarketdata -e HFMD_API_KEY=$HFMD_API_KEY -- hfmarketdata-mcp | |
| 12 | +``` | |
| 13 | + | |
| 14 | +## Script — `scripts/demo.tape` | |
| 15 | + | |
| 16 | +```tape | |
| 17 | +Output docs/demo.gif | |
| 18 | +Set Theme "Catppuccin Mocha" | |
| 19 | +Set FontSize 15 | |
| 20 | +Set Width 1240 | |
| 21 | +Set Height 720 | |
| 22 | +Set TypingSpeed 40ms | |
| 23 | + | |
| 24 | +Type "claude" | |
| 25 | +Enter | |
| 26 | +Sleep 3s | |
| 27 | +Type "Plot me the CL term structure as an ASCII chart and tell me if it is in contango" | |
| 28 | +Enter | |
| 29 | +Sleep 18s | |
| 30 | +Type "Compare ESZ25 and ESH26 over 30 days" | |
| 31 | +Enter | |
| 32 | +Sleep 18s | |
| 33 | +Type "Screen US stocks with PE<15 and FCF yield>6% and show their last 3 quarters" | |
| 34 | +Enter | |
| 35 | +Sleep 20s | |
| 36 | +Ctrl+C | |
| 37 | +``` | |
| 38 | + | |
| 39 | +```bash | |
| 40 | +vhs scripts/demo.tape # writes docs/demo.gif (~4-8 MB; keep it under 10 MB — lower Width or Sleep if needed) | |
| 41 | +``` | |
| 42 | + | |
| 43 | +## Manual alternative | |
| 44 | + | |
| 45 | +1. `QuickTime Player → File → New Screen Recording`, select the terminal window (1240×720 px area). | |
| 46 | +2. Run the three prompts above in Claude Code with the `hfmarketdata` server enabled. | |
| 47 | +3. Convert: `ffmpeg -i demo.mov -vf "fps=12,scale=1240:-1:flags=lanczos" -loop 0 docs/demo.gif` (or `gifski --fps 12 --width 1240 -o docs/demo.gif demo.mov`). | |
| 48 | + | |
| 49 | +## Checklist before committing the GIF | |
| 50 | + | |
| 51 | +- No API key visible (use `HFMD_API_KEY=…` from the environment, never typed on screen). | |
| 52 | +- Shows at least one tool call line (`get_term_structure`, `get_bars`) and one result table. | |
| 53 | +- < 10 MB; first frame readable (the GIF is the README hero). | |
| 54 | +- Copy to `hfmarketdata/web/public/downloads/mcp-demo.gif` so the Integrations page can display it without external CDNs. | |
added
mcp/src/client.ts
+206 −0
@@ -0,0 +1,206 @@ | ||
| 1 | +/** | |
| 2 | + * Minimal HTTP client for the HF Market Data API (native fetch, no dependencies). | |
| 3 | + * | |
| 4 | + * - Base URL from `HFMD_BASE_URL` (default https://www.hfmarketdata.io). | |
| 5 | + * - Optional bearer key from `HFMD_API_KEY` (keyless mode works with reduced limits). | |
| 6 | + * - Always requests `format=json`, parses both the v1 envelope `{count,data}` and the | |
| 7 | + * v2 envelope `{data,meta}`, surfaces `X-RateLimit-*` headers and maps every failure to | |
| 8 | + * a single `HfmdError` shape (`{status, code, message, docs, retryAfter}`). | |
| 9 | + */ | |
| 10 | + | |
| 11 | +export const DEFAULT_BASE_URL = 'https://www.hfmarketdata.io'; | |
| 12 | +export const USER_AGENT = 'hfmarketdata-mcp/1.0.0 (+https://www.hfmarketdata.io/integrations/mcp)'; | |
| 13 | + | |
| 14 | +export type Params = Record<string, string | number | boolean | string[] | undefined | null>; | |
| 15 | + | |
| 16 | +export interface RateInfo { | |
| 17 | + limitRequests: number | null; | |
| 18 | + remainingRequests: number | null; | |
| 19 | + limitRows: number | null; | |
| 20 | + remainingRows: number | null; | |
| 21 | + /** Seconds (or epoch seconds, depending on the server) until the window resets. */ | |
| 22 | + reset: number | null; | |
| 23 | + rowCount: number | null; | |
| 24 | + retryAfter: number | null; | |
| 25 | +} | |
| 26 | + | |
| 27 | +export interface ApiResult<T = unknown> { | |
| 28 | + status: number; | |
| 29 | + url: string; | |
| 30 | + body: T; | |
| 31 | + rate: RateInfo; | |
| 32 | + ms: number; | |
| 33 | +} | |
| 34 | + | |
| 35 | +export class HfmdError extends Error { | |
| 36 | + status: number; | |
| 37 | + code: string; | |
| 38 | + docs?: string; | |
| 39 | + details?: unknown; | |
| 40 | + rate?: RateInfo; | |
| 41 | + url?: string; | |
| 42 | + | |
| 43 | + constructor(status: number, code: string, message: string, extra: { docs?: string; details?: unknown; rate?: RateInfo; url?: string } = {}) { | |
| 44 | + super(message); | |
| 45 | + this.name = 'HfmdError'; | |
| 46 | + this.status = status; | |
| 47 | + this.code = code; | |
| 48 | + this.docs = extra.docs; | |
| 49 | + this.details = extra.details; | |
| 50 | + this.rate = extra.rate; | |
| 51 | + this.url = extra.url; | |
| 52 | + } | |
| 53 | + | |
| 54 | + /** Human readable, single paragraph, safe to return to an LLM. */ | |
| 55 | + describe(): string { | |
| 56 | + const parts = [`HF Market Data error ${this.status} ${this.code}: ${this.message}`]; | |
| 57 | + if (this.status === 429) { | |
| 58 | + const ra = this.rate?.retryAfter; | |
| 59 | + parts.push(ra != null ? `Retry after ${ra}s.` : 'Retry later.'); | |
| 60 | + parts.push('Keyless mode allows 30 requests/hour; set HFMD_API_KEY (free account = 120 req/min) or narrow the query.'); | |
| 61 | + } else if (this.status === 401) { | |
| 62 | + parts.push('Check HFMD_API_KEY (format hfmd_live_…) or unset it to use keyless mode.'); | |
| 63 | + } else if (this.status === 404 && this.code === 'NOT_FOUND') { | |
| 64 | + parts.push('This endpoint may not be deployed yet on this server (v2 futures/fundamentals roll out progressively). Check hfmarketdata://status.'); | |
| 65 | + } | |
| 66 | + if (this.docs) parts.push(`Docs: ${this.docs}`); | |
| 67 | + if (this.url) parts.push(`URL: ${this.url}`); | |
| 68 | + return parts.join(' '); | |
| 69 | + } | |
| 70 | +} | |
| 71 | + | |
| 72 | +export interface ClientOptions { | |
| 73 | + baseUrl?: string; | |
| 74 | + apiKey?: string; | |
| 75 | + fetchImpl?: typeof fetch; | |
| 76 | + timeoutMs?: number; | |
| 77 | +} | |
| 78 | + | |
| 79 | +function num(h: Headers, k: string): number | null { | |
| 80 | + const v = h.get(k); | |
| 81 | + if (v == null || v === '') return null; | |
| 82 | + const n = Number(v); | |
| 83 | + return Number.isFinite(n) ? n : null; | |
| 84 | +} | |
| 85 | + | |
| 86 | +export function rateFromHeaders(h: Headers): RateInfo { | |
| 87 | + return { | |
| 88 | + limitRequests: num(h, 'x-ratelimit-limit-requests') ?? num(h, 'x-ratelimit-limit'), | |
| 89 | + remainingRequests: num(h, 'x-ratelimit-remaining-requests') ?? num(h, 'x-ratelimit-remaining'), | |
| 90 | + limitRows: num(h, 'x-ratelimit-limit-rows'), | |
| 91 | + remainingRows: num(h, 'x-ratelimit-remaining-rows'), | |
| 92 | + reset: num(h, 'x-ratelimit-reset'), | |
| 93 | + rowCount: num(h, 'x-row-count'), | |
| 94 | + retryAfter: num(h, 'retry-after'), | |
| 95 | + }; | |
| 96 | +} | |
| 97 | + | |
| 98 | +export function describeRate(rate: RateInfo | undefined): string | null { | |
| 99 | + if (!rate) return null; | |
| 100 | + const bits: string[] = []; | |
| 101 | + if (rate.remainingRequests != null) bits.push(`requests ${rate.remainingRequests}${rate.limitRequests != null ? `/${rate.limitRequests}` : ''} left`); | |
| 102 | + if (rate.remainingRows != null) bits.push(`rows ${rate.remainingRows}${rate.limitRows != null ? `/${rate.limitRows}` : ''} left`); | |
| 103 | + if (rate.reset != null) bits.push(`reset in ${rate.reset}s`); | |
| 104 | + return bits.length ? `Rate limit: ${bits.join(' · ')}` : null; | |
| 105 | +} | |
| 106 | + | |
| 107 | +export class HfmdClient { | |
| 108 | + readonly baseUrl: string; | |
| 109 | + readonly apiKey: string | undefined; | |
| 110 | + private readonly fetchImpl: typeof fetch; | |
| 111 | + private readonly timeoutMs: number; | |
| 112 | + | |
| 113 | + constructor(opts: ClientOptions = {}) { | |
| 114 | + this.baseUrl = (opts.baseUrl ?? process.env.HFMD_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, ''); | |
| 115 | + const key = opts.apiKey ?? process.env.HFMD_API_KEY; | |
| 116 | + this.apiKey = key && key.trim() ? key.trim() : undefined; | |
| 117 | + this.fetchImpl = opts.fetchImpl ?? globalThis.fetch; | |
| 118 | + this.timeoutMs = opts.timeoutMs ?? 60_000; | |
| 119 | + if (typeof this.fetchImpl !== 'function') { | |
| 120 | + throw new Error('global fetch is not available: hfmarketdata-mcp needs Node.js >= 20'); | |
| 121 | + } | |
| 122 | + } | |
| 123 | + | |
| 124 | + get keyless(): boolean { | |
| 125 | + return !this.apiKey; | |
| 126 | + } | |
| 127 | + | |
| 128 | + /** Websocket URL derived from the base URL (https → wss). */ | |
| 129 | + get wsUrl(): string { | |
| 130 | + return this.baseUrl.replace(/^http/, 'ws') + '/v1/stream'; | |
| 131 | + } | |
| 132 | + | |
| 133 | + buildUrl(path: string, params: Params = {}): string { | |
| 134 | + const u = new URL(this.baseUrl + (path.startsWith('/') ? path : `/${path}`)); | |
| 135 | + for (const [k, v] of Object.entries(params)) { | |
| 136 | + if (v === undefined || v === null || v === '') continue; | |
| 137 | + u.searchParams.set(k, Array.isArray(v) ? v.join(',') : String(v)); | |
| 138 | + } | |
| 139 | + if (!u.searchParams.has('format')) u.searchParams.set('format', 'json'); | |
| 140 | + return u.toString(); | |
| 141 | + } | |
| 142 | + | |
| 143 | + async get<T = unknown>(path: string, params: Params = {}): Promise<ApiResult<T>> { | |
| 144 | + const url = this.buildUrl(path, params); | |
| 145 | + const headers: Record<string, string> = { Accept: 'application/json', 'User-Agent': USER_AGENT }; | |
| 146 | + if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`; | |
| 147 | + const ctrl = new AbortController(); | |
| 148 | + const timer = setTimeout(() => ctrl.abort(), this.timeoutMs); | |
| 149 | + const t0 = Date.now(); | |
| 150 | + let res: Response; | |
| 151 | + try { | |
| 152 | + res = await this.fetchImpl(url, { method: 'GET', headers, signal: ctrl.signal }); | |
| 153 | + } catch (e) { | |
| 154 | + clearTimeout(timer); | |
| 155 | + const msg = e instanceof Error ? e.message : String(e); | |
| 156 | + const code = ctrl.signal.aborted ? 'TIMEOUT' : 'NETWORK_ERROR'; | |
| 157 | + throw new HfmdError(0, code, ctrl.signal.aborted ? `request timed out after ${this.timeoutMs} ms` : `network error: ${msg}`, { url }); | |
| 158 | + } | |
| 159 | + clearTimeout(timer); | |
| 160 | + const ms = Date.now() - t0; | |
| 161 | + const rate = rateFromHeaders(res.headers); | |
| 162 | + const ct = res.headers.get('content-type') || ''; | |
| 163 | + let body: unknown; | |
| 164 | + const text = await res.text(); | |
| 165 | + if (ct.includes('json') || text.startsWith('{') || text.startsWith('[')) { | |
| 166 | + try { | |
| 167 | + body = JSON.parse(text); | |
| 168 | + } catch { | |
| 169 | + body = text; | |
| 170 | + } | |
| 171 | + } else { | |
| 172 | + body = text; | |
| 173 | + } | |
| 174 | + if (!res.ok) throw errorFromResponse(res.status, body, rate, url); | |
| 175 | + return { status: res.status, url, body: body as T, rate, ms }; | |
| 176 | + } | |
| 177 | +} | |
| 178 | + | |
| 179 | +export function errorFromResponse(status: number, body: unknown, rate: RateInfo, url: string): HfmdError { | |
| 180 | + const b = (body ?? {}) as Record<string, unknown>; | |
| 181 | + const err = (b.error ?? {}) as Record<string, unknown>; | |
| 182 | + let code = typeof err.code === 'string' ? err.code : undefined; | |
| 183 | + let message = typeof err.message === 'string' ? err.message : undefined; | |
| 184 | + if (!message) { | |
| 185 | + const detail = b.detail; | |
| 186 | + if (typeof detail === 'string') message = detail; | |
| 187 | + else if (Array.isArray(detail)) message = detail.map((d) => (d && typeof d === 'object' && 'msg' in d ? `${(d as any).loc?.join('.') ?? ''}: ${(d as any).msg}` : JSON.stringify(d))).join('; '); | |
| 188 | + else if (typeof body === 'string' && body.trim()) message = body.trim().slice(0, 300); | |
| 189 | + } | |
| 190 | + if (!code) { | |
| 191 | + code = | |
| 192 | + status === 429 ? 'RATE_LIMIT_EXCEEDED' | |
| 193 | + : status === 401 ? 'INVALID_API_KEY' | |
| 194 | + : status === 403 ? 'FORBIDDEN' | |
| 195 | + : status === 404 ? 'NOT_FOUND' | |
| 196 | + : status === 400 || status === 422 ? 'INVALID_PARAMETER' | |
| 197 | + : status >= 500 ? 'INTERNAL_ERROR' | |
| 198 | + : 'HTTP_ERROR'; | |
| 199 | + } | |
| 200 | + return new HfmdError(status, code, message ?? `HTTP ${status}`, { | |
| 201 | + docs: typeof err.docs === 'string' ? err.docs : undefined, | |
| 202 | + details: err.details, | |
| 203 | + rate, | |
| 204 | + url, | |
| 205 | + }); | |
| 206 | +} | |
added
mcp/src/format.ts
+136 −0
@@ -0,0 +1,136 @@ | ||
| 1 | +/** | |
| 2 | + * Turns API payloads into compact, LLM-friendly text while keeping tool outputs bounded. | |
| 3 | + * | |
| 4 | + * Tabular payloads are rendered as `{"columns":[…],"rows":[[…]]}` (about half the tokens of an | |
| 5 | + * array of objects). Above `fullRowsThreshold` rows only the head/tail are kept, together with the | |
| 6 | + * total count, the columns, and a hint to narrow the request. | |
| 7 | + */ | |
| 8 | +import { describeRate, type ApiResult, type RateInfo } from './client.js'; | |
| 9 | + | |
| 10 | +export const DEFAULT_LIMIT = 500; | |
| 11 | +export const MAX_LIMIT = 5000; | |
| 12 | +export const FULL_ROWS_THRESHOLD = 200; | |
| 13 | +export const HEAD_TAIL = 15; | |
| 14 | + | |
| 15 | +export type Row = Record<string, unknown>; | |
| 16 | + | |
| 17 | +export interface Extracted { | |
| 18 | + rows: Row[] | null; | |
| 19 | + meta: Record<string, unknown>; | |
| 20 | + /** Anything that is not the rows nor the meta (e.g. v1 `tickers` list). */ | |
| 21 | + rest: Record<string, unknown>; | |
| 22 | +} | |
| 23 | + | |
| 24 | +/** Understand both envelopes: v1 `{count,data}` and v2 `{data,meta}`, plus bare arrays. */ | |
| 25 | +export function extract(body: unknown): Extracted { | |
| 26 | + if (Array.isArray(body)) return { rows: body.every(isRow) ? (body as Row[]) : null, meta: {}, rest: body.every(isRow) ? {} : { data: body } }; | |
| 27 | + if (!body || typeof body !== 'object') return { rows: null, meta: {}, rest: { value: body } }; | |
| 28 | + const b = body as Record<string, unknown>; | |
| 29 | + const meta: Record<string, unknown> = typeof b.meta === 'object' && b.meta ? { ...(b.meta as Record<string, unknown>) } : {}; | |
| 30 | + if (typeof b.count === 'number' && meta.count === undefined) meta.count = b.count; | |
| 31 | + const rest: Record<string, unknown> = {}; | |
| 32 | + for (const [k, v] of Object.entries(b)) if (k !== 'data' && k !== 'meta' && k !== 'count') rest[k] = v; | |
| 33 | + const data = b.data; | |
| 34 | + if (Array.isArray(data)) { | |
| 35 | + if (data.every(isRow)) return { rows: data as Row[], meta, rest }; | |
| 36 | + return { rows: null, meta, rest: { ...rest, data } }; | |
| 37 | + } | |
| 38 | + if (data && typeof data === 'object') return { rows: null, meta, rest: { ...rest, data } }; | |
| 39 | + return { rows: null, meta, rest }; | |
| 40 | +} | |
| 41 | + | |
| 42 | +function isRow(x: unknown): x is Row { | |
| 43 | + return !!x && typeof x === 'object' && !Array.isArray(x); | |
| 44 | +} | |
| 45 | + | |
| 46 | +export function columnsOf(rows: Row[]): string[] { | |
| 47 | + const cols: string[] = []; | |
| 48 | + const seen = new Set<string>(); | |
| 49 | + for (const r of rows.slice(0, 50)) for (const k of Object.keys(r)) if (!seen.has(k)) { seen.add(k); cols.push(k); } | |
| 50 | + return cols; | |
| 51 | +} | |
| 52 | + | |
| 53 | +function cell(v: unknown): unknown { | |
| 54 | + if (typeof v === 'number' && !Number.isInteger(v)) return Number(v.toFixed(6)); | |
| 55 | + return v === undefined ? null : v; | |
| 56 | +} | |
| 57 | + | |
| 58 | +export function toTable(rows: Row[], columns = columnsOf(rows)): { columns: string[]; rows: unknown[][] } { | |
| 59 | + return { columns, rows: rows.map((r) => columns.map((c) => cell(r[c]))) }; | |
| 60 | +} | |
| 61 | + | |
| 62 | +export interface RenderOptions { | |
| 63 | + /** Rows above which the output is summarised (head + tail). */ | |
| 64 | + fullRowsThreshold?: number; | |
| 65 | + headTail?: number; | |
| 66 | + title?: string; | |
| 67 | + hint?: string; | |
| 68 | +} | |
| 69 | + | |
| 70 | +/** Render a full API result as text. */ | |
| 71 | +export function renderResult(result: ApiResult, opts: RenderOptions = {}): string { | |
| 72 | + const { rows, meta, rest } = extract(result.body); | |
| 73 | + const lines: string[] = []; | |
| 74 | + if (opts.title) lines.push(opts.title); | |
| 75 | + const out: Record<string, unknown> = {}; | |
| 76 | + if (rows) { | |
| 77 | + const threshold = opts.fullRowsThreshold ?? FULL_ROWS_THRESHOLD; | |
| 78 | + const ht = opts.headTail ?? HEAD_TAIL; | |
| 79 | + const total = rows.length; | |
| 80 | + const columns = columnsOf(rows); | |
| 81 | + out.row_count = total; | |
| 82 | + if (typeof meta.count === 'number' && meta.count !== total) out.count_reported = meta.count; | |
| 83 | + out.columns = columns; | |
| 84 | + if (total <= threshold) { | |
| 85 | + out.rows = toTable(rows, columns).rows; | |
| 86 | + } else { | |
| 87 | + out.truncated = true; | |
| 88 | + out.first_rows = toTable(rows.slice(0, ht), columns).rows; | |
| 89 | + out.last_rows = toTable(rows.slice(-ht), columns).rows; | |
| 90 | + out.numeric_summary = numericSummary(rows, columns); | |
| 91 | + out.hint = opts.hint ?? `Showing ${ht} first and ${ht} last rows of ${total}. Narrow the date range, use a coarser timeframe, or lower \`limit\` to see every row.`; | |
| 92 | + } | |
| 93 | + if (meta.next_cursor) out.next_cursor = meta.next_cursor; | |
| 94 | + } | |
| 95 | + const metaRest = { ...meta }; | |
| 96 | + delete metaRest.count; | |
| 97 | + if (Object.keys(metaRest).length) out.meta = metaRest; | |
| 98 | + for (const [k, v] of Object.entries(rest)) out[k] = shrink(v); | |
| 99 | + lines.push(JSON.stringify(out)); | |
| 100 | + const rate = describeRate(result.rate); | |
| 101 | + if (rate) lines.push(rate); | |
| 102 | + return lines.join('\n'); | |
| 103 | +} | |
| 104 | + | |
| 105 | +/** Long arrays inside non-tabular payloads (e.g. 7 000 tickers) get truncated with a count. */ | |
| 106 | +export function shrink(v: unknown, max = 400): unknown { | |
| 107 | + if (Array.isArray(v) && v.length > max) return { count: v.length, first: v.slice(0, max), truncated: true }; | |
| 108 | + if (v && typeof v === 'object' && !Array.isArray(v)) { | |
| 109 | + const o: Record<string, unknown> = {}; | |
| 110 | + for (const [k, x] of Object.entries(v as Record<string, unknown>)) o[k] = shrink(x, max); | |
| 111 | + return o; | |
| 112 | + } | |
| 113 | + return v; | |
| 114 | +} | |
| 115 | + | |
| 116 | +export function numericSummary(rows: Row[], columns: string[]): Record<string, { min: number; max: number; first: number | null; last: number | null }> { | |
| 117 | + const s: Record<string, { min: number; max: number; first: number | null; last: number | null }> = {}; | |
| 118 | + for (const c of columns) { | |
| 119 | + let min = Infinity, max = -Infinity, first: number | null = null, last: number | null = null, n = 0; | |
| 120 | + for (const r of rows) { | |
| 121 | + const v = r[c]; | |
| 122 | + if (typeof v !== 'number' || !Number.isFinite(v)) continue; | |
| 123 | + n++; | |
| 124 | + if (first === null) first = v; | |
| 125 | + last = v; | |
| 126 | + if (v < min) min = v; | |
| 127 | + if (v > max) max = v; | |
| 128 | + } | |
| 129 | + if (n >= 2 && c !== 'datetime') s[c] = { min: Number(min.toFixed(6)), max: Number(max.toFixed(6)), first, last }; | |
| 130 | + } | |
| 131 | + return s; | |
| 132 | +} | |
| 133 | + | |
| 134 | +export function rateLine(rate: RateInfo | undefined): string { | |
| 135 | + return describeRate(rate) ?? 'Rate limit: headers not provided by the server'; | |
| 136 | +} | |
added
mcp/src/index.ts
+62 −0
@@ -0,0 +1,62 @@ | ||
| 1 | +#!/usr/bin/env node | |
| 2 | +/** | |
| 3 | + * `hfmarketdata-mcp` — stdio entry point. | |
| 4 | + * | |
| 5 | + * HFMD_API_KEY=hfmd_live_… hfmarketdata-mcp | |
| 6 | + * hfmarketdata-mcp --version | --help | --selftest | |
| 7 | + * | |
| 8 | + * All logs go to stderr (stdout is the MCP channel). | |
| 9 | + */ | |
| 10 | +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; | |
| 11 | +import { HfmdClient } from './client.js'; | |
| 12 | +import { createServer, SERVER_VERSION } from './server.js'; | |
| 13 | +import { TOOL_NAMES } from './tools.js'; | |
| 14 | + | |
| 15 | +const argv = process.argv.slice(2); | |
| 16 | + | |
| 17 | +if (argv.includes('--version') || argv.includes('-v')) { | |
| 18 | + console.log(SERVER_VERSION); | |
| 19 | + process.exit(0); | |
| 20 | +} | |
| 21 | + | |
| 22 | +if (argv.includes('--help') || argv.includes('-h')) { | |
| 23 | + console.log(`hfmarketdata-mcp ${SERVER_VERSION} — MCP server for https://www.hfmarketdata.io | |
| 24 | + | |
| 25 | +Usage: hfmarketdata-mcp start the stdio MCP server (used by Claude Code, Cursor, Codex…) | |
| 26 | + hfmarketdata-mcp --selftest call /v1/status and print the rate-limit headroom | |
| 27 | +Env: HFMD_API_KEY optional bearer key (free account: 120 req/min; keyless: 30 req/h per IP) | |
| 28 | + HFMD_BASE_URL API base URL (default https://www.hfmarketdata.io) | |
| 29 | +Tools: ${TOOL_NAMES.join(', ')} | |
| 30 | +Resources: hfmarketdata://status, hfmarketdata://limits | |
| 31 | +Prompts: term-structure-analysis, compare-contracts, fundamentals-snapshot`); | |
| 32 | + process.exit(0); | |
| 33 | +} | |
| 34 | + | |
| 35 | +if (argv.includes('--selftest')) { | |
| 36 | + const client = new HfmdClient(); | |
| 37 | + client | |
| 38 | + .get<Record<string, unknown>>('/v1/status') | |
| 39 | + .then((r) => { | |
| 40 | + const datasets = (r.body?.datasets ?? {}) as Record<string, unknown>; | |
| 41 | + console.log(`OK ${client.baseUrl} (${r.ms} ms, ${client.keyless ? 'keyless' : 'api key set'}) — datasets: ${Object.keys(datasets).join(', ') || 'n/a'}`); | |
| 42 | + console.log(JSON.stringify(r.rate)); | |
| 43 | + process.exit(0); | |
| 44 | + }) | |
| 45 | + .catch((e) => { | |
| 46 | + console.error(String(e?.describe?.() ?? e)); | |
| 47 | + process.exit(1); | |
| 48 | + }); | |
| 49 | +} else { | |
| 50 | + const { server, client } = createServer(); | |
| 51 | + const transport = new StdioServerTransport(); | |
| 52 | + server | |
| 53 | + .connect(transport) | |
| 54 | + .then(() => console.error(`hfmarketdata-mcp ${SERVER_VERSION} ready on stdio — ${client.baseUrl} (${client.keyless ? 'keyless, 30 req/h' : 'API key set'})`)) | |
| 55 | + .catch((e) => { | |
| 56 | + console.error('hfmarketdata-mcp failed to start:', e); | |
| 57 | + process.exit(1); | |
| 58 | + }); | |
| 59 | + const shutdown = () => { server.close().finally(() => process.exit(0)); }; | |
| 60 | + process.on('SIGINT', shutdown); | |
| 61 | + process.on('SIGTERM', shutdown); | |
| 62 | +} | |
added
mcp/src/prompts.ts
+96 −0
@@ -0,0 +1,96 @@ | ||
| 1 | +/** | |
| 2 | + * Reusable analysis prompts. Each returns a single user message that tells the model which tools to | |
| 3 | + * call and how to structure the answer. | |
| 4 | + */ | |
| 5 | +import { z } from 'zod'; | |
| 6 | + | |
| 7 | +export interface PromptDef<S extends Record<string, z.ZodTypeAny> = Record<string, z.ZodTypeAny>> { | |
| 8 | + name: string; | |
| 9 | + title: string; | |
| 10 | + description: string; | |
| 11 | + args: S; | |
| 12 | + build: (args: z.infer<z.ZodObject<S>>) => string; | |
| 13 | +} | |
| 14 | + | |
| 15 | +function def<S extends Record<string, z.ZodTypeAny>>(p: PromptDef<S>): PromptDef<S> { | |
| 16 | + return p; | |
| 17 | +} | |
| 18 | + | |
| 19 | +export const termStructureAnalysis = def({ | |
| 20 | + name: 'term-structure-analysis', | |
| 21 | + title: 'Term structure analysis', | |
| 22 | + description: 'Analyse the futures curve of a root (contango/backwardation, spreads, roll yield) on a date, optionally versus an earlier date.', | |
| 23 | + args: { | |
| 24 | + root: z.string().describe('Futures root, e.g. CL, NG, ES'), | |
| 25 | + as_of: z.string().optional().describe('Curve date YYYY-MM-DD (default latest)'), | |
| 26 | + compare_to: z.string().optional().describe('Earlier date to compare the curve with, YYYY-MM-DD'), | |
| 27 | + }, | |
| 28 | + build: ({ root, as_of, compare_to }) => [ | |
| 29 | + `Analyse the ${root.toUpperCase()} futures term structure${as_of ? ` as of ${as_of}` : ' (latest available)'}${compare_to ? ` and compare it with ${compare_to}` : ''}.`, | |
| 30 | + '', | |
| 31 | + 'Steps:', | |
| 32 | + `1. Call get_term_structure(root="${root.toUpperCase()}"${as_of ? `, as_of="${as_of}"` : ''}).${compare_to ? ` Then call it again with as_of="${compare_to}".` : ''}`, | |
| 33 | + '2. Determine whether the curve is in contango (upward sloping) or backwardation (downward sloping); quantify the front-to-second spread in price and in % annualised (spread / front price × 365 / days between expiries).', | |
| 34 | + '3. Note any kinks (seasonality for NG/ZC, delivery-month effects for CL) and where volume/open interest concentrate.', | |
| 35 | + '4. Explain the implied roll yield for a long front-month position and what the shape suggests about inventories / financing.', | |
| 36 | + '5. If the API reports missing prices, say so explicitly — never fill gaps.', | |
| 37 | + '', | |
| 38 | + 'Output: a short table (contract · expiry · price · spread vs front · OI) followed by 4-6 bullet conclusions.', | |
| 39 | + ].join('\n'), | |
| 40 | +}); | |
| 41 | + | |
| 42 | +export const compareContracts = def({ | |
| 43 | + name: 'compare-contracts', | |
| 44 | + title: 'Compare two futures contracts', | |
| 45 | + description: 'Compare two individual futures contracts (e.g. ESZ25 vs ESH26) over a window: performance, spread, volume/OI migration, calendar-spread behaviour.', | |
| 46 | + args: { | |
| 47 | + symbol_a: z.string().describe('First contract symbol, e.g. ESZ25'), | |
| 48 | + symbol_b: z.string().describe('Second contract symbol, e.g. ESH26'), | |
| 49 | + days: z.string().optional().describe('Look-back window in calendar days (default 30)'), | |
| 50 | + }, | |
| 51 | + build: ({ symbol_a, symbol_b, days }) => { | |
| 52 | + const n = Number(days) > 0 ? Number(days) : 30; | |
| 53 | + const a = symbol_a.toUpperCase(); | |
| 54 | + const b = symbol_b.toUpperCase(); | |
| 55 | + return [ | |
| 56 | + `Compare the futures contracts ${a} and ${b} over the last ${n} days.`, | |
| 57 | + '', | |
| 58 | + 'Steps:', | |
| 59 | + `1. Call get_coverage(kind="futures_contract", symbol="${a}") and the same for ${b} to know the available range and any gaps.`, | |
| 60 | + `2. Call get_bars(asset="futures_contract", symbol="${a}", timeframe="1day", start=<today - ${n} days>) and the same for ${b}.`, | |
| 61 | + '3. Align both series on datetime and compute: total return, annualised volatility, the daily calendar spread (B − A) and its trend, and the share of volume/open interest in each contract (roll migration).', | |
| 62 | + '4. Flag whichever contract looks like the front month and estimate the likely roll window from the volume crossover.', | |
| 63 | + '5. Mention explicitly if a day is missing in one leg — do not interpolate.', | |
| 64 | + '', | |
| 65 | + 'Output: a compact comparison table then 3-5 bullets of interpretation. Ask before pulling intraday data (large).', | |
| 66 | + ].join('\n'); | |
| 67 | + }, | |
| 68 | +}); | |
| 69 | + | |
| 70 | +export const fundamentalsSnapshot = def({ | |
| 71 | + name: 'fundamentals-snapshot', | |
| 72 | + title: 'Fundamentals snapshot', | |
| 73 | + description: 'One-page fundamental picture of a US company: latest ratios, last quarters, filings calendar and the caveats of point-in-time data.', | |
| 74 | + args: { | |
| 75 | + ticker: z.string().describe('US ticker, e.g. AAPL'), | |
| 76 | + quarters: z.string().optional().describe('Number of recent quarters to show (default 4)'), | |
| 77 | + }, | |
| 78 | + build: ({ ticker, quarters }) => { | |
| 79 | + const q = Number(quarters) > 0 ? Number(quarters) : 4; | |
| 80 | + const t = ticker.toUpperCase(); | |
| 81 | + return [ | |
| 82 | + `Build a fundamentals snapshot of ${t}.`, | |
| 83 | + '', | |
| 84 | + 'Steps:', | |
| 85 | + `1. get_ratios(ticker="${t}") for the current valuation / profitability / leverage picture.`, | |
| 86 | + `2. get_financial_statements(ticker="${t}", statement="income", period="quarterly", limit=${q}) and the same for "cashflow" (revenue, operating income, net income, operating cash flow, capex → free cash flow).`, | |
| 87 | + `3. get_filings(ticker="${t}", forms=["10-K","10-Q","8-K"], limit=8) to date the numbers and spot recent events.`, | |
| 88 | + `4. Optionally get_ratios_daily(ticker="${t}", metrics=["pe","fcf_yield"], start=<1 year ago>) to place today's valuation in its 1-year range.`, | |
| 89 | + '5. Every figure must cite the period end and the filing date it comes from (point-in-time). Say when a metric is unavailable rather than estimating it.', | |
| 90 | + '', | |
| 91 | + 'Output: headline (price-agnostic) summary, a quarterly table, valuation context, 3 risks/observations drawn only from the data returned.', | |
| 92 | + ].join('\n'); | |
| 93 | + }, | |
| 94 | +}); | |
| 95 | + | |
| 96 | +export const PROMPTS: PromptDef<any>[] = [termStructureAnalysis, compareContracts, fundamentalsSnapshot]; | |
added
mcp/src/resources.ts
+61 −0
@@ -0,0 +1,61 @@ | ||
| 1 | +/** | |
| 2 | + * MCP resources: `hfmarketdata://status` (dataset inventory) and `hfmarketdata://limits` | |
| 3 | + * (rate-limit tiers + the caller's current headroom when the server exposes /v1/limits). | |
| 4 | + */ | |
| 5 | +import { HfmdClient, HfmdError } from './client.js'; | |
| 6 | + | |
| 7 | +export const TIERS = [ | |
| 8 | + { tier: 'keyless (per IP)', window: '1 hour', requests: 30, rows: 100_000, max_rows_per_request: 5_000 }, | |
| 9 | + { tier: 'free (account + API key)', window: '1 minute', requests: 120, rows: 1_000_000, max_rows_per_request: 50_000 }, | |
| 10 | + { tier: 'high_usage (on request: contact@spboucher.ai)', window: '1 minute', requests: 600, rows: 10_000_000, max_rows_per_request: 200_000 }, | |
| 11 | +]; | |
| 12 | + | |
| 13 | +export interface ResourceDef { | |
| 14 | + name: string; | |
| 15 | + uri: string; | |
| 16 | + title: string; | |
| 17 | + description: string; | |
| 18 | + mimeType: string; | |
| 19 | + read: (client: HfmdClient) => Promise<string>; | |
| 20 | +} | |
| 21 | + | |
| 22 | +export const statusResource: ResourceDef = { | |
| 23 | + name: 'status', | |
| 24 | + uri: 'hfmarketdata://status', | |
| 25 | + title: 'HF Market Data — dataset status', | |
| 26 | + description: 'Live inventory of the data lake: number of symbols per asset class / timeframe / adjustment, futures roots, options quarters, last update.', | |
| 27 | + mimeType: 'application/json', | |
| 28 | + async read(client) { | |
| 29 | + const r = await client.get('/v1/status'); | |
| 30 | + return JSON.stringify({ base_url: client.baseUrl, fetched_ms: r.ms, ...(r.body as Record<string, unknown>) }, null, 1); | |
| 31 | + }, | |
| 32 | +}; | |
| 33 | + | |
| 34 | +export const limitsResource: ResourceDef = { | |
| 35 | + name: 'limits', | |
| 36 | + uri: 'hfmarketdata://limits', | |
| 37 | + title: 'HF Market Data — rate limits & tiers', | |
| 38 | + description: 'The three tiers (keyless / free / high usage) with their request and row quotas, whether this server runs with an API key, and the live headroom when /v1/limits is available.', | |
| 39 | + mimeType: 'application/json', | |
| 40 | + async read(client) { | |
| 41 | + const out: Record<string, unknown> = { | |
| 42 | + mode: client.keyless ? 'keyless (set HFMD_API_KEY for the free tier: 120 req/min)' : 'api key configured', | |
| 43 | + tiers: TIERS, | |
| 44 | + notes: [ | |
| 45 | + 'Parquet responses count half the rows against the quota; bulk downloads are quota-exempt; 304 responses are free.', | |
| 46 | + 'Every response carries X-RateLimit-Limit-Requests / -Remaining-Requests / -Limit-Rows / -Remaining-Rows / -Reset; 429 adds Retry-After.', | |
| 47 | + 'Create a free account at https://www.hfmarketdata.io/signup — keys look like hfmd_live_….', | |
| 48 | + ], | |
| 49 | + }; | |
| 50 | + try { | |
| 51 | + const r = await client.get('/v1/limits'); | |
| 52 | + out.live = r.body; | |
| 53 | + out.rate_headers = r.rate; | |
| 54 | + } catch (e) { | |
| 55 | + out.live = e instanceof HfmdError ? `unavailable (${e.status} ${e.code})` : 'unavailable'; | |
| 56 | + } | |
| 57 | + return JSON.stringify(out, null, 1); | |
| 58 | + }, | |
| 59 | +}; | |
| 60 | + | |
| 61 | +export const RESOURCES: ResourceDef[] = [statusResource, limitsResource]; | |
added
mcp/src/server.ts
+77 −0
@@ -0,0 +1,77 @@ | ||
| 1 | +/** | |
| 2 | + * Assemble the MCP server: 14 tools, 2 resources, 3 prompts, on top of an `HfmdClient`. | |
| 3 | + * Exported so that it can be embedded (tests use an in-memory transport). | |
| 4 | + */ | |
| 5 | +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; | |
| 6 | +import type { CallToolResult, GetPromptResult } from '@modelcontextprotocol/sdk/types.js'; | |
| 7 | +import { HfmdClient, type ClientOptions } from './client.js'; | |
| 8 | +import { PROMPTS } from './prompts.js'; | |
| 9 | +import { RESOURCES } from './resources.js'; | |
| 10 | +import { TOOLS, runTool, type ToolContext } from './tools.js'; | |
| 11 | + | |
| 12 | +export const SERVER_NAME = 'hfmarketdata'; | |
| 13 | +export const SERVER_VERSION = '1.0.0'; | |
| 14 | + | |
| 15 | +export interface ServerOptions extends ClientOptions { | |
| 16 | + client?: HfmdClient; | |
| 17 | + toolContext?: ToolContext; | |
| 18 | +} | |
| 19 | + | |
| 20 | +export function createServer(opts: ServerOptions = {}): { server: McpServer; client: HfmdClient } { | |
| 21 | + const client = opts.client ?? new HfmdClient(opts); | |
| 22 | + const server = new McpServer( | |
| 23 | + { name: SERVER_NAME, version: SERVER_VERSION }, | |
| 24 | + { | |
| 25 | + instructions: [ | |
| 26 | + 'HF Market Data: open high-frequency market data (stocks, ETFs, futures, crypto, indices, FX at 1-minute to daily since ~2007/2010; options chains with Greeks since 2010; SEC fundamentals point-in-time).', | |
| 27 | + 'Use search_symbols to resolve symbols, get_bars for prices, the futures tools for contracts/curves/continuous series, and the fundamentals tools for statements, ratios, screens and filings.', | |
| 28 | + 'Outputs are compact tables ({columns, rows}); results above 200 rows are summarised — narrow the range or lower `limit` to see everything.', | |
| 29 | + client.keyless | |
| 30 | + ? 'Running keyless (30 requests/hour per IP). Set HFMD_API_KEY for 120 requests/minute (free account at https://www.hfmarketdata.io/signup).' | |
| 31 | + : 'An API key is configured (free tier: 120 requests/minute).', | |
| 32 | + 'Intraday timestamps are naive US/Eastern; daily bars are dates. Missing values are null — never invent data.', | |
| 33 | + ].join(' '), | |
| 34 | + }, | |
| 35 | + ); | |
| 36 | + | |
| 37 | + for (const t of TOOLS) { | |
| 38 | + server.registerTool( | |
| 39 | + t.name, | |
| 40 | + { | |
| 41 | + title: t.title, | |
| 42 | + description: t.description, | |
| 43 | + inputSchema: t.schema, | |
| 44 | + annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: t.name !== 'subscribe_filings', openWorldHint: true }, | |
| 45 | + }, | |
| 46 | + async (args: unknown): Promise<CallToolResult> => { | |
| 47 | + const { text, isError } = await runTool(t.name, client, args, opts.toolContext); | |
| 48 | + return { content: [{ type: 'text', text }], isError: isError || undefined }; | |
| 49 | + }, | |
| 50 | + ); | |
| 51 | + } | |
| 52 | + | |
| 53 | + for (const r of RESOURCES) { | |
| 54 | + server.registerResource(r.name, r.uri, { title: r.title, description: r.description, mimeType: r.mimeType }, async (uri) => { | |
| 55 | + let text: string; | |
| 56 | + try { | |
| 57 | + text = await r.read(client); | |
| 58 | + } catch (e) { | |
| 59 | + text = JSON.stringify({ error: e instanceof Error ? e.message : String(e) }); | |
| 60 | + } | |
| 61 | + return { contents: [{ uri: uri.href, mimeType: r.mimeType, text }] }; | |
| 62 | + }); | |
| 63 | + } | |
| 64 | + | |
| 65 | + for (const p of PROMPTS) { | |
| 66 | + server.registerPrompt(p.name, { title: p.title, description: p.description, argsSchema: p.args }, (args: Record<string, unknown>): GetPromptResult => ({ | |
| 67 | + messages: [{ role: 'user' as const, content: { type: 'text' as const, text: p.build(args as never) } }], | |
| 68 | + })); | |
| 69 | + } | |
| 70 | + | |
| 71 | + return { server, client }; | |
| 72 | +} | |
| 73 | + | |
| 74 | +export { HfmdClient, HfmdError } from './client.js'; | |
| 75 | +export { TOOLS, TOOL_NAMES, runTool } from './tools.js'; | |
| 76 | +export { RESOURCES } from './resources.js'; | |
| 77 | +export { PROMPTS } from './prompts.js'; | |
added
mcp/src/stream.ts
+79 −0
@@ -0,0 +1,79 @@ | ||
| 1 | +/** | |
| 2 | + * Bounded consumer for the filings websocket (`wss://…/v1/stream`). | |
| 3 | + * | |
| 4 | + * Opens the socket, sends one `subscribe` message, then collects messages until either | |
| 5 | + * `maxMessages` are received or `timeoutMs` elapses, and closes. Uses the global `WebSocket` | |
| 6 | + * (Node >= 22, or Node 20 with `--experimental-websocket`). | |
| 7 | + */ | |
| 8 | + | |
| 9 | +export interface SubscribeOptions { | |
| 10 | + url: string; | |
| 11 | + channel?: string; | |
| 12 | + tickers?: string[] | 'all'; | |
| 13 | + forms?: string[]; | |
| 14 | + apiKey?: string; | |
| 15 | + maxMessages?: number; | |
| 16 | + timeoutMs?: number; | |
| 17 | + /** Injected for tests. */ | |
| 18 | + WebSocketImpl?: typeof WebSocket; | |
| 19 | +} | |
| 20 | + | |
| 21 | +export interface SubscribeResult { | |
| 22 | + url: string; | |
| 23 | + subscription: Record<string, unknown>; | |
| 24 | + messages: unknown[]; | |
| 25 | + stopped_because: 'max_messages' | 'timeout' | 'closed' | 'error'; | |
| 26 | + elapsed_ms: number; | |
| 27 | + error?: string; | |
| 28 | +} | |
| 29 | + | |
| 30 | +export const MAX_TIMEOUT_MS = 120_000; | |
| 31 | +export const MAX_MESSAGES = 100; | |
| 32 | + | |
| 33 | +export async function subscribeFilings(opts: SubscribeOptions): Promise<SubscribeResult> { | |
| 34 | + const WS = opts.WebSocketImpl ?? (globalThis as any).WebSocket as typeof WebSocket | undefined; | |
| 35 | + if (!WS) { | |
| 36 | + throw new Error('WebSocket is not available in this Node.js runtime. Use Node >= 22, or run Node 20 with --experimental-websocket.'); | |
| 37 | + } | |
| 38 | + const timeoutMs = Math.min(Math.max(opts.timeoutMs ?? 30_000, 1_000), MAX_TIMEOUT_MS); | |
| 39 | + const maxMessages = Math.min(Math.max(opts.maxMessages ?? 10, 1), MAX_MESSAGES); | |
| 40 | + const subscription: Record<string, unknown> = { action: 'subscribe', channel: opts.channel ?? 'filings', tickers: opts.tickers ?? 'all' }; | |
| 41 | + if (opts.forms?.length) subscription.forms = opts.forms; | |
| 42 | + if (opts.apiKey) subscription.api_key = opts.apiKey; | |
| 43 | + | |
| 44 | + const url = new URL(opts.url); | |
| 45 | + if (opts.apiKey) url.searchParams.set('api_key', opts.apiKey); // servers commonly accept the key as a query param on ws | |
| 46 | + const t0 = Date.now(); | |
| 47 | + const messages: unknown[] = []; | |
| 48 | + | |
| 49 | + return new Promise<SubscribeResult>((resolve) => { | |
| 50 | + let done = false; | |
| 51 | + let ws: WebSocket; | |
| 52 | + const finish = (why: SubscribeResult['stopped_because'], error?: string) => { | |
| 53 | + if (done) return; | |
| 54 | + done = true; | |
| 55 | + clearTimeout(timer); | |
| 56 | + try { ws?.close(); } catch { /* ignore */ } | |
| 57 | + const publicSub = { ...subscription }; | |
| 58 | + delete publicSub.api_key; | |
| 59 | + resolve({ url: opts.url, subscription: publicSub, messages, stopped_because: why, elapsed_ms: Date.now() - t0, error }); | |
| 60 | + }; | |
| 61 | + const timer = setTimeout(() => finish('timeout'), timeoutMs); | |
| 62 | + try { | |
| 63 | + ws = new WS(url.toString()); | |
| 64 | + } catch (e) { | |
| 65 | + finish('error', e instanceof Error ? e.message : String(e)); | |
| 66 | + return; | |
| 67 | + } | |
| 68 | + ws.addEventListener('open', () => ws.send(JSON.stringify(subscription))); | |
| 69 | + ws.addEventListener('message', (ev: MessageEvent) => { | |
| 70 | + const raw = typeof ev.data === 'string' ? ev.data : String(ev.data); | |
| 71 | + let parsed: unknown = raw; | |
| 72 | + try { parsed = JSON.parse(raw); } catch { /* keep raw text */ } | |
| 73 | + messages.push(parsed); | |
| 74 | + if (messages.length >= maxMessages) finish('max_messages'); | |
| 75 | + }); | |
| 76 | + ws.addEventListener('error', () => finish('error', 'websocket error (is the stream endpoint deployed? check hfmarketdata://status)')); | |
| 77 | + ws.addEventListener('close', () => finish('closed')); | |
| 78 | + }); | |
| 79 | +} | |
added
mcp/src/tools.ts
+429 −0
@@ -0,0 +1,429 @@ | ||
| 1 | +/** | |
| 2 | + * Tool catalogue. Each tool is a pure function `(client, args) => text` so it can be unit-tested | |
| 3 | + * without an MCP transport; `server.ts` wraps them into MCP tools. | |
| 4 | + * | |
| 5 | + * Endpoint map (HF Market Data): | |
| 6 | + * v1 (live) /v1/{asset}/tickers · /v1/bars/{asset}/{ticker} · /v1/bars/{asset}?tickers= · | |
| 7 | + * /v1/options/{tickers,chain,expirations,history} | |
| 8 | + * v2 futures /v1/futures/roots · /v1/futures/{root}/contracts · /v1/futures/contract/{symbol}/bars · | |
| 9 | + * /v1/futures/{root}/chain · /v1/futures/{root}/continuous · /v1/futures/{root}/term-structure · | |
| 10 | + * /v1/futures/contract/{symbol}/coverage | |
| 11 | + * v2 fundamentals /v1/fundamentals/{ticker}/{statements,facts/{concept},ratios,ratios/daily,filings,coverage} · | |
| 12 | + * /v1/fundamentals/screener · /v1/fundamentals/frames/{concept} | |
| 13 | + */ | |
| 14 | +import { z } from 'zod'; | |
| 15 | +import { HfmdClient, HfmdError, type Params } from './client.js'; | |
| 16 | +import { DEFAULT_LIMIT, MAX_LIMIT, extract, renderResult, rateLine, shrink } from './format.js'; | |
| 17 | +import { MAX_MESSAGES, MAX_TIMEOUT_MS, subscribeFilings, type SubscribeOptions } from './stream.js'; | |
| 18 | + | |
| 19 | +export type Shape = z.ZodRawShape; | |
| 20 | + | |
| 21 | +export interface ToolContext { | |
| 22 | + /** Injected websocket implementation (tests). */ | |
| 23 | + WebSocketImpl?: typeof WebSocket; | |
| 24 | +} | |
| 25 | + | |
| 26 | +export interface ToolDef<S extends Shape = Shape> { | |
| 27 | + name: string; | |
| 28 | + title: string; | |
| 29 | + description: string; | |
| 30 | + schema: S; | |
| 31 | + handler: (client: HfmdClient, args: z.infer<z.ZodObject<S>>, ctx?: ToolContext) => Promise<string>; | |
| 32 | +} | |
| 33 | + | |
| 34 | +function def<S extends Shape>(t: ToolDef<S>): ToolDef<S> { | |
| 35 | + return t; | |
| 36 | +} | |
| 37 | + | |
| 38 | +// --- shared schema fragments ------------------------------------------------------------------- | |
| 39 | + | |
| 40 | +const V1_ASSETS = ['stock', 'etf', 'crypto', 'index', 'fx', 'futures'] as const; | |
| 41 | +const TIMEFRAMES = ['1min', '5min', '30min', '1hour', '1day'] as const; | |
| 42 | +const INTERVAL_ALIAS: Record<string, string> = { '1m': '1min', '5m': '5min', '30m': '30min', '1h': '1hour', '1d': '1day', '1min': '1min', '5min': '5min', '30min': '30min', '1hour': '1hour', '1day': '1day' }; | |
| 43 | +const V2_INTERVAL: Record<string, string> = { '1min': '1m', '5min': '5m', '30min': '30m', '1hour': '1h', '1day': '1d', '1m': '1m', '5m': '5m', '30m': '30m', '1h': '1h', '1d': '1d' }; | |
| 44 | + | |
| 45 | +const limitSchema = z.number().int().min(1).max(MAX_LIMIT).default(DEFAULT_LIMIT) | |
| 46 | + .describe(`Maximum rows to fetch (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}). Results above 200 rows are summarised (head/tail + numeric ranges); lower the range or the limit to see every row.`); | |
| 47 | +const dateSchema = (what: string) => z.string().regex(/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?)?$/, 'expected YYYY-MM-DD or YYYY-MM-DDTHH:MM').optional().describe(what); | |
| 48 | +const timeframeSchema = z.enum([...TIMEFRAMES, '1m', '5m', '30m', '1h', '1d']).default('1day') | |
| 49 | + .describe('Bar size: 1min, 5min, 30min, 1hour or 1day (aliases 1m/5m/30m/1h/1d accepted). Intraday timestamps are naive US/Eastern; daily bars are dates.'); | |
| 50 | + | |
| 51 | +function tf(v: string | undefined): string { | |
| 52 | + return INTERVAL_ALIAS[v ?? '1day'] ?? '1day'; | |
| 53 | +} | |
| 54 | + | |
| 55 | +const TICKER_RE = /^[A-Za-z0-9._^=:-]{1,20}$/; | |
| 56 | +const symbol = (what: string) => z.string().regex(TICKER_RE, 'invalid symbol').transform((s) => s.trim().toUpperCase()).describe(what); | |
| 57 | + | |
| 58 | +// --- tools ------------------------------------------------------------------------------------- | |
| 59 | + | |
| 60 | +export const searchSymbols = def({ | |
| 61 | + name: 'search_symbols', | |
| 62 | + title: 'Search symbols', | |
| 63 | + description: | |
| 64 | + 'Find tickers, futures roots or optionable underlyings available in HF Market Data. ' + | |
| 65 | + 'asset=stock|etf|crypto|index|fx lists tickers (substring search, e.g. "AAP"); asset=futures lists futures roots (ES, CL, NG, 6E…) with names and data ranges; ' + | |
| 66 | + 'asset=options lists underlyings with options chains. Use this before get_bars when unsure of the exact symbol.', | |
| 67 | + schema: { | |
| 68 | + asset: z.enum([...V1_ASSETS, 'options']).describe('Universe to search.'), | |
| 69 | + query: z.string().max(40).optional().describe('Substring/prefix to match (case-insensitive). Omit to list the first `limit` symbols.'), | |
| 70 | + timeframe: timeframeSchema.optional().describe('For stock/etf/crypto/index/fx: only symbols with data at this bar size (default 1day).'), | |
| 71 | + limit: z.number().int().min(1).max(2000).default(100).describe('Max symbols returned (default 100).'), | |
| 72 | + }, | |
| 73 | + async handler(client, a) { | |
| 74 | + const q = a.query?.trim(); | |
| 75 | + if (a.asset === 'futures') { | |
| 76 | + const r = await client.get('/v1/futures/roots', { limit: 1000 }); | |
| 77 | + const { rows } = extract(r.body); | |
| 78 | + let list = rows ?? []; | |
| 79 | + if (q) { | |
| 80 | + const needle = q.toUpperCase(); | |
| 81 | + list = list.filter((x) => String(x.root ?? '').toUpperCase().includes(needle) || String(x.name ?? '').toUpperCase().includes(needle) || String(x.asset_class ?? '').toUpperCase().includes(needle)); | |
| 82 | + } | |
| 83 | + return renderResult({ ...r, body: { data: list.slice(0, a.limit), meta: { count: list.length, filtered_by: q ?? null } } }, { title: `Futures roots${q ? ` matching "${q}"` : ''}` }); | |
| 84 | + } | |
| 85 | + if (a.asset === 'options') { | |
| 86 | + const r = await client.get('/v1/options/tickers', { search: q, limit: a.limit }); | |
| 87 | + return renderResult(r, { title: `Optionable underlyings${q ? ` matching "${q}"` : ''}` }); | |
| 88 | + } | |
| 89 | + const r = await client.get<{ tickers?: string[]; count?: number }>(`/v1/${a.asset}/tickers`, { search: q, limit: a.limit, timeframe: tf(a.timeframe) }); | |
| 90 | + const body = r.body ?? {}; | |
| 91 | + const tickers = Array.isArray(body.tickers) ? body.tickers : []; | |
| 92 | + return [ | |
| 93 | + `${a.asset} tickers${q ? ` matching "${q}"` : ''}: ${body.count ?? tickers.length} available, showing ${Math.min(tickers.length, a.limit)}`, | |
| 94 | + JSON.stringify(shrink({ ...body, tickers: tickers.slice(0, a.limit) }, a.limit)), | |
| 95 | + rateLine(r.rate), | |
| 96 | + ].join('\n'); | |
| 97 | + }, | |
| 98 | +}); | |
| 99 | + | |
| 100 | +export const getBars = def({ | |
| 101 | + name: 'get_bars', | |
| 102 | + title: 'Get OHLCV bars', | |
| 103 | + description: | |
| 104 | + 'Historical OHLCV bars (open, high, low, close, volume) for one or several symbols. ' + | |
| 105 | + 'asset=stock|etf|crypto|index|fx use the v1 bars endpoint (adjustment adj_splitdiv by default for stocks/ETFs); ' + | |
| 106 | + 'asset=futures returns the vendor continuous series (adjustment contin_UNadj|contin_adj_ratio|contin_adj_absolute); ' + | |
| 107 | + 'asset=futures_contract takes an individual contract symbol such as ESZ25 or CLM26 (v2). ' + | |
| 108 | + 'Timeframes 1min…1day since ~2007 (2010 for futures). Dates YYYY-MM-DD; intraday datetimes are naive US/Eastern. ' + | |
| 109 | + 'Keep ranges small for intraday data (1 day of 1-min bars ≈ 390-1440 rows).', | |
| 110 | + schema: { | |
| 111 | + asset: z.enum([...V1_ASSETS, 'futures_contract']).describe('Asset class of the symbol(s).'), | |
| 112 | + symbol: z.union([symbol('Ticker / root / contract symbol, e.g. AAPL, SPY, BTCUSD, ES, ESZ25.'), z.array(symbol('Ticker')).min(1).max(20)]) | |
| 113 | + .describe('One symbol, or up to 20 symbols (stock/etf/crypto/index/fx only) fetched in a single request.'), | |
| 114 | + timeframe: timeframeSchema, | |
| 115 | + start: dateSchema('First bar (inclusive), YYYY-MM-DD or YYYY-MM-DDTHH:MM.'), | |
| 116 | + end: dateSchema('Last bar (inclusive), YYYY-MM-DD or YYYY-MM-DDTHH:MM.'), | |
| 117 | + adjustment: z.string().max(30).optional().describe('stock/etf: UNADJUSTED | adj_split | adj_splitdiv (default). futures continuous: contin_UNadj (default) | contin_adj_ratio | contin_adj_absolute. Ignored for other assets.'), | |
| 118 | + session: z.enum(['rth', 'eth', 'all']).optional().describe('futures_contract only: regular hours, extended hours or all (default all).'), | |
| 119 | + order: z.enum(['asc', 'desc']).default('asc').describe('Sort by datetime. Use desc with a small limit to get the latest bars.'), | |
| 120 | + limit: limitSchema, | |
| 121 | + }, | |
| 122 | + async handler(client, a) { | |
| 123 | + const timeframe = tf(a.timeframe); | |
| 124 | + if (a.asset === 'futures_contract') { | |
| 125 | + const sym = Array.isArray(a.symbol) ? a.symbol[0] : a.symbol; | |
| 126 | + if (Array.isArray(a.symbol) && a.symbol.length > 1) throw new HfmdError(400, 'INVALID_PARAMETER', 'futures_contract accepts a single contract symbol per call (call the tool once per contract).'); | |
| 127 | + const r = await client.get(`/v1/futures/contract/${encodeURIComponent(sym)}/bars`, { | |
| 128 | + interval: V2_INTERVAL[timeframe], from: a.start, to: a.end, session: a.session, order: a.order, limit: a.limit, | |
| 129 | + }); | |
| 130 | + return renderResult(r, { title: `${sym} ${timeframe} bars (individual futures contract)` }); | |
| 131 | + } | |
| 132 | + const common: Params = { timeframe, adjustment: a.adjustment, start: a.start, end: a.end, order: a.order, limit: a.limit }; | |
| 133 | + if (Array.isArray(a.symbol)) { | |
| 134 | + if (a.symbol.length === 1) return renderResult(await client.get(`/v1/bars/${a.asset}/${encodeURIComponent(a.symbol[0])}`, common), { title: `${a.symbol[0]} ${timeframe} bars` }); | |
| 135 | + const r = await client.get(`/v1/bars/${a.asset}`, { ...common, tickers: a.symbol }); | |
| 136 | + return renderResult(r, { title: `${a.symbol.join(', ')} ${timeframe} bars (multi, ${a.limit} rows total across tickers)` }); | |
| 137 | + } | |
| 138 | + const r = await client.get(`/v1/bars/${a.asset}/${encodeURIComponent(a.symbol)}`, common); | |
| 139 | + return renderResult(r, { title: `${a.symbol} ${timeframe} bars${a.asset === 'futures' ? ' (vendor continuous, ' + (a.adjustment ?? 'contin_UNadj') + ')' : ''}` }); | |
| 140 | + }, | |
| 141 | +}); | |
| 142 | + | |
| 143 | +export const getFuturesContracts = def({ | |
| 144 | + name: 'get_futures_contracts', | |
| 145 | + title: 'List futures contracts', | |
| 146 | + description: | |
| 147 | + 'List the individual contracts (expirations) of a futures root, e.g. ES → ESH25, ESM25, ESU25, ESZ25… with expiration/last-trading/first-notice dates, ' + | |
| 148 | + 'status (active/expired), data range, average daily volume and last open interest. Use it to pick contract symbols for get_bars(asset=futures_contract) or get_coverage.', | |
| 149 | + schema: { | |
| 150 | + root: symbol('Futures root, e.g. ES, NQ, CL, NG, GC, ZN, 6E.'), | |
| 151 | + status: z.enum(['active', 'expired', 'all']).default('all').describe('Filter by contract status.'), | |
| 152 | + year: z.number().int().min(2000).max(2100).optional().describe('Only contracts expiring in this year.'), | |
| 153 | + limit: z.number().int().min(1).max(1000).default(200).describe('Max contracts returned.'), | |
| 154 | + }, | |
| 155 | + async handler(client, a) { | |
| 156 | + const r = await client.get(`/v1/futures/${encodeURIComponent(a.root)}/contracts`, { status: a.status === 'all' ? undefined : a.status, year: a.year, limit: a.limit }); | |
| 157 | + return renderResult(r, { title: `${a.root} contracts (${a.status})`, fullRowsThreshold: 400 }); | |
| 158 | + }, | |
| 159 | +}); | |
| 160 | + | |
| 161 | +export const getFuturesChain = def({ | |
| 162 | + name: 'get_futures_chain', | |
| 163 | + title: 'Futures chain as of a date', | |
| 164 | + description: | |
| 165 | + 'The chain of listed contracts for a root as of a given date (default today): front month first, with expirations, days to expiry, last price, volume and open interest. ' + | |
| 166 | + 'Answers "which CL contract was the front month on 2024-03-15?" and gives the symbols needed for compare/term-structure work.', | |
| 167 | + schema: { | |
| 168 | + root: symbol('Futures root, e.g. ES, CL.'), | |
| 169 | + as_of: dateSchema('Reference date YYYY-MM-DD (default: latest available).'), | |
| 170 | + depth: z.number().int().min(1).max(60).optional().describe('Number of contracts along the curve (default: all listed on as_of).'), | |
| 171 | + }, | |
| 172 | + async handler(client, a) { | |
| 173 | + const r = await client.get(`/v1/futures/${encodeURIComponent(a.root)}/chain`, { as_of: a.as_of, depth: a.depth }); | |
| 174 | + return renderResult(r, { title: `${a.root} chain as of ${a.as_of ?? 'latest'}` }); | |
| 175 | + }, | |
| 176 | +}); | |
| 177 | + | |
| 178 | +export const getContinuous = def({ | |
| 179 | + name: 'get_continuous', | |
| 180 | + title: 'Custom continuous futures series', | |
| 181 | + description: | |
| 182 | + 'Build a continuous futures series server-side from individual contracts (v2): choose the roll rule (roll=volume rolls when the next contract out-trades the front; ' + | |
| 183 | + 'roll=open_interest on OI; roll=calendar N days before expiry), the price adjustment (adjust=none | back_adjusted (additive, preserves point changes) | ratio (multiplicative, preserves returns)) ' + | |
| 184 | + 'and the depth (1 = front month, 2 = second month…). The response meta lists the roll dates. Prefer this over get_bars(asset=futures) when the roll methodology matters.', | |
| 185 | + schema: { | |
| 186 | + root: symbol('Futures root, e.g. ES, CL, NG.'), | |
| 187 | + roll: z.string().max(30).default('volume').describe('Roll rule: volume (default), open_interest, calendar (with roll_days), or any rule listed by the API.'), | |
| 188 | + adjust: z.string().max(30).default('back_adjusted').describe('Price adjustment: none, back_adjusted (default), ratio.'), | |
| 189 | + depth: z.number().int().min(1).max(12).default(1).describe('Curve position: 1 = front month (default), 2 = second month, …'), | |
| 190 | + roll_days: z.number().int().min(0).max(60).optional().describe('For roll=calendar: business days before expiry to roll.'), | |
| 191 | + timeframe: timeframeSchema, | |
| 192 | + start: dateSchema('From date (inclusive).'), | |
| 193 | + end: dateSchema('To date (inclusive).'), | |
| 194 | + session: z.enum(['rth', 'eth', 'all']).optional().describe('Trading session filter (intraday).'), | |
| 195 | + limit: limitSchema, | |
| 196 | + }, | |
| 197 | + async handler(client, a) { | |
| 198 | + const timeframe = tf(a.timeframe); | |
| 199 | + const r = await client.get(`/v1/futures/${encodeURIComponent(a.root)}/continuous`, { | |
| 200 | + roll: a.roll, adjust: a.adjust, depth: a.depth, roll_days: a.roll_days, interval: V2_INTERVAL[timeframe], from: a.start, to: a.end, session: a.session, limit: a.limit, | |
| 201 | + }); | |
| 202 | + return renderResult(r, { title: `${a.root} continuous (roll=${a.roll}, adjust=${a.adjust}, depth=${a.depth}, ${timeframe})` }); | |
| 203 | + }, | |
| 204 | +}); | |
| 205 | + | |
| 206 | +export const getTermStructure = def({ | |
| 207 | + name: 'get_term_structure', | |
| 208 | + title: 'Futures term structure', | |
| 209 | + description: | |
| 210 | + 'The futures curve of a root on a date: every listed contract with its settlement/last price, volume, open interest, days to expiry, plus spreads vs the front month. ' + | |
| 211 | + 'Upward-sloping curve = contango (later contracts more expensive: storage/financing), downward = backwardation (tight spot market). Compare two dates by calling it twice.', | |
| 212 | + schema: { | |
| 213 | + root: symbol('Futures root, e.g. CL, NG, ES, ZC.'), | |
| 214 | + as_of: dateSchema('Curve date YYYY-MM-DD (default: latest).'), | |
| 215 | + depth: z.number().int().min(2).max(60).optional().describe('Number of contracts along the curve.'), | |
| 216 | + }, | |
| 217 | + async handler(client, a) { | |
| 218 | + const r = await client.get(`/v1/futures/${encodeURIComponent(a.root)}/term-structure`, { as_of: a.as_of, depth: a.depth }); | |
| 219 | + return renderResult(r, { title: `${a.root} term structure as of ${a.as_of ?? 'latest'}` }); | |
| 220 | + }, | |
| 221 | +}); | |
| 222 | + | |
| 223 | +export const getOptionsChain = def({ | |
| 224 | + name: 'get_options_chain', | |
| 225 | + title: 'Options chain with Greeks', | |
| 226 | + description: | |
| 227 | + 'End-of-day options chain of a US stock/ETF for one trade date: strike, expiry, call/put, bid/ask/last, volume, open interest, implied volatility and Greeks (delta, gamma, theta, vega). ' + | |
| 228 | + 'Chains are large: always filter by expiry and/or a strike range, or set list_expirations=true first to see the available expirations. History since 2010 by quarter.', | |
| 229 | + schema: { | |
| 230 | + ticker: symbol('Underlying ticker, e.g. AAPL, SPY, TSLA.'), | |
| 231 | + trade_date: dateSchema('Trade date YYYY-MM-DD (default: latest available).'), | |
| 232 | + expiry: dateSchema('Only this expiration date YYYY-MM-DD.'), | |
| 233 | + call_put: z.enum(['C', 'P', 'call', 'put']).optional().describe('Only calls or only puts.'), | |
| 234 | + strike_min: z.number().optional().describe('Minimum strike.'), | |
| 235 | + strike_max: z.number().optional().describe('Maximum strike.'), | |
| 236 | + min_volume: z.number().int().min(0).optional().describe('Drop contracts below this daily volume.'), | |
| 237 | + list_expirations: z.boolean().default(false).describe('If true, return only the expiration dates listed on trade_date (cheap) instead of the chain.'), | |
| 238 | + limit: limitSchema, | |
| 239 | + }, | |
| 240 | + async handler(client, a) { | |
| 241 | + if (a.list_expirations) { | |
| 242 | + const r = await client.get(`/v1/options/expirations/${encodeURIComponent(a.ticker)}`, { trade_date: a.trade_date }); | |
| 243 | + return renderResult(r, { title: `${a.ticker} option expirations${a.trade_date ? ` on ${a.trade_date}` : ''}` }); | |
| 244 | + } | |
| 245 | + const cp = a.call_put ? (a.call_put[0].toUpperCase()) : undefined; | |
| 246 | + const r = await client.get(`/v1/options/chain/${encodeURIComponent(a.ticker)}`, { | |
| 247 | + trade_date: a.trade_date, expiry: a.expiry, call_put: cp, strike_min: a.strike_min, strike_max: a.strike_max, min_volume: a.min_volume, limit: a.limit, | |
| 248 | + }); | |
| 249 | + return renderResult(r, { title: `${a.ticker} options chain${a.trade_date ? ` ${a.trade_date}` : ''}${a.expiry ? ` exp ${a.expiry}` : ''}` }); | |
| 250 | + }, | |
| 251 | +}); | |
| 252 | + | |
| 253 | +export const getCoverage = def({ | |
| 254 | + name: 'get_coverage', | |
| 255 | + title: 'Data coverage / gaps', | |
| 256 | + description: | |
| 257 | + 'What data exists for a symbol and where the holes are. kind=futures_contract → per-timeframe first/last bar, bar counts and gaps > 3 business days for a contract such as ESZ25; ' + | |
| 258 | + 'kind=fundamentals → which statements/concepts/filings are available for a ticker and since when. Use it before drawing conclusions from sparse series (missing values are never invented by the API).', | |
| 259 | + schema: { | |
| 260 | + kind: z.enum(['futures_contract', 'fundamentals']).describe('Coverage type.'), | |
| 261 | + symbol: symbol('Contract symbol (ESZ25) or ticker (AAPL).'), | |
| 262 | + }, | |
| 263 | + async handler(client, a) { | |
| 264 | + const path = a.kind === 'futures_contract' ? `/v1/futures/contract/${encodeURIComponent(a.symbol)}/coverage` : `/v1/fundamentals/${encodeURIComponent(a.symbol)}/coverage`; | |
| 265 | + const r = await client.get(path); | |
| 266 | + return renderResult(r, { title: `${a.symbol} coverage (${a.kind})` }); | |
| 267 | + }, | |
| 268 | +}); | |
| 269 | + | |
| 270 | +export const getFinancialStatements = def({ | |
| 271 | + name: 'get_financial_statements', | |
| 272 | + title: 'Financial statements (SEC XBRL)', | |
| 273 | + description: | |
| 274 | + 'Standardised income statement, balance sheet and cash-flow statement of a US company from SEC XBRL filings, annual or quarterly, point-in-time (each row carries filed_at/accession so you know when the number became public). ' + | |
| 275 | + 'Pass concept (e.g. Revenues, NetIncomeLoss, us-gaap:Assets) to get the full history of a single XBRL fact including restatements instead of statements.', | |
| 276 | + schema: { | |
| 277 | + ticker: symbol('US ticker, e.g. AAPL, MSFT.'), | |
| 278 | + statement: z.enum(['income', 'balance', 'cashflow', 'all']).default('all').describe('Which statement(s).'), | |
| 279 | + period: z.enum(['annual', 'quarterly', 'ttm', 'all']).default('quarterly').describe('Fiscal period type.'), | |
| 280 | + concept: z.string().max(120).optional().describe('XBRL concept for a single-fact history (overrides statement).'), | |
| 281 | + start: dateSchema('Earliest period end date.'), | |
| 282 | + end: dateSchema('Latest period end date.'), | |
| 283 | + limit: z.number().int().min(1).max(MAX_LIMIT).default(40).describe('Max rows (default 40: ~10 years of quarters for one statement).'), | |
| 284 | + }, | |
| 285 | + async handler(client, a) { | |
| 286 | + if (a.concept) { | |
| 287 | + const r = await client.get(`/v1/fundamentals/${encodeURIComponent(a.ticker)}/facts/${encodeURIComponent(a.concept)}`, { period: a.period === 'all' ? undefined : a.period, from: a.start, to: a.end, limit: a.limit }); | |
| 288 | + return renderResult(r, { title: `${a.ticker} fact ${a.concept}` }); | |
| 289 | + } | |
| 290 | + const r = await client.get(`/v1/fundamentals/${encodeURIComponent(a.ticker)}/statements`, { | |
| 291 | + statement: a.statement === 'all' ? undefined : a.statement, period: a.period === 'all' ? undefined : a.period, from: a.start, to: a.end, limit: a.limit, | |
| 292 | + }); | |
| 293 | + return renderResult(r, { title: `${a.ticker} ${a.statement} statements (${a.period})`, fullRowsThreshold: 300 }); | |
| 294 | + }, | |
| 295 | +}); | |
| 296 | + | |
| 297 | +export const getRatios = def({ | |
| 298 | + name: 'get_ratios', | |
| 299 | + title: 'Latest fundamental ratios', | |
| 300 | + description: | |
| 301 | + 'Current valuation, profitability, leverage and growth ratios of a US company computed from its latest filings and price: P/E, P/B, EV/EBITDA, FCF yield, ROE, ROA, gross/operating/net margins, debt/equity, current ratio, revenue and EPS growth… ' + | |
| 302 | + 'Each ratio states the period it is based on. For a time series use get_ratios_daily.', | |
| 303 | + schema: { | |
| 304 | + ticker: symbol('US ticker, e.g. AAPL.'), | |
| 305 | + }, | |
| 306 | + async handler(client, a) { | |
| 307 | + const r = await client.get(`/v1/fundamentals/${encodeURIComponent(a.ticker)}/ratios`); | |
| 308 | + return renderResult(r, { title: `${a.ticker} ratios (latest)` }); | |
| 309 | + }, | |
| 310 | +}); | |
| 311 | + | |
| 312 | +export const getRatiosDaily = def({ | |
| 313 | + name: 'get_ratios_daily', | |
| 314 | + title: 'Daily ratio history', | |
| 315 | + description: | |
| 316 | + 'Daily history of price-based ratios (P/E, P/B, P/S, EV/EBITDA, FCF yield, dividend yield, market cap…) recomputed every trading day with the fundamentals known at that date (point-in-time, no look-ahead). ' + | |
| 317 | + 'Ideal for valuation charts and backtests. Narrow the date range or pick a few metrics to keep the output small.', | |
| 318 | + schema: { | |
| 319 | + ticker: symbol('US ticker.'), | |
| 320 | + metrics: z.array(z.string().max(40)).max(20).optional().describe('Subset of ratio columns, e.g. ["pe","pb","fcf_yield"]. Default: all.'), | |
| 321 | + start: dateSchema('From date.'), | |
| 322 | + end: dateSchema('To date.'), | |
| 323 | + limit: limitSchema, | |
| 324 | + }, | |
| 325 | + async handler(client, a) { | |
| 326 | + const r = await client.get(`/v1/fundamentals/${encodeURIComponent(a.ticker)}/ratios/daily`, { metrics: a.metrics, from: a.start, to: a.end, limit: a.limit }); | |
| 327 | + return renderResult(r, { title: `${a.ticker} daily ratios` }); | |
| 328 | + }, | |
| 329 | +}); | |
| 330 | + | |
| 331 | +export const screenFundamentals = def({ | |
| 332 | + name: 'screen_fundamentals', | |
| 333 | + title: 'Fundamental screener', | |
| 334 | + description: | |
| 335 | + 'Screen US stocks on fundamental ratios. filters is a comma-separated list of `metric<op>value` conditions with ops <, <=, >, >=, =, e.g. "pe<15,roe>0.15,fcf_yield>0.06,market_cap>1e9". ' + | |
| 336 | + 'Ratios are decimals (0.15 = 15 %). sort like "fcf_yield:desc". Optional as_of date screens with the data known on that day (point-in-time). Costs 2 requests of quota.', | |
| 337 | + schema: { | |
| 338 | + filters: z.string().min(1).max(500).describe('Conditions, e.g. "pe<15,roe>0.15". Known metrics: pe, pb, ps, ev_ebitda, fcf_yield, dividend_yield, roe, roa, gross_margin, operating_margin, net_margin, debt_to_equity, current_ratio, revenue_growth, eps_growth, market_cap, sector.'), | |
| 339 | + sort: z.string().max(60).optional().describe('metric:asc|desc, e.g. "fcf_yield:desc".'), | |
| 340 | + as_of: dateSchema('Point-in-time date YYYY-MM-DD (default: today).'), | |
| 341 | + columns: z.array(z.string().max(40)).max(30).optional().describe('Extra columns to return besides the filtered metrics.'), | |
| 342 | + limit: z.number().int().min(1).max(1000).default(50).describe('Max companies (default 50).'), | |
| 343 | + }, | |
| 344 | + async handler(client, a) { | |
| 345 | + const r = await client.get('/v1/fundamentals/screener', { filters: a.filters, sort: a.sort, as_of: a.as_of, columns: a.columns, limit: a.limit }); | |
| 346 | + return renderResult(r, { title: `Screener: ${a.filters}${a.sort ? ` sorted by ${a.sort}` : ''}` }); | |
| 347 | + }, | |
| 348 | +}); | |
| 349 | + | |
| 350 | +export const getFilings = def({ | |
| 351 | + name: 'get_filings', | |
| 352 | + title: 'SEC filings', | |
| 353 | + description: | |
| 354 | + 'SEC EDGAR filings of a US company (10-K, 10-Q, 8-K, 4, 13F, S-1, DEF 14A…): form type, filed date, period, accession number, primary document URL and a short description. ' + | |
| 355 | + 'Filter by forms and dates. For live notifications use subscribe_filings.', | |
| 356 | + schema: { | |
| 357 | + ticker: symbol('US ticker or CIK, e.g. AAPL.'), | |
| 358 | + forms: z.array(z.string().max(12)).max(20).optional().describe('Form types to keep, e.g. ["10-K","10-Q","8-K"].'), | |
| 359 | + start: dateSchema('Filed on/after this date.'), | |
| 360 | + end: dateSchema('Filed on/before this date.'), | |
| 361 | + limit: z.number().int().min(1).max(1000).default(50).describe('Max filings (default 50).'), | |
| 362 | + }, | |
| 363 | + async handler(client, a) { | |
| 364 | + const r = await client.get(`/v1/fundamentals/${encodeURIComponent(a.ticker)}/filings`, { forms: a.forms, from: a.start, to: a.end, limit: a.limit }); | |
| 365 | + return renderResult(r, { title: `${a.ticker} filings${a.forms ? ` (${a.forms.join(', ')})` : ''}` }); | |
| 366 | + }, | |
| 367 | +}); | |
| 368 | + | |
| 369 | +export const subscribeFilingsTool = def({ | |
| 370 | + name: 'subscribe_filings', | |
| 371 | + title: 'Listen to the live filings stream', | |
| 372 | + description: | |
| 373 | + 'Open the websocket wss://…/v1/stream, subscribe to the `filings` channel (new SEC filings as they are published) and return the messages received within a bounded window. ' + | |
| 374 | + `The call blocks until max_messages messages arrive or timeout_seconds elapse (max ${MAX_TIMEOUT_MS / 1000}s), then closes the socket — it does NOT stay subscribed between calls. ` + | |
| 375 | + 'Outside SEC business hours (roughly 06:00-22:00 US/Eastern, weekdays) expect zero messages. Requires Node >= 22 (global WebSocket) or Node 20 with --experimental-websocket.', | |
| 376 | + schema: { | |
| 377 | + tickers: z.union([z.literal('all'), z.array(symbol('Ticker')).min(1).max(50)]).default('all').describe('Tickers to watch, or "all".'), | |
| 378 | + forms: z.array(z.string().max(12)).max(20).optional().describe('Form types to keep, e.g. ["8-K","4"]. Default: all forms.'), | |
| 379 | + max_messages: z.number().int().min(1).max(MAX_MESSAGES).default(10).describe('Stop after this many messages (default 10).'), | |
| 380 | + timeout_seconds: z.number().int().min(1).max(MAX_TIMEOUT_MS / 1000).default(30).describe('Stop after this many seconds (default 30, max 120).'), | |
| 381 | + }, | |
| 382 | + async handler(client, a, ctx) { | |
| 383 | + const opts: SubscribeOptions = { | |
| 384 | + url: client.wsUrl, channel: 'filings', tickers: a.tickers, forms: a.forms, apiKey: client.apiKey, | |
| 385 | + maxMessages: a.max_messages, timeoutMs: a.timeout_seconds * 1000, WebSocketImpl: ctx?.WebSocketImpl, | |
| 386 | + }; | |
| 387 | + const res = await subscribeFilings(opts); | |
| 388 | + const head = `Filings stream: ${res.messages.length} message(s) in ${(res.elapsed_ms / 1000).toFixed(1)}s, stopped because ${res.stopped_because}${res.error ? ` (${res.error})` : ''}.`; | |
| 389 | + const note = res.messages.length === 0 && res.stopped_because === 'timeout' ? ' No filing was published during the window — this is normal outside EDGAR hours; increase timeout_seconds or widen tickers/forms.' : ''; | |
| 390 | + return [head + note, JSON.stringify({ url: res.url, subscription: res.subscription, messages: res.messages })].join('\n'); | |
| 391 | + }, | |
| 392 | +}); | |
| 393 | + | |
| 394 | +export const TOOLS: ToolDef<any>[] = [ | |
| 395 | + searchSymbols, | |
| 396 | + getBars, | |
| 397 | + getFuturesContracts, | |
| 398 | + getFuturesChain, | |
| 399 | + getContinuous, | |
| 400 | + getTermStructure, | |
| 401 | + getOptionsChain, | |
| 402 | + getCoverage, | |
| 403 | + getFinancialStatements, | |
| 404 | + getRatios, | |
| 405 | + getRatiosDaily, | |
| 406 | + screenFundamentals, | |
| 407 | + getFilings, | |
| 408 | + subscribeFilingsTool, | |
| 409 | +]; | |
| 410 | + | |
| 411 | +export const TOOL_NAMES = TOOLS.map((t) => t.name); | |
| 412 | + | |
| 413 | +/** Run a tool by name with schema validation; used by tests and by the MCP wrapper. */ | |
| 414 | +export async function runTool(name: string, client: HfmdClient, rawArgs: unknown, ctx?: ToolContext): Promise<{ text: string; isError: boolean }> { | |
| 415 | + const tool = TOOLS.find((t) => t.name === name); | |
| 416 | + if (!tool) return { text: `Unknown tool ${name}. Available: ${TOOL_NAMES.join(', ')}`, isError: true }; | |
| 417 | + const parsed = z.object(tool.schema).safeParse(rawArgs ?? {}); | |
| 418 | + if (!parsed.success) { | |
| 419 | + const issues = parsed.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`).join('; '); | |
| 420 | + return { text: `Invalid arguments for ${name}: ${issues}`, isError: true }; | |
| 421 | + } | |
| 422 | + try { | |
| 423 | + return { text: await tool.handler(client, parsed.data, ctx), isError: false }; | |
| 424 | + } catch (e) { | |
| 425 | + if (e instanceof HfmdError) return { text: e.describe(), isError: true }; | |
| 426 | + const msg = e instanceof Error ? e.message : String(e); | |
| 427 | + return { text: `${name} failed: ${msg}`, isError: true }; | |
| 428 | + } | |
| 429 | +} | |
added
mcp/test/client.test.ts
+74 −0
@@ -0,0 +1,74 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { HfmdClient, HfmdError, errorFromResponse, rateFromHeaders } from '../src/client.js'; | |
| 3 | +import { RATE_HEADERS, mockFetch } from './helpers.js'; | |
| 4 | + | |
| 5 | +describe('HfmdClient', () => { | |
| 6 | + it('builds URLs with format=json and joins array params', () => { | |
| 7 | + const c = new HfmdClient({ baseUrl: 'https://api.test/', apiKey: '', fetchImpl: fetch }); | |
| 8 | + const u = c.buildUrl('/v1/bars/stock', { tickers: ['AAPL', 'MSFT'], limit: 10, start: undefined, end: '' }); | |
| 9 | + expect(u).toBe('https://api.test/v1/bars/stock?tickers=AAPL%2CMSFT&limit=10&format=json'); | |
| 10 | + expect(c.wsUrl).toBe('wss://api.test/v1/stream'); | |
| 11 | + }); | |
| 12 | + | |
| 13 | + it('sends the bearer header only when a key is set', async () => { | |
| 14 | + let seen: Headers | undefined; | |
| 15 | + const f = (async (_u: any, init?: RequestInit) => { | |
| 16 | + seen = new Headers(init?.headers as any); | |
| 17 | + return new Response('{"ok":true}', { headers: { 'content-type': 'application/json' } }); | |
| 18 | + }) as typeof fetch; | |
| 19 | + await new HfmdClient({ baseUrl: 'https://api.test', apiKey: 'hfmd_live_x', fetchImpl: f }).get('/v1/status'); | |
| 20 | + expect(seen?.get('authorization')).toBe('Bearer hfmd_live_x'); | |
| 21 | + await new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: f }).get('/v1/status'); | |
| 22 | + expect(seen?.get('authorization')).toBeNull(); | |
| 23 | + expect(seen?.get('user-agent')).toContain('hfmarketdata-mcp'); | |
| 24 | + }); | |
| 25 | + | |
| 26 | + it('parses rate-limit headers', async () => { | |
| 27 | + const m = mockFetch([{ match: '/v1/status', body: { ok: 1 }, headers: RATE_HEADERS }]); | |
| 28 | + const r = await new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: m.fetch }).get('/v1/status'); | |
| 29 | + expect(r.rate.remainingRequests).toBe(29); | |
| 30 | + expect(r.rate.limitRows).toBe(100000); | |
| 31 | + expect(r.rate.rowCount).toBe(5); | |
| 32 | + expect(rateFromHeaders(new Headers()).remainingRequests).toBeNull(); | |
| 33 | + }); | |
| 34 | + | |
| 35 | + it('maps v2 error envelopes', async () => { | |
| 36 | + const m = mockFetch([{ match: '/v1/futures/XX/chain', status: 404, body: { error: { code: 'ROOT_NOT_FOUND', message: 'Unknown futures root XX', docs: 'https://d/errors#root_not_found' }, detail: 'Unknown futures root XX' } }]); | |
| 37 | + const c = new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: m.fetch }); | |
| 38 | + const err = await c.get('/v1/futures/XX/chain').catch((e) => e); | |
| 39 | + expect(err).toBeInstanceOf(HfmdError); | |
| 40 | + expect(err.status).toBe(404); | |
| 41 | + expect(err.code).toBe('ROOT_NOT_FOUND'); | |
| 42 | + expect(err.describe()).toContain('ROOT_NOT_FOUND'); | |
| 43 | + expect(err.describe()).toContain('https://d/errors#root_not_found'); | |
| 44 | + }); | |
| 45 | + | |
| 46 | + it('maps legacy v1 {detail} errors and validation arrays', () => { | |
| 47 | + const e1 = errorFromResponse(404, { detail: 'Unknown ticker' }, rateFromHeaders(new Headers()), 'u'); | |
| 48 | + expect(e1.code).toBe('NOT_FOUND'); | |
| 49 | + expect(e1.message).toBe('Unknown ticker'); | |
| 50 | + const e2 = errorFromResponse(422, { detail: [{ loc: ['query', 'limit'], msg: 'must be > 0' }] }, rateFromHeaders(new Headers()), 'u'); | |
| 51 | + expect(e2.code).toBe('INVALID_PARAMETER'); | |
| 52 | + expect(e2.message).toContain('query.limit: must be > 0'); | |
| 53 | + const e3 = errorFromResponse(503, 'Service Unavailable', rateFromHeaders(new Headers()), 'u'); | |
| 54 | + expect(e3.code).toBe('INTERNAL_ERROR'); | |
| 55 | + }); | |
| 56 | + | |
| 57 | + it('explains 429 with Retry-After and the keyless hint', async () => { | |
| 58 | + const m = mockFetch([{ match: '/v1/bars', status: 429, body: { error: { code: 'RATE_LIMIT_EXCEEDED', message: 'quota exhausted' } }, headers: { 'retry-after': '41', 'x-ratelimit-remaining-requests': '0' } }]); | |
| 59 | + const c = new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: m.fetch }); | |
| 60 | + const err: HfmdError = await c.get('/v1/bars/stock/AAPL').catch((e) => e); | |
| 61 | + expect(err.status).toBe(429); | |
| 62 | + const text = err.describe(); | |
| 63 | + expect(text).toContain('Retry after 41s'); | |
| 64 | + expect(text).toContain('HFMD_API_KEY'); | |
| 65 | + }); | |
| 66 | + | |
| 67 | + it('wraps network failures', async () => { | |
| 68 | + const f = (async () => { throw new TypeError('fetch failed'); }) as unknown as typeof fetch; | |
| 69 | + const c = new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: f }); | |
| 70 | + const err: HfmdError = await c.get('/v1/status').catch((e) => e); | |
| 71 | + expect(err.code).toBe('NETWORK_ERROR'); | |
| 72 | + expect(err.status).toBe(0); | |
| 73 | + }); | |
| 74 | +}); | |
added
mcp/test/format.test.ts
+58 −0
@@ -0,0 +1,58 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { extract, renderResult, shrink } from '../src/format.js'; | |
| 3 | +import { rateFromHeaders } from '../src/client.js'; | |
| 4 | +import { bars } from './helpers.js'; | |
| 5 | + | |
| 6 | +const rate = rateFromHeaders(new Headers({ 'x-ratelimit-remaining-requests': '7', 'x-ratelimit-limit-requests': '30' })); | |
| 7 | + | |
| 8 | +describe('extract', () => { | |
| 9 | + it('understands the v1 envelope', () => { | |
| 10 | + const e = extract({ count: 2, data: [{ a: 1 }, { a: 2 }] }); | |
| 11 | + expect(e.rows).toHaveLength(2); | |
| 12 | + expect(e.meta.count).toBe(2); | |
| 13 | + }); | |
| 14 | + it('understands the v2 envelope and keeps meta', () => { | |
| 15 | + const e = extract({ data: [{ a: 1 }], meta: { count: 1, next_cursor: 'abc', roll_dates: ['2025-03-14'] } }); | |
| 16 | + expect(e.rows).toHaveLength(1); | |
| 17 | + expect(e.meta.next_cursor).toBe('abc'); | |
| 18 | + }); | |
| 19 | + it('keeps non-tabular payloads in rest', () => { | |
| 20 | + const e = extract({ asset: 'stock', count: 3, tickers: ['A', 'B', 'C'] }); | |
| 21 | + expect(e.rows).toBeNull(); | |
| 22 | + expect(e.rest.tickers).toEqual(['A', 'B', 'C']); | |
| 23 | + }); | |
| 24 | +}); | |
| 25 | + | |
| 26 | +describe('renderResult', () => { | |
| 27 | + it('renders small tables in full as columns/rows', () => { | |
| 28 | + const text = renderResult({ status: 200, url: 'u', body: { count: 3, data: bars(3) }, rate, ms: 1 }, { title: 'T' }); | |
| 29 | + expect(text.startsWith('T\n')).toBe(true); | |
| 30 | + const json = JSON.parse(text.split('\n')[1]); | |
| 31 | + expect(json.row_count).toBe(3); | |
| 32 | + expect(json.columns).toEqual(['ticker', 'datetime', 'open', 'high', 'low', 'close', 'volume']); | |
| 33 | + expect(json.rows).toHaveLength(3); | |
| 34 | + expect(json.truncated).toBeUndefined(); | |
| 35 | + expect(text).toContain('Rate limit: requests 7/30 left'); | |
| 36 | + }); | |
| 37 | + | |
| 38 | + it('summarises large tables with head/tail, numeric ranges and a hint', () => { | |
| 39 | + const text = renderResult({ status: 200, url: 'u', body: { data: bars(600), meta: { count: 600, next_cursor: 'n' } }, rate, ms: 1 }); | |
| 40 | + const json = JSON.parse(text.split('\n')[0]); | |
| 41 | + expect(json.truncated).toBe(true); | |
| 42 | + expect(json.row_count).toBe(600); | |
| 43 | + expect(json.first_rows).toHaveLength(15); | |
| 44 | + expect(json.last_rows).toHaveLength(15); | |
| 45 | + expect(json.numeric_summary.close.min).toBe(100.5); | |
| 46 | + expect(json.numeric_summary.close.max).toBe(699.5); | |
| 47 | + expect(json.hint).toContain('Narrow'); | |
| 48 | + expect(json.next_cursor).toBe('n'); | |
| 49 | + expect(text.length).toBeLessThan(6000); | |
| 50 | + }); | |
| 51 | + | |
| 52 | + it('shrinks long arrays inside non-tabular payloads', () => { | |
| 53 | + const s = shrink({ tickers: Array.from({ length: 1000 }, (_, i) => `T${i}`) }, 100) as any; | |
| 54 | + expect(s.tickers.count).toBe(1000); | |
| 55 | + expect(s.tickers.first).toHaveLength(100); | |
| 56 | + expect(s.tickers.truncated).toBe(true); | |
| 57 | + }); | |
| 58 | +}); | |
added
mcp/test/helpers.ts
+88 −0
@@ -0,0 +1,88 @@ | ||
| 1 | +import { HfmdClient } from '../src/client.js'; | |
| 2 | + | |
| 3 | +export interface MockRoute { | |
| 4 | + /** Substring or RegExp matched against the full URL. */ | |
| 5 | + match: string | RegExp; | |
| 6 | + status?: number; | |
| 7 | + body?: unknown; | |
| 8 | + headers?: Record<string, string>; | |
| 9 | +} | |
| 10 | + | |
| 11 | +export interface MockFetch { | |
| 12 | + fetch: typeof fetch; | |
| 13 | + calls: string[]; | |
| 14 | +} | |
| 15 | + | |
| 16 | +export function mockFetch(routes: MockRoute[]): MockFetch { | |
| 17 | + const calls: string[] = []; | |
| 18 | + const f = (async (input: RequestInfo | URL, init?: RequestInit) => { | |
| 19 | + const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url; | |
| 20 | + calls.push(url); | |
| 21 | + void init; | |
| 22 | + const route = routes.find((r) => (typeof r.match === 'string' ? url.includes(r.match) : r.match.test(url))); | |
| 23 | + if (!route) return new Response(JSON.stringify({ detail: 'Not Found' }), { status: 404, headers: { 'content-type': 'application/json' } }); | |
| 24 | + const status = route.status ?? 200; | |
| 25 | + const headers = { 'content-type': 'application/json', ...(route.headers ?? {}) }; | |
| 26 | + return new Response(typeof route.body === 'string' ? route.body : JSON.stringify(route.body ?? {}), { status, headers }); | |
| 27 | + }) as typeof fetch; | |
| 28 | + return { fetch: f, calls }; | |
| 29 | +} | |
| 30 | + | |
| 31 | +export function clientWith(routes: MockRoute[], opts: { apiKey?: string } = {}): { client: HfmdClient; calls: string[] } { | |
| 32 | + const m = mockFetch(routes); | |
| 33 | + const client = new HfmdClient({ baseUrl: 'https://api.test', apiKey: opts.apiKey ?? '', fetchImpl: m.fetch }); | |
| 34 | + return { client, calls: m.calls }; | |
| 35 | +} | |
| 36 | + | |
| 37 | +export function bars(n: number, start = '2025-01-01', ticker = 'AAPL') { | |
| 38 | + const rows = []; | |
| 39 | + const d = new Date(start + 'T00:00:00Z'); | |
| 40 | + for (let i = 0; i < n; i++) { | |
| 41 | + const date = new Date(d.getTime() + i * 86_400_000).toISOString().slice(0, 10); | |
| 42 | + rows.push({ ticker, datetime: date, open: 100 + i, high: 101 + i, low: 99 + i, close: 100.5 + i, volume: 1000 + i }); | |
| 43 | + } | |
| 44 | + return rows; | |
| 45 | +} | |
| 46 | + | |
| 47 | +export const RATE_HEADERS = { | |
| 48 | + 'x-ratelimit-limit-requests': '30', | |
| 49 | + 'x-ratelimit-remaining-requests': '29', | |
| 50 | + 'x-ratelimit-limit-rows': '100000', | |
| 51 | + 'x-ratelimit-remaining-rows': '99500', | |
| 52 | + 'x-ratelimit-reset': '3599', | |
| 53 | + 'x-row-count': '5', | |
| 54 | +}; | |
| 55 | + | |
| 56 | +/** Minimal fake WebSocket emitting canned messages after open. */ | |
| 57 | +export function fakeWebSocketClass(messages: unknown[], opts: { failOpen?: boolean; delayMs?: number } = {}) { | |
| 58 | + const instances: FakeWS[] = []; | |
| 59 | + class FakeWS { | |
| 60 | + url: string; | |
| 61 | + sent: string[] = []; | |
| 62 | + closed = false; | |
| 63 | + private listeners: Record<string, ((ev: any) => void)[]> = {}; | |
| 64 | + constructor(url: string) { | |
| 65 | + this.url = url; | |
| 66 | + instances.push(this); | |
| 67 | + setTimeout(() => { | |
| 68 | + if (opts.failOpen) return this.emit('error', {}); | |
| 69 | + this.emit('open', {}); | |
| 70 | + for (const m of messages) setTimeout(() => this.emit('message', { data: typeof m === 'string' ? m : JSON.stringify(m) }), opts.delayMs ?? 1); | |
| 71 | + }, 1); | |
| 72 | + } | |
| 73 | + addEventListener(type: string, cb: (ev: any) => void) { | |
| 74 | + (this.listeners[type] ??= []).push(cb); | |
| 75 | + } | |
| 76 | + emit(type: string, ev: unknown) { | |
| 77 | + if (this.closed) return; | |
| 78 | + for (const cb of this.listeners[type] ?? []) cb(ev); | |
| 79 | + } | |
| 80 | + send(data: string) { | |
| 81 | + this.sent.push(data); | |
| 82 | + } | |
| 83 | + close() { | |
| 84 | + this.closed = true; | |
| 85 | + } | |
| 86 | + } | |
| 87 | + return { FakeWS: FakeWS as unknown as typeof WebSocket, instances }; | |
| 88 | +} | |
added
mcp/test/server.test.ts
+77 −0
@@ -0,0 +1,77 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; | |
| 3 | +import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; | |
| 4 | +import { createServer } from '../src/server.js'; | |
| 5 | +import { RATE_HEADERS, bars, clientWith } from './helpers.js'; | |
| 6 | + | |
| 7 | +async function connected(routes: Parameters<typeof clientWith>[0]) { | |
| 8 | + const { client: http, calls } = clientWith(routes); | |
| 9 | + const { server } = createServer({ client: http }); | |
| 10 | + const [a, b] = InMemoryTransport.createLinkedPair(); | |
| 11 | + await server.connect(a); | |
| 12 | + const mcp = new Client({ name: 'test', version: '0.0.0' }); | |
| 13 | + await mcp.connect(b); | |
| 14 | + return { mcp, server, calls }; | |
| 15 | +} | |
| 16 | + | |
| 17 | +describe('MCP server (in-memory transport)', () => { | |
| 18 | + it('lists 14 tools, 2 resources and 3 prompts with JSON schemas', async () => { | |
| 19 | + const { mcp, server } = await connected([]); | |
| 20 | + const tools = await mcp.listTools(); | |
| 21 | + expect(tools.tools.map((t) => t.name)).toHaveLength(14); | |
| 22 | + const getBars = tools.tools.find((t) => t.name === 'get_bars')!; | |
| 23 | + expect(getBars.inputSchema.type).toBe('object'); | |
| 24 | + expect(Object.keys((getBars.inputSchema as any).properties)).toContain('timeframe'); | |
| 25 | + expect((getBars.inputSchema as any).required).toEqual(['asset', 'symbol']); | |
| 26 | + const resources = await mcp.listResources(); | |
| 27 | + expect(resources.resources.map((r) => r.uri).sort()).toEqual(['hfmarketdata://limits', 'hfmarketdata://status']); | |
| 28 | + const prompts = await mcp.listPrompts(); | |
| 29 | + expect(prompts.prompts.map((p) => p.name).sort()).toEqual(['compare-contracts', 'fundamentals-snapshot', 'term-structure-analysis']); | |
| 30 | + await server.close(); | |
| 31 | + }); | |
| 32 | + | |
| 33 | + it('calls a tool end-to-end and returns text content', async () => { | |
| 34 | + const { mcp, server, calls } = await connected([{ match: '/v1/bars/stock/AAPL', body: { count: 2, data: bars(2) }, headers: RATE_HEADERS }]); | |
| 35 | + const res = await mcp.callTool({ name: 'get_bars', arguments: { asset: 'stock', symbol: 'AAPL', timeframe: '1d', limit: 2 } }); | |
| 36 | + expect(res.isError).toBeFalsy(); | |
| 37 | + const text = (res.content as any)[0].text as string; | |
| 38 | + expect(text).toContain('"row_count":2'); | |
| 39 | + expect(calls[0]).toContain('timeframe=1day'); | |
| 40 | + await server.close(); | |
| 41 | + }); | |
| 42 | + | |
| 43 | + it('returns isError for API failures and for bad arguments', async () => { | |
| 44 | + const { mcp, server } = await connected([{ match: '/v1/futures/ZZ/chain', status: 404, body: { error: { code: 'ROOT_NOT_FOUND', message: 'nope' } } }]); | |
| 45 | + const bad = await mcp.callTool({ name: 'get_futures_chain', arguments: { root: 'ZZ' } }); | |
| 46 | + expect(bad.isError).toBe(true); | |
| 47 | + expect((bad.content as any)[0].text).toContain('ROOT_NOT_FOUND'); | |
| 48 | + const invalid = await mcp.callTool({ name: 'get_futures_chain', arguments: { root: 'ZZ', depth: 999 } }); | |
| 49 | + expect(invalid.isError).toBe(true); | |
| 50 | + await server.close(); | |
| 51 | + }); | |
| 52 | + | |
| 53 | + it('reads both resources', async () => { | |
| 54 | + const { mcp, server } = await connected([{ match: '/v1/status', body: { datasets: { stock: { '1day': { adj_splitdiv: 7664 } } } } }]); | |
| 55 | + const status = await mcp.readResource({ uri: 'hfmarketdata://status' }); | |
| 56 | + expect((status.contents[0] as any).text).toContain('7664'); | |
| 57 | + const limits = await mcp.readResource({ uri: 'hfmarketdata://limits' }); | |
| 58 | + const text = (limits.contents[0] as any).text as string; | |
| 59 | + expect(text).toContain('keyless'); | |
| 60 | + expect(text).toContain('"requests": 120'); | |
| 61 | + expect(text).toContain('unavailable (404'); | |
| 62 | + await server.close(); | |
| 63 | + }); | |
| 64 | + | |
| 65 | + it('renders prompts with arguments', async () => { | |
| 66 | + const { mcp, server } = await connected([]); | |
| 67 | + const p = await mcp.getPrompt({ name: 'compare-contracts', arguments: { symbol_a: 'esz25', symbol_b: 'esh26', days: '45' } }); | |
| 68 | + const text = (p.messages[0].content as any).text as string; | |
| 69 | + expect(text).toContain('ESZ25'); | |
| 70 | + expect(text).toContain('45 days'); | |
| 71 | + const ts = await mcp.getPrompt({ name: 'term-structure-analysis', arguments: { root: 'cl' } }); | |
| 72 | + expect((ts.messages[0].content as any).text).toContain('get_term_structure(root="CL")'); | |
| 73 | + const fs = await mcp.getPrompt({ name: 'fundamentals-snapshot', arguments: { ticker: 'aapl' } }); | |
| 74 | + expect((fs.messages[0].content as any).text).toContain('get_ratios(ticker="AAPL")'); | |
| 75 | + await server.close(); | |
| 76 | + }); | |
| 77 | +}); | |
added
mcp/test/tools.test.ts
+252 −0
@@ -0,0 +1,252 @@ | ||
| 1 | +import { describe, expect, it } from 'vitest'; | |
| 2 | +import { TOOLS, TOOL_NAMES, runTool } from '../src/tools.js'; | |
| 3 | +import { RATE_HEADERS, bars, clientWith, fakeWebSocketClass } from './helpers.js'; | |
| 4 | + | |
| 5 | +const EXPECTED = [ | |
| 6 | + 'search_symbols', 'get_bars', 'get_futures_contracts', 'get_futures_chain', 'get_continuous', 'get_term_structure', | |
| 7 | + 'get_options_chain', 'get_coverage', 'get_financial_statements', 'get_ratios', 'get_ratios_daily', 'screen_fundamentals', | |
| 8 | + 'get_filings', 'subscribe_filings', | |
| 9 | +]; | |
| 10 | + | |
| 11 | +describe('tool catalogue', () => { | |
| 12 | + it('exposes exactly the 14 documented tools with descriptions', () => { | |
| 13 | + expect(TOOL_NAMES).toEqual(EXPECTED); | |
| 14 | + for (const t of TOOLS) { | |
| 15 | + expect(t.description.length).toBeGreaterThan(80); | |
| 16 | + expect(Object.keys(t.schema).length).toBeGreaterThan(0); | |
| 17 | + } | |
| 18 | + }); | |
| 19 | + | |
| 20 | + it('rejects invalid arguments before calling the API', async () => { | |
| 21 | + const { client, calls } = clientWith([]); | |
| 22 | + const r = await runTool('get_bars', client, { asset: 'stock', symbol: 'AAPL', start: '01/02/2025' }); | |
| 23 | + expect(r.isError).toBe(true); | |
| 24 | + expect(r.text).toContain('start'); | |
| 25 | + expect(calls).toHaveLength(0); | |
| 26 | + const r2 = await runTool('nope', client, {}); | |
| 27 | + expect(r2.isError).toBe(true); | |
| 28 | + }); | |
| 29 | +}); | |
| 30 | + | |
| 31 | +describe('search_symbols', () => { | |
| 32 | + it('lists stock tickers via /v1/{asset}/tickers with search', async () => { | |
| 33 | + const { client, calls } = clientWith([{ match: '/v1/stock/tickers', body: { asset: 'stock', timeframe: '1day', count: 3, tickers: ['AAPL', 'AAP', 'AAPG'] } }]); | |
| 34 | + const r = await runTool('search_symbols', client, { asset: 'stock', query: 'AAP', limit: 2 }); | |
| 35 | + expect(r.isError).toBe(false); | |
| 36 | + expect(calls[0]).toContain('/v1/stock/tickers?search=AAP&limit=2&timeframe=1day&format=json'); | |
| 37 | + expect(r.text).toContain('"tickers":["AAPL","AAP"]'); | |
| 38 | + }); | |
| 39 | + it('lists futures roots and filters client-side', async () => { | |
| 40 | + const { client, calls } = clientWith([{ match: '/v1/futures/roots', body: { data: [{ root: 'ES', name: 'E-mini S&P 500' }, { root: 'CL', name: 'Crude Oil WTI' }], meta: { count: 2 } } }]); | |
| 41 | + const r = await runTool('search_symbols', client, { asset: 'futures', query: 'crude' }); | |
| 42 | + expect(calls[0]).toContain('/v1/futures/roots'); | |
| 43 | + const json = JSON.parse(r.text.split('\n')[1]); | |
| 44 | + expect(json.row_count).toBe(1); | |
| 45 | + expect(json.rows[0][0]).toBe('CL'); | |
| 46 | + }); | |
| 47 | + it('lists optionable underlyings', async () => { | |
| 48 | + const { client, calls } = clientWith([{ match: '/v1/options/tickers', body: { count: 1, data: [{ ticker: 'AAPL' }] } }]); | |
| 49 | + const r = await runTool('search_symbols', client, { asset: 'options', query: 'AAP' }); | |
| 50 | + expect(calls[0]).toContain('/v1/options/tickers?search=AAP'); | |
| 51 | + expect(r.isError).toBe(false); | |
| 52 | + }); | |
| 53 | +}); | |
| 54 | + | |
| 55 | +describe('get_bars', () => { | |
| 56 | + it('fetches v1 bars with timeframe aliases and start/end, and shows rate headroom', async () => { | |
| 57 | + const { client, calls } = clientWith([{ match: '/v1/bars/stock/AAPL', body: { count: 5, data: bars(5) }, headers: RATE_HEADERS }]); | |
| 58 | + const r = await runTool('get_bars', client, { asset: 'stock', symbol: 'aapl', timeframe: '1h', start: '2025-01-01', end: '2025-01-10', limit: 100 }); | |
| 59 | + expect(r.isError).toBe(false); | |
| 60 | + const u = new URL(calls[0]); | |
| 61 | + expect(u.pathname).toBe('/v1/bars/stock/AAPL'); | |
| 62 | + expect(u.searchParams.get('timeframe')).toBe('1hour'); | |
| 63 | + expect(u.searchParams.get('start')).toBe('2025-01-01'); | |
| 64 | + expect(u.searchParams.get('end')).toBe('2025-01-10'); | |
| 65 | + expect(u.searchParams.get('limit')).toBe('100'); | |
| 66 | + expect(u.searchParams.get('format')).toBe('json'); | |
| 67 | + expect(r.text).toContain('AAPL 1hour bars'); | |
| 68 | + expect(r.text).toContain('Rate limit: requests 29/30 left'); | |
| 69 | + }); | |
| 70 | + it('uses the multi-ticker endpoint for arrays', async () => { | |
| 71 | + const { client, calls } = clientWith([{ match: '/v1/bars/etf?', body: { count: 2, data: [...bars(1, '2025-01-01', 'SPY'), ...bars(1, '2025-01-01', 'QQQ')] } }]); | |
| 72 | + const r = await runTool('get_bars', client, { asset: 'etf', symbol: ['SPY', 'QQQ'] }); | |
| 73 | + expect(r.isError).toBe(false); | |
| 74 | + const u = new URL(calls[0]); | |
| 75 | + expect(u.pathname).toBe('/v1/bars/etf'); | |
| 76 | + expect(u.searchParams.get('tickers')).toBe('SPY,QQQ'); | |
| 77 | + expect(u.searchParams.get('timeframe')).toBe('1day'); | |
| 78 | + }); | |
| 79 | + it('routes futures_contract to the v2 contract bars endpoint with interval/from/to', async () => { | |
| 80 | + const { client, calls } = clientWith([{ match: '/v1/futures/contract/ESZ25/bars', body: { data: bars(2, '2025-09-01', 'ES'), meta: { symbol: 'ESZ25', interval: '1d', count: 2 } } }]); | |
| 81 | + const r = await runTool('get_bars', client, { asset: 'futures_contract', symbol: 'ESZ25', start: '2025-09-01', end: '2025-09-30', session: 'rth' }); | |
| 82 | + const u = new URL(calls[0]); | |
| 83 | + expect(u.searchParams.get('interval')).toBe('1d'); | |
| 84 | + expect(u.searchParams.get('from')).toBe('2025-09-01'); | |
| 85 | + expect(u.searchParams.get('to')).toBe('2025-09-30'); | |
| 86 | + expect(u.searchParams.get('session')).toBe('rth'); | |
| 87 | + expect(r.text).toContain('"symbol":"ESZ25"'); | |
| 88 | + }); | |
| 89 | + it('summarises large results', async () => { | |
| 90 | + const { client } = clientWith([{ match: '/v1/bars/stock/AAPL', body: { count: 500, data: bars(500) } }]); | |
| 91 | + const r = await runTool('get_bars', client, { asset: 'stock', symbol: 'AAPL' }); | |
| 92 | + expect(r.text).toContain('"truncated":true'); | |
| 93 | + expect(r.text).toContain('"row_count":500'); | |
| 94 | + }); | |
| 95 | + it('surfaces 404 TICKER_NOT_FOUND as a readable error', async () => { | |
| 96 | + const { client } = clientWith([{ match: '/v1/bars/stock/ZZZZ', status: 404, body: { error: { code: 'TICKER_NOT_FOUND', message: 'Unknown ticker ZZZZ' }, detail: 'Unknown ticker ZZZZ' } }]); | |
| 97 | + const r = await runTool('get_bars', client, { asset: 'stock', symbol: 'ZZZZ' }); | |
| 98 | + expect(r.isError).toBe(true); | |
| 99 | + expect(r.text).toContain('404 TICKER_NOT_FOUND'); | |
| 100 | + }); | |
| 101 | +}); | |
| 102 | + | |
| 103 | +describe('futures tools', () => { | |
| 104 | + it('get_futures_contracts', async () => { | |
| 105 | + const { client, calls } = clientWith([{ match: '/v1/futures/ES/contracts', body: { data: [{ symbol: 'ESZ25', expiration_date: '2025-12-19', status: 'active' }], meta: { count: 1 } } }]); | |
| 106 | + const r = await runTool('get_futures_contracts', client, { root: 'es', status: 'active', year: 2025 }); | |
| 107 | + expect(r.isError).toBe(false); | |
| 108 | + const u = new URL(calls[0]); | |
| 109 | + expect(u.searchParams.get('status')).toBe('active'); | |
| 110 | + expect(u.searchParams.get('year')).toBe('2025'); | |
| 111 | + expect(r.text).toContain('ESZ25'); | |
| 112 | + }); | |
| 113 | + it('get_futures_chain', async () => { | |
| 114 | + const { client, calls } = clientWith([{ match: '/v1/futures/CL/chain', body: { data: [{ symbol: 'CLX24', dte: 30 }], meta: { as_of: '2024-09-15' } } }]); | |
| 115 | + const r = await runTool('get_futures_chain', client, { root: 'CL', as_of: '2024-09-15', depth: 6 }); | |
| 116 | + expect(r.isError).toBe(false); | |
| 117 | + expect(new URL(calls[0]).searchParams.get('as_of')).toBe('2024-09-15'); | |
| 118 | + expect(r.text).toContain('"as_of":"2024-09-15"'); | |
| 119 | + }); | |
| 120 | + it('get_continuous passes roll/adjust/depth/interval', async () => { | |
| 121 | + const { client, calls } = clientWith([{ match: '/v1/futures/ES/continuous', body: { data: bars(3, '2025-01-01', 'ES'), meta: { count: 3, roll_dates: ['2025-03-14'] } } }]); | |
| 122 | + const r = await runTool('get_continuous', client, { root: 'ES', roll: 'open_interest', adjust: 'ratio', depth: 2, timeframe: '1day', start: '2025-01-01' }); | |
| 123 | + const u = new URL(calls[0]); | |
| 124 | + expect(u.searchParams.get('roll')).toBe('open_interest'); | |
| 125 | + expect(u.searchParams.get('adjust')).toBe('ratio'); | |
| 126 | + expect(u.searchParams.get('depth')).toBe('2'); | |
| 127 | + expect(u.searchParams.get('interval')).toBe('1d'); | |
| 128 | + expect(u.searchParams.get('from')).toBe('2025-01-01'); | |
| 129 | + expect(r.text).toContain('roll_dates'); | |
| 130 | + }); | |
| 131 | + it('get_term_structure', async () => { | |
| 132 | + const { client, calls } = clientWith([{ match: '/v1/futures/NG/term-structure', body: { data: [{ symbol: 'NGX25', price: 3.1 }, { symbol: 'NGZ25', price: 3.4 }], meta: { as_of: '2025-09-01', shape: 'contango' } } }]); | |
| 133 | + const r = await runTool('get_term_structure', client, { root: 'NG', as_of: '2025-09-01' }); | |
| 134 | + expect(calls[0]).toContain('/v1/futures/NG/term-structure?as_of=2025-09-01'); | |
| 135 | + expect(r.text).toContain('contango'); | |
| 136 | + }); | |
| 137 | + it('get_coverage for a contract and for fundamentals', async () => { | |
| 138 | + const { client, calls } = clientWith([ | |
| 139 | + { match: '/v1/futures/contract/ESZ25/coverage', body: { symbol: 'ESZ25', timeframes: { '1day': { first: '2024-12-20', last: '2025-09-03', bars: 180 } }, gaps: [] } }, | |
| 140 | + { match: '/v1/fundamentals/AAPL/coverage', body: { ticker: 'AAPL', statements_since: '2009-09-26', filings: 210 } }, | |
| 141 | + ]); | |
| 142 | + const r1 = await runTool('get_coverage', client, { kind: 'futures_contract', symbol: 'ESZ25' }); | |
| 143 | + const r2 = await runTool('get_coverage', client, { kind: 'fundamentals', symbol: 'AAPL' }); | |
| 144 | + expect(r1.isError).toBe(false); | |
| 145 | + expect(r2.isError).toBe(false); | |
| 146 | + expect(calls[0]).toContain('/v1/futures/contract/ESZ25/coverage'); | |
| 147 | + expect(calls[1]).toContain('/v1/fundamentals/AAPL/coverage'); | |
| 148 | + expect(r1.text).toContain('"bars":180'); | |
| 149 | + }); | |
| 150 | +}); | |
| 151 | + | |
| 152 | +describe('options', () => { | |
| 153 | + it('get_options_chain filters and normalises call_put', async () => { | |
| 154 | + const { client, calls } = clientWith([{ match: '/v1/options/chain/AAPL', body: { count: 1, data: [{ strike: 200, call_put: 'C', delta: 0.5 }] } }]); | |
| 155 | + const r = await runTool('get_options_chain', client, { ticker: 'AAPL', trade_date: '2025-06-20', expiry: '2025-07-18', call_put: 'call', strike_min: 190, strike_max: 210 }); | |
| 156 | + const u = new URL(calls[0]); | |
| 157 | + expect(u.searchParams.get('call_put')).toBe('C'); | |
| 158 | + expect(u.searchParams.get('strike_min')).toBe('190'); | |
| 159 | + expect(u.searchParams.get('trade_date')).toBe('2025-06-20'); | |
| 160 | + expect(r.text).toContain('"delta"'); | |
| 161 | + }); | |
| 162 | + it('get_options_chain list_expirations', async () => { | |
| 163 | + const { client, calls } = clientWith([{ match: '/v1/options/expirations/SPY', body: { count: 2, data: [{ expiry: '2025-07-18' }, { expiry: '2025-08-15' }] } }]); | |
| 164 | + const r = await runTool('get_options_chain', client, { ticker: 'SPY', list_expirations: true }); | |
| 165 | + expect(calls[0]).toContain('/v1/options/expirations/SPY'); | |
| 166 | + expect(r.text).toContain('2025-08-15'); | |
| 167 | + }); | |
| 168 | +}); | |
| 169 | + | |
| 170 | +describe('fundamentals tools', () => { | |
| 171 | + it('get_financial_statements', async () => { | |
| 172 | + const { client, calls } = clientWith([{ match: '/v1/fundamentals/AAPL/statements', body: { data: [{ period_end: '2025-06-28', revenue: 94e9, filed_at: '2025-08-01' }], meta: { count: 1 } } }]); | |
| 173 | + const r = await runTool('get_financial_statements', client, { ticker: 'AAPL', statement: 'income', period: 'quarterly', limit: 4 }); | |
| 174 | + const u = new URL(calls[0]); | |
| 175 | + expect(u.searchParams.get('statement')).toBe('income'); | |
| 176 | + expect(u.searchParams.get('period')).toBe('quarterly'); | |
| 177 | + expect(u.searchParams.get('limit')).toBe('4'); | |
| 178 | + expect(r.text).toContain('filed_at'); | |
| 179 | + }); | |
| 180 | + it('get_financial_statements with concept → facts endpoint', async () => { | |
| 181 | + const { client, calls } = clientWith([{ match: '/v1/fundamentals/AAPL/facts/Revenues', body: { data: [{ period_end: '2024-09-28', value: 391e9 }], meta: {} } }]); | |
| 182 | + const r = await runTool('get_financial_statements', client, { ticker: 'AAPL', concept: 'Revenues', period: 'annual' }); | |
| 183 | + expect(calls[0]).toContain('/v1/fundamentals/AAPL/facts/Revenues'); | |
| 184 | + expect(r.isError).toBe(false); | |
| 185 | + }); | |
| 186 | + it('get_ratios', async () => { | |
| 187 | + const { client, calls } = clientWith([{ match: '/v1/fundamentals/MSFT/ratios?', body: { data: { pe: 33.1, roe: 0.38, fcf_yield: 0.025 }, meta: { as_of: '2025-09-03' } } }]); | |
| 188 | + const r = await runTool('get_ratios', client, { ticker: 'MSFT' }); | |
| 189 | + expect(calls[0]).toContain('/v1/fundamentals/MSFT/ratios?format=json'); | |
| 190 | + expect(r.text).toContain('"pe":33.1'); | |
| 191 | + }); | |
| 192 | + it('get_ratios_daily passes metrics/from/to', async () => { | |
| 193 | + const { client, calls } = clientWith([{ match: '/v1/fundamentals/MSFT/ratios/daily', body: { data: [{ date: '2025-01-02', pe: 30 }], meta: { count: 1 } } }]); | |
| 194 | + const r = await runTool('get_ratios_daily', client, { ticker: 'MSFT', metrics: ['pe', 'pb'], start: '2025-01-01', end: '2025-02-01' }); | |
| 195 | + const u = new URL(calls[0]); | |
| 196 | + expect(u.searchParams.get('metrics')).toBe('pe,pb'); | |
| 197 | + expect(u.searchParams.get('from')).toBe('2025-01-01'); | |
| 198 | + expect(r.isError).toBe(false); | |
| 199 | + }); | |
| 200 | + it('screen_fundamentals', async () => { | |
| 201 | + const { client, calls } = clientWith([{ match: '/v1/fundamentals/screener', body: { data: [{ ticker: 'XYZ', pe: 12, fcf_yield: 0.08 }], meta: { count: 1, as_of: '2025-09-03' } } }]); | |
| 202 | + const r = await runTool('screen_fundamentals', client, { filters: 'pe<15,fcf_yield>0.06', sort: 'fcf_yield:desc', limit: 20 }); | |
| 203 | + const u = new URL(calls[0]); | |
| 204 | + expect(u.searchParams.get('filters')).toBe('pe<15,fcf_yield>0.06'); | |
| 205 | + expect(u.searchParams.get('sort')).toBe('fcf_yield:desc'); | |
| 206 | + expect(r.text).toContain('XYZ'); | |
| 207 | + }); | |
| 208 | + it('get_filings', async () => { | |
| 209 | + const { client, calls } = clientWith([{ match: '/v1/fundamentals/AAPL/filings', body: { data: [{ form: '10-K', filed_at: '2024-11-01', url: 'https://sec.gov/x' }], meta: { count: 1 } } }]); | |
| 210 | + const r = await runTool('get_filings', client, { ticker: 'AAPL', forms: ['10-K', '10-Q'], start: '2024-01-01' }); | |
| 211 | + const u = new URL(calls[0]); | |
| 212 | + expect(u.searchParams.get('forms')).toBe('10-K,10-Q'); | |
| 213 | + expect(r.text).toContain('10-K'); | |
| 214 | + }); | |
| 215 | +}); | |
| 216 | + | |
| 217 | +describe('subscribe_filings', () => { | |
| 218 | + it('collects messages until max_messages then closes', async () => { | |
| 219 | + const { client } = clientWith([]); | |
| 220 | + const { FakeWS, instances } = fakeWebSocketClass([{ form: '8-K', ticker: 'AAPL' }, { form: '4', ticker: 'MSFT' }, { form: '10-Q', ticker: 'NVDA' }]); | |
| 221 | + const r = await runTool('subscribe_filings', client, { tickers: ['AAPL', 'MSFT'], forms: ['8-K', '4'], max_messages: 2, timeout_seconds: 5 }, { WebSocketImpl: FakeWS }); | |
| 222 | + expect(r.isError).toBe(false); | |
| 223 | + expect(r.text).toContain('2 message(s)'); | |
| 224 | + expect(r.text).toContain('stopped because max_messages'); | |
| 225 | + expect(instances[0].url).toBe('wss://api.test/v1/stream'); | |
| 226 | + const sub = JSON.parse(instances[0].sent[0]); | |
| 227 | + expect(sub).toEqual({ action: 'subscribe', channel: 'filings', tickers: ['AAPL', 'MSFT'], forms: ['8-K', '4'] }); | |
| 228 | + expect(instances[0].closed).toBe(true); | |
| 229 | + }); | |
| 230 | + it('times out gracefully with zero messages and explains', async () => { | |
| 231 | + const { client } = clientWith([]); | |
| 232 | + const { FakeWS } = fakeWebSocketClass([]); | |
| 233 | + const r = await runTool('subscribe_filings', client, { max_messages: 5, timeout_seconds: 1 }, { WebSocketImpl: FakeWS }); | |
| 234 | + expect(r.isError).toBe(false); | |
| 235 | + expect(r.text).toContain('0 message(s)'); | |
| 236 | + expect(r.text).toContain('stopped because timeout'); | |
| 237 | + expect(r.text).toContain('normal outside EDGAR hours'); | |
| 238 | + }); | |
| 239 | + it('reports websocket errors without throwing', async () => { | |
| 240 | + const { client } = clientWith([]); | |
| 241 | + const { FakeWS } = fakeWebSocketClass([], { failOpen: true }); | |
| 242 | + const r = await runTool('subscribe_filings', client, { timeout_seconds: 2 }, { WebSocketImpl: FakeWS }); | |
| 243 | + expect(r.text).toContain('stopped because error'); | |
| 244 | + }); | |
| 245 | + it('never leaks the api key in the returned subscription', async () => { | |
| 246 | + const { client } = clientWith([], { apiKey: 'hfmd_live_secret' }); | |
| 247 | + const { FakeWS, instances } = fakeWebSocketClass([{ ok: 1 }]); | |
| 248 | + const r = await runTool('subscribe_filings', client, { max_messages: 1, timeout_seconds: 2 }, { WebSocketImpl: FakeWS }); | |
| 249 | + expect(JSON.parse(instances[0].sent[0]).api_key).toBe('hfmd_live_secret'); | |
| 250 | + expect(r.text).not.toContain('hfmd_live_secret'); | |
| 251 | + }); | |
| 252 | +}); | |
added
mcp/tsconfig.json
+18 −0
@@ -0,0 +1,18 @@ | ||
| 1 | +{ | |
| 2 | + "compilerOptions": { | |
| 3 | + "target": "ES2022", | |
| 4 | + "module": "NodeNext", | |
| 5 | + "moduleResolution": "NodeNext", | |
| 6 | + "lib": ["ES2022", "DOM"], | |
| 7 | + "outDir": "dist", | |
| 8 | + "rootDir": "src", | |
| 9 | + "declaration": true, | |
| 10 | + "strict": true, | |
| 11 | + "esModuleInterop": true, | |
| 12 | + "skipLibCheck": true, | |
| 13 | + "forceConsistentCasingInFileNames": true, | |
| 14 | + "resolveJsonModule": true, | |
| 15 | + "types": ["node"] | |
| 16 | + }, | |
| 17 | + "include": ["src/**/*.ts"] | |
| 18 | +} | |
added
mcp/vitest.config.ts
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +import { defineConfig } from 'vitest/config'; | |
| 2 | + | |
| 3 | +export default defineConfig({ | |
| 4 | + test: { | |
| 5 | + include: ['test/**/*.test.ts'], | |
| 6 | + testTimeout: 15_000, | |
| 7 | + }, | |
| 8 | +}); | |
| 9 | ||