diff --git a/src/js-host-api/eslint.config.mjs b/src/js-host-api/eslint.config.mjs
index b4eeb37..07f7582 100644
--- a/src/js-host-api/eslint.config.mjs
+++ b/src/js-host-api/eslint.config.mjs
@@ -20,6 +20,7 @@ export default [
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
+ performance: 'readonly',
},
},
rules: {
diff --git a/src/js-host-api/examples/mcp-server/README.md b/src/js-host-api/examples/mcp-server/README.md
new file mode 100644
index 0000000..1c333c2
--- /dev/null
+++ b/src/js-host-api/examples/mcp-server/README.md
@@ -0,0 +1,721 @@
+# ๐ Hyperlight JS โ MCP Server Example
+
+> _"The only winning move is to play... inside a sandbox."_ โ WarGames (1983), adapted
+
+An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that
+lets AI agents execute JavaScript code inside a
+[Hyperlight](https://github.com/deislabs/hyperlight-js) micro-VM sandbox
+with strict CPU time limits.
+
+
+
+
+
+## What It Does
+
+This MCP server exposes a single tool โ **`execute_javascript`** โ that:
+
+1. Takes arbitrary JavaScript source code from an AI agent
+2. Executes it inside an isolated Hyperlight sandbox (no filesystem, no network, no host access)
+3. Enforces a **configurable CPU time limit** (default 1000ms, with a 5000ms wall-clock backstop)
+4. Returns the result (or a timeout/error message) back to the agent
+
+The sandbox automatically recovers after timeouts via snapshot/restore,
+so subsequent invocations work without manual intervention.
+
+## Architecture
+
+```
+โโโโโโโโโโโโโโโโโโโโโโโ
+โ AI Agent โ (Copilot Chat, Copilot CLI, Claude Desktop, Cursor, etc.)
+โ "Calculate ฯ to โ
+โ 100 digits" โ
+โโโโโโโโโโฌโโโโโโโโโโโโโ
+ โ MCP (stdio)
+ โผ
+โโโโโโโโโโโโโโโโโโโโโโโ
+โ MCP Server โ server.js โ @modelcontextprotocol/sdk
+โ execute_javascript โ
+โ tool handler โ
+โโโโโโโโโโฌโโโโโโโโโโโโโ
+ โ callHandler({ cpuTimeoutMs: 1000 })
+ โผ
+โโโโโโโโโโโโโโโโโโโโโโโ
+โ Hyperlight Sandbox โ Isolated micro-VM
+โ QuickJS Engine โ No I/O, no host access
+โ โโโโโโโโโโโโโโโโโ โ
+โ โ User's JS codeโ โ
+โ โโโโโโโโโโโโโโโโโ โ
+โโโโโโโโโโโโโโโโโโโโโโโ
+```
+
+## Prerequisites
+
+### 1. Build Hyperlight JS
+
+From the repository root:
+
+```bash
+# Build the runtime and native module (recommended)
+just build-js-host-api release
+```
+
+Or manually:
+
+```bash
+# Build the runtime binary
+just build release
+
+# Build the Node.js native module
+cd src/js-host-api
+npm install
+npm run build
+```
+
+### 2. Install MCP Server Dependencies
+
+```bash
+cd src/js-host-api/examples/mcp-server
+npm install
+```
+
+### 3. Verify It Works
+
+```bash
+# Run the smoke test suite
+npm test
+```
+
+You should see all tests pass, including timeout enforcement and recovery.
+
+## Client Configuration
+
+### VS Code โ GitHub Copilot Chat
+
+Add to your workspace `.vscode/mcp.json`:
+
+```json
+{
+ "servers": {
+ "hyperlight-sandbox": {
+ "type": "stdio",
+ "command": "node",
+ "args": ["src/js-host-api/examples/mcp-server/server.js"]
+ }
+ }
+}
+```
+
+Or add to your VS Code `settings.json`:
+
+```json
+{
+ "mcp": {
+ "servers": {
+ "hyperlight-sandbox": {
+ "type": "stdio",
+ "command": "node",
+ "args": ["src/js-host-api/examples/mcp-server/server.js"],
+ "cwd": "${workspaceFolder}"
+ }
+ }
+ }
+}
+```
+
+Then in Copilot Chat, the `execute_javascript` tool will be available.
+Use **Agent mode** (`@workspace` or the agent panel) to interact with MCP tools.
+
+### Claude Desktop
+
+Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
+or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
+
+```json
+{
+ "mcpServers": {
+ "hyperlight-sandbox": {
+ "command": "node",
+ "args": [
+ "/absolute/path/to/hyperlight-js/src/js-host-api/examples/mcp-server/server.js"
+ ]
+ }
+ }
+}
+```
+
+Restart Claude Desktop after editing the config.
+
+### Cursor
+
+Add to your Cursor MCP settings (Settings โ MCP Servers โ Add):
+
+```json
+{
+ "mcpServers": {
+ "hyperlight-sandbox": {
+ "command": "node",
+ "args": [
+ "/absolute/path/to/hyperlight-js/src/js-host-api/examples/mcp-server/server.js"
+ ]
+ }
+ }
+}
+```
+
+### Claude CLI
+
+```bash
+claude mcp add hyperlight-sandbox -- node /absolute/path/to/server.js
+```
+
+### GitHub Copilot CLI
+
+The new [GitHub Copilot CLI](https://github.com/github/copilot-cli) (`copilot` command)
+supports MCP servers via `~/.copilot/mcp-config.json`.
+
+**Option A โ Interactive setup** (inside a `copilot` session):
+
+```
+/mcp add
+```
+
+Fill in the fields (name: `hyperlight-sandbox`, command: `node`, args: path to
+`server.js`) and press Ctrl+S to save.
+
+**Option B โ Manual config** โ edit (or create) `~/.copilot/mcp-config.json`:
+
+```json
+{
+ "mcpServers": {
+ "hyperlight-sandbox": {
+ "type": "stdio",
+ "command": "node",
+ "args": [
+ "/absolute/path/to/hyperlight-js/src/js-host-api/examples/mcp-server/server.js"
+ ]
+ }
+ }
+}
+```
+
+Then start a session and try a prompt:
+
+```bash
+copilot
+# > Write a function that computes all prime factors of a number, run it on 123456789
+```
+
+> **Tip:** Use `--allow-tool 'hyperlight-sandbox'` to auto-approve the sandbox
+> tool without per-call prompts.
+
+#### Demo Script
+
+Ready-made demo scripts are included for both **Linux/macOS** (bash) and
+**Windows** (PowerShell 7+) to demonstrate the Copilot CLI integration
+end-to-end โ no manual config required.
+
+##### Linux / macOS (bash)
+
+```bash
+cd src/js-host-api/examples/mcp-server
+
+# Interactive mode โ walks you through each demo with pause-between-prompts
+./demo-copilot-cli.sh
+
+# Headless mode โ runs all demos non-interactively (CI-friendly)
+./demo-copilot-cli.sh --headless
+
+# Run a single custom prompt
+./demo-copilot-cli.sh --prompt "Calculate the first 100 Fibonacci numbers" --headless
+
+# Use a specific model (default: claude-opus-4.6)
+./demo-copilot-cli.sh --model gpt-4o --headless
+
+# Show the JavaScript code the model generated
+./demo-copilot-cli.sh --show-code --headless
+
+# Show the copilot CLI command being executed (for debugging/copying)
+./demo-copilot-cli.sh --show-command --headless
+
+# Install the MCP server permanently into ~/.copilot/mcp-config.json
+./demo-copilot-cli.sh --install
+
+# Remove it again
+./demo-copilot-cli.sh --uninstall
+```
+
+##### Windows (PowerShell 7+)
+
+```powershell
+cd src\js-host-api\examples\mcp-server
+
+# Interactive mode
+.\demo-copilot-cli.ps1
+
+# Headless mode โ runs all demos non-interactively
+.\demo-copilot-cli.ps1 -Mode Headless
+
+# Run a single custom prompt
+.\demo-copilot-cli.ps1 -Prompt "Calculate the first 100 Fibonacci numbers" -Mode Headless
+
+# Use a specific model
+.\demo-copilot-cli.ps1 -Model gpt-4o -Mode Headless
+
+# Show the JavaScript code the model generated
+.\demo-copilot-cli.ps1 -ShowCode -Mode Headless
+
+# Show the copilot CLI command being executed
+.\demo-copilot-cli.ps1 -ShowCommand -Mode Headless
+
+# Combine flags freely
+.\demo-copilot-cli.ps1 -Prompt "Solve 8-queens" -ShowCode -ShowCommand -Model gpt-4o -Mode Headless
+
+# Custom sandbox limits
+.\demo-copilot-cli.ps1 -CpuTimeout 2000 -HeapSize 32 -Mode Headless
+
+# Install the MCP server permanently
+.\demo-copilot-cli.ps1 -Mode Install
+
+# Remove it again
+.\demo-copilot-cli.ps1 -Mode Uninstall
+```
+
+> **Note:** The PowerShell script requires PowerShell 7+ (`pwsh`). It is
+> **not** compatible with Windows PowerShell 5.1 (`powershell.exe`).
+
+##### Parameter reference
+
+| Bash flag | PowerShell param | Description |
+| ------------------- | ------------------- | -------------------------------------------------------- |
+| `--headless` | `-Mode Headless` | Non-interactive mode โ runs and exits (CI-friendly) |
+| `--install` | `-Mode Install` | Install MCP config permanently |
+| `--uninstall` | `-Mode Uninstall` | Remove MCP config |
+| `--prompt ` | `-Prompt ` | Run a single custom prompt instead of built-in demos |
+| `--model ` | `-Model ` | LLM model to use (default: `claude-opus-4.6`) |
+| `--show-code` | `-ShowCode` | Display the generated JavaScript source code |
+| `--show-command` | `-ShowCommand` | Display the copilot CLI command line being executed |
+| `--cpu-timeout `| `-CpuTimeout ` | CPU time limit per execution (default: 1000ms) |
+| `--wall-timeout `| `-WallTimeout `| Wall-clock backstop per execution (default: 5000ms) |
+| `--heap-size ` | `-HeapSize ` | Guest heap size (default: 16MB) |
+| `--stack-size ` | `-StackSize ` | Guest stack size (default: 1MB) |
+
+**What the script does:**
+
+1. Checks prerequisites (Node.js, Copilot CLI, built native addon)
+2. Creates a temporary MCP config for the session (or installs permanently with `--install`)
+3. Runs three demo prompts through Copilot CLI's programmatic mode (`-p`):
+ - **ฯ calculation** โ Machin formula to 50 decimal places
+ - **Sieve of Eratosthenes** โ all primes below 10,000
+ - **Maze generation** โ 25ร25 recursive backtracking maze as ASCII art
+4. Displays a per-prompt timing breakdown (model generation vs. tool execution)
+5. Reports pass/fail results
+
+**Security model:**
+
+The script uses the [documented](https://docs.github.com/copilot/concepts/agents/about-copilot-cli)
+`--allow-all-tools` + `--deny-tool` pattern:
+
+| Flag | Purpose |
+| -------------------------- | ----------------------------------------------------- |
+| `--allow-all-tools` | Required for `-p` (non-interactive) mode |
+| `--deny-tool 'shell'` | Blocks **all** shell command execution |
+| `--deny-tool 'write'` | Blocks **all** file write/edit operations |
+| `--deny-tool 'read'` | Blocks **all** file read operations |
+| `--deny-tool 'fetch'` | Blocks **all** web fetch/HTTP operations |
+| `-s` | Silent โ agent response only, no stats or retry noise |
+| `--disable-builtin-mcps` | Removes the GitHub MCP server |
+| `--no-custom-instructions` | Ignores workspace AGENTS.md / copilot-instructions.md |
+| `--no-ask-user` | No clarifying questions in programmatic mode |
+| `--model ` | LLM model to use (default: `claude-opus-4.6`) |
+
+`--deny-tool` takes precedence over `--allow-all-tools`, so the agent can
+_only_ call our MCP sandbox tool โ no shell access, no file writes, no file reads, no web fetches.
+
+**Model selection:**
+
+The `--model` / `-Model` flag selects which LLM model the Copilot CLI uses
+(default: `claude-opus-4.6`):
+
+```bash
+# Bash
+./demo-copilot-cli.sh --model gpt-4o --headless
+./demo-copilot-cli.sh --model claude-sonnet-4 --headless
+```
+
+```powershell
+# PowerShell
+.\demo-copilot-cli.ps1 -Model gpt-4o -Mode Headless
+.\demo-copilot-cli.ps1 -Model claude-sonnet-4 -Mode Headless
+```
+
+**Timing & observability:**
+
+After each prompt, the script displays a timing breakdown showing where time
+was spent:
+
+```
+โฑ Timing breakdown:
+โฑ Copilot CLI (total round-trip) 12.345s
+ ๐ค Model 10.200s (LLM code generation + response)
+ ๐ง Tool execution 0.145s (MCP tool total)
+ โโ Sandbox init: 120ms
+ โโ Handler setup: 2ms
+ โโ Compile & load: 8ms
+ โโ Snapshot: 5ms
+ โโ JS execution: 10ms
+```
+
+- **Model time** is derived by subtracting tool execution time from the total
+ Copilot CLI round-trip. It includes LLM inference, code generation, and
+ response formatting.
+- **Tool execution** is measured server-side by the MCP server, broken down
+ into sandbox init (first call only), handler setup, compilation, snapshot,
+ and actual JavaScript execution.
+- The MCP server writes timing data to a JSON-lines file via the
+ `HYPERLIGHT_TIMING_LOG` environment variable (set automatically by the
+ demo script).
+
+**Code inspection (`--show-code` / `-ShowCode`):**
+
+Display the JavaScript source that the model generated and sent to the sandbox:
+
+```bash
+# Bash
+./demo-copilot-cli.sh --show-code --headless
+./demo-copilot-cli.sh --show-code --model gpt-4o
+```
+
+```powershell
+# PowerShell
+.\demo-copilot-cli.ps1 -ShowCode -Mode Headless
+.\demo-copilot-cli.ps1 -ShowCode -Model gpt-4o -Mode Headless
+```
+
+The generated code is displayed between the Copilot CLI output and the timing
+breakdown:
+
+```
+๐ Generated code:
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const DIGITS = 50;
+ const SCALE = 10n ** BigInt(DIGITS + 10);
+ function arccot(x) { ... }
+ ...
+ return { pi: formatted };
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+```
+
+This is useful for comparing how different models approach the same problem,
+or debugging when results are unexpected. The server writes the received code
+to a temp file via the `HYPERLIGHT_CODE_LOG` environment variable (set
+automatically by the demo script when `--show-code` / `-ShowCode` is active).
+
+**Custom prompts (`--prompt` / `-Prompt`):**
+
+Run a single custom prompt instead of the built-in demo set:
+
+```bash
+# Bash โ headless custom prompt
+./demo-copilot-cli.sh --prompt "Implement quicksort and sort 1000 random numbers" --headless
+
+# Bash โ interactive: runs your prompt first, then offers built-in demos
+./demo-copilot-cli.sh --prompt "Solve the 8-queens problem"
+```
+
+```powershell
+# PowerShell โ headless custom prompt
+.\demo-copilot-cli.ps1 -Prompt "Implement quicksort and sort 1000 random numbers" -Mode Headless
+
+# PowerShell โ interactive: runs your prompt first, then offers built-in demos
+.\demo-copilot-cli.ps1 -Prompt "Solve the 8-queens problem"
+```
+
+In **headless** mode with `--prompt` / `-Prompt`, only the custom prompt runs
+and the script exits. In **interactive** mode the behaviour is the same โ the
+custom prompt runs and the script exits (built-in demos are skipped).
+
+**Command inspection (`--show-command` / `-ShowCommand`):**
+
+Display the full copilot CLI command being executed for each prompt. Useful for
+debugging or copying the command to run manually:
+
+```bash
+./demo-copilot-cli.sh --show-command --headless
+```
+
+```powershell
+.\demo-copilot-cli.ps1 -ShowCommand -Mode Headless
+```
+
+Output (when MCP server is not yet installed, with non-default sandbox limits):
+
+```
+๐ง Copy-pasteable command:
+
+โ The MCP server must be installed before this command will work.
+ Install it now:
+
+ ./demo-copilot-cli.sh --install --cpu-timeout 5000 --heap-size 32
+
+ To remove it later:
+
+ ./demo-copilot-cli.sh --uninstall
+
+ copilot \
+ -p '' \
+ -s \
+ --allow-all-tools \
+ --deny-tool shell \
+ --deny-tool write \
+ --deny-tool read \
+ --deny-tool fetch \
+ --no-custom-instructions \
+ --no-ask-user \
+ --disable-builtin-mcps \
+ --model claude-opus-4.6
+```
+
+Once installed, only the command is shown (no warning).
+
+### Any MCP-Compatible Client
+
+The server uses **stdio transport** โ launch it as a child process and
+communicate via NDJSON (newline-delimited JSON) over stdin/stdout:
+
+```bash
+# Each message is JSON.stringify(msg) + '\n'
+node /path/to/server.js
+```
+
+## Example Prompts ๐ฏ
+
+Here are some creative prompts to try with your AI agent. Each one will
+generate JavaScript, send it to the Hyperlight sandbox via the MCP tool,
+and return the result.
+
+### ๐ข Mathematics
+
+> **"Calculate ฯ to 50 decimal places using the BaileyโBorweinโPlouffe formula"**
+>
+> Tests: BigInt arithmetic, series computation, precision handling
+
+> **"Find all prime numbers below 10,000 using the Sieve of Eratosthenes and return the count and the last 10 primes"**
+>
+> Tests: Array operations, algorithmic efficiency, memory usage
+
+> **"Compute the first 100 digits of Euler's number (e) using the Taylor series"**
+>
+> Tests: Factorial computation, convergence, floating-point handling
+
+> **"Run a Monte Carlo simulation with 100,000 random dart throws to estimate ฯ"**
+>
+> Tests: Random number generation, statistical methods, loop performance
+
+### ๐งฎ Algorithms & Data Structures
+
+> **"Implement quicksort and mergesort, sort an array of 5,000 random numbers with each, and compare their execution times"**
+>
+> Tests: Sorting algorithms, Date.now() timing, recursion depth
+
+> **"Solve the Tower of Hanoi for 15 disks โ return the total number of moves and the first 10 moves"**
+>
+> Tests: Recursive algorithms, exponential growth (2ยนโต - 1 = 32,767 moves)
+
+> **"Find the longest common subsequence of 'AGGTAB' and 'GXTXAYB' using dynamic programming"**
+>
+> Tests: 2D array operations, DP table construction
+
+> **"Implement a trie data structure, insert 1000 random 8-letter words, then search for 100 of them and measure lookup time"**
+>
+> Tests: Object/Map construction, string manipulation, performance measurement
+
+### ๐จ Creative & Visual
+
+> **"Generate a Sierpinski triangle as ASCII art with depth 5"**
+>
+> Tests: Recursive patterns, string building, spatial reasoning
+
+> **"Create a text-based Mandelbrot set visualization using ASCII characters for a 60ร30 grid"**
+>
+> Tests: Complex number arithmetic, nested loops, character mapping
+
+> **"Generate a maze using recursive backtracking on an 21ร21 grid and render it as ASCII"**
+>
+> Tests: Graph traversal, random selection, 2D grid manipulation
+
+### ๐ Cryptography & Encoding
+
+> **"Implement a Caesar cipher, encrypt 'HELLO WORLD' with shift 13 (ROT13), then decrypt it back"**
+>
+> Tests: Character code manipulation, string transformation, round-trip verification
+
+> **"Convert the first 20 Fibonacci numbers to different bases (binary, octal, hex) and return a formatted table"**
+>
+> Tests: Number base conversion, string formatting, data presentation
+
+### ๐งฌ Simulations
+
+> **"Simulate Conway's Game of Life on a 30ร30 grid for 50 generations, starting with a random pattern. Return the final grid and population count per generation"**
+>
+> Tests: 2D array operations, cellular automata rules, state tracking
+
+> **"Simulate a simple particle system: 100 particles with random velocities bouncing inside a 100ร100 box for 1000 timesteps. Return the final positions and total collisions"**
+>
+> Tests: Physics simulation, collision detection, numerical computation
+
+> **"Model a simple predator-prey ecosystem (LotkaโVolterra equations) with Euler's method for 1000 timesteps"**
+>
+> Tests: Differential equations, numerical methods, data collection
+
+### ๐งช Brain Teasers
+
+> **"Solve the 8-queens problem and return all 92 unique solutions"**
+>
+> Tests: Backtracking, constraint satisfaction, combinatorial search
+
+> **"Generate all valid combinations of balanced parentheses for n=8 and count them (should be Catalan number Cโ = 1430)"**
+>
+> Tests: Recursive generation, Catalan numbers, combinatorics
+
+> **"Find all Pythagorean triples where aยฒ + bยฒ = cยฒ and c < 500"**
+>
+> Tests: Number theory, nested loop optimization, mathematical verification
+
+## How the Code Execution Works
+
+When the AI agent calls `execute_javascript`, the server:
+
+1. **Wraps** the code as the body of a `handler(event)` function
+2. **Loads** it into the Hyperlight sandbox (QuickJS engine inside a micro-VM)
+3. **Snapshots** the sandbox state (for recovery after timeouts)
+4. **Executes** with `cpuTimeoutMs: 1000` and `wallClockTimeoutMs: 5000`
+5. **Returns** the JSON-serializable result, or an error message
+6. **Recovers** automatically if execution times out (snapshot/restore)
+7. **Logs timing** (if `HYPERLIGHT_TIMING_LOG` is set) โ a JSON-lines record
+ with `initMs`, `setupMs`, `compileMs`, `snapshotMs`, `executeMs`, and `totalMs`
+8. **Logs code** (if `HYPERLIGHT_CODE_LOG` is set) โ writes the received
+ JavaScript source to the specified file for inspection
+
+### Writing Code for the Sandbox
+
+The code runs as a function body. Use `return` to produce output:
+
+```javascript
+// โ
Simple computation
+let x = 2 + 2;
+return { answer: x };
+
+// โ
Complex computation
+const primes = [];
+for (let n = 2; primes.length < 100; n++) {
+ let isPrime = true;
+ for (let d = 2; d * d <= n; d++) {
+ if (n % d === 0) {
+ isPrime = false;
+ break;
+ }
+ }
+ if (isPrime) primes.push(n);
+}
+return { first100Primes: primes, count: primes.length };
+
+// โ This won't work โ no I/O
+fetch('https://example.com'); // fetch is not available
+require('fs'); // require is not available
+console.log('hello'); // console is not available
+```
+
+## Security
+
+The Hyperlight sandbox provides **hardware-level isolation**:
+
+- ๐ **No filesystem access** โ can't read or write files
+- ๐ **No network access** โ can't make HTTP requests
+- ๐ฅ๏ธ **No host access** โ can't access environment variables, processes, or system calls
+- โฑ๏ธ **CPU bounded** โ configurable limit (default 1000ms), enforced by the hypervisor
+- ๐พ **Memory bounded** โ configurable (default 16MB heap, 1MB stack)
+- ๐ **Automatic recovery** โ sandbox rebuilds after failures
+
+This makes it safe to execute untrusted, AI-generated code.
+
+## Environment Variables
+
+| Variable | Default | Description |
+| ------------------------------ | -------- | ---------------------------------------------------------------------------------- |
+| `HYPERLIGHT_CPU_TIMEOUT_MS` | `1000` | Maximum CPU time per execution (milliseconds). The hypervisor hard-kills the guest when exceeded. |
+| `HYPERLIGHT_WALL_TIMEOUT_MS` | `5000` | Maximum wall-clock time per execution (milliseconds). Backstop for edge cases where CPU time alone doesn't catch the issue. |
+| `HYPERLIGHT_HEAP_SIZE_MB` | `16` | Guest heap size in megabytes. Increase for memory-heavy computations (large arrays, BigInt work). |
+| `HYPERLIGHT_STACK_SIZE_MB` | `1` | Guest stack size in megabytes. Increase for deeply recursive algorithms. |
+| `HYPERLIGHT_TIMING_LOG` | โ | Path to a file. When set, the server appends one JSON line per tool call with a timing breakdown (init, setup, compile, snapshot, execute, total). Used by the demo script to show model vs. tool time. |
+| `HYPERLIGHT_CODE_LOG` | โ | Path to a file. When set, the server writes the received JavaScript source code on each tool call. Used by the demo script's `--show-code` flag. |
+
+Example โ tighten limits for a multi-tenant deployment:
+
+```bash
+HYPERLIGHT_CPU_TIMEOUT_MS=500 HYPERLIGHT_HEAP_SIZE_MB=8 node server.js
+```
+
+## Troubleshooting
+
+### "Cannot find module '../../lib.js'"
+
+The native module hasn't been built. Run from the repo root:
+
+```bash
+just build-js-host-api release
+```
+
+### "Execution timed out"
+
+The code exceeded the CPU time limit (default: 1000ms). Options:
+
+- **Increase the timeout** โ set the `HYPERLIGHT_CPU_TIMEOUT_MS` environment
+ variable, or use the demo script's `--cpu-timeout` / `-CpuTimeout` flag:
+
+ ```bash
+ # Bash โ 5 second CPU limit
+ ./demo-copilot-cli.sh --cpu-timeout 5000 --headless
+
+ # Or via environment variable (works with any MCP client)
+ HYPERLIGHT_CPU_TIMEOUT_MS=5000 node server.js
+ ```
+
+ ```powershell
+ # PowerShell โ 5 second CPU limit
+ .\demo-copilot-cli.ps1 -CpuTimeout 5000 -Mode Headless
+
+ # Or via environment variable
+ $env:HYPERLIGHT_CPU_TIMEOUT_MS = 5000; node server.js
+ ```
+
+- **Increase the wall-clock backstop** โ if the CPU limit is fine but the
+ overall execution is being killed, raise `HYPERLIGHT_WALL_TIMEOUT_MS`
+ (default: 5000ms) via `--wall-timeout` / `-WallTimeout`.
+
+- Reduce iteration counts or use more efficient algorithms
+- Break the problem into smaller pieces
+
+### Server doesn't start
+
+Check that:
+
+1. Node.js >= 18 is installed
+2. The native module is built (`ls src/js-host-api/js-host-api.*.node`)
+3. Dependencies are installed (`cd examples/mcp-server && npm install`)
+
+## Files
+
+| File | Description |
+| ------------------------------- | --------------------------------------------------------------------------------- |
+| `server.js` | MCP server โ stdio transport, `execute_javascript` tool |
+| `demo-copilot-cli.sh` | Bash demo script (Linux/macOS) โ see [Demo Script](#demo-script) |
+| `demo-copilot-cli.ps1` | PowerShell demo script (Windows) โ see [Demo Script](#demo-script) |
+| `demo.gif` | Animated demo of the Copilot CLI integration |
+| `tests/mcp-server.test.js` | Vitest integration tests โ validates the server end-to-end |
+| `tests/prompt-examples.test.js` | Vitest tests for all README example prompts |
+| `tests/timing.test.js` | Vitest tests for timing log output (HYPERLIGHT_TIMING_LOG) |
+| `tests/config.test.js` | Vitest tests for env-var configuration (custom limits, invalid values, fallbacks) |
+| `vitest.config.js` | Vitest configuration |
+| `package.json` | Dependencies and scripts |
+| `README.md` | You are here ๐ |
diff --git a/src/js-host-api/examples/mcp-server/demo-copilot-cli.ps1 b/src/js-host-api/examples/mcp-server/demo-copilot-cli.ps1
new file mode 100644
index 0000000..c8ea733
--- /dev/null
+++ b/src/js-host-api/examples/mcp-server/demo-copilot-cli.ps1
@@ -0,0 +1,801 @@
+#Requires -Version 7.0
+<#
+.SYNOPSIS
+ Hyperlight JS ร GitHub Copilot CLI Demo (PowerShell edition)
+
+.DESCRIPTION
+ "Open the pod bay doors, HAL." โ 2001: A Space Odyssey (1968)
+ ... except this sandbox actually listens when you tell it to stop.
+
+ This script demonstrates using the Hyperlight JS MCP server with
+ GitHub Copilot CLI. It:
+ 1. Verifies prerequisites (Copilot CLI + Node.js + built guest)
+ 2. Configures the MCP server (persistent or session-only)
+ 3. Runs example prompts via Copilot CLI programmatic mode
+
+.PARAMETER Mode
+ Operating mode: Interactive (default), Headless, Install, or Uninstall.
+
+.PARAMETER Model
+ LLM model to use (default: claude-opus-4.6).
+
+.PARAMETER Prompt
+ A custom prompt to run instead of the built-in demos. Runs the single
+ prompt and exits (in both Headless and Interactive modes).
+
+.PARAMETER ShowCode
+ Display the JavaScript code generated by the model.
+
+.PARAMETER ShowCommand
+ Display the copilot CLI command line being executed. Useful for debugging
+ or copying the command to run manually.
+
+.PARAMETER CpuTimeout
+ CPU time limit per execution in milliseconds (default: 1000).
+
+.PARAMETER WallTimeout
+ Wall-clock backstop per execution in milliseconds (default: 5000).
+
+.PARAMETER HeapSize
+ Guest heap size in megabytes (default: 16).
+
+.PARAMETER StackSize
+ Guest stack size in megabytes (default: 1).
+
+.EXAMPLE
+ .\demo-copilot-cli.ps1
+ # Interactive โ walks you through it
+
+.EXAMPLE
+ .\demo-copilot-cli.ps1 -Mode Headless
+ # Non-interactive โ runs all demos
+
+.EXAMPLE
+ .\demo-copilot-cli.ps1 -Mode Install
+ # Install MCP config permanently
+
+.EXAMPLE
+ .\demo-copilot-cli.ps1 -Model gpt-4o -Mode Headless
+ # Use a specific model
+
+.EXAMPLE
+ .\demo-copilot-cli.ps1 -Prompt "Calculate the first 100 Fibonacci numbers" -Mode Headless
+ # Run a single custom prompt
+
+.EXAMPLE
+ .\demo-copilot-cli.ps1 -ShowCommand -Mode Headless
+ # Show the copilot CLI command being used
+
+.EXAMPLE
+ .\demo-copilot-cli.ps1 -CpuTimeout 2000 -HeapSize 32 -Mode Headless
+ # Custom sandbox limits
+#>
+
+[CmdletBinding()]
+param(
+ [ValidateSet('Interactive', 'Headless', 'Install', 'Uninstall')]
+ [string]$Mode = 'Interactive',
+
+ [string]$Model = 'claude-opus-4.6',
+
+ [string]$Prompt,
+
+ [switch]$ShowCode,
+
+ [switch]$ShowCommand,
+
+ [ValidateRange(1, [int]::MaxValue)]
+ [int]$CpuTimeout = $(if ($env:HYPERLIGHT_CPU_TIMEOUT_MS) { [int]$env:HYPERLIGHT_CPU_TIMEOUT_MS } else { 1000 }),
+
+ [ValidateRange(1, [int]::MaxValue)]
+ [int]$WallTimeout = $(if ($env:HYPERLIGHT_WALL_TIMEOUT_MS) { [int]$env:HYPERLIGHT_WALL_TIMEOUT_MS } else { 5000 }),
+
+ [ValidateRange(1, [int]::MaxValue)]
+ [int]$HeapSize = $(if ($env:HYPERLIGHT_HEAP_SIZE_MB) { [int]$env:HYPERLIGHT_HEAP_SIZE_MB } else { 16 }),
+
+ [ValidateRange(1, [int]::MaxValue)]
+ [int]$StackSize = $(if ($env:HYPERLIGHT_STACK_SIZE_MB) { [int]$env:HYPERLIGHT_STACK_SIZE_MB } else { 1 })
+)
+
+# โโ Strict Mode โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+# โโ Encoding โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Ensure native commands (copilot, node) emit and receive UTF-8.
+# Without this, Windows mangles ฯ โ โงร, โ โ ฮรรถ, etc.
+# "We're not in Kansas anymore." โ The Wizard of Oz (1939, close enough)
+[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
+$OutputEncoding = [System.Text.Encoding]::UTF8
+
+# โโ Paths โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
+$ServerJs = Join-Path $ScriptDir 'server.js'
+
+# Copilot CLI config paths โ "These go to eleven." โ Spinal Tap (1984)
+# Copilot CLI's --config-dir defaults to ~/.copilot on ALL platforms,
+# NOT %APPDATA% on Windows. Using the wrong path means copilot never
+# sees the installed config โ "Nobody puts Baby in the wrong corner."
+# โ Dirty Dancing (1987)
+if ($env:XDG_CONFIG_HOME) {
+ $CopilotConfigDir = $env:XDG_CONFIG_HOME
+} else {
+ $CopilotConfigDir = Join-Path $HOME '.copilot'
+}
+$McpConfigFile = Join-Path $CopilotConfigDir 'mcp-config.json'
+$McpServerName = 'hyperlight-sandbox'
+
+# โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function Write-Info { param([string]$Message) Write-Host "โน $Message" -ForegroundColor Cyan }
+function Write-Ok { param([string]$Message) Write-Host "โ
$Message" -ForegroundColor Green }
+function Write-Warn { param([string]$Message) Write-Host "โ $Message" -ForegroundColor Yellow }
+function Write-Fail { param([string]$Message) Write-Host "โ $Message" -ForegroundColor Red; exit 1 }
+
+function Write-Banner {
+ Write-Host ''
+ Write-Host '๐ Hyperlight JS ร GitHub Copilot CLI Demo' -ForegroundColor Cyan
+ Write-Host ''
+}
+
+function Write-Separator {
+ Write-Host ''
+ Write-Host 'โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ' -ForegroundColor DarkGray
+ Write-Host ''
+}
+
+function Get-NowMs {
+ [long]([DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds())
+}
+
+function Write-Elapsed {
+ param([string]$Phase, [long]$StartMs)
+ $elapsed = (Get-NowMs) - $StartMs
+ $secs = [math]::Floor($elapsed / 1000)
+ $frac = $elapsed % 1000
+ Write-Host ("โฑ {0,-40} {1}.{2:D3}s" -f $Phase, $secs, $frac) -ForegroundColor DarkGray
+}
+
+# โโ Prerequisite Checks โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function Test-Prerequisites {
+ Write-Info 'Checking prerequisites...'
+
+ # Node.js
+ $nodeBin = Get-Command node -ErrorAction SilentlyContinue
+ if (-not $nodeBin) { Write-Fail 'Node.js is required but not installed. Install from https://nodejs.org' }
+ $nodeVersion = & node --version 2>&1
+ Write-Ok "Node.js $nodeVersion"
+
+ # Copilot CLI
+ $script:CopilotBin = $null
+ $copilotCmd = Get-Command copilot -ErrorAction SilentlyContinue
+ if ($copilotCmd) {
+ $script:CopilotBin = $copilotCmd.Source
+ } else {
+ # Check typical VS Code Server location (Linux remote / WSL)
+ $vscodePath = Join-Path $HOME '.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli/copilot'
+ if (Test-Path $vscodePath) {
+ $script:CopilotBin = $vscodePath
+ }
+ # Check typical Windows location
+ if (-not $script:CopilotBin -and ($IsWindows -or $env:OS -match 'Windows')) {
+ $winPath = Join-Path $env:LOCALAPPDATA 'Programs\copilot-cli\copilot.exe'
+ if (Test-Path $winPath) { $script:CopilotBin = $winPath }
+ }
+ }
+ if (-not $script:CopilotBin) {
+ Write-Fail 'GitHub Copilot CLI not found. Install with: npm install -g @github/copilot'
+ }
+ Write-Ok "Copilot CLI found at: $($script:CopilotBin)"
+
+ # Server script
+ if (-not (Test-Path $ServerJs)) { Write-Fail "Server script not found at: $ServerJs" }
+ Write-Ok "MCP server: $ServerJs"
+
+ # npm dependencies
+ $nodeModules = Join-Path $ScriptDir 'node_modules'
+ if (-not (Test-Path $nodeModules)) {
+ Write-Warn 'node_modules not found โ installing dependencies...'
+ # Delegate to cmd.exe to dodge PowerShell's long-standing issues
+ # with .cmd shim argument mangling. "Short Circuit" (1986) โ we
+ # need the real npm, not Johnny 5 having a stroke.
+ Push-Location $ScriptDir
+ try {
+ & cmd /c 'npm install'
+ if ($LASTEXITCODE -ne 0) { Write-Fail 'npm install failed' }
+ } finally {
+ Pop-Location
+ }
+ }
+ Write-Ok 'Dependencies installed'
+
+ # Native addon smoke test โ verify the .node binary loads and has
+ # the methods we need. The old check started server.js and waited
+ # for a banner that never came (stdio transport blocks on connect).
+ # This just imports lib.js which validates the native addon at
+ # module load time. Instant, reliable, catches the exact failure.
+ # "Trust, but verify." โ Reagan (1987)
+ Write-Info 'Verifying native addon loads correctly...'
+ $libJs = Join-Path $ScriptDir '../../lib.js'
+ # node --input-type=module so we can use dynamic import() from stdin
+ $smokeResult = & node --input-type=module -e "import('file:///' + '$($libJs -replace '\\','/')').then(() => console.log('OK')).catch(e => { console.error(e.message); process.exit(1); })" 2>&1
+ if ($LASTEXITCODE -ne 0) {
+ $errMsg = ($smokeResult | Out-String).Trim()
+ Write-Fail "Native addon failed to load โ rebuild with 'just build'.`n$errMsg"
+ }
+ Write-Ok 'Native addon loads successfully'
+
+ Write-Host ''
+}
+
+# โโ MCP Config Management โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function Build-McpJson {
+ <#
+ .SYNOPSIS
+ Build the JSON MCP config, forwarding sandbox limits + observability
+ env vars. Copilot CLI spawns its own server process, so we must
+ embed config in the MCP config's "env" field.
+
+ Uses native PowerShell JSON instead of node -e to avoid PS7's
+ native-command argument mangling with multi-line strings.
+ "Who you gonna call?" โ Ghostbusters (1984). ConvertTo-Json.
+ .PARAMETER ForInstall
+ When set, uses fixed well-known temp paths for HYPERLIGHT_TIMING_LOG
+ and HYPERLIGHT_CODE_LOG instead of per-session random paths. This
+ lets Copilot spawn the server with predictable log locations.
+ #>
+ param([switch]$ForInstall)
+
+ $env:HYPERLIGHT_CPU_TIMEOUT_MS = $CpuTimeout
+ $env:HYPERLIGHT_WALL_TIMEOUT_MS = $WallTimeout
+ $env:HYPERLIGHT_HEAP_SIZE_MB = $HeapSize
+ $env:HYPERLIGHT_STACK_SIZE_MB = $StackSize
+
+ $config = [ordered]@{
+ mcpServers = [ordered]@{
+ $McpServerName = (Build-McpServerEntry -ForInstall:$ForInstall)
+ }
+ }
+
+ ConvertTo-Json $config -Depth 5
+}
+
+function Build-McpServerEntry {
+ <#
+ .SYNOPSIS
+ Build a single MCP server entry (type, command, args, env).
+ Shared by Build-McpJson (full config) and Install-McpConfig
+ (merge into existing config).
+ #>
+ param([switch]$ForInstall)
+
+ # Build the env block โ sandbox limits always included, observability
+ # vars only when set AND this is not a permanent install.
+ $envHash = [ordered]@{
+ HYPERLIGHT_CPU_TIMEOUT_MS = [string]$CpuTimeout
+ HYPERLIGHT_WALL_TIMEOUT_MS = [string]$WallTimeout
+ HYPERLIGHT_HEAP_SIZE_MB = [string]$HeapSize
+ HYPERLIGHT_STACK_SIZE_MB = [string]$StackSize
+ }
+
+ if ($ForInstall) {
+ # Permanent install: use fixed well-known paths so Copilot can
+ # spawn the server with predictable log locations.
+ # "Roads? Where we're going, we don't need roads." โ Back to the Future (1985)
+ $tempDir = [System.IO.Path]::GetTempPath()
+ $envHash['HYPERLIGHT_TIMING_LOG'] = (Join-Path $tempDir 'hyperlight-timing.jsonl') -replace '\\', '/'
+ $envHash['HYPERLIGHT_CODE_LOG'] = (Join-Path $tempDir 'hyperlight-code.js') -replace '\\', '/'
+ } else {
+ # Per-session: use the current env vars (random per-session paths
+ # set by the script to avoid clobbering between concurrent runs).
+ if ($env:HYPERLIGHT_TIMING_LOG) { $envHash['HYPERLIGHT_TIMING_LOG'] = $env:HYPERLIGHT_TIMING_LOG }
+ if ($env:HYPERLIGHT_CODE_LOG) { $envHash['HYPERLIGHT_CODE_LOG'] = $env:HYPERLIGHT_CODE_LOG }
+ }
+
+ # Use forward slashes in the server path for JSON portability
+ $serverPath = $ServerJs -replace '\\', '/'
+
+ [ordered]@{
+ type = 'stdio'
+ command = 'node'
+ args = @($serverPath)
+ env = $envHash
+ }
+}
+
+function Install-McpConfig {
+ Write-Info "Installing MCP server config to $McpConfigFile..."
+
+ if (-not (Test-Path $CopilotConfigDir)) {
+ New-Item -ItemType Directory -Path $CopilotConfigDir -Force | Out-Null
+ }
+
+ if (Test-Path $McpConfigFile) {
+ $existingRaw = Get-Content $McpConfigFile -Raw
+ if ($existingRaw -match [regex]::Escape($McpServerName)) {
+ Write-Ok "Already configured in $McpConfigFile"
+ return
+ }
+
+ # Backup existing config
+ $backupPath = "$McpConfigFile.bak"
+ Copy-Item $McpConfigFile $backupPath
+ Write-Warn "Backed up existing config to $backupPath"
+
+ # Merge: add our server to existing config โ pure PowerShell,
+ # no node -e shenanigans. "Do. Or do not. There is no try."
+ # โ Yoda, The Empire Strikes Back (1980)
+ $existingConfig = $existingRaw | ConvertFrom-Json -AsHashtable
+ if (-not $existingConfig.ContainsKey('mcpServers')) {
+ $existingConfig['mcpServers'] = @{}
+ }
+ $existingConfig['mcpServers'][$McpServerName] = Build-McpServerEntry -ForInstall
+ $existingConfig | ConvertTo-Json -Depth 5 | Set-Content -Path $McpConfigFile -Encoding utf8NoBOM
+ } else {
+ Build-McpJson -ForInstall | Set-Content -Path $McpConfigFile -Encoding utf8NoBOM
+ }
+
+ Write-Ok "Installed! Config written to $McpConfigFile"
+ Write-Host (Get-Content $McpConfigFile -Raw) -ForegroundColor DarkGray
+}
+
+function Uninstall-McpConfig {
+ if (-not (Test-Path $McpConfigFile)) {
+ Write-Warn "No config file found at $McpConfigFile"
+ return
+ }
+
+ $content = Get-Content $McpConfigFile -Raw
+ if ($content -notmatch [regex]::Escape($McpServerName)) {
+ Write-Warn "$McpServerName not found in $McpConfigFile"
+ return
+ }
+
+ # Pure PowerShell JSON manipulation โ no node -e needed.
+ # "Wax on, wax off." โ The Karate Kid (1984)
+ $config = $content | ConvertFrom-Json -AsHashtable
+ if ($config.ContainsKey('mcpServers')) {
+ $config['mcpServers'].Remove($McpServerName)
+ }
+
+ if (($config['mcpServers'] ?? @{}).Count -eq 0) {
+ Remove-Item $McpConfigFile
+ Write-Host 'Config file removed (no servers remaining).'
+ } else {
+ $config | ConvertTo-Json -Depth 5 | Set-Content -Path $McpConfigFile -Encoding utf8NoBOM
+ Write-Host 'Server removed from config.'
+ }
+ Write-Ok "Uninstalled $McpServerName from Copilot CLI config"
+}
+
+function Test-ConfigConflict {
+ <#
+ .SYNOPSIS
+ Warn if a permanent install exists โ the script's per-session
+ --additional-mcp-config will override it for the duration of
+ the session. Same server name = temp config wins via augment
+ merge semantics. "Danger, Will Robinson!" โ Lost in Space
+ (syndicated throughout the 80s)
+ #>
+ if ((Test-Path $McpConfigFile) -and
+ ((Get-Content $McpConfigFile -Raw) -match [regex]::Escape($McpServerName))) {
+ Write-Warn "A permanent install of '$McpServerName' exists in $McpConfigFile."
+ Write-Warn 'The script''s per-session config (--additional-mcp-config) will override it.'
+ Write-Warn 'To use the permanent config directly, just run: copilot'
+ }
+}
+
+# โโ Demo Prompts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Each entry is @{ Title = '...'; Prompt = '...' }
+$DemoPrompts = @(
+ @{
+ Title = 'Calculate ฯ to 50 decimal places'
+ Prompt = 'Write a function using the Machin formula to calculate ฯ to 50 decimal places. Return the result as a JSON object with the value as a string.'
+ }
+ @{
+ Title = 'Sieve of Eratosthenes'
+ Prompt = 'Find all prime numbers below 10,000 using the Sieve of Eratosthenes. Return JSON with the count and the last 10 primes.'
+ }
+ @{
+ Title = 'Maze Generation'
+ Prompt = 'Generate a random 25ร25 maze using recursive backtracking. Return it as ASCII art using # for walls and spaces for paths, plus the dimensions as JSON.'
+ }
+)
+
+# โโ Run Prompt โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function Invoke-DemoPrompt {
+ <#
+ .SYNOPSIS
+ Run a single prompt via Copilot CLI programmatic mode.
+ Returns $true on success, $false on failure.
+ #>
+ param(
+ [string]$Title,
+ [string]$Prompt
+ )
+
+ $promptStart = Get-NowMs
+
+ Write-Host "๐ฏ $Title" -ForegroundColor White
+ Write-Host "Prompt: $Prompt" -ForegroundColor DarkGray
+ Write-Host "Model: $Model" -ForegroundColor DarkGray
+
+ # Timing log โ the MCP server writes a JSON-lines record here each
+ # time execute_javascript is called. Like Knight Rider's KITT
+ # dashboard but for sandbox performance.
+ # Avoid GetTempFileName() โ it creates an orphan .tmp file we'd
+ # never clean up. Construct path directly instead.
+ $timingLog = Join-Path ([System.IO.Path]::GetTempPath()) "hyperlight-timing-$PID-$(Get-Random).jsonl"
+ $env:HYPERLIGHT_TIMING_LOG = $timingLog
+
+ # Code log โ the MCP server dumps the received JS source here
+ $codeLog = $null
+ if ($ShowCode) {
+ $codeLog = Join-Path ([System.IO.Path]::GetTempPath()) "hyperlight-code-$PID-$(Get-Random).js"
+ $env:HYPERLIGHT_CODE_LOG = $codeLog
+ } else {
+ Remove-Item Env:\HYPERLIGHT_CODE_LOG -ErrorAction SilentlyContinue
+ }
+
+ # Build MCP config to a temp file โ utf8NoBOM to avoid confusing
+ # copilot CLI's JSON parser with a BOM header.
+ $mcpTmp = Join-Path ([System.IO.Path]::GetTempPath()) "hyperlight-mcp-$PID-$(Get-Random).json"
+ Build-McpJson | Set-Content -Path $mcpTmp -Encoding utf8NoBOM
+
+ # Structured prompt โ steers the agent to call our tool directly.
+ # Avoids the "Full Metal Jacket" problem where the agent goes on
+ # a recon mission reading every file instead of calling the tool.
+ $fullPrompt = @"
+TASK: $Prompt
+
+INSTRUCTIONS: You have an MCP tool called 'execute_javascript' from the 'hyperlight-sandbox' server. This tool takes a single parameter 'code' containing JavaScript source code. The code runs in a sandboxed QuickJS engine, NOT Node.js โ there is no require(), no fetch(), no fs. The code MUST use 'return ' at the top level to produce output (like a function body). Call the execute_javascript tool with your JavaScript code now. Do NOT read files, start servers, run shell commands, or do anything else. After you receive the tool result, present the answer in ONE short response and STOP. Do NOT attempt any follow-up actions, additional tool calls, or continuation. Your task is complete the moment you present the result.
+"@
+
+ # Write the prompt to a temp file and pass via @file to avoid PS7's
+ # native-command argument mangling with multi-line here-strings.
+ # The copilot CLI reads -p @file the same as -p "string".
+ # "Nobody puts Baby in a corner." โ Dirty Dancing (1987)
+ $promptFile = Join-Path ([System.IO.Path]::GetTempPath()) "hyperlight-prompt-$PID-$(Get-Random).txt"
+ $fullPrompt | Set-Content -Path $promptFile -Encoding utf8NoBOM
+
+ # Build the command args list for copilot CLI.
+ # We assemble it as an array so we can display it with -ShowCommand
+ # before actually executing it.
+ $copilotArgs = @(
+ '-p', $fullPrompt,
+ '-s',
+ '--additional-mcp-config', "@$mcpTmp",
+ '--allow-all-tools',
+ '--deny-tool', 'shell',
+ '--deny-tool', 'write',
+ '--deny-tool', 'read',
+ '--deny-tool', 'fetch',
+ '--no-custom-instructions',
+ '--no-ask-user',
+ '--disable-builtin-mcps',
+ '--model', $Model
+ )
+
+ # Emit the command if -ShowCommand was requested.
+ # "Show me the money!" โ Jerry Maguire (1996... close enough to the 80s)
+ if ($ShowCommand) {
+ # Copy-pasteable command with the full structured prompt.
+ # Omits --additional-mcp-config because the temp file gets
+ # cleaned up. Relies on a permanent install.
+ $escapedPrompt = $fullPrompt -replace "'", "''"
+ $mcpInstalled = (Test-Path $McpConfigFile) -and
+ ((Get-Content $McpConfigFile -Raw) -match [regex]::Escape($McpServerName))
+
+ # Build the install command with any non-default sandbox limits
+ # so the permanent config gets the same settings.
+ # "If you build it, he will come." โ Field of Dreams (1989)
+ $installFlags = ''
+ if ($CpuTimeout -ne 1000) { $installFlags += " -CpuTimeout $CpuTimeout" }
+ if ($WallTimeout -ne 5000) { $installFlags += " -WallTimeout $WallTimeout" }
+ if ($HeapSize -ne 16) { $installFlags += " -HeapSize $HeapSize" }
+ if ($StackSize -ne 1) { $installFlags += " -StackSize $StackSize" }
+
+ Write-Host ''
+ Write-Host '๐ง Copy-pasteable command:' -ForegroundColor Cyan
+ if (-not $mcpInstalled) {
+ Write-Host ''
+ Write-Host 'โ The MCP server must be installed before this command will work.' -ForegroundColor Yellow
+ Write-Host ' Install it now:' -ForegroundColor Yellow
+ Write-Host ''
+ Write-Host (" .\demo-copilot-cli.ps1 -Mode Install{0}" -f $installFlags) -ForegroundColor Yellow
+ Write-Host ''
+ Write-Host ' To remove it later:' -ForegroundColor Yellow
+ Write-Host ''
+ Write-Host ' .\demo-copilot-cli.ps1 -Mode Uninstall' -ForegroundColor Yellow
+ }
+ Write-Host ''
+ Write-Host ' copilot `'
+ Write-Host (" -p '{0}' ``" -f $escapedPrompt)
+ Write-Host ' -s `'
+ Write-Host ' --allow-all-tools `'
+ Write-Host ' --deny-tool shell `'
+ Write-Host ' --deny-tool write `'
+ Write-Host ' --deny-tool read `'
+ Write-Host ' --deny-tool fetch `'
+ Write-Host ' --no-custom-instructions `'
+ Write-Host ' --no-ask-user `'
+ Write-Host ' --disable-builtin-mcps `'
+ Write-Host (" --model {0}" -f $Model)
+ Write-Host ''
+ }
+
+ # Flags rationale (from `copilot --help`):
+ #
+ # --additional-mcp-config @file Register our MCP server for this session
+ # only (no permanent install required).
+ #
+ # --allow-all-tools REQUIRED for -p (non-interactive) mode.
+ # Individual --allow-tool patterns only work
+ # in interactive mode's permission prompts.
+ # See `copilot --help` for details.
+ #
+ # --available-tools Restrict model to ONLY our MCP tool plus
+ # internal tools the agent needs to function
+ # (task_complete, report_intent). The model
+ # cannot call shell, file write, web fetch,
+ # or any other tool. This is the security
+ # layer โ even though --allow-all-tools is
+ # set, only whitelisted tools are visible.
+ #
+ # --no-custom-instructions Don't load AGENTS.md / copilot-instructions.md
+ # which could confuse the agent.
+ #
+ # --no-ask-user Don't ask clarifying questions in -p mode.
+ #
+ # --disable-builtin-mcps Don't load the GitHub MCP server โ we only
+ # want our sandbox.
+ #
+ # -p Programmatic (non-interactive) mode.
+ #
+ # Security: use the documented --allow-all-tools + --deny-tool pattern
+ # from https://docs.github.com/copilot/concepts/agents/about-copilot-cli
+ #
+ # --allow-all-tools : auto-approve all tools (required for -p mode)
+ # --deny-tool 'shell' : BLOCK all shell commands (takes precedence)
+ # --deny-tool 'write' : BLOCK all file write/edit operations
+ # --deny-tool 'read' : BLOCK file reading (agent should only use our MCP tool)
+ # --deny-tool 'fetch' : BLOCK web/HTTP requests
+ # -s : Silent โ agent response only, no stats/retry noise
+ #
+ # --deny-tool takes precedence over --allow-all-tools per the docs.
+ # Internal plumbing tools (task_complete, report_intent) are NOT
+ # in the shell/write/MCP categories so they remain available.
+ # Our MCP tool (hyperlight-sandbox โ execute_javascript) is allowed
+ # because it's not denied.
+ #
+ # -s suppresses the agentic loop retry noise ("Continuing autonomously",
+ # "Response was interrupted due to a server error") that occurs when
+ # the model's continuation turn hits transient API errors after
+ # already completing the actual task.
+ #
+ # We also filter out the "Execution failed" line that leaks through
+ # -s when the agentic continuation gives up โ the actual task was
+ # already successful at that point. The exit code is still 0.
+ #
+ # Like WarGames taught us: the only winning move is not to grant
+ # shell access. โ WarGames (1983)
+ $copilotStart = Get-NowMs
+
+ try {
+ # Use --% (stop-parsing token) to prevent PS from mangling
+ # the native command arguments. Pass prompt via temp file.
+ $rawOutput = & $script:CopilotBin `
+ -p $fullPrompt `
+ -s `
+ --additional-mcp-config "@$mcpTmp" `
+ --allow-all-tools `
+ --deny-tool shell `
+ --deny-tool write `
+ --deny-tool read `
+ --deny-tool fetch `
+ --no-custom-instructions `
+ --no-ask-user `
+ --disable-builtin-mcps `
+ --model $Model 2>&1
+
+ # Filter output:
+ # 1. Drop "Execution failed:" noise from agentic continuation retries
+ # 2. Strip leading blank lines the CLI emits while waiting for the
+ # model โ keeps blank lines *within* the response body intact.
+ # "Every breath you take, every blank you makeโฆ" โ The Police (1983)
+ $lines = $rawOutput -split "`n" |
+ Where-Object { $_ -notmatch '^Execution failed:' }
+
+ # Strip leading blank lines (keep blanks within the body)
+ $seenContent = $false
+ $filteredLines = @()
+ foreach ($line in $lines) {
+ if (-not $seenContent -and $line.Trim() -eq '') { continue }
+ $seenContent = $true
+ $filteredLines += $line
+ }
+
+ # Output the response
+ $filteredLines | ForEach-Object { Write-Host $_ }
+ $success = $true
+ }
+ catch {
+ Write-Warn "Prompt failed (Copilot may not be authenticated or the model may be unavailable)"
+ Write-Warn $_.Exception.Message
+ $success = $false
+ }
+
+ $copilotElapsedMs = (Get-NowMs) - $copilotStart
+
+ # โโ Show generated code (if -ShowCode) โโโโโโโโโโโโโโโโโโโโโโโ
+ if ($ShowCode -and $codeLog -and (Test-Path $codeLog) -and (Get-Item $codeLog).Length -gt 0) {
+ Write-Host ''
+ Write-Host '๐ Generated code:' -ForegroundColor White
+ Write-Host 'โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ' -ForegroundColor DarkGray
+ Get-Content $codeLog | ForEach-Object {
+ Write-Host " $_" -ForegroundColor DarkGray
+ }
+ Write-Host 'โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ' -ForegroundColor DarkGray
+ }
+
+ # โโ Timing breakdown โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Write-Host ''
+ Write-Host 'โฑ Timing breakdown:' -ForegroundColor White
+ Write-Elapsed 'Copilot CLI (total round-trip)' $copilotStart
+
+ if ((Test-Path $timingLog) -and (Get-Item $timingLog).Length -gt 0) {
+ # Parse the last timing record (in case the model called the
+ # tool more than once, the last call is the interesting one).
+ # Pure PowerShell โ no node -e needed. "ConvertFrom-Json is
+ # the one who knocks." โ adapted from Breaking Bad (close to 80s)
+ $lastLine = Get-Content $timingLog -Tail 1
+ $timing = $lastLine | ConvertFrom-Json
+
+ $toolTotalMs = [int]$timing.totalMs
+ $toolInitMs = [int]$timing.initMs
+ $toolSetupMs = [int]$timing.setupMs
+ $toolCompileMs = [int]$timing.compileMs
+ $toolSnapMs = [int]$timing.snapshotMs
+ $toolExecMs = [int]$timing.executeMs
+
+ $modelMs = $copilotElapsedMs - $toolTotalMs
+ $modelSecs = [math]::Floor($modelMs / 1000)
+ $modelFrac = $modelMs % 1000
+ $toolSecs = [math]::Floor($toolTotalMs / 1000)
+ $toolFrac = $toolTotalMs % 1000
+
+ Write-Host (" ๐ค {0,-36} {1}.{2:D3}s (LLM code generation + response)" -f 'Model', $modelSecs, $modelFrac) -ForegroundColor Cyan
+ Write-Host (" ๐ง {0,-36} {1}.{2:D3}s (MCP tool total)" -f 'Tool execution', $toolSecs, $toolFrac) -ForegroundColor Cyan
+
+ if ($toolInitMs -gt 0) {
+ Write-Host (" โโ Sandbox init: {0,5}ms" -f $toolInitMs) -ForegroundColor DarkGray
+ }
+ Write-Host (" โโ Handler setup: {0,5}ms" -f $toolSetupMs) -ForegroundColor DarkGray
+ Write-Host (" โโ Compile & load: {0,5}ms" -f $toolCompileMs) -ForegroundColor DarkGray
+ Write-Host (" โโ Snapshot: {0,5}ms" -f $toolSnapMs) -ForegroundColor DarkGray
+ Write-Host (" โโ JS execution: {0,5}ms" -f $toolExecMs) -ForegroundColor DarkGray
+ } else {
+ Write-Warn 'No tool timing data โ the model may not have called the tool'
+ }
+
+ Write-Elapsed "Total for '$Title'" $promptStart
+
+ # Clean up temp files
+ Remove-Item $mcpTmp -ErrorAction SilentlyContinue
+ Remove-Item $timingLog -ErrorAction SilentlyContinue
+ Remove-Item $promptFile -ErrorAction SilentlyContinue
+ if ($codeLog) { Remove-Item $codeLog -ErrorAction SilentlyContinue }
+
+ return $success
+}
+
+# โโ Main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+Write-Banner
+Write-Info "Using model: $Model"
+Write-Info "Sandbox limits: CPU ${CpuTimeout}ms, wall ${WallTimeout}ms, heap ${HeapSize}MB, stack ${StackSize}MB"
+
+switch ($Mode) {
+ 'Install' {
+ Test-Prerequisites
+ Install-McpConfig
+ Write-Host ''
+ Write-Info 'You can now use it in Copilot CLI:'
+ Write-Host ' copilot' -ForegroundColor White
+ Write-Host ' > Calculate ฯ to 50 decimal places using the Machin formula' -ForegroundColor DarkGray
+ exit 0
+ }
+
+ 'Uninstall' {
+ Uninstall-McpConfig
+ exit 0
+ }
+
+ 'Headless' {
+ Test-Prerequisites
+ Test-ConfigConflict
+
+ # If a custom prompt was given, run just that one and exit.
+ # "There can be only one." โ Highlander (1986)
+ if ($Prompt) {
+ Write-Info 'Running custom prompt in headless mode...'
+ Write-Separator
+ $runStart = Get-NowMs
+ $result = Invoke-DemoPrompt -Title 'Custom prompt' -Prompt $Prompt
+ Write-Separator
+ Write-Elapsed 'Custom prompt' $runStart
+ exit ([int](-not $result))
+ }
+
+ Write-Info "Running $($DemoPrompts.Count) demo prompts in headless mode..."
+ Write-Separator
+
+ $runStart = Get-NowMs
+ $passed = 0
+ $failed = 0
+
+ foreach ($demo in $DemoPrompts) {
+ $result = Invoke-DemoPrompt -Title $demo.Title -Prompt $demo.Prompt
+ if ($result) { $passed++ } else { $failed++ }
+ Write-Separator
+ }
+
+ Write-Elapsed "All $($DemoPrompts.Count) prompts" $runStart
+ Write-Host ''
+ Write-Host "Results: $passed passed" -ForegroundColor Green
+ if ($failed -gt 0) {
+ Write-Host " $failed failed" -ForegroundColor Red
+ }
+ exit $failed
+ }
+
+ default {
+ # Interactive mode
+ Test-Prerequisites
+ Test-ConfigConflict
+
+ # Run the custom prompt if one was provided, then exit
+ if ($Prompt) {
+ Write-Info 'Running your custom prompt...'
+ Write-Separator
+ Invoke-DemoPrompt -Title 'Custom prompt' -Prompt $Prompt | Out-Null
+ Write-Separator
+ Write-Info 'Done! "I''ll be back." โ The Terminator (1984)'
+ exit 0
+ }
+
+ Write-Info "This demo will run $($DemoPrompts.Count) prompts through Copilot CLI,"
+ Write-Info 'each executing JavaScript inside a Hyperlight sandbox.'
+ Write-Host ''
+ Write-Info 'The MCP server is configured per-session (no permanent install).'
+ Write-Info "To install permanently, run: .\demo-copilot-cli.ps1 -Mode Install"
+ Write-Separator
+
+ Write-Host 'Ready to run demos?' -ForegroundColor Yellow
+ Read-Host 'Press Enter to continue (or Ctrl+C to cancel)'
+ Write-Host ''
+
+ for ($i = 0; $i -lt $DemoPrompts.Count; $i++) {
+ $demo = $DemoPrompts[$i]
+ Invoke-DemoPrompt -Title $demo.Title -Prompt $demo.Prompt | Out-Null
+ Write-Separator
+
+ if ($i + 1 -lt $DemoPrompts.Count) {
+ $answer = Read-Host 'Next demo? (Enter to continue, q to quit)'
+ if ($answer -eq 'q') {
+ # "I'll be back." โ The Terminator (1984)
+ Write-Info 'Thanks for watching! "I''ll be back." โ The Terminator (1984)'
+ exit 0
+ }
+ }
+ }
+
+ Write-Host '๐ All demos complete!' -ForegroundColor Green
+ # "That's all, folks!" โ Porky Pig (well, 1930s, close enough)
+ Write-Host '"That''s all, folks!" โ Porky Pig (well, 1930s, close enough)' -ForegroundColor DarkGray
+ }
+}
diff --git a/src/js-host-api/examples/mcp-server/demo-copilot-cli.sh b/src/js-host-api/examples/mcp-server/demo-copilot-cli.sh
new file mode 100755
index 0000000..0170a9f
--- /dev/null
+++ b/src/js-host-api/examples/mcp-server/demo-copilot-cli.sh
@@ -0,0 +1,735 @@
+#!/usr/bin/env bash
+# โโ Hyperlight JS ร GitHub Copilot CLI Demo โโโโโโโโโโโโโโโโโโโโโโโโโ
+#
+# "Open the pod bay doors, HAL." โ 2001: A Space Odyssey (1968)
+# ... except this sandbox actually listens when you tell it to stop.
+#
+# This script demonstrates using the Hyperlight JS MCP server with
+# GitHub Copilot CLI. It:
+# 1. Verifies prerequisites (Copilot CLI + Node.js + built guest)
+# 2. Configures the MCP server (persistent or session-only)
+# 3. Runs example prompts via Copilot CLI programmatic mode
+#
+# Usage:
+# ./demo-copilot-cli.sh # Interactive โ walks you through it
+# ./demo-copilot-cli.sh --headless # Non-interactive โ runs all demos
+# ./demo-copilot-cli.sh --prompt "Calculate pi to 50 digits" # Run a single prompt
+# ./demo-copilot-cli.sh --install # Install MCP config permanently
+# ./demo-copilot-cli.sh --uninstall # Remove MCP config
+# ./demo-copilot-cli.sh --model gpt-4o # Use a specific model
+# ./demo-copilot-cli.sh --cpu-timeout 2000 --heap-size 32 # Custom limits
+#
+# Options:
+# --model LLM model to use (default: claude-opus-4.6)
+# --show-code Display the JavaScript code generated by the model
+# --show-command Display the copilot CLI command line being executed
+# --prompt Run a single custom prompt instead of built-in demos
+# --cpu-timeout CPU time limit per execution (default: 1000)
+# --wall-timeout Wall-clock backstop per execution (default: 5000)
+# --heap-size Guest heap size in megabytes (default: 16)
+# --stack-size Guest stack size in megabytes (default: 1)
+#
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+set -euo pipefail
+
+# โโ Colours & Formatting โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+MAGENTA='\033[0;35m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+DIM='\033[2m'
+RESET='\033[0m'
+
+# โโ Defaults โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# "These go to eleven." โ Spinal Tap (1984)
+DEFAULT_MODEL="claude-opus-4.6"
+MODEL="${DEFAULT_MODEL}"
+SHOW_CODE=false
+SHOW_COMMAND=false
+CUSTOM_PROMPT=""
+
+# Sandbox limits โ can be overridden via CLI flags or env vars.
+# CLI flags take precedence over env vars, env vars over defaults.
+CPU_TIMEOUT="${HYPERLIGHT_CPU_TIMEOUT_MS:-1000}"
+WALL_TIMEOUT="${HYPERLIGHT_WALL_TIMEOUT_MS:-5000}"
+HEAP_SIZE="${HYPERLIGHT_HEAP_SIZE_MB:-16}"
+STACK_SIZE="${HYPERLIGHT_STACK_SIZE_MB:-1}"
+
+# โโ Paths โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SERVER_JS="${SCRIPT_DIR}/server.js"
+COPILOT_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.copilot}"
+MCP_CONFIG_FILE="${COPILOT_CONFIG_DIR}/mcp-config.json"
+MCP_SERVER_NAME="hyperlight-sandbox"
+
+# โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+info() { echo -e "${CYAN}โน${RESET} $*"; }
+ok() { echo -e "${GREEN}โ
${RESET} $*"; }
+warn() { echo -e "${YELLOW}โ ${RESET} $*"; }
+fail() { echo -e "${RED}โ${RESET} $*" >&2; exit 1; }
+
+banner() {
+ echo ""
+ echo -e "${BOLD}${CYAN}๐ Hyperlight JS ร GitHub Copilot CLI Demo${RESET}"
+ echo ""
+}
+
+separator() {
+ echo -e "${DIM}\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${RESET}\n"
+}
+
+# Timing helpers โ because "Time... is not on my side" โ The Rolling Stones (1964, close enough)
+# Uses millisecond precision via date +%s%3N (GNU coreutils)
+now_ms() {
+ date +%s%3N
+}
+
+# Log elapsed time since a given start timestamp (ms)
+# Usage: log_elapsed "phase description" "$start_ms"
+log_elapsed() {
+ local phase="$1"
+ local start="$2"
+ local end
+ end="$(now_ms)"
+ local elapsed_ms=$(( end - start ))
+ local elapsed_s elapsed_frac
+ elapsed_s=$(( elapsed_ms / 1000 ))
+ elapsed_frac=$(( elapsed_ms % 1000 ))
+ printf "${DIM}โฑ %-40s %d.%03ds${RESET}\n" "${phase}" "${elapsed_s}" "${elapsed_frac}"
+}
+
+# โโ Prerequisite Checks โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+check_prerequisites() {
+ info "Checking prerequisites..."
+
+ # Node.js
+ if ! command -v node &>/dev/null; then
+ fail "Node.js is required but not installed. Install from https://nodejs.org"
+ fi
+ ok "Node.js $(node --version)"
+
+ # Copilot CLI
+ if command -v copilot &>/dev/null; then
+ COPILOT_BIN="copilot"
+ elif [[ -x "${HOME}/.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli/copilot" ]]; then
+ COPILOT_BIN="${HOME}/.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli/copilot"
+ else
+ fail "GitHub Copilot CLI not found. Install with: npm install -g @github/copilot"
+ fi
+ ok "Copilot CLI found at: ${COPILOT_BIN}"
+
+ # Server script
+ if [[ ! -f "${SERVER_JS}" ]]; then
+ fail "Server script not found at: ${SERVER_JS}"
+ fi
+ ok "MCP server: ${SERVER_JS}"
+
+ # npm dependencies
+ if [[ ! -d "${SCRIPT_DIR}/node_modules" ]]; then
+ warn "node_modules not found โ installing dependencies..."
+ (cd "${SCRIPT_DIR}" && npm install) || fail "npm install failed"
+ fi
+ ok "Dependencies installed"
+
+ # Native addon smoke test โ verify the .node binary loads and has
+ # the methods we need. The old check started server.js and waited
+ # for a banner that never came (stdio transport blocks on connect).
+ # This just imports lib.js which validates the native addon at
+ # module load time. Instant, reliable, catches the exact failure.
+ # "Trust, but verify." โ Reagan (1987)
+ info "Verifying native addon loads correctly..."
+ local lib_js="${SCRIPT_DIR}/../../lib.js"
+ local smoke_err
+ smoke_err="$(node --input-type=module -e "import('${lib_js}').then(() => console.log('OK')).catch(e => { console.error(e.message); process.exit(1); })" 2>&1)"
+ if [[ $? -ne 0 ]]; then
+ fail "Native addon failed to load โ rebuild with 'just build'.\n${smoke_err}"
+ fi
+ ok "Native addon loads successfully"
+
+ echo ""
+}
+
+# โโ MCP Config Management โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Build the JSON config for this MCP server.
+# Passes HYPERLIGHT_TIMING_LOG and HYPERLIGHT_CODE_LOG via the env
+# field so the Copilot CLI-spawned server process receives them โ
+# environment inheritance doesn't work because Copilot CLI is the
+# parent process, not our shell.
+build_mcp_json() {
+ # Forward sandbox configuration + observability env vars to the
+ # MCP server process. Copilot CLI spawns the server itself, so
+ # normal env inheritance from our shell doesn't apply โ we must
+ # embed them in the MCP config's "env" field.
+ local install_mode="${1:-}"
+
+ export HYPERLIGHT_CPU_TIMEOUT_MS="${CPU_TIMEOUT}"
+ export HYPERLIGHT_WALL_TIMEOUT_MS="${WALL_TIMEOUT}"
+ export HYPERLIGHT_HEAP_SIZE_MB="${HEAP_SIZE}"
+ export HYPERLIGHT_STACK_SIZE_MB="${STACK_SIZE}"
+
+ node -e "
+ const installMode = '${install_mode}' === 'install';
+ const env = {
+ HYPERLIGHT_CPU_TIMEOUT_MS: process.env.HYPERLIGHT_CPU_TIMEOUT_MS,
+ HYPERLIGHT_WALL_TIMEOUT_MS: process.env.HYPERLIGHT_WALL_TIMEOUT_MS,
+ HYPERLIGHT_HEAP_SIZE_MB: process.env.HYPERLIGHT_HEAP_SIZE_MB,
+ HYPERLIGHT_STACK_SIZE_MB: process.env.HYPERLIGHT_STACK_SIZE_MB,
+ };
+ if (installMode) {
+ // Permanent install: fixed well-known paths so Copilot can
+ // spawn the server with predictable log locations.
+ // \"Roads? Where we're going, we don't need roads.\" โ Back to the Future (1985)
+ env.HYPERLIGHT_TIMING_LOG = '/tmp/hyperlight-timing.jsonl';
+ env.HYPERLIGHT_CODE_LOG = '/tmp/hyperlight-code.js';
+ } else {
+ if (process.env.HYPERLIGHT_TIMING_LOG) env.HYPERLIGHT_TIMING_LOG = process.env.HYPERLIGHT_TIMING_LOG;
+ if (process.env.HYPERLIGHT_CODE_LOG) env.HYPERLIGHT_CODE_LOG = process.env.HYPERLIGHT_CODE_LOG;
+ }
+
+ const server = {
+ type: 'stdio',
+ command: 'node',
+ args: ['${SERVER_JS}'],
+ env,
+ };
+
+ console.log(JSON.stringify({ mcpServers: { '${MCP_SERVER_NAME}': server } }, null, 4));
+ "
+}
+
+# Install config permanently into ~/.copilot/mcp-config.json
+install_mcp_config() {
+ info "Installing MCP server config to ${MCP_CONFIG_FILE}..."
+ mkdir -p "${COPILOT_CONFIG_DIR}"
+
+ if [[ -f "${MCP_CONFIG_FILE}" ]]; then
+ # Check if already configured
+ if grep -q "${MCP_SERVER_NAME}" "${MCP_CONFIG_FILE}" 2>/dev/null; then
+ ok "Already configured in ${MCP_CONFIG_FILE}"
+ return 0
+ fi
+
+ # Backup existing config
+ cp "${MCP_CONFIG_FILE}" "${MCP_CONFIG_FILE}.bak"
+ warn "Backed up existing config to ${MCP_CONFIG_FILE}.bak"
+
+ # Merge: add our server to existing mcpServers with full env
+ # block including fixed log paths for the permanent install.
+ # Use node for reliable JSON merging.
+ node -e "
+ const fs = require('fs');
+ const existing = JSON.parse(fs.readFileSync('${MCP_CONFIG_FILE}.bak', 'utf8'));
+ existing.mcpServers = existing.mcpServers || {};
+ existing.mcpServers['${MCP_SERVER_NAME}'] = {
+ type: 'stdio',
+ command: 'node',
+ args: ['${SERVER_JS}'],
+ env: {
+ HYPERLIGHT_CPU_TIMEOUT_MS: '${CPU_TIMEOUT}',
+ HYPERLIGHT_WALL_TIMEOUT_MS: '${WALL_TIMEOUT}',
+ HYPERLIGHT_HEAP_SIZE_MB: '${HEAP_SIZE}',
+ HYPERLIGHT_STACK_SIZE_MB: '${STACK_SIZE}',
+ HYPERLIGHT_TIMING_LOG: '/tmp/hyperlight-timing.jsonl',
+ HYPERLIGHT_CODE_LOG: '/tmp/hyperlight-code.js',
+ },
+ };
+ fs.writeFileSync('${MCP_CONFIG_FILE}', JSON.stringify(existing, null, 4) + '\n');
+ "
+ else
+ build_mcp_json install > "${MCP_CONFIG_FILE}"
+ fi
+
+ ok "Installed! Config written to ${MCP_CONFIG_FILE}"
+ echo -e "${DIM}$(cat "${MCP_CONFIG_FILE}")${RESET}"
+}
+
+# Remove our server from the config
+uninstall_mcp_config() {
+ if [[ ! -f "${MCP_CONFIG_FILE}" ]]; then
+ warn "No config file found at ${MCP_CONFIG_FILE}"
+ return 0
+ fi
+
+ if ! grep -q "${MCP_SERVER_NAME}" "${MCP_CONFIG_FILE}" 2>/dev/null; then
+ warn "${MCP_SERVER_NAME} not found in ${MCP_CONFIG_FILE}"
+ return 0
+ fi
+
+ node -e "
+ const fs = require('fs');
+ const config = JSON.parse(fs.readFileSync('${MCP_CONFIG_FILE}', 'utf8'));
+ delete config.mcpServers?.['${MCP_SERVER_NAME}'];
+ if (Object.keys(config.mcpServers || {}).length === 0) {
+ fs.unlinkSync('${MCP_CONFIG_FILE}');
+ console.log('Config file removed (no servers remaining).');
+ } else {
+ fs.writeFileSync('${MCP_CONFIG_FILE}', JSON.stringify(config, null, 4) + '\n');
+ console.log('Server removed from config.');
+ }
+ "
+ ok "Uninstalled ${MCP_SERVER_NAME} from Copilot CLI config"
+}
+
+# Warn if a permanent install exists โ the script's per-session
+# --additional-mcp-config will override it for the duration of the
+# session. Same server name = temp config wins via augment merge
+# semantics. "Danger, Will Robinson!" โ Lost in Space (syndicated
+# throughout the 80s)
+check_config_conflict() {
+ if [[ -f "${MCP_CONFIG_FILE}" ]] && grep -q "${MCP_SERVER_NAME}" "${MCP_CONFIG_FILE}" 2>/dev/null; then
+ warn "A permanent install of '${MCP_SERVER_NAME}' exists in ${MCP_CONFIG_FILE}."
+ warn "The script's per-session config (--additional-mcp-config) will override it."
+ warn "To use the permanent config directly, just run: copilot"
+ fi
+}
+
+# โโ Demo Prompts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Array of demo prompts โ each is [title, prompt]
+DEMO_PROMPTS=(
+ "Calculate ฯ to 50 decimal places"
+ "Write a function using the Machin formula to calculate ฯ to 50 decimal places. Return the result as a JSON object with the value as a string."
+
+ "Sieve of Eratosthenes"
+ "Find all prime numbers below 10,000 using the Sieve of Eratosthenes. Return JSON with the count and the last 10 primes."
+
+ "Maze Generation"
+ "Generate a random 25ร25 maze using recursive backtracking. Return it as ASCII art using # for walls and spaces for paths, plus the dimensions as JSON."
+)
+DEMO_COUNT=$(( ${#DEMO_PROMPTS[@]} / 2 ))
+
+# Run a single prompt via Copilot CLI programmatic mode
+run_prompt() {
+ local title="$1"
+ local prompt="$2"
+ local prompt_start
+ prompt_start="$(now_ms)"
+
+ echo -e "${BOLD}๐ฏ ${title}${RESET}"
+ echo -e "${DIM}Prompt: ${prompt}${RESET}"
+ echo -e "${DIM}Model: ${MODEL}${RESET}"
+
+ # Flags rationale (from `copilot --help`):
+ #
+ # --additional-mcp-config @file Register our MCP server for this session
+ # only (no permanent install required).
+ #
+ # --allow-all-tools REQUIRED for -p (non-interactive) mode.
+ # Individual --allow-tool patterns only work
+ # in interactive mode's permission prompts.
+ # See `copilot --help` for details.
+ #
+ # --available-tools Restrict model to ONLY our MCP tool plus
+ # internal tools the agent needs to function
+ # (task_complete, report_intent). The model
+ # cannot call shell, file write, web fetch,
+ # or any other tool. This is the security
+ # layer โ even though --allow-all-tools is
+ # set, only whitelisted tools are visible.
+ #
+ # --no-custom-instructions Don't load AGENTS.md / copilot-instructions.md
+ # which could confuse the agent.
+ #
+ # --no-ask-user Don't ask clarifying questions in -p mode.
+ #
+ # --disable-builtin-mcps Don't load the GitHub MCP server โ we only
+ # want our sandbox.
+ #
+ # -p Programmatic (non-interactive) mode.
+ #
+ # Write MCP config to a temp file โ the --additional-mcp-config
+ # flag accepts a file path when prefixed with @.
+ # Timing log file โ the MCP server writes a JSON-lines record here
+ # each time execute_javascript is called. We read it afterwards to
+ # show a model-vs-tool breakdown. Like Knight Rider's KITT dashboard
+ # but for sandbox performance.
+ #
+ # Must be set BEFORE build_mcp_json so the env field is included
+ # in the MCP config (Copilot CLI spawns the server, not our shell,
+ # so normal env inheritance doesn't apply).
+ local timing_log
+ timing_log="$(mktemp "${TMPDIR:-/tmp}/hyperlight-timing-XXXXXX.jsonl")"
+ export HYPERLIGHT_TIMING_LOG="${timing_log}"
+
+ # Code log โ the MCP server dumps the received JS source here
+ local code_log=""
+ if [[ "${SHOW_CODE}" == true ]]; then
+ code_log="$(mktemp "${TMPDIR:-/tmp}/hyperlight-code-XXXXXX.js")"
+ export HYPERLIGHT_CODE_LOG="${code_log}"
+ else
+ unset HYPERLIGHT_CODE_LOG 2>/dev/null || true
+ fi
+
+ local mcp_tmp
+ mcp_tmp="$(mktemp "${TMPDIR:-/tmp}/hyperlight-mcp-XXXXXX.json")"
+ build_mcp_json > "${mcp_tmp}"
+
+ # shellcheck disable=SC2064
+ trap "rm -f '${mcp_tmp}' '${timing_log}' '${code_log:-}'" RETURN
+
+ # The prompt is deliberately structured to steer the agent:
+ # 1. Lead with the task โ what JS code to write
+ # 2. Explicitly name the tool to call
+ # 3. Remind it the code must use `return` to produce output
+ # 4. Tell it NOT to read files, start servers, or use shell
+ #
+ # This avoids the "Full Metal Jacket" problem where the agent
+ # goes on a recon mission reading every file in the repo instead
+ # of just calling the damn tool.
+ local full_prompt
+ full_prompt="TASK: ${prompt}
+
+INSTRUCTIONS: You have an MCP tool called 'execute_javascript' from the 'hyperlight-sandbox' server. \
+This tool takes a single parameter 'code' containing JavaScript source code. \
+The code runs in a sandboxed QuickJS engine, NOT Node.js โ there is no require(), no fetch(), no fs. \
+The code MUST use 'return ' at the top level to produce output (like a function body). \
+Call the execute_javascript tool with your JavaScript code now. \
+Do NOT read files, start servers, run shell commands, or do anything else. \
+After you receive the tool result, present the answer in ONE short response and STOP. \
+Do NOT attempt any follow-up actions, additional tool calls, or continuation. \
+Your task is complete the moment you present the result."
+
+ # Security: use the documented --allow-all-tools + --deny-tool pattern
+ # from https://docs.github.com/copilot/concepts/agents/about-copilot-cli
+ #
+ # --allow-all-tools : auto-approve all tools (required for -p mode)
+ # --deny-tool 'shell' : BLOCK all shell commands (takes precedence)
+ # --deny-tool 'write' : BLOCK all file write/edit operations
+ # --deny-tool 'read' : BLOCK file reading (agent should only use our MCP tool)
+ # --deny-tool 'fetch' : BLOCK web/HTTP requests
+ # -s : Silent โ agent response only, no stats/retry noise
+ #
+ # --deny-tool takes precedence over --allow-all-tools per the docs.
+ # Internal plumbing tools (task_complete, report_intent) are NOT
+ # in the shell/write/MCP categories so they remain available.
+ # Our MCP tool (hyperlight-sandbox โ execute_javascript) is allowed
+ # because it's not denied.
+ #
+ # -s suppresses the agentic loop retry noise ("Continuing autonomously",
+ # "Response was interrupted due to a server error") that occurs when
+ # the model's continuation turn hits transient API errors after
+ # already completing the actual task.
+ #
+ # We also filter out the "Execution failed" line that leaks through
+ # -s when the agentic continuation gives up โ the actual task was
+ # already successful at that point. The exit code is still 0.
+ #
+ # Like WarGames taught us: the only winning move is not to grant
+ # shell access.
+
+ # โShow me the money!โ โ Jerry Maguire (1996... close enough to the 80s)
+ if [[ "${SHOW_COMMAND}" == true ]]; then
+ # Copy-pasteable command with the full structured prompt.
+ # Uses double quotes for -p so single quotes in the prompt
+ # don't need escaping. Omits --additional-mcp-config because
+ # the temp file gets cleaned up. Relies on a permanent install.
+ local mcp_installed=false
+ if [[ -f "${MCP_CONFIG_FILE}" ]] && grep -q "${MCP_SERVER_NAME}" "${MCP_CONFIG_FILE}" 2>/dev/null; then
+ mcp_installed=true
+ fi
+
+ # Build the install command with any non-default sandbox limits
+ # so the permanent config gets the same settings.
+ # "If you build it, he will come." โ Field of Dreams (1989)
+ local install_flags=""
+ [[ "${CPU_TIMEOUT}" != "1000" ]] && install_flags+=" --cpu-timeout ${CPU_TIMEOUT}"
+ [[ "${WALL_TIMEOUT}" != "5000" ]] && install_flags+=" --wall-timeout ${WALL_TIMEOUT}"
+ [[ "${HEAP_SIZE}" != "16" ]] && install_flags+=" --heap-size ${HEAP_SIZE}"
+ [[ "${STACK_SIZE}" != "1" ]] && install_flags+=" --stack-size ${STACK_SIZE}"
+
+ echo ""
+ echo -e "${CYAN}${BOLD}๐ง Copy-pasteable command:${RESET}"
+ if [[ "${mcp_installed}" != true ]]; then
+ echo ""
+ echo -e "${YELLOW}โ The MCP server must be installed before this command will work.${RESET}"
+ echo -e "${YELLOW} Install it now:${RESET}"
+ echo ""
+ echo -e "${YELLOW} ./demo-copilot-cli.sh --install${install_flags}${RESET}"
+ echo ""
+ echo -e "${YELLOW} To remove it later:${RESET}"
+ echo ""
+ echo -e "${YELLOW} ./demo-copilot-cli.sh --uninstall${RESET}"
+ fi
+ echo ""
+ echo " copilot \\"
+ echo " -p \"${full_prompt}\" \\"
+ echo " -s \\"
+ echo " --allow-all-tools \\"
+ echo " --deny-tool shell \\"
+ echo " --deny-tool write \\"
+ echo " --deny-tool read \\"
+ echo " --deny-tool fetch \\"
+ echo " --no-custom-instructions \\"
+ echo " --no-ask-user \\"
+ echo " --disable-builtin-mcps \\"
+ echo " --model ${MODEL}"
+ echo ""
+ fi
+
+ local copilot_start
+ copilot_start="$(now_ms)"
+
+ # Pipe through two filters:
+ # 1. Drop "Execution failed:" noise from agentic continuation retries
+ # 2. Strip leading blank lines the CLI emits while waiting for the
+ # model โ keeps blank lines *within* the response body intact.
+ # "Every breath you take, every blank you makeโฆ" โ The Police (1983)
+ "${COPILOT_BIN}" \
+ -p "${full_prompt}" \
+ -s \
+ --additional-mcp-config "@${mcp_tmp}" \
+ --allow-all-tools \
+ --deny-tool 'shell' \
+ --deny-tool 'write' \
+ --deny-tool 'read' \
+ --deny-tool 'fetch' \
+ --no-custom-instructions \
+ --no-ask-user \
+ --disable-builtin-mcps \
+ --model "${MODEL}" 2>&1 \
+ | grep -v '^Execution failed:' \
+ | awk 'NF{p=1} p' || {
+ warn "Prompt failed (Copilot may not be authenticated or the model may be unavailable)"
+ return 1
+ }
+
+ local copilot_elapsed_ms
+ copilot_elapsed_ms=$(( $(now_ms) - copilot_start ))
+
+ # โโ Timing breakdown โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Read the MCP server's timing log to split "model thinking" from
+ # "tool execution". The server writes one JSON line per tool call.
+ # โโ Show generated code (if --show-code) โโโโโโโโโโโโโโโโโโโโโ
+ if [[ "${SHOW_CODE}" == true && -n "${code_log}" && -s "${code_log}" ]]; then
+ echo -e "\n${BOLD}๐ Generated code:${RESET}"
+ echo -e "${DIM}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${RESET}"
+ # Use sed to indent and dim each line for readability
+ sed "s/^/ /" "${code_log}" | while IFS= read -r line; do
+ echo -e "${DIM}${line}${RESET}"
+ done
+ echo -e "${DIM}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${RESET}"
+ fi
+
+ echo -e "\n${BOLD}โฑ Timing breakdown:${RESET}"
+ log_elapsed "Copilot CLI (total round-trip)" "${copilot_start}"
+
+ if [[ -s "${timing_log}" ]]; then
+ # Parse the last timing record (in case the model called the
+ # tool more than once, the last call is the interesting one).
+ # Single node invocation outputs space-separated values.
+ local timing_values
+ timing_values="$(tail -1 "${timing_log}" | node -e "
+ process.stdin.resume();
+ let d = '';
+ process.stdin.on('data', c => d += c);
+ process.stdin.on('end', () => {
+ const t = JSON.parse(d);
+ // Output: totalMs initMs setupMs compileMs snapshotMs executeMs
+ console.log([t.totalMs, t.initMs, t.setupMs, t.compileMs, t.snapshotMs, t.executeMs].join(' '));
+ });
+ ")"
+
+ # Read into individual variables
+ local tool_total_ms tool_init_ms tool_setup_ms tool_compile_ms tool_snap_ms tool_exec_ms
+ read -r tool_total_ms tool_init_ms tool_setup_ms tool_compile_ms tool_snap_ms tool_exec_ms <<< "${timing_values}"
+
+ # Model time โ total copilot time minus tool time
+ local model_ms=$(( copilot_elapsed_ms - tool_total_ms ))
+
+ printf " ${CYAN}%-38s${RESET} %d.%03ds ${DIM}(LLM code generation + response)${RESET}\n" \
+ "๐ค Model" "$(( model_ms / 1000 ))" "$(( model_ms % 1000 ))"
+ printf " ${CYAN}%-38s${RESET} %d.%03ds ${DIM}(MCP tool total)${RESET}\n" \
+ "๐ง Tool execution" "$(( tool_total_ms / 1000 ))" "$(( tool_total_ms % 1000 ))"
+
+ # Sub-breakdown of tool time (indented)
+ if (( tool_init_ms > 0 )); then
+ printf " ${DIM}โโ Sandbox init: %5dms${RESET}\n" "${tool_init_ms}"
+ fi
+ printf " ${DIM}โโ Handler setup: %5dms${RESET}\n" "${tool_setup_ms}"
+ printf " ${DIM}โโ Compile & load: %5dms${RESET}\n" "${tool_compile_ms}"
+ printf " ${DIM}โโ Snapshot: %5dms${RESET}\n" "${tool_snap_ms}"
+ printf " ${DIM}โโ JS execution: %5dms${RESET}\n" "${tool_exec_ms}"
+ else
+ warn "No tool timing data โ the model may not have called the tool"
+ fi
+
+ log_elapsed "Total for '${title}'" "${prompt_start}"
+}
+
+# โโ Main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+main() {
+ # Parse arguments โ extract --model before dispatching mode
+ local mode="interactive"
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --model)
+ if [[ -z "${2:-}" ]]; then
+ fail "--model requires a value (e.g. --model claude-opus-4.6)"
+ fi
+ MODEL="$2"
+ shift 2
+ ;;
+ --show-code)
+ SHOW_CODE=true
+ shift
+ ;;
+ --show-command)
+ SHOW_COMMAND=true
+ shift
+ ;;
+ --prompt)
+ if [[ -z "${2:-}" ]]; then fail "--prompt requires a value"; fi
+ CUSTOM_PROMPT="$2"
+ shift 2
+ ;;
+ --cpu-timeout)
+ if [[ -z "${2:-}" ]]; then fail "--cpu-timeout requires a value in ms"; fi
+ CPU_TIMEOUT="$2"
+ shift 2
+ ;;
+ --wall-timeout)
+ if [[ -z "${2:-}" ]]; then fail "--wall-timeout requires a value in ms"; fi
+ WALL_TIMEOUT="$2"
+ shift 2
+ ;;
+ --heap-size)
+ if [[ -z "${2:-}" ]]; then fail "--heap-size requires a value in MB"; fi
+ HEAP_SIZE="$2"
+ shift 2
+ ;;
+ --stack-size)
+ if [[ -z "${2:-}" ]]; then fail "--stack-size requires a value in MB"; fi
+ STACK_SIZE="$2"
+ shift 2
+ ;;
+ --install|--uninstall|--headless)
+ mode="$1"
+ shift
+ ;;
+ *)
+ fail "Unknown argument: $1\nUsage: $0 [--headless] [--install] [--uninstall] [--model ] [--prompt ] [--show-code] [--show-command] [--cpu-timeout ] [--wall-timeout ] [--heap-size ] [--stack-size ]"
+ ;;
+ esac
+ done
+
+ banner
+ info "Using model: ${BOLD}${MODEL}${RESET}"
+ info "Sandbox limits: CPU ${BOLD}${CPU_TIMEOUT}ms${RESET}, wall ${BOLD}${WALL_TIMEOUT}ms${RESET}, heap ${BOLD}${HEAP_SIZE}MB${RESET}, stack ${BOLD}${STACK_SIZE}MB${RESET}"
+
+ case "${mode}" in
+ --install)
+ check_prerequisites
+ install_mcp_config
+ echo ""
+ info "You can now use it in Copilot CLI:"
+ echo -e " ${BOLD}copilot${RESET}"
+ echo -e " ${DIM}> Calculate ฯ to 50 decimal places using the Machin formula${RESET}"
+ exit 0
+ ;;
+ --uninstall)
+ uninstall_mcp_config
+ exit 0
+ ;;
+ --headless)
+ check_prerequisites
+ check_config_conflict
+
+ # If a custom prompt was given, run just that one and exit.
+ # "There can be only one." โ Highlander (1986)
+ if [[ -n "${CUSTOM_PROMPT}" ]]; then
+ info "Running custom prompt in headless mode..."
+ separator
+ local run_start
+ run_start="$(now_ms)"
+ if run_prompt "Custom prompt" "${CUSTOM_PROMPT}"; then
+ separator
+ log_elapsed "Custom prompt" "${run_start}"
+ exit 0
+ else
+ separator
+ log_elapsed "Custom prompt" "${run_start}"
+ exit 1
+ fi
+ fi
+
+ info "Running ${DEMO_COUNT} demo prompts in headless mode..."
+ separator
+
+ local run_start
+ run_start="$(now_ms)"
+ local passed=0
+ local failed=0
+
+ for (( i=0; i<${#DEMO_PROMPTS[@]}; i+=2 )); do
+ local title="${DEMO_PROMPTS[$i]}"
+ local prompt="${DEMO_PROMPTS[$((i+1))]}"
+
+ if run_prompt "${title}" "${prompt}"; then
+ passed=$((passed + 1))
+ else
+ failed=$((failed + 1))
+ fi
+ separator
+ done
+
+ log_elapsed "All ${DEMO_COUNT} prompts" "${run_start}"
+ echo -e "\n${BOLD}Results: ${GREEN}${passed} passed${RESET}"
+ if (( failed > 0 )); then
+ echo -e " ${RED}${failed} failed${RESET}"
+ fi
+ exit "${failed}"
+ ;;
+ interactive|*)
+ check_prerequisites
+ check_config_conflict
+
+ # Run the custom prompt first if one was provided
+ if [[ -n "${CUSTOM_PROMPT}" ]]; then
+ info "Running your custom prompt..."
+ separator
+ run_prompt "Custom prompt" "${CUSTOM_PROMPT}"
+ separator
+ info "Done! \"I'll be back.\" โ The Terminator (1984)"
+ exit 0
+ fi
+
+ info "This demo will run ${DEMO_COUNT} prompts through Copilot CLI,"
+ info "each executing JavaScript inside a Hyperlight sandbox."
+ echo ""
+ info "The MCP server is configured per-session (no permanent install)."
+ info "To install permanently, run: ${BOLD}$0 --install${RESET}"
+ separator
+
+ echo -e "${YELLOW}Ready to run demos?${RESET}"
+ read -rp "Press Enter to continue (or Ctrl+C to cancel)... "
+ echo ""
+
+ for (( i=0; i<${#DEMO_PROMPTS[@]}; i+=2 )); do
+ local title="${DEMO_PROMPTS[$i]}"
+ local prompt="${DEMO_PROMPTS[$((i+1))]}"
+
+ run_prompt "${title}" "${prompt}"
+ separator
+
+ if (( i + 2 < ${#DEMO_PROMPTS[@]} )); then
+ read -rp "Next demo? (Enter to continue, q to quit) " answer
+ if [[ "${answer}" == "q" ]]; then
+ info "Thanks for watching! \"I'll be back.\" โ The Terminator (1984)"
+ exit 0
+ fi
+ fi
+ done
+
+ echo -e "${GREEN}${BOLD}๐ All demos complete!${RESET}"
+ echo -e "${DIM}\"That's all, folks!\" โ Porky Pig (well, 1930s, close enough)${RESET}"
+ ;;
+ esac
+}
+
+main "$@"
diff --git a/src/js-host-api/examples/mcp-server/demo.gif b/src/js-host-api/examples/mcp-server/demo.gif
new file mode 100644
index 0000000..21edb11
Binary files /dev/null and b/src/js-host-api/examples/mcp-server/demo.gif differ
diff --git a/src/js-host-api/examples/mcp-server/package-lock.json b/src/js-host-api/examples/mcp-server/package-lock.json
new file mode 100644
index 0000000..ceeeb1f
--- /dev/null
+++ b/src/js-host-api/examples/mcp-server/package-lock.json
@@ -0,0 +1,2592 @@
+{
+ "name": "@hyperlight/mcp-server-example",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@hyperlight/mcp-server-example",
+ "version": "0.1.0",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.26.0",
+ "zod": "^3.25.0"
+ },
+ "devDependencies": {
+ "vitest": "^4.0.18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.9",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
+ "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
+ "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==",
+ "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/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
+ "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.0.18",
+ "@vitest/utils": "4.0.18",
+ "chai": "^6.2.1",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
+ "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.0.18",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
+ "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
+ "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.0.18",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
+ "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.18",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
+ "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
+ "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.0.18",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "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": {
+ "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/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/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": {
+ "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": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "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/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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/eventsource-parser": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
+ "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
+ "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "10.0.1"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "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-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.2.tgz",
+ "integrity": "sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==",
+ "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/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/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
+ "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+ "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-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/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jose": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
+ "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "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/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/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "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/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/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/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/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "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/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "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/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/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/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "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/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/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/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/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
+ "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
+ "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.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/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.0.18",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
+ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.0.18",
+ "@vitest/mocker": "4.0.18",
+ "@vitest/pretty-format": "4.0.18",
+ "@vitest/runner": "4.0.18",
+ "@vitest/snapshot": "4.0.18",
+ "@vitest/spy": "4.0.18",
+ "@vitest/utils": "4.0.18",
+ "es-module-lexer": "^1.7.0",
+ "expect-type": "^1.2.2",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^3.10.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3",
+ "vite": "^6.0.0 || ^7.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.0.18",
+ "@vitest/browser-preview": "4.0.18",
+ "@vitest/browser-webdriverio": "4.0.18",
+ "@vitest/ui": "4.0.18",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.1",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
+ "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25 || ^4"
+ }
+ }
+ }
+}
diff --git a/src/js-host-api/examples/mcp-server/package.json b/src/js-host-api/examples/mcp-server/package.json
new file mode 100644
index 0000000..f971d86
--- /dev/null
+++ b/src/js-host-api/examples/mcp-server/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@hyperlight/mcp-server-example",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "description": "MCP server that executes JavaScript in a Hyperlight sandbox โ safe, isolated, CPU-bounded",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "test": "vitest run"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.26.0",
+ "zod": "^3.25.0"
+ },
+ "devDependencies": {
+ "vitest": "^4.0.18"
+ }
+}
diff --git a/src/js-host-api/examples/mcp-server/server.js b/src/js-host-api/examples/mcp-server/server.js
new file mode 100644
index 0000000..b94bf05
--- /dev/null
+++ b/src/js-host-api/examples/mcp-server/server.js
@@ -0,0 +1,404 @@
+#!/usr/bin/env node
+
+// โโ Hyperlight JS MCP Server โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+//
+// An MCP (Model Context Protocol) server that allows AI agents to
+// execute JavaScript code inside a Hyperlight sandbox with strict
+// CPU time bounding.
+//
+// "In the sandbox, no one can hear you scream." โ Alien (1979), adapted
+//
+// Features:
+// - Isolated execution via Hyperlight (no filesystem, no network)
+// - CPU time limit: configurable (default 1000ms) via HYPERLIGHT_CPU_TIMEOUT_MS
+// - Wall-clock backstop: configurable (default 5000ms) via HYPERLIGHT_WALL_TIMEOUT_MS
+// - Automatic snapshot/restore recovery after timeouts
+// - Sandbox reuse across invocations for performance
+// - Optional timing log (HYPERLIGHT_TIMING_LOG) for performance analysis
+//
+// Transport: stdio (standard for local MCP integrations)
+// Tool: execute_javascript
+//
+// Usage:
+// node server.js
+//
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import { z } from 'zod';
+import { createRequire } from 'node:module';
+import { appendFileSync } from 'node:fs';
+
+const require = createRequire(import.meta.url);
+const { SandboxBuilder } = require('../../lib.js');
+
+// โโ Defaults โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+/** Default maximum CPU time per execution (milliseconds). */
+const DEFAULT_CPU_TIMEOUT_MS = 1000;
+
+/** Default maximum wall-clock time per execution (milliseconds). */
+const DEFAULT_WALL_CLOCK_TIMEOUT_MS = 5000;
+
+/** Default guest heap size in megabytes. */
+const DEFAULT_HEAP_SIZE_MB = 16;
+
+/** Default guest stack size in megabytes. */
+const DEFAULT_STACK_SIZE_MB = 1;
+
+// โโ Configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+//
+// All sandbox limits are configurable via environment variables.
+// The server operator sets the ceiling โ the calling agent cannot
+// override them. "The power is yours!" โ Captain Planet (1990โฆ close enough)
+
+/**
+ * Parse a positive integer from an environment variable, falling back
+ * to `defaultVal` when the variable is unset, empty, or not a valid
+ * positive integer.
+ *
+ * @param {string|undefined} raw โ raw env-var value
+ * @param {number} defaultVal โ fallback
+ * @returns {number}
+ */
+function parsePositiveInt(raw, defaultVal) {
+ if (raw === undefined || raw === '') return defaultVal;
+ const n = Number(raw);
+ if (!Number.isFinite(n) || n <= 0 || !Number.isInteger(n)) {
+ console.error(
+ `[hyperlight] Warning: ignoring invalid value "${raw}", using default ${defaultVal}`
+ );
+ return defaultVal;
+ }
+ return n;
+}
+
+/** Maximum CPU time per execution (milliseconds).
+ * Override with HYPERLIGHT_CPU_TIMEOUT_MS. */
+const CPU_TIMEOUT_MS = parsePositiveInt(
+ process.env.HYPERLIGHT_CPU_TIMEOUT_MS,
+ DEFAULT_CPU_TIMEOUT_MS
+);
+
+/** Maximum wall-clock time per execution (milliseconds). Backstop for
+ * edge cases where CPU time alone doesn't catch the issue.
+ * Override with HYPERLIGHT_WALL_TIMEOUT_MS. */
+const WALL_CLOCK_TIMEOUT_MS = parsePositiveInt(
+ process.env.HYPERLIGHT_WALL_TIMEOUT_MS,
+ DEFAULT_WALL_CLOCK_TIMEOUT_MS
+);
+
+/** Guest heap size in bytes. Override with HYPERLIGHT_HEAP_SIZE_MB (megabytes). */
+const HEAP_SIZE_BYTES =
+ parsePositiveInt(process.env.HYPERLIGHT_HEAP_SIZE_MB, DEFAULT_HEAP_SIZE_MB) * 1024 * 1024;
+
+/** Guest stack size in bytes. Override with HYPERLIGHT_STACK_SIZE_MB (megabytes). */
+const STACK_SIZE_BYTES =
+ parsePositiveInt(process.env.HYPERLIGHT_STACK_SIZE_MB, DEFAULT_STACK_SIZE_MB) * 1024 * 1024;
+
+/**
+ * Path to a timing log file. When set (via the HYPERLIGHT_TIMING_LOG
+ * environment variable), the server appends one JSON line per tool
+ * invocation with a breakdown of sandbox init, setup, compile, and
+ * execution times. The demo script reads this to show where time went.
+ */
+const TIMING_LOG_PATH = process.env.HYPERLIGHT_TIMING_LOG || null;
+
+/**
+ * Path to a code log file. When set (via the HYPERLIGHT_CODE_LOG
+ * environment variable), the server writes the received JavaScript
+ * source code to this file on each tool invocation. The demo script
+ * reads it back to show what the model generated.
+ */
+const CODE_LOG_PATH = process.env.HYPERLIGHT_CODE_LOG || null;
+
+// โโ Sandbox Lifecycle โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+//
+// The sandbox follows a state machine:
+//
+// [null] โโbuildโโโถ [ProtoJSSandbox] โโloadRuntimeโโโถ [JSSandbox]
+// โ
+// addHandler + getLoadedSandbox
+// โ
+// โผ
+// [LoadedJSSandbox]
+// โ โ
+// callHandler unload
+// โ โ
+// โผ โผ
+// (result) [JSSandbox]
+//
+// Between invocations, we keep the sandbox in [JSSandbox] state so we
+// can register new handler code for each execution request. After a
+// timeout or unrecoverable error, jsSandbox is set to null and
+// rebuilt on the next call.
+
+/** @type {import('../../index.d.ts').JSSandbox | null} */
+let jsSandbox = null;
+
+/**
+ * Build a fresh sandbox from scratch.
+ * Called once on first invocation, and again after unrecoverable errors.
+ */
+async function initializeSandbox() {
+ const builder = new SandboxBuilder();
+ builder.setHeapSize(HEAP_SIZE_BYTES);
+ builder.setStackSize(STACK_SIZE_BYTES);
+
+ const proto = await builder.build();
+ jsSandbox = await proto.loadRuntime();
+
+ // Log to stderr โ stdout is reserved for MCP protocol messages
+ console.error('[hyperlight] Sandbox initialized');
+}
+
+/**
+ * Execute arbitrary JavaScript code inside the Hyperlight sandbox.
+ *
+ * The code is wrapped as the body of a `handler(event)` function.
+ * Use `return` to produce a JSON-serializable result. The `event`
+ * object is an empty `{}` โ provided for API consistency but usually
+ * not needed.
+ *
+ * @param {string} code โ JavaScript source to execute
+ * @returns {Promise<{success: boolean, result?: any, error?: string}>}
+ */
+async function executeJavaScript(code) {
+ /** Timing record โ filled in progressively, written at the end. */
+ const timing = {
+ initMs: 0,
+ setupMs: 0,
+ compileMs: 0,
+ executeMs: 0,
+ snapshotMs: 0,
+ totalMs: 0,
+ };
+ const totalStart = performance.now();
+
+ // Lazy initialization โ build sandbox on first call
+ if (jsSandbox === null) {
+ const initStart = performance.now();
+ await initializeSandbox();
+ timing.initMs = Math.round(performance.now() - initStart);
+ }
+
+ // Wrap user code as a handler function body.
+ // The user writes code as if inside a function:
+ // let x = 2 + 2;
+ // return { answer: x };
+ //
+ // We wrap it as:
+ // function handler(event) {
+ // let x = 2 + 2;
+ // return { answer: x };
+ // }
+ const wrappedCode = `function handler(event) {\n${code}\n}`;
+
+ // โโ Register handler โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const setupStart = performance.now();
+ try {
+ jsSandbox.clearHandlers();
+ jsSandbox.addHandler('execute', wrappedCode);
+ } catch (err) {
+ // Sandbox in bad state โ force reinit on next call
+ jsSandbox = null;
+ return { success: false, error: `Setup error: ${err.message}` };
+ }
+ timing.setupMs = Math.round(performance.now() - setupStart);
+
+ // โโ Compile & load โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const compileStart = performance.now();
+ let loaded;
+ try {
+ loaded = await jsSandbox.getLoadedSandbox();
+ } catch (err) {
+ // Compilation failed (syntax error in user code, or sandbox consumed)
+ // The JSSandbox may be consumed โ reinitialize on next call
+ jsSandbox = null;
+ return { success: false, error: `Compilation error: ${err.message}` };
+ }
+ timing.compileMs = Math.round(performance.now() - compileStart);
+
+ // โโ Snapshot for timeout recovery โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const snapStart = performance.now();
+ let snapshot;
+ try {
+ snapshot = await loaded.snapshot();
+ } catch (err) {
+ jsSandbox = null;
+ return { success: false, error: `Snapshot error: ${err.message}` };
+ }
+ timing.snapshotMs = Math.round(performance.now() - snapStart);
+
+ // โโ Execute with CPU + wall-clock guards โโโโโโโโโโโโโโโโโโโโโ
+ const execStart = performance.now();
+ try {
+ const result = await loaded.callHandler(
+ 'execute',
+ {},
+ {
+ cpuTimeoutMs: CPU_TIMEOUT_MS,
+ wallClockTimeoutMs: WALL_CLOCK_TIMEOUT_MS,
+ }
+ );
+ timing.executeMs = Math.round(performance.now() - execStart);
+ timing.totalMs = Math.round(performance.now() - totalStart);
+ writeTiming(timing);
+
+ // Success โ return to JSSandbox state for the next invocation
+ jsSandbox = await loaded.unload();
+ return { success: true, result };
+ } catch (err) {
+ // โโ Build a user-friendly error message โโโโโโโโโโโโโโโโโโ
+ let errorMessage;
+ if (err.code === 'ERR_CANCELLED') {
+ errorMessage =
+ `Execution timed out โ CPU limit: ${CPU_TIMEOUT_MS}ms, ` +
+ `wall-clock limit: ${WALL_CLOCK_TIMEOUT_MS}ms. ` +
+ 'Try a less expensive computation or reduce iteration count.';
+ } else {
+ errorMessage = `Runtime error: ${err.message}`;
+ }
+
+ // โโ Attempt recovery โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ try {
+ if (loaded.poisoned) {
+ await loaded.restore(snapshot);
+ }
+ jsSandbox = await loaded.unload();
+ } catch {
+ // Recovery failed โ sandbox will be rebuilt on next call
+ jsSandbox = null;
+ }
+
+ timing.executeMs = Math.round(performance.now() - execStart);
+ timing.totalMs = Math.round(performance.now() - totalStart);
+ writeTiming(timing);
+
+ return { success: false, error: errorMessage };
+ }
+}
+
+/**
+ * Append a JSON timing record to the timing log file (if configured).
+ * The demo script reads these to show model vs. tool time breakdown.
+ *
+ * @param {Record} timing
+ */
+function writeTiming(timing) {
+ if (!TIMING_LOG_PATH) return;
+ try {
+ appendFileSync(TIMING_LOG_PATH, JSON.stringify(timing) + '\n');
+ } catch {
+ // Best-effort โ don't let logging failures break execution
+ console.error('[hyperlight] Warning: failed to write timing log');
+ }
+}
+
+// โโ MCP Server Setup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+const mcpServer = new McpServer({
+ name: 'hyperlight-js-sandbox',
+ version: '0.1.0',
+});
+
+mcpServer.registerTool(
+ 'execute_javascript',
+ {
+ title: 'Execute JavaScript in Hyperlight Sandbox',
+ description: [
+ 'Execute JavaScript code inside an isolated Hyperlight micro-VM.',
+ '',
+ 'The code runs as the body of a function โ use `return` to produce',
+ `a JSON-serializable result. CPU time is hard-limited to ${CPU_TIMEOUT_MS}ms`,
+ `with a ${WALL_CLOCK_TIMEOUT_MS}ms wall-clock backstop.`,
+ `Memory: ${HEAP_SIZE_BYTES / (1024 * 1024)}MB heap, ${STACK_SIZE_BYTES / (1024 * 1024)}MB stack.`,
+ '',
+ 'The sandbox has NO access to:',
+ ' - Filesystem, network, or host environment',
+ ' - Node.js APIs (require, process, fs, etc.)',
+ ' - Browser APIs (fetch, DOM, setTimeout, etc.)',
+ '',
+ 'The sandbox DOES support:',
+ ' - Full ECMAScript (ES2023) โ variables, functions, classes, closures',
+ ' - Math, String, Array, Object, Map, Set, JSON, RegExp',
+ ' - BigInt, Symbol, Proxy, Reflect, Promise (sync resolution)',
+ ' - Typed arrays (Uint8Array, Float64Array, etc.)',
+ ' - Structured algorithms, data processing, pure computation',
+ '',
+ 'Tips:',
+ ' - Use `return { ... }` to send back structured results',
+ ` - Keep iteration counts reasonable (${CPU_TIMEOUT_MS}ms CPU limit)`,
+ ' - No I/O โ all data must be computed, not fetched',
+ ' - Date.now() is available for timing within the sandbox',
+ ].join('\n'),
+ inputSchema: z.object({
+ code: z
+ .string()
+ .describe(
+ 'JavaScript code to execute as a function body. ' +
+ 'Use `return` to produce output. ' +
+ 'Example: `let x = 2 + 2; return { result: x };`'
+ ),
+ }),
+ },
+ async ({ code }) => {
+ // Log the received code if configured (demo --show-code flag)
+ if (CODE_LOG_PATH) {
+ try {
+ appendFileSync(CODE_LOG_PATH, code);
+ } catch {
+ console.error('[hyperlight] Warning: failed to write code log');
+ }
+ }
+
+ const startTime = Date.now();
+ const { success, result, error } = await executeJavaScript(code);
+ const elapsed = Date.now() - startTime;
+
+ if (success) {
+ return {
+ content: [
+ {
+ type: 'text',
+ text: JSON.stringify(result, null, 2),
+ },
+ ],
+ };
+ } else {
+ return {
+ content: [
+ {
+ type: 'text',
+ text: `โ ${error}\n\n(elapsed: ${elapsed}ms)`,
+ },
+ ],
+ isError: true,
+ };
+ }
+ }
+);
+
+// โโ Start Server โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+const transport = new StdioServerTransport();
+await mcpServer.connect(transport);
+
+// Log to stderr โ stdout is reserved for MCP JSON-RPC messages
+console.error('๐ Hyperlight JS MCP Server running on stdio');
+console.error(` CPU timeout: ${CPU_TIMEOUT_MS}ms`);
+console.error(` Wall-clock timeout: ${WALL_CLOCK_TIMEOUT_MS}ms`);
+console.error(` Heap size: ${HEAP_SIZE_BYTES / (1024 * 1024)}MB`);
+console.error(` Stack size: ${STACK_SIZE_BYTES / (1024 * 1024)}MB`);
+
+// โโ Graceful Shutdown โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+const shutdown = async () => {
+ console.error('[hyperlight] Shutting down...');
+ await mcpServer.close();
+ process.exit(0);
+};
+
+process.on('SIGINT', shutdown);
+process.on('SIGTERM', shutdown);
diff --git a/src/js-host-api/examples/mcp-server/tests/config.test.js b/src/js-host-api/examples/mcp-server/tests/config.test.js
new file mode 100644
index 0000000..491a4fd
--- /dev/null
+++ b/src/js-host-api/examples/mcp-server/tests/config.test.js
@@ -0,0 +1,270 @@
+// โโ Hyperlight JS MCP Server โ Configuration Tests โโโโโโโโโโโโโโโโโโ
+//
+// Validates that sandbox limits (CPU timeout, wall-clock timeout, heap
+// size, stack size) are configurable via environment variables, that
+// invalid values fall back to defaults gracefully, and that the tool
+// description dynamically reflects the configured limits.
+//
+// "You can tune a piano, but you can't tuna fish."
+// โ REO Speedwagon (1978โฆ close enough to the 80s)
+//
+// Each describe block spawns a separate server process with different
+// env var configurations to test the behaviour in isolation.
+//
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+import { describe, it, expect, beforeAll, afterAll } from 'vitest';
+import { spawn } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const SERVER_PATH = join(__dirname, '..', 'server.js');
+const PROTOCOL_VERSION = '2025-11-25';
+
+// โโ NDJSON Utilities โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function send(proc, message) {
+ proc.stdin.write(JSON.stringify(message) + '\n');
+}
+
+function waitForResponse(proc) {
+ return new Promise((resolve, reject) => {
+ let buffer = '';
+ const onData = (chunk) => {
+ buffer += chunk.toString();
+ const idx = buffer.indexOf('\n');
+ if (idx === -1) return;
+ const line = buffer.slice(0, idx).replace(/\r$/, '');
+ buffer = buffer.slice(idx + 1);
+ proc.stdout.off('data', onData);
+ if (line.length === 0) return;
+ try {
+ resolve(JSON.parse(line));
+ } catch (_err) {
+ reject(new Error(`Invalid JSON from server: ${line}`));
+ }
+ };
+ proc.stdout.on('data', onData);
+ });
+}
+
+/**
+ * Spawn a server with the given env overrides, perform the MCP
+ * handshake, and return { server, messageId, callTool, listTools }.
+ *
+ * @param {Record} envOverrides
+ * @returns {Promise<{server: import('node:child_process').ChildProcess, messageId: {value: number}, callTool: (code: string) => Promise