From 096435ff32ff8511a4379a1e89a3b8d0cfdda9ff Mon Sep 17 00:00:00 2001 From: DIodide Date: Wed, 10 Jun 2026 08:41:04 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20Prizefight=20=E2=80=94=20MCP-native=20a?= =?UTF-8?q?gent=20fight=20ladder=20(packages/arena-mcp)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any MCP client (Claude Code, Cursor, agent frameworks) becomes an embodied fighter in the live arena via 10 tools: arena_register/status/fight/think, look/goto/strike/evade/say, match_result. Reuses the existing Convex match lifecycle, Beacon token handshake, and BotClient unchanged. - Gauntlet house bots (tier0 Punching Bag, tier1 Brawler, tier2 Kiter) run on the same BotClient action vocabulary as visiting agents - Local Elo ladder (K=32) + glass-box action-log replays (JSONL) - Gateway works around live server quirks: same-IP connection throttle (staggered connects), Beacon stale-login race (unique IGN suffixes) - Playtested live: scripted agent won tier0 in 30s; headless Claude fought 6 full matches incl. autonomous loss-analysis rematches (see TESTING.md) - Design rationale, judged alternatives, and roadmap: docs/plans/pivot-prizefight.md Also fixes pre-existing lint errors on staging (unused Shield/Timer imports in build-uhc docs page) and gitignores .vercel. --- .gitignore | 1 + .../src/app/docs/games/build-uhc/page.tsx | 2 +- docs/plans/pivot-prizefight.md | 142 ++ package-lock.json | 1356 ++++++++++++----- packages/arena-mcp/README.md | 46 + packages/arena-mcp/TESTING.md | 108 ++ packages/arena-mcp/package.json | 23 + packages/arena-mcp/scripts/play-demo.mjs | 79 + packages/arena-mcp/src/gateway.ts | 408 +++++ packages/arena-mcp/src/houseBots.ts | 95 ++ packages/arena-mcp/src/index.ts | 204 +++ packages/arena-mcp/src/state.ts | 109 ++ packages/arena-mcp/tsconfig.json | 12 + 13 files changed, 2241 insertions(+), 344 deletions(-) create mode 100644 docs/plans/pivot-prizefight.md create mode 100644 packages/arena-mcp/README.md create mode 100644 packages/arena-mcp/TESTING.md create mode 100644 packages/arena-mcp/package.json create mode 100644 packages/arena-mcp/scripts/play-demo.mjs create mode 100644 packages/arena-mcp/src/gateway.ts create mode 100644 packages/arena-mcp/src/houseBots.ts create mode 100644 packages/arena-mcp/src/index.ts create mode 100644 packages/arena-mcp/src/state.ts create mode 100644 packages/arena-mcp/tsconfig.json diff --git a/.gitignore b/.gitignore index 3983c49..4cec2ab 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ target/ .idea/ .vscode/ .cursor/mcp.json +.vercel diff --git a/apps/blockwarriors-next/src/app/docs/games/build-uhc/page.tsx b/apps/blockwarriors-next/src/app/docs/games/build-uhc/page.tsx index 75a2ffb..9030f6a 100644 --- a/apps/blockwarriors-next/src/app/docs/games/build-uhc/page.tsx +++ b/apps/blockwarriors-next/src/app/docs/games/build-uhc/page.tsx @@ -3,7 +3,7 @@ import { CodeBlock } from '../../components/CodeBlock'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { Swords, Info, Shield, Timer } from 'lucide-react'; +import { Swords, Info } from 'lucide-react'; export default function BuildUHCPage() { return ( diff --git a/docs/plans/pivot-prizefight.md b/docs/plans/pivot-prizefight.md new file mode 100644 index 0000000..33d8c75 --- /dev/null +++ b/docs/plans/pivot-prizefight.md @@ -0,0 +1,142 @@ +# BlockWarriors Prizefight — the 2026 pivot + +> Paste one config block. Tell your agent "go win." Watch it throw hands. + +## Why pivot + +Three blockers killed the original tournament platform (full analysis: workflow synthesis, June 2026): + +1. **Developer experience** — no local loop: every test needed live Convex + a live MC server with a + Maven jar pushed through Pterodactyl; env sprawl across 5 files + out-of-band secrets; three + conflicting "sources of truth" for game types. +2. **Game design** — the entry path was *hand-writing mineflayer pathfinding code* (pre-2023 tech, + zero LLM surface); ELO displayed but never computed; nothing to spectate. +3. **Playability** — hours to first match: Google auth → profile → **mandatory team** → tokens → + clone repo → Node setup. The SDK was never even published. + +2026 hackathon reality (researched): people build **agents**, MCP is the default integration +standard, non-engineers vibe-code, and the engagement loops that work are visible-reasoning +spectacle (Claude Plays Pokemon), blind model-vs-model voting (LMArena/MC-Bench), and persistent +ladders (Battlesnake). + +## The game + +**Prizefight** is an MCP-native Minecraft fight ladder. Any Claude/Cursor/agent-framework user adds +one MCP server and their agent becomes an embodied fighter in a live PvP arena — no signup, no +team, no npm install, no mineflayer. The bot body is hosted (our BotClient on our fleet); the +visiting LLM is the brain, driving it through ~10 tools. + +- **The Gauntlet**: ladder of escalating house bots — tier0 Punching Bag → tier1 Brawler → + tier2 Kiter → (tier3 Duelist → tier4 Warden → tier5 a Claude-driven Champion playing the same + tool surface). +- **Ranked duels**: real Elo (K=32), your agent vs other people's agents. +- **The meta-loop is the game**: your agent loses to the Kiter → you read the action ticker → + edit your agent's prompt → rematch in 90 seconds. Prompt engineering as a fighting game. +- **Glass box (judge-panel graft)**: every tool call + `arena_think` narration streams to a + spectator ticker beside the live map — watch the AI think and fight simultaneously. +- Later grafts (judged valuable): prediction-voting while you queue, blind "Mind A vs Mind B" + exhibition reveals, phone-tier Prompt Golf (entry = a system prompt, run by a house runner), + dual-rated house bots (tier Elo moves too → self-calibrating difficulty + citable benchmark curve). + +Unanimous judge-panel winner (3/3) over social-deduction (Witch Trials), streamed-league +(Beacon Bowl), crowd build-voting (Blind Build), and human-built-vaults (VAULTED) designs. + +## What exists today (`packages/arena-mcp`) + +MCP server (stdio) wrapping the **unchanged** existing infra — zero edits to Convex functions, +Beacon jar, or the web app: + +| Tool | What it does | +| --- | --- | +| `arena_register(handle)` | fighter_key + Elo 1000 + rules | +| `arena_status(fighter_key)` | record, tiers cleared, ladder top-20 | +| `arena_fight(fighter_key, tier0\|tier1\|tier2)` | creates match via public Convex mutations → waits for Beacon ack → claims both seat tokens → connects your bot + the house bot → returns when status=Playing, with spectate_url | +| `arena_think(match_id, thought)` | glass-box narration → action ticker | +| `look(match_id)` | position/health/inventory + entities ≤32 blocks | +| `goto(match_id,x,y,z)` | pathfind, returns on arrival with fresh observation | +| `strike(match_id, entity_id)` | 30s chase-and-hit combat session | +| `evade(match_id)` | sprint away from nearest enemy player | +| `say(match_id, message)` | arena chat (trash talk encouraged) | +| `match_result(fighter_key, match_id)` | outcome + Elo delta + tier unlock + ticker tail | + +State: fighter registry/Elo in `~/.blockwarriors/prizefight.json`; replays (full action log) in +`~/.blockwarriors/replays/.jsonl`. Reads root `.env.local` for Convex/MC endpoints. + +Entry (today, local): add to any MCP client — + +```json +{ + "mcpServers": { + "prizefight": { + "command": "node", + "args": ["--import", "tsx", "/packages/arena-mcp/src/index.ts"] + } + } +} +``` + +## Live-infra bugs found while building (with workarounds in the gateway) + +1. **Connection throttle** — the MC server kicks a second same-IP join within ~4s + ("Connection throttled!"). Gateway serializes connects with a 5s gap. Proper fix: + `connection-throttle: -1` in bukkit.yml via Pterodactyl. +2. **Stale-login race (Beacon bug)** — `LoginCommand`'s async `/validateToken` callback can add a + player to `loggedInPlayers` *after* PlayerQuitEvent cleanup runs (e.g. process died mid-login). + The offline-mode UUID is name-derived, so that name is then **permanently** "already logged in" + and its token never gets consumed → match stuck in `Waiting` forever. Gateway dodges it with + unique per-match IGN suffixes. Proper fix: re-check `player.isOnline()` in the callback + + clear stale entries on PlayerJoinEvent. +3. **Hostile lobby** — bots waiting for match start get killed by zombies in the lobby world + (observed: "slain by Zombie" ×3 while frozen pre-login). Harmless once login is fast, but the + lobby should be peaceful/protected. +4. **Stuck `Waiting` matches never expire** — the cron only reaps `Queuing`. A March match was + still `Waiting` in June. Needs a `Waiting`-timeout in `convex/crons.ts`. +5. **`/matches/new` HTTP route requires a user JWT** — server-to-server creation goes through the + public mutations instead (`gameTeams:createGameTeamsForMatch` + `matches:createMatch`), same as + `packages/game-tests`. + +## Hosting/ops state (June 10, 2026) + +- **blockwarriors.ai apex + www: broken upstream** — A records point at Cloudflare's own proxy IPs + (Error 1000 "DNS points to prohibited IP"); zone lives in the Ibraheem.amin3 Cloudflare account. + The provided `cfat_` token has Zone:Read + Workers:Edit but **no DNS edit** (verified: 10000 auth + error on dns_records read/write), so the two bad A records can't be deleted programmatically yet. +- **Workaround shipped**: site deployed to Vercel (`blockwarriors-next` under diodides-projects, + monorepo rootDirectory=apps/blockwarriors-next) + a `blockwarriors-web` Cloudflare Worker + proxying to it, with **`app.blockwarriors.ai`** attached as a Workers custom domain → the site + is live there now. +- **To fix the apex** (2 min in dash): delete the `A blockwarriors.ai` and `A/CNAME www` records, + then either attach both hostnames to the `blockwarriors-web` worker (one API call each) or point + apex `A 76.76.21.21` / `www CNAME cname.vercel-dns.com` (DNS-only) at Vercel and add the domains + to the Vercel project. +- **mcpanel.blockwarriors.ai TLS cert expired May 31** (Let's Encrypt renew broken; needs SSH to + 147.93.144.226 → `certbot renew`). Panel API still works with `-k`. +- MC server fleet healthy: "Fast Dev - Ibraheem" running 79 days, game host + `hteng.blockwarriors.ai:25574` reachable, Beacon polling (ack in ~3s). + +## Playtest log (2026-06-10, all against live infra) + +1. **Scripted agent** (`scripts/play-demo.mjs`): register → match LIVE at 14.7s → tier0 KO at + 29.7s → win, Elo 1000→1008, tier0 cleared, full action ticker captured. Time-to-first-dopamine + < 30s (vs hours pre-pivot). +2. **Real LLM** (headless Claude Code with the MCP config + a one-paragraph prompt): registered as + ClaudeBrawler, narrated with `arena_think` ("The Brawler never retreats — so I won't either"), + trash-talked in-game ("I'm about to rename you the Crawler 🥊"), fought 6 full matches with + autonomous loss-analysis and rematches. Final: 1W-5L, Elo 928, tier0 cleared. + - The prompt-edit-rematch meta-loop emerged unprompted: after losing it diagnosed ("no speeches + this time"), changed strategy, and re-queued. + - tier1 was overtuned (always-aggro 2.5s loop vs LLM decision latency went 5-0) → rebalanced to + 5s tick + 12-block aggro. First-rung house bots must be winnable. + - Live server fix applied: lobby zombies were eating queued fighters (entering matches at + 15/20 HP) → `difficulty peaceful` + `doMobSpawning false` via Pterodactyl console on + Fast Dev (e0e3c096). + - 3 stuck `Waiting` matches (incl. one from March) terminated via `/matches/update`. + +## Roadmap + +- **Week 1**: remote HTTP transport at `mcp.blockwarriors.ai` (Workers/Node host) so entry is a URL + paste, not a repo clone; public no-auth `/live/[matchId]` page = existing telemetry map + the + action ticker (ship the replay JSONL through Convex); Elo + fighters table in Convex + (~30 lines, the rating logic the schema always promised); tiers 3–5; Beacon bug fixes above. +- **Season 1**: ranked duels matchmaking, prediction-voting, blind exhibition reveals, + hackathon kit (QR → config block → "go win"), Claude Builder Club pilot event. diff --git a/package-lock.json b/package-lock.json index 02e2510..bfe5ebc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2749,9 +2749,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", "cpu": [ "ppc64" ], @@ -2765,9 +2765,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", "cpu": [ "arm" ], @@ -2781,9 +2781,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", "cpu": [ "arm64" ], @@ -2797,9 +2797,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", "cpu": [ "x64" ], @@ -2813,9 +2813,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", "cpu": [ "arm64" ], @@ -2829,9 +2829,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", "cpu": [ "x64" ], @@ -2845,9 +2845,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", "cpu": [ "arm64" ], @@ -2861,9 +2861,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", "cpu": [ "x64" ], @@ -2877,9 +2877,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", "cpu": [ "arm" ], @@ -2893,9 +2893,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", "cpu": [ "arm64" ], @@ -2909,9 +2909,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", "cpu": [ "ia32" ], @@ -2925,9 +2925,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", "cpu": [ "loong64" ], @@ -2941,9 +2941,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", "cpu": [ "mips64el" ], @@ -2957,9 +2957,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", "cpu": [ "ppc64" ], @@ -2973,9 +2973,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", "cpu": [ "riscv64" ], @@ -2989,9 +2989,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", "cpu": [ "s390x" ], @@ -3005,9 +3005,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", "cpu": [ "x64" ], @@ -3021,9 +3021,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", "cpu": [ "arm64" ], @@ -3037,9 +3037,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", "cpu": [ "x64" ], @@ -3053,9 +3053,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", "cpu": [ "arm64" ], @@ -3069,9 +3069,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", "cpu": [ "x64" ], @@ -3101,9 +3101,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", "cpu": [ "x64" ], @@ -3117,9 +3117,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", "cpu": [ "arm64" ], @@ -3133,9 +3133,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", "cpu": [ "ia32" ], @@ -3149,9 +3149,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", "cpu": [ "x64" ], @@ -3367,6 +3367,18 @@ "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", "license": "MIT" }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", @@ -4066,6 +4078,68 @@ "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", "license": "MIT" }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -4174,6 +4248,10 @@ "node": ">=12.4.0" } }, + "node_modules/@packages/arena-mcp": { + "resolved": "packages/arena-mcp", + "link": true + }, "node_modules/@packages/backend": { "resolved": "packages/backend", "link": true @@ -6916,6 +6994,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -7576,6 +7693,15 @@ ], "license": "MIT" }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -7599,7 +7725,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -7613,7 +7738,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7823,6 +7947,15 @@ "dev": true, "license": "MIT" }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -7831,13 +7964,14 @@ "license": "MIT" }, "node_modules/convex": { - "version": "1.28.2", - "resolved": "https://registry.npmjs.org/convex/-/convex-1.28.2.tgz", - "integrity": "sha512-KzNsLbcVXb1OhpVQ+vHMgu+hjrsQ1ks5BZwJ2lR8O+nfbeJXE6tHbvsg1H17+ooUDvIDBSMT3vXS+AlodDhTnQ==", + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/convex/-/convex-1.41.0.tgz", + "integrity": "sha512-euxVf6yfpB7/VGKOobkLgjpbJidsUgW+b0ezonEyCUPqlpHFwR4/yIiI1hjjErzraiw91GxrtxpXQClMLNqU+w==", "license": "Apache-2.0", "dependencies": { - "esbuild": "0.25.4", - "prettier": "^3.0.0" + "esbuild": "0.27.0", + "prettier": "^3.0.0", + "ws": "8.20.1" }, "bin": { "convex": "bin/main.js" @@ -7849,6 +7983,7 @@ "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", + "@clerk/react": "^6.4.3", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "peerDependenciesMeta": { @@ -7858,6 +7993,9 @@ "@clerk/clerk-react": { "optional": true }, + "@clerk/react": { + "optional": true + }, "react": { "optional": true } @@ -8310,6 +8448,15 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -8383,7 +8530,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -8409,6 +8555,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.249", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.249.tgz", @@ -8450,6 +8602,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/endian-toggle": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/endian-toggle/-/endian-toggle-0.0.0.tgz", @@ -8656,7 +8817,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8666,7 +8826,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8704,7 +8863,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -8761,9 +8919,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", - "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -8773,31 +8931,48 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.4", - "@esbuild/android-arm": "0.25.4", - "@esbuild/android-arm64": "0.25.4", - "@esbuild/android-x64": "0.25.4", - "@esbuild/darwin-arm64": "0.25.4", - "@esbuild/darwin-x64": "0.25.4", - "@esbuild/freebsd-arm64": "0.25.4", - "@esbuild/freebsd-x64": "0.25.4", - "@esbuild/linux-arm": "0.25.4", - "@esbuild/linux-arm64": "0.25.4", - "@esbuild/linux-ia32": "0.25.4", - "@esbuild/linux-loong64": "0.25.4", - "@esbuild/linux-mips64el": "0.25.4", - "@esbuild/linux-ppc64": "0.25.4", - "@esbuild/linux-riscv64": "0.25.4", - "@esbuild/linux-s390x": "0.25.4", - "@esbuild/linux-x64": "0.25.4", - "@esbuild/netbsd-arm64": "0.25.4", - "@esbuild/netbsd-x64": "0.25.4", - "@esbuild/openbsd-arm64": "0.25.4", - "@esbuild/openbsd-x64": "0.25.4", - "@esbuild/sunos-x64": "0.25.4", - "@esbuild/win32-arm64": "0.25.4", - "@esbuild/win32-ia32": "0.25.4", - "@esbuild/win32-x64": "0.25.4" + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/escalade": { @@ -9353,6 +9528,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -9377,141 +9561,475 @@ "node": ">=0.8.x" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/fast-equals": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", - "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">=8.6.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "license": "ISC", + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" } }, - "node_modules/fflate": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", - "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", - "license": "MIT" - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.6" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/express/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/find-root": { + "node_modules/express/node_modules/content-disposition": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "node_modules/express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">= 18.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, + "node_modules/express/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-equals": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", + "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -9566,6 +10084,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -9692,7 +10219,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -9726,7 +10252,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -9860,7 +10385,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9922,7 +10446,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9997,6 +10520,35 @@ "react-is": "^16.7.0" } }, + "node_modules/hono": { + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -10069,7 +10621,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/input-otp": { @@ -10106,6 +10657,24 @@ "node": ">=12" } }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -10396,6 +10965,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -10597,9 +11172,9 @@ } }, "node_modules/jose": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", - "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -10655,6 +11230,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -10939,7 +11520,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11552,7 +12132,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11661,11 +12240,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -11791,6 +12381,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11893,6 +12492,15 @@ "node": ">= 6" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -12292,6 +12900,19 @@ "protodef-validator": "cli.js" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -12319,6 +12940,21 @@ "node": ">=16.0.0" } }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12358,6 +12994,46 @@ "node": ">=0.12" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -12759,6 +13435,15 @@ "type-fest": "^4.41.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -12840,6 +13525,32 @@ "integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==", "license": "MIT" }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12948,6 +13659,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/satori": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/satori/-/satori-0.12.2.tgz", @@ -13052,6 +13769,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -13122,7 +13845,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13142,7 +13864,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13159,7 +13880,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13178,7 +13898,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -13396,6 +14115,15 @@ "dev": true, "license": "MIT" }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -13976,6 +14704,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -14859,6 +15596,15 @@ "tiny-inflate": "^1.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -15230,9 +15976,29 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xmlhttprequest-ssl": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", @@ -15330,6 +16096,15 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, "node_modules/zod-validation-error": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", @@ -15372,6 +16147,21 @@ } } }, + "packages/arena-mcp": { + "name": "@packages/arena-mcp", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "@packages/bot-client": "*", + "convex": "^1.31.0", + "mineflayer": "^4.33.0", + "zod": "^4.1.12" + }, + "devDependencies": { + "tsx": "^4.19.0", + "typescript": "^5.6.0" + } + }, "packages/backend": { "name": "@packages/backend", "version": "1.0.0", @@ -15480,126 +16270,6 @@ "devDependencies": { "typescript": "^5.8.3" } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.7.tgz", - "integrity": "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.7.tgz", - "integrity": "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.7.tgz", - "integrity": "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.7.tgz", - "integrity": "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.7.tgz", - "integrity": "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.7.tgz", - "integrity": "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.7.tgz", - "integrity": "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.7.tgz", - "integrity": "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/packages/arena-mcp/README.md b/packages/arena-mcp/README.md new file mode 100644 index 0000000..ee59363 --- /dev/null +++ b/packages/arena-mcp/README.md @@ -0,0 +1,46 @@ +# @packages/arena-mcp — BlockWarriors Prizefight + +> Paste one config block. Tell your agent "go win." Watch it throw hands. + +An MCP server that turns any LLM agent (Claude Code, Cursor, any MCP client) into an embodied +fighter in the live BlockWarriors Minecraft arena. The bot body is hosted via mineflayer +(`@packages/bot-client`); the visiting model is the brain. + +## Quick start + +Requires the repo root `.env.local` (Convex URL/secret + Minecraft host). + +```json +{ + "mcpServers": { + "prizefight": { + "command": "node", + "args": ["--import", "tsx", "/packages/arena-mcp/src/index.ts"] + } + } +} +``` + +Then prompt your agent: + +> Register as IronGolem99 in the prizefight arena, fight tier0, and win. + +## Tools + +`arena_register`, `arena_status`, `arena_fight` (tier0 Punching Bag / tier1 Brawler / tier2 Kiter), +`arena_think` (glass-box narration), `look`, `goto`, `strike`, `evade`, `say`, `match_result`. + +## How it works + +`arena_fight` drives the existing match lifecycle end to end: public Convex mutations create the +match → Beacon (Paper plugin) acknowledges within ~5s and mints one-time seat tokens → the gateway +connects your fighter and the house bot (staggered 5s for the server's connection throttle, unique +IGN suffixes to dodge a stale-login server bug) → both `/login ` → match flips to Playing. +House bots run simple behavior loops on the same BotClient action vocabulary that visiting agents +use. + +Fighter registry + Elo: `~/.blockwarriors/prizefight.json`. Replays (the full action ticker): +`~/.blockwarriors/replays/.jsonl`. + +See `docs/plans/pivot-prizefight.md` for the full design, judged alternatives, infra bugs found, +and the roadmap (remote HTTP transport, public /live ticker page, Convex-side Elo, tiers 3–5). diff --git a/packages/arena-mcp/TESTING.md b/packages/arena-mcp/TESTING.md new file mode 100644 index 0000000..8dabc3f --- /dev/null +++ b/packages/arena-mcp/TESTING.md @@ -0,0 +1,108 @@ +# Prizefight — manual testing guide + +Everything below runs against the live stack (Convex `abundant-ferret-667` + the Fast Dev +Minecraft server). No local servers needed. + +## 0. Prereqs (one time) + +```bash +npm ci +``` + +Repo root `.env.local` must contain (already true on the main dev machine): + +``` +CONVEX_URL=https://abundant-ferret-667.convex.cloud +CONVEX_SITE_URL=https://abundant-ferret-667.convex.site +CONVEX_HTTP_SECRET= +MINECRAFT_HOST=hteng.blockwarriors.ai +MINECRAFT_PORT=25574 +``` + +Health check before testing (all should return data / 200): + +```bash +# Beacon alive? (should list 0 matches, success:true) +curl -s -H "Authorization: Bearer $CONVEX_HTTP_SECRET" \ + "https://abundant-ferret-667.convex.site/matches?status=Queuing" +# MC server reachable? +nc -z hteng.blockwarriors.ai 25574 && echo ok +``` + +## 1. Smoke test (no LLM, ~60s) + +A scripted agent registers, fights tier0, and must WIN (exit code 0): + +```bash +node packages/arena-mcp/scripts/play-demo.mjs +``` + +Expected: `FIGHT LIVE` within ~20s, `FINAL: { "outcome": "win", ... }` within ~60s, +Elo goes 1000 → ~1008, `tiers_cleared: ["tier0"]`. + +## 2. The real thing — your agent plays via MCP + +Add the server to Claude Code (or paste the JSON into Cursor / any MCP client): + +```bash +claude mcp add prizefight -- node --import tsx \ + "$(pwd)/packages/arena-mcp/src/index.ts" +``` + +Then in a Claude session: + +> Register as in the prizefight arena, fight tier0, then try tier1. +> Narrate with arena_think before key actions and trash talk once with say. +> If you lose, analyze why from the observations and rematch with a better strategy. + +Headless one-liner version: + +```bash +claude -p "Register as TestFighter in the prizefight arena and beat tier0, then tier1. \ +Use arena_think to narrate. Report your final Elo." \ + --allowedTools "mcp__prizefight__arena_register,mcp__prizefight__arena_status,mcp__prizefight__arena_fight,mcp__prizefight__arena_think,mcp__prizefight__look,mcp__prizefight__goto,mcp__prizefight__strike,mcp__prizefight__evade,mcp__prizefight__say,mcp__prizefight__match_result" \ + --max-turns 50 +``` + +## 3. What to watch while a fight runs + +- **Spectate page** (live 1Hz telemetry): the `spectate_url` returned by `arena_fight` — + `https://blockwarriors.ai/dashboard/matches/` (needs dashboard login). +- **Glass-box replay** (the action ticker, written when the match ends): + `cat ~/.blockwarriors/replays/.jsonl` +- **Ladder / records**: `cat ~/.blockwarriors/prizefight.json` +- **In-game**: join `hteng.blockwarriors.ai:25574` with a 1.20.6 client. Note: players who + don't `/login` stay frozen in the lobby, so you'll see fighters pass through but can't follow + them into the match world yet (public spectator mode is on the roadmap). +- **Queue state** (debugging a stuck fight): + ```bash + for s in Queuing Waiting Playing; do echo -n "$s: "; \ + curl -s -H "Authorization: Bearer $CONVEX_HTTP_SECRET" \ + "https://abundant-ferret-667.convex.site/matches?status=$s" | head -c 200; echo; done + ``` + +## 4. Expected behaviors & known quirks + +| Behavior | Why | +| --- | --- | +| ~10s between the two bot joins | server connection-throttle; gateway staggers connects 5s | +| Fighter IGNs get a random suffix (`Handle_a3f2`) | dodges Beacon's stale-login bug (see pivot doc) | +| `arena_fight` errors `ARENA_OFFLINE` | Beacon/MC server down — check the panel | +| `MATCH_OVER` from look/strike | the match already resolved; call `match_result` | +| tier1 Brawler is beatable but mean | rebalanced to 5s aggro tick after the 5-0 playtest | +| One active match per fighter | finish (or `match_result`) before re-queuing | + +## 5. Reset between test sessions (optional) + +```bash +rm -f ~/.blockwarriors/prizefight.json # wipes ladder + fighter keys +rm -rf ~/.blockwarriors/replays # wipes replays +``` + +Stale matches stuck in `Waiting` (e.g. after killing a fight mid-handshake) can be cleared with: + +```bash +curl -s -X POST -H "Authorization: Bearer $CONVEX_HTTP_SECRET" -H "Content-Type: application/json" \ + "https://abundant-ferret-667.convex.site/matches/update" \ + -d '{"updates":[{"match_id":"","match_status":"Terminated"}]}' +``` diff --git a/packages/arena-mcp/package.json b/packages/arena-mcp/package.json new file mode 100644 index 0000000..d6d9958 --- /dev/null +++ b/packages/arena-mcp/package.json @@ -0,0 +1,23 @@ +{ + "name": "@packages/arena-mcp", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "BlockWarriors Prizefight — MCP gateway: any agent fights in the Minecraft arena via ~10 tools", + "main": "src/index.ts", + "scripts": { + "dev": "node --import tsx src/index.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "@packages/bot-client": "*", + "convex": "^1.31.0", + "mineflayer": "^4.33.0", + "zod": "^4.1.12" + }, + "devDependencies": { + "tsx": "^4.19.0", + "typescript": "^5.6.0" + } +} diff --git a/packages/arena-mcp/scripts/play-demo.mjs b/packages/arena-mcp/scripts/play-demo.mjs new file mode 100644 index 0000000..02408a3 --- /dev/null +++ b/packages/arena-mcp/scripts/play-demo.mjs @@ -0,0 +1,79 @@ +// Drives the Prizefight MCP server through a full tier0 fight as a scripted agent. +// Usage: node packages/arena-mcp/scripts/play-demo.mjs +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const serverEntry = path.resolve(__dirname, "..", "src", "index.ts"); + +const t0 = Date.now(); +const log = (...a) => console.log(((Date.now() - t0) / 1000).toFixed(1) + "s", ...a); + +const transport = new StdioClientTransport({ + command: "node", + args: ["--import", "tsx", serverEntry], + stderr: "inherit", +}); +const client = new Client({ name: "demo-agent", version: "0.0.1" }); +await client.connect(transport); +log("connected to MCP server"); + +const call = async (name, args) => { + const res = await client.callTool({ name, arguments: args }); + const text = res.content?.[0]?.text ?? "{}"; + const data = JSON.parse(text); + log(`${name} ->`, JSON.stringify(data).slice(0, 220)); + if (res.isError) throw new Error(`${name}: ${text.slice(0, 300)}`); + return data; +}; + +const tools = await client.listTools(); +log("tools:", tools.tools.map((t) => t.name).join(", ")); + +const reg = await call("arena_register", { handle: "DemoSlugger" }); +const key = reg.fighter_key; + +const fight = await call("arena_fight", { fighter_key: key, opponent: "tier0" }); +const matchId = fight.match_id; +log("FIGHT LIVE", matchId, "spectate:", fight.spectate_url); + +await call("arena_think", { match_id: matchId, thought: "Find the bag, close distance, swing until it drops." }); + +let obs = await call("look", { match_id: matchId }); +let target = obs.entities?.find((e) => e.isPlayer); +if (!target) { + await call("goto", { match_id: matchId, x: obs.you.position.x + 5, y: obs.you.position.y, z: obs.you.position.z }); + obs = await call("look", { match_id: matchId }); + target = obs.entities?.find((e) => e.isPlayer); +} +if (!target) throw new Error("no opponent visible"); + +await call("say", { match_id: matchId, message: "You are about to get folded, bag." }); + +// keep striking until the match resolves (tier0 = ~20 fist hits) +for (let round = 1; round <= 12; round++) { + try { + obs = await call("strike", { match_id: matchId, entity_id: target.id }); + const t = obs.entities?.find((e) => e.isPlayer); + if (t) target = t; + } catch (e) { + log("strike ended:", String(e.message).slice(0, 120)); + break; + } + const result = await call("match_result", { fighter_key: key, match_id: matchId }); + if (result.status === "finished") break; +} + +let final = await call("match_result", { fighter_key: key, match_id: matchId }); +for (let i = 0; i < 20 && final.status !== "finished"; i++) { + await new Promise((r) => setTimeout(r, 3000)); + final = await call("match_result", { fighter_key: key, match_id: matchId }); +} +log("FINAL:", JSON.stringify(final, null, 1)); +const status = await call("arena_status", { fighter_key: key }); +log("PROFILE:", JSON.stringify(status)); + +await client.close(); +process.exit(final.outcome === "win" ? 0 : 1); diff --git a/packages/arena-mcp/src/gateway.ts b/packages/arena-mcp/src/gateway.ts new file mode 100644 index 0000000..3718821 --- /dev/null +++ b/packages/arena-mcp/src/gateway.ts @@ -0,0 +1,408 @@ +import { ConvexHttpClient } from "convex/browser"; +import { BotClient } from "@packages/bot-client"; +import type { BotState } from "@packages/bot-client"; +import fs from "node:fs"; +import path from "node:path"; +import crypto from "node:crypto"; +import { TIERS, startHouseBehavior, type TierSpec } from "./houseBots.js"; +import { REPLAY_DIR, type Fighter } from "./state.js"; + +export interface GatewayConfig { + convexUrl: string; + convexSiteUrl: string; + convexHttpSecret: string; + minecraftHost: string; + minecraftPort: number; + spectateBaseUrl: string; +} + +export interface ActionLogEntry { + t: number; + actor: string; + type: string; + detail: string; +} + +export interface MatchSession { + matchId: string; + fighterKey: string; + handle: string; + tier: TierSpec; + playerBotId: string; + playerIgn: string; + houseBotId: string; + playerTeamId: string; + spectateUrl: string; + status: "playing" | "finished" | "error"; + result?: { outcome: "win" | "loss" | "draw"; eloDelta?: number; detail: string }; + stopHouseBot: () => void; + actionLog: ActionLogEntry[]; + startedAt: number; +} + +export class ArenaError extends Error { + constructor( + public code: string, + message: string, + public retryAfterS?: number + ) { + super(message); + } +} + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +export class ArenaGateway { + private convex: ConvexHttpClient; + private bots = new BotClient(); + private sessions = new Map(); + private connectChain: Promise = Promise.resolve(); + private loginWaiters = new Map void>(); + + constructor(private config: GatewayConfig) { + this.convex = new ConvexHttpClient(config.convexUrl); + this.bots.setCallbacks( + (botId, state) => this.onBotUpdate(botId, state), + (botId, username, message) => this.onBotMessage(botId, username, message), + () => {} + ); + } + + // ---------- Convex helpers ---------- + + private async siteGet(p: string): Promise { + const res = await fetch(this.config.convexSiteUrl + p, { + headers: { Authorization: `Bearer ${this.config.convexHttpSecret}` }, + }); + const json = await res.json(); + if (!res.ok || json.success === false) { + throw new ArenaError("CONVEX_ERROR", `GET ${p} -> ${res.status}: ${JSON.stringify(json).slice(0, 200)}`); + } + return json.data ?? json; + } + + private async getMatch(matchId: string): Promise { + return this.siteGet(`/matches?id=${encodeURIComponent(matchId)}`); + } + + // ---------- bot plumbing ---------- + + private onBotUpdate(_botId: string, _state: BotState): void {} + + private onBotMessage(botId: string, username: string, message: string): void { + const waiter = this.loginWaiters.get(botId); + if (waiter && username === "Server") waiter(message); + const session = this.sessionForBot(botId); + if (session && username !== "Server") { + this.logAction(session, username, "chat", message); + } + } + + private sessionForBot(botId: string): MatchSession | undefined { + for (const s of this.sessions.values()) { + if (s.playerBotId === botId || s.houseBotId === botId) return s; + } + return undefined; + } + + /** Server kicks same-IP joins within ~4s (connection throttle) — serialize with a gap. */ + private queueConnect(fn: () => Promise): Promise { + const next = this.connectChain.then(fn); + this.connectChain = next.catch(() => {}).then(() => sleep(5000)); + return next; + } + + /** Connect a bot and wait for Beacon's "Successfully logged in." confirmation. */ + private async connectAndLogin(botId: string, ign: string, token: string): Promise { + await this.queueConnect(async () => { + const loginResult = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + this.loginWaiters.delete(botId); + reject(new ArenaError("LOGIN_TIMEOUT", `${ign}: no login confirmation within 25s`)); + }, 25000); + this.loginWaiters.set(botId, (msg) => { + if (msg.includes("Successfully logged in")) { + clearTimeout(timeout); + this.loginWaiters.delete(botId); + resolve(); + } else if (msg.includes("Failed to log in") || msg.includes("already logged in")) { + clearTimeout(timeout); + this.loginWaiters.delete(botId); + reject(new ArenaError("LOGIN_REJECTED", `${ign}: ${msg}`)); + } + }); + }); + await this.bots.createBot(botId, ign, token, this.config.minecraftHost, this.config.minecraftPort); + await this.bots.waitForSpawn(botId, 30000); + await loginResult; + }); + } + + // ---------- public API ---------- + + async createFight(fighter: Fighter, tierId: string): Promise { + const tier = TIERS[tierId]; + if (!tier) { + throw new ArenaError("UNKNOWN_OPPONENT", `Unknown opponent "${tierId}". Available: ${Object.keys(TIERS).join(", ")}`); + } + const active = [...this.sessions.values()].find( + (s) => s.fighterKey === fighter.fighterKey && s.status === "playing" + ); + if (active) { + throw new ArenaError("ALREADY_FIGHTING", `Finish match ${active.matchId} first (or call match_result on it).`); + } + + // 1. create teams + match (same public mutations the test harness uses) + const { blueTeamId, redTeamId } = (await this.convex.mutation( + "gameTeams:createGameTeamsForMatch" as any, + { redTeamBots: [], blueTeamBots: [] } + )) as { blueTeamId: string; redTeamId: string }; + const matchId = (await this.convex.mutation("matches:createMatch" as any, { + matchType: "pvp", + matchStatus: "Queuing", + blueTeamId, + redTeamId, + mode: "practice", + })) as string; + + // 2. wait for Beacon acknowledge (Queuing -> Waiting) + let match = await this.getMatch(matchId); + const ackDeadline = Date.now() + 30000; + while (match.match_status === "Queuing" && Date.now() < ackDeadline) { + await sleep(2000); + match = await this.getMatch(matchId); + } + if (match.match_status !== "Waiting") { + throw new ArenaError( + "ARENA_OFFLINE", + `The arena server did not pick up the match (status: ${match.match_status}). It may be down or restarting.`, + 120 + ); + } + + // 3. fetch the one-time seat tokens + const tokensByMatch = await this.siteGet(`/matches/tokens?match_ids=${matchId}`); + const tokens: Array<{ token: string; game_team_id: string }> = tokensByMatch[matchId] ?? []; + if (tokens.length < 2) throw new ArenaError("NO_TOKENS", "Expected 2 seat tokens"); + + // 4. connect player bot then house bot (unique IGNs dodge the stale-login server bug) + const suffix = crypto.randomBytes(2).toString("hex"); + const playerIgn = `${fighter.handle.replace(/[^A-Za-z0-9_]/g, "").slice(0, 11)}_${suffix}`.slice(0, 16); + const houseIgn = `${tier.ign.slice(0, 11)}_${suffix}`.slice(0, 16); + const playerBotId = `player_${matchId}`; + const houseBotId = `house_${matchId}`; + + await this.connectAndLogin(playerBotId, playerIgn, tokens[0].token); + await this.connectAndLogin(houseBotId, houseIgn, tokens[1].token); + + // 5. wait for Playing + const playDeadline = Date.now() + 60000; + match = await this.getMatch(matchId); + while (match.match_status !== "Playing" && Date.now() < playDeadline) { + await sleep(2000); + match = await this.getMatch(matchId); + } + if (match.match_status !== "Playing") { + this.teardownBots(playerBotId, houseBotId); + throw new ArenaError("START_TIMEOUT", `Match never started (status: ${match.match_status})`); + } + + const session: MatchSession = { + matchId, + fighterKey: fighter.fighterKey, + handle: fighter.handle, + tier, + playerBotId, + playerIgn, + houseBotId, + playerTeamId: tokens[0].game_team_id, + spectateUrl: `${this.config.spectateBaseUrl}/dashboard/matches/${matchId}`, + status: "playing", + stopHouseBot: () => {}, + actionLog: [], + startedAt: Date.now(), + }; + session.stopHouseBot = startHouseBehavior(this.bots, houseBotId, tier.id); + this.sessions.set(matchId, session); + this.logAction(session, "arena", "match_start", `${fighter.handle} (${playerIgn}) vs ${tier.name} — ${tier.description}`); + void this.watchUntilFinished(session); + return session; + } + + private async watchUntilFinished(session: MatchSession): Promise { + const deadline = Date.now() + 5 * 60 * 1000; + while (Date.now() < deadline && session.status === "playing") { + await sleep(3000); + let match; + try { + match = await this.getMatch(session.matchId); + } catch { + continue; + } + if (["Finished", "Terminated"].includes(match.match_status)) { + const winnerTeam = match.winner_team_id ?? null; + const outcome: "win" | "loss" | "draw" = + winnerTeam == null ? "draw" : winnerTeam === session.playerTeamId ? "win" : "loss"; + this.finishSession(session, outcome, `match ${match.match_status}, winner_team=${winnerTeam ?? "none"}`); + return; + } + // if the player bot died and got booted, beacon's quit-forfeit path resolves the match shortly after + } + if (session.status === "playing") { + this.finishSession(session, "draw", "gateway watchdog: 5 minute cap reached"); + } + } + + private finishSession(session: MatchSession, outcome: "win" | "loss" | "draw", detail: string): void { + if (session.status !== "playing") return; + session.status = "finished"; + session.result = { outcome, detail }; + session.stopHouseBot(); + this.logAction(session, "arena", "match_end", `${outcome.toUpperCase()} — ${detail}`); + this.teardownBots(session.playerBotId, session.houseBotId); + this.writeReplay(session); + } + + private teardownBots(...botIds: string[]): void { + for (const id of botIds) { + try { + this.bots.removeBot(id); + } catch { + // already gone + } + } + } + + // ---------- in-match actions (the visiting agent's verbs) ---------- + + getSession(matchId: string): MatchSession { + const session = this.sessions.get(matchId); + if (!session) throw new ArenaError("UNKNOWN_MATCH", `No session for match ${matchId}`); + return session; + } + + private requirePlaying(matchId: string): MatchSession { + const session = this.getSession(matchId); + if (session.status !== "playing") { + throw new ArenaError("MATCH_OVER", `Match is ${session.status}: ${session.result?.detail ?? ""}. Call match_result.`); + } + return session; + } + + look(matchId: string): any { + const session = this.requirePlaying(matchId); + const state = this.bots.getBotState(session.playerBotId); + if (!state) throw new ArenaError("BOT_GONE", "Your fighter is no longer connected"); + this.logAction(session, session.handle, "look", "surveyed the arena"); + return this.observation(session, state); + } + + private observation(session: MatchSession, state: BotState): any { + return { + you: { + ign: state.ign, + status: state.status, + position: state.position + ? { x: Math.round(state.position.x * 10) / 10, y: Math.round(state.position.y), z: Math.round(state.position.z * 10) / 10 } + : null, + health: state.health, + currentAction: state.currentAction, + }, + entities: state.nearbyEntities + .filter((e) => e.distance <= 32) + .slice(0, 20) + .map((e) => ({ + id: e.id, + name: e.displayName || e.name, + isPlayer: e.isPlayer, + distance: Math.round(e.distance * 10) / 10, + position: { x: Math.round(e.position.x), y: Math.round(e.position.y), z: Math.round(e.position.z) }, + health: e.health, + })), + opponent: session.tier.name, + match_status: session.status, + elapsed_s: Math.round((Date.now() - session.startedAt) / 1000), + }; + } + + async goto(matchId: string, x: number, y: number, z: number): Promise { + const session = this.requirePlaying(matchId); + this.logAction(session, session.handle, "goto", `-> (${x}, ${y}, ${z})`); + await this.bots.executeCommand(session.playerBotId, { type: "goto", payload: { x, y, z } }); + // wait for arrival (or 10s), then report a fresh observation + const deadline = Date.now() + 10000; + while (Date.now() < deadline) { + await sleep(500); + const state = this.bots.getBotState(session.playerBotId); + if (!state?.position) break; + const d = Math.hypot(state.position.x - x, state.position.y - y, state.position.z - z); + if (d < 2) break; + } + const state = this.bots.getBotState(session.playerBotId); + if (!state) throw new ArenaError("BOT_GONE", "Your fighter is no longer connected"); + return this.observation(session, state); + } + + async strike(matchId: string, entityId: number): Promise { + const session = this.requirePlaying(matchId); + const state = this.bots.getBotState(session.playerBotId); + const target = state?.nearbyEntities.find((e) => e.id === entityId); + this.logAction(session, session.handle, "strike", `attacks ${target?.displayName || target?.name || `#${entityId}`}`); + await this.bots.executeCommand(session.playerBotId, { type: "attack_entity", payload: { entityId } }); + await sleep(3000); // let the attack session land hits before reporting back + const fresh = this.bots.getBotState(session.playerBotId); + if (!fresh) throw new ArenaError("BOT_GONE", "Your fighter is no longer connected"); + return this.observation(session, fresh); + } + + async evade(matchId: string): Promise { + const session = this.requirePlaying(matchId); + const state = this.bots.getBotState(session.playerBotId); + if (!state?.position) throw new ArenaError("BOT_GONE", "Your fighter is no longer connected"); + const threat = state.nearbyEntities.filter((e) => e.isPlayer).sort((a, b) => a.distance - b.distance)[0]; + let dx = 1, dz = 0; + if (threat) { + dx = state.position.x - threat.position.x; + dz = state.position.z - threat.position.z; + const n = Math.max(Math.hypot(dx, dz), 0.01); + dx /= n; dz /= n; + } + this.logAction(session, session.handle, "evade", `retreats from ${threat?.name ?? "the unknown"}`); + await this.bots.executeCommand(session.playerBotId, { type: "sprint", payload: { enabled: true } }); + return this.goto(matchId, state.position.x + dx * 10, state.position.y, state.position.z + dz * 10); + } + + async say(matchId: string, message: string): Promise { + const session = this.requirePlaying(matchId); + this.logAction(session, session.handle, "say", message); + await this.bots.executeCommand(session.playerBotId, { type: "chat", payload: { message: message.slice(0, 100) } }); + } + + think(matchId: string, thought: string): void { + const session = this.getSession(matchId); + this.logAction(session, session.handle, "think", thought); + } + + logAction(session: MatchSession, actor: string, type: string, detail: string): void { + session.actionLog.push({ t: Date.now() - session.startedAt, actor, type, detail }); + } + + private writeReplay(session: MatchSession): void { + try { + fs.mkdirSync(REPLAY_DIR, { recursive: true }); + const file = path.join(REPLAY_DIR, `${session.matchId}.jsonl`); + const lines = session.actionLog.map((e) => JSON.stringify(e)).join("\n"); + fs.writeFileSync(file, lines + "\n"); + } catch { + // replay persistence is best-effort + } + } + + async shutdown(): Promise { + for (const session of this.sessions.values()) { + if (session.status === "playing") { + this.finishSession(session, "draw", "gateway shutdown"); + } + } + } +} diff --git a/packages/arena-mcp/src/houseBots.ts b/packages/arena-mcp/src/houseBots.ts new file mode 100644 index 0000000..f631bdb --- /dev/null +++ b/packages/arena-mcp/src/houseBots.ts @@ -0,0 +1,95 @@ +import type { BotClient } from "@packages/bot-client"; + +export interface TierSpec { + id: string; + name: string; + ign: string; + elo: number; + description: string; +} + +export const TIERS: Record = { + tier0: { + id: "tier0", + name: "Punching Bag", + ign: "PunchingBag", + elo: 800, + description: "Stands still. Land a killing blow to clear the tier.", + }, + tier1: { + id: "tier1", + name: "Brawler", + ign: "Brawler", + elo: 950, + description: "Attacks the nearest player on sight and never retreats.", + }, + tier2: { + id: "tier2", + name: "Kiter", + ign: "Kiter", + elo: 1100, + description: "Keeps its distance, strafes, and punishes face-tanking.", + }, +}; + +/** + * Drives a house bot's behavior loop. Returns a stop function. + * House bots use the same BotClient action vocabulary as visiting agents. + */ +export function startHouseBehavior( + client: BotClient, + botId: string, + tier: string +): () => void { + if (tier === "tier0") { + return () => {}; + } + + // tier1 rebalance from live playtest: a 2.5s always-aggro loop beat LLM decision + // latency 5 matches straight — first-rung house bots must be winnable. + const tickMs = tier === "tier1" ? 5000 : 2500; + const aggroRange = tier === "tier1" ? 12 : 64; + + const interval = setInterval(async () => { + const state = client.getBotState(botId); + if (!state || state.status !== "online" || !state.position) return; + + const players = state.nearbyEntities + .filter((e) => e.isPlayer) + .sort((a, b) => a.distance - b.distance); + const target = players[0]; + if (!target || target.distance > aggroRange) return; + + try { + if (tier === "tier1") { + await client.executeCommand(botId, { + type: "attack_entity", + payload: { entityId: target.id }, + }); + } else if (tier === "tier2") { + if (target.distance < 5) { + const dx = state.position.x - target.position.x; + const dz = state.position.z - target.position.z; + const norm = Math.max(Math.hypot(dx, dz), 0.01); + await client.executeCommand(botId, { + type: "goto", + payload: { + x: state.position.x + (dx / norm) * 8, + y: state.position.y, + z: state.position.z + (dz / norm) * 8, + }, + }); + } else if (target.distance < 9) { + await client.executeCommand(botId, { + type: "attack_entity", + payload: { entityId: target.id }, + }); + } + } + } catch { + // behavior loop must never crash the gateway + } + }, tickMs); + + return () => clearInterval(interval); +} diff --git a/packages/arena-mcp/src/index.ts b/packages/arena-mcp/src/index.ts new file mode 100644 index 0000000..00afd3c --- /dev/null +++ b/packages/arena-mcp/src/index.ts @@ -0,0 +1,204 @@ +#!/usr/bin/env node +// BlockWarriors Prizefight — MCP gateway. +// Paste one config block, tell your agent "go win", watch it throw hands. +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { config as dotenv } from "dotenv"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { ArenaGateway, ArenaError } from "./gateway.js"; +import { TIERS } from "./houseBots.js"; +import { registerFighter, getFighter, getLadder, recordResult } from "./state.js"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, "..", "..", ".."); +dotenv({ path: path.join(repoRoot, ".env.local") }); +dotenv({ path: path.join(repoRoot, ".env") }); + +const gateway = new ArenaGateway({ + convexUrl: process.env.CONVEX_URL || "https://abundant-ferret-667.convex.cloud", + convexSiteUrl: process.env.CONVEX_SITE_URL || "https://abundant-ferret-667.convex.site", + convexHttpSecret: process.env.CONVEX_HTTP_SECRET || "", + minecraftHost: process.env.MINECRAFT_HOST || "hteng.blockwarriors.ai", + minecraftPort: parseInt(process.env.MINECRAFT_PORT || "25574", 10), + spectateBaseUrl: process.env.SPECTATE_BASE_URL || "https://app.blockwarriors.ai", +}); + +const RULES = `PRIZEFIGHT RULES +- 1v1 PvP in a disposable arena. Win by taking your opponent to 0 HP. +- If a fighter disconnects, the survivor wins (forfeit). +- Climb the gauntlet: ${Object.values(TIERS).map((t) => `${t.id}=${t.name} (Elo ${t.elo})`).join(", ")}. +- Glass box: every tool call is logged to the public action ticker. Use arena_think to narrate strategy — spectators see it live. +- Etiquette: say() trash talk is encouraged.`; + +const server = new McpServer({ name: "blockwarriors-prizefight", version: "0.1.0" }); + +const ok = (data: unknown) => ({ content: [{ type: "text" as const, text: JSON.stringify(data, null, 1) }] }); +const fail = (e: unknown) => { + if (e instanceof ArenaError) { + return { content: [{ type: "text" as const, text: JSON.stringify({ error: e.code, message: e.message, retry_after_s: e.retryAfterS }) }], isError: true }; + } + return { content: [{ type: "text" as const, text: JSON.stringify({ error: "INTERNAL", message: String(e) }) }], isError: true }; +}; + +server.registerTool( + "arena_register", + { + description: "Register as a fighter in the BlockWarriors Prizefight arena. Returns your fighter_key (keep it for all other calls), starting Elo, and the rules.", + inputSchema: { handle: z.string().min(2).max(20).describe("Your fighter handle, e.g. IronGolem99") }, + }, + async ({ handle }) => { + try { + const f = registerFighter(handle); + return ok({ fighter_key: f.fighterKey, handle: f.handle, elo: f.elo, record: `${f.wins}W-${f.losses}L`, tiers_cleared: f.tiersCleared, rules: RULES }); + } catch (e) { return fail(e); } + } +); + +server.registerTool( + "arena_status", + { + description: "Your fighter profile, active match (if any), and the top of the Elo ladder.", + inputSchema: { fighter_key: z.string() }, + }, + async ({ fighter_key }) => { + try { + const f = getFighter(fighter_key); + if (!f) throw new ArenaError("UNKNOWN_FIGHTER", "Register first with arena_register"); + return ok({ handle: f.handle, elo: f.elo, record: `${f.wins}W-${f.losses}L-${f.draws}D`, tiers_cleared: f.tiersCleared, ladder: getLadder() }); + } catch (e) { return fail(e); } + } +); + +server.registerTool( + "arena_fight", + { + description: "Enter a match. Creates the arena, spawns your fighter and the opponent, returns when the fight is LIVE. Opponents: tier0 (Punching Bag), tier1 (Brawler), tier2 (Kiter). Then use look/goto/strike/evade/say to fight, and arena_think to narrate.", + inputSchema: { + fighter_key: z.string(), + opponent: z.enum(["tier0", "tier1", "tier2"]).describe("Gauntlet tier to challenge"), + }, + }, + async ({ fighter_key, opponent }) => { + try { + const f = getFighter(fighter_key); + if (!f) throw new ArenaError("UNKNOWN_FIGHTER", "Register first with arena_register"); + const s = await gateway.createFight(f, opponent); + return ok({ + match_id: s.matchId, + you: s.playerIgn, + opponent: `${s.tier.name} (Elo ${s.tier.elo}) — ${s.tier.description}`, + spectate_url: s.spectateUrl, + rules: RULES, + hint: "Start with look(match_id) to find your opponent, then strike(match_id, entity_id).", + }); + } catch (e) { return fail(e); } + } +); + +server.registerTool( + "arena_think", + { + description: "Narrate your strategy before acting (REQUIRED etiquette in ranked). Spectators see this beside the live match — never leave the ticker silent.", + inputSchema: { match_id: z.string(), thought: z.string().max(280) }, + }, + async ({ match_id, thought }) => { + try { + gateway.think(match_id, thought); + return ok({ logged: true }); + } catch (e) { return fail(e); } + } +); + +server.registerTool( + "look", + { + description: "Survey the arena: your position/health and all entities within 32 blocks (your opponent is the other player).", + inputSchema: { match_id: z.string() }, + }, + async ({ match_id }) => { + try { return ok(gateway.look(match_id)); } catch (e) { return fail(e); } + } +); + +server.registerTool( + "goto", + { + description: "Pathfind to coordinates. Returns when you arrive (or after 10s) with a fresh observation.", + inputSchema: { match_id: z.string(), x: z.number(), y: z.number(), z: z.number() }, + }, + async ({ match_id, x, y, z: zc }) => { + try { return ok(await gateway.goto(match_id, x, y, zc)); } catch (e) { return fail(e); } + } +); + +server.registerTool( + "strike", + { + description: "Chase and attack an entity (30s auto-combat session: pathfinds to the target and swings every 0.5s in range). Get entity_id from look().", + inputSchema: { match_id: z.string(), entity_id: z.number() }, + }, + async ({ match_id, entity_id }) => { + try { return ok(await gateway.strike(match_id, entity_id)); } catch (e) { return fail(e); } + } +); + +server.registerTool( + "evade", + { + description: "Sprint away from the nearest enemy player. Use when low on health.", + inputSchema: { match_id: z.string() }, + }, + async ({ match_id }) => { + try { return ok(await gateway.evade(match_id)); } catch (e) { return fail(e); } + } +); + +server.registerTool( + "say", + { + description: "Talk in the arena chat. Trash talk is part of the sport.", + inputSchema: { match_id: z.string(), message: z.string().max(100) }, + }, + async ({ match_id, message }) => { + try { + await gateway.say(match_id, message); + return ok({ sent: true }); + } catch (e) { return fail(e); } + } +); + +server.registerTool( + "match_result", + { + description: "Final (or current) result of a match. Applies Elo and tier unlocks once the match is over.", + inputSchema: { fighter_key: z.string(), match_id: z.string() }, + }, + async ({ fighter_key, match_id }) => { + try { + const f = getFighter(fighter_key); + if (!f) throw new ArenaError("UNKNOWN_FIGHTER", "Register first with arena_register"); + const s = gateway.getSession(match_id); + if (s.status === "playing") { + return ok({ status: "playing", elapsed_s: Math.round((Date.now() - s.startedAt) / 1000), hint: "Fight is still live — keep going." }); + } + if (!s.result) throw new ArenaError("NO_RESULT", "Match ended without a result"); + if (s.result.eloDelta === undefined) { + const { elo, eloDelta } = recordResult(fighter_key, match_id, s.tier.name, s.tier.elo, s.result.outcome, s.tier.id); + s.result.eloDelta = eloDelta; + return ok({ status: "finished", outcome: s.result.outcome, detail: s.result.detail, elo, elo_delta: eloDelta, tiers_cleared: getFighter(fighter_key)!.tiersCleared, action_log_tail: s.actionLog.slice(-10) }); + } + return ok({ status: "finished", outcome: s.result.outcome, detail: s.result.detail, elo: f.elo, elo_delta: s.result.eloDelta, action_log_tail: s.actionLog.slice(-10) }); + } catch (e) { return fail(e); } + } +); + +const transport = new StdioServerTransport(); +await server.connect(transport); +console.error("[prizefight] MCP gateway ready"); + +process.on("SIGINT", async () => { + await gateway.shutdown(); + process.exit(0); +}); diff --git a/packages/arena-mcp/src/state.ts b/packages/arena-mcp/src/state.ts new file mode 100644 index 0000000..27d59ba --- /dev/null +++ b/packages/arena-mcp/src/state.ts @@ -0,0 +1,109 @@ +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import crypto from "node:crypto"; + +export interface Fighter { + handle: string; + fighterKey: string; + elo: number; + wins: number; + losses: number; + draws: number; + tiersCleared: string[]; + createdAt: number; +} + +export interface FightRecord { + matchId: string; + opponent: string; + result: "win" | "loss" | "draw"; + eloDelta: number; + finishedAt: number; +} + +interface Registry { + fighters: Record; + history: FightRecord[]; +} + +const DATA_DIR = path.join(os.homedir(), ".blockwarriors"); +const REGISTRY_PATH = path.join(DATA_DIR, "prizefight.json"); + +export const REPLAY_DIR = path.join(DATA_DIR, "replays"); + +function load(): Registry { + try { + return JSON.parse(fs.readFileSync(REGISTRY_PATH, "utf8")); + } catch { + return { fighters: {}, history: [] }; + } +} + +function save(registry: Registry): void { + fs.mkdirSync(DATA_DIR, { recursive: true }); + fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2)); +} + +export function registerFighter(handle: string): Fighter { + const registry = load(); + const existing = Object.values(registry.fighters).find( + (f) => f.handle.toLowerCase() === handle.toLowerCase() + ); + if (existing) return existing; + + const fighter: Fighter = { + handle, + fighterKey: `pf_${crypto.randomBytes(12).toString("hex")}`, + elo: 1000, + wins: 0, + losses: 0, + draws: 0, + tiersCleared: [], + createdAt: Date.now(), + }; + registry.fighters[fighter.fighterKey] = fighter; + save(registry); + return fighter; +} + +export function getFighter(fighterKey: string): Fighter | null { + return load().fighters[fighterKey] ?? null; +} + +export function getLadder(): Array> { + return Object.values(load().fighters) + .sort((a, b) => b.elo - a.elo) + .slice(0, 20) + .map(({ handle, elo, wins, losses }) => ({ handle, elo, wins, losses })); +} + +const K = 32; + +export function recordResult( + fighterKey: string, + matchId: string, + opponent: string, + opponentElo: number, + result: "win" | "loss" | "draw", + tierCleared?: string +): { elo: number; eloDelta: number } { + const registry = load(); + const fighter = registry.fighters[fighterKey]; + if (!fighter) throw new Error(`Unknown fighter key`); + + const expected = 1 / (1 + 10 ** ((opponentElo - fighter.elo) / 400)); + const actual = result === "win" ? 1 : result === "loss" ? 0 : 0.5; + const eloDelta = Math.round(K * (actual - expected)); + + fighter.elo += eloDelta; + if (result === "win") fighter.wins += 1; + else if (result === "loss") fighter.losses += 1; + else fighter.draws += 1; + if (tierCleared && result === "win" && !fighter.tiersCleared.includes(tierCleared)) { + fighter.tiersCleared.push(tierCleared); + } + registry.history.push({ matchId, opponent, result, eloDelta, finishedAt: Date.now() }); + save(registry); + return { elo: fighter.elo, eloDelta }; +} diff --git a/packages/arena-mcp/tsconfig.json b/packages/arena-mcp/tsconfig.json new file mode 100644 index 0000000..200d08f --- /dev/null +++ b/packages/arena-mcp/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "esModuleInterop": true + }, + "include": ["src"] +}