diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93444d218..c6e19e593 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,35 @@ jobs: working-directory: ./clients/web run: npm run test:coverage + - name: Install CLI client dependencies + working-directory: ./clients/cli + run: npm install + + - name: Run CLI tests + working-directory: ./clients/cli + run: npm run test + + - name: Install TUI client dependencies + working-directory: ./clients/tui + run: npm install + + - name: Run TUI tests + working-directory: ./clients/tui + run: npm run test + + - name: Install launcher dependencies + working-directory: ./clients/launcher + run: npm install + + - name: Build TUI and launcher + run: npm run build:tui && npm run build:launcher + + - name: Smoke test launcher + run: | + node clients/launcher/build/index.js --help + node clients/launcher/build/index.js --cli --help + node clients/launcher/build/index.js --tui --help + - name: Cache Playwright browsers uses: actions/cache@v4 with: diff --git a/.gitignore b/.gitignore index ced7cf116..4c704b3e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,8 @@ .DS_Store .claude/ node_modules -dist +clients/web/dist +clients/**/build test-servers/build .env /.playwright-mcp/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..940a6a698 --- /dev/null +++ b/.npmignore @@ -0,0 +1,12 @@ +# npm pack uses .npmignore instead of .gitignore when this file exists. +# Built artifacts are whitelisted via package.json "files". + +node_modules +.env +.idea +.vscode +.DS_Store +.claude/ +*.log +coverage +storybook-static diff --git a/AGENTS.md b/AGENTS.md index 386694e0a..62f6fea62 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,9 +20,9 @@ inspector/ │ │ │ # quiet-config-warnings.mjs (--import hook for `npm run dev`: drops benign │ │ │ # node-only UNRESOLVED_IMPORT warnings Rolldown prints at config load) │ │ └── static/ # sandbox_proxy.html (served by sandbox-controller for MCP Apps tab) -│ ├── cli/ # CLI client -│ ├── tui/ # TUI client -│ ├── launcher/ # Shared launcher +│ ├── cli/ # CLI client (tsup bundle, @inspector/core alias) +│ ├── tui/ # TUI client (Ink + React, tsup bundle) +│ ├── launcher/ # Shared launcher (relative imports into sibling build/ outputs) ├── core/ # Shared core code (no package.json — consumed via the `@inspector/core` vite alias) │ ├── auth/ # OAuth: state machine, providers, discovery, storage │ │ ├── browser/ # Browser-side OAuth (sessionStorage, BrowserNavigation) @@ -110,6 +110,9 @@ All work should be driven by items on the project board. - Ensure test coverage for each file is at least 90% - In unit tests that expect error output, suppress it from the console - Run unit tests with `npm run test` (or `npm run test:watch` during development) from `clients/web/` +- Run CLI tests with `npm run test` from `clients/cli/` (builds test-servers + CLI bin first via `pretest`) +- Run TUI tests with `npm run test` from `clients/tui/` +- From repo root, `npm run test` runs web unit tests, then CLI and TUI tests - Run `npm run test:coverage` to verify the per-file gate: lines ≥ 90, statements ≥ 85, functions ≥ 80, branches ≥ 50 (CI enforces this gate). Branches is intentionally relaxed because Mantine portal/media-query branches are not exercisable under happy-dom; new business-logic branches should still be covered. - Run `npm run test:integration` (also from `clients/web/`) for the v1.5-ported InspectorClient + transport + auth integration suite. It runs under a separate `integration` vitest project in node env (no happy-dom) with 30s timeouts. The script builds `test-servers/` first via `tsc -p ../../test-servers --noCheck` so the stdio MCP test server can be spawned as a real subprocess. CI runs it as its own step after unit tests. - Test files live alongside the source as `.test.tsx` (or `.test.ts` for non-React modules). v1.5-ported integration tests live under `clients/web/src/test/integration/`, mirroring the `core/` source layout (`mcp/`, `mcp/node/`, `mcp/remote/`, `auth/`, `auth/node/`, `storage/`). Any test file under that folder is automatically picked up by the `integration` vitest project (node env, 30s timeouts) via the folder glob in `vite.config.ts` — placement is the manifest, there is no enumeration to keep in sync. Tests outside the folder run in the `unit` project (happy-dom). When adding a new test for, e.g., `core/mcp/remote/foo.ts`, put it at `src/test/integration/mcp/remote/foo.test.ts`. diff --git a/clients/cli/README.md b/clients/cli/README.md new file mode 100644 index 000000000..fd3d8f87f --- /dev/null +++ b/clients/cli/README.md @@ -0,0 +1,107 @@ +# MCP Inspector CLI Client + +The CLI mode enables programmatic interaction with MCP servers from the command line. It is ideal for scripting, automation, continuous integration, and establishing an efficient feedback loop with AI coding assistants. + +## Running the CLI + +You can run the CLI client directly via `npx`: + +```bash +npx @modelcontextprotocol/inspector --cli node build/index.js +``` + +The CLI mode supports operations across tools, resources, and prompts, returning structured JSON output. + +### Examples + +**Basic usage** + +```bash +npx @modelcontextprotocol/inspector --cli node build/index.js +``` + +**With a configuration file** + +```bash +npx @modelcontextprotocol/inspector --cli --config path/to/config.json --server myserver +``` + +**List available tools** + +```bash +npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list +``` + +**Call a specific tool** + +```bash +npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg key=value --tool-arg another=value2 +``` + +**Call a tool with JSON arguments** + +```bash +npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg 'options={"format": "json", "max_tokens": 100}' +``` + +**List available resources** + +```bash +npx @modelcontextprotocol/inspector --cli node build/index.js --method resources/list +``` + +**List available prompts** + +```bash +npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list +``` + +### Remote Servers + +You can also connect to remote MCP servers using HTTP or SSE transports. + +**Connect to a remote MCP server (default is SSE)** + +```bash +npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com +``` + +**Connect with Streamable HTTP transport** + +```bash +npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list +``` + +**Pass custom headers** + +```bash +npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list --header "X-API-Key: your-api-key" +``` + +## Options + +### MCP server (which server to connect to) + +Options that specify the MCP server (config file, ad-hoc command/URL, env vars, headers) are shared by the Web, CLI, and TUI and are documented in [MCP server configuration](../../docs/mcp-server-configuration.md): `--config`, `--server`, `-e`, `--cwd`, `--header`, `--transport`, `--server-url`, and the positional `[target...]`. + +### CLI-specific (what to invoke) + +| Option | Description | +| ----------------------------- | ----------------------------------------------------------------------------------------- | +| `--method ` | MCP method to invoke (e.g. `tools/list`, `tools/call`, `resources/list`, `prompts/list`). | +| `--tool-name ` | Tool name (for `tools/call`). | +| `--tool-arg ` | Tool argument; repeat for multiple. Use `key='{"json":true}'` for JSON. | +| `--uri ` | Resource URI (for `resources/read`). | +| `--prompt-name ` | Prompt name (for `prompts/get`). | +| `--prompt-args ` | Prompt arguments; repeat for multiple. | +| `--log-level ` | Logging level for `logging/setLevel` (e.g. `debug`, `info`). | +| `--metadata ` | General metadata (key=value); applied to all methods. | +| `--tool-metadata ` | Tool-specific metadata for `tools/call`. | + +## Why use the CLI? + +While the Web Client provides a rich visual interface, the CLI is designed for: + +- **Automation**: Ideal for CI/CD pipelines and batch processing. +- **AI Coding Assistants**: Provides a direct, machine-readable interface (JSON) for tools like Cursor or Claude to verify changes immediately. +- **Log Analysis**: Easier integration with command-line utilities (like `jq`) to process and analyze MCP server output. diff --git a/clients/cli/__tests__/README.md b/clients/cli/__tests__/README.md new file mode 100644 index 000000000..dd3f5ccca --- /dev/null +++ b/clients/cli/__tests__/README.md @@ -0,0 +1,44 @@ +# CLI Tests + +## Running Tests + +```bash +# Run all tests +npm test + +# Run in watch mode (useful for test file changes; won't work on CLI source changes without rebuild) +npm run test:watch + +# Run specific test file +npm run test:cli # cli.test.ts +npm run test:cli-tools # tools.test.ts +npm run test:cli-headers # headers.test.ts +npm run test:cli-metadata # metadata.test.ts +``` + +## Test Files + +- `cli.test.ts` - Basic CLI functionality: CLI mode, environment variables, config files, resources, prompts, logging, transport types +- `tools.test.ts` - Tool-related tests: Tool discovery, JSON argument parsing, error handling, prompts +- `headers.test.ts` - Header parsing and validation +- `metadata.test.ts` - Metadata functionality: General metadata, tool-specific metadata, parsing, merging, validation + +## Helpers + +The `helpers/` directory contains shared utilities: + +- `cli-runner.ts` - Spawns CLI as subprocess and captures output +- `test-mcp-server.ts` - Standalone stdio MCP server script for stdio transport testing +- `instrumented-server.ts` - In-process MCP test server for HTTP/SSE transports with request recording +- `assertions.ts` - Custom assertion helpers for CLI output validation +- `fixtures.ts` - Test config file generators and temporary directory management + +## Notes + +- Tests run in parallel across files (Vitest default) +- Tests within a file run sequentially (we have isolated config files and ports, so we could get more aggressive if desired) +- Config files use `crypto.randomUUID()` for uniqueness in parallel execution +- HTTP/SSE servers use dynamic port allocation to avoid conflicts +- Coverage is not used because much of the code that we want to measure is run by a spawned process, so it can't be tracked by Vitest +- /sample-config.json is no longer used by tests - not clear if this file serves some other purpose so leaving it for now +- All tests now use built-in MCP test servers, there are no external dependencies on servers from a registry diff --git a/clients/cli/__tests__/cli.test.ts b/clients/cli/__tests__/cli.test.ts new file mode 100644 index 000000000..c7ea66745 --- /dev/null +++ b/clients/cli/__tests__/cli.test.ts @@ -0,0 +1,859 @@ +import { describe, it, expect } from "vitest"; +import { runCli } from "./helpers/cli-runner.js"; +import { + expectCliSuccess, + expectCliFailure, + expectValidJson, +} from "./helpers/assertions.js"; +import { + NO_SERVER_SENTINEL, + createSampleTestConfig, + createTestConfig, + createInvalidConfig, + deleteConfigFile, +} from "./helpers/fixtures.js"; +import { + getTestMcpServerCommand, + createTestServerHttp, + createEchoTool, + createTestServerInfo, +} from "@modelcontextprotocol/inspector-test-server"; + +describe("CLI Tests", () => { + describe("Basic CLI Mode", () => { + it("should execute tools/list successfully", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + + // Validate expected tools from test-mcp-server + const toolNames = json.tools.map((tool: { name: string }) => tool.name); + expect(toolNames).toContain("echo"); + expect(toolNames).toContain("get_sum"); + expect(toolNames).toContain("get_annotated_message"); + }); + + it("should fail with nonexistent method", async () => { + const result = await runCli([ + NO_SERVER_SENTINEL, + "--cli", + "--method", + "nonexistent/method", + ]); + + expectCliFailure(result); + }); + + it("should fail without method", async () => { + const result = await runCli([NO_SERVER_SENTINEL, "--cli"]); + + expectCliFailure(result); + }); + }); + + describe("Environment Variables", () => { + it("should accept environment variables", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "-e", + "KEY1=value1", + "-e", + "KEY2=value2", + "--cli", + "--method", + "resources/read", + "--uri", + "test://env", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("contents"); + expect(Array.isArray(json.contents)).toBe(true); + expect(json.contents.length).toBeGreaterThan(0); + + // Parse the env vars from the resource + const envVars = JSON.parse(json.contents[0].text); + expect(envVars.KEY1).toBe("value1"); + expect(envVars.KEY2).toBe("value2"); + }); + + it("should reject invalid environment variable format", async () => { + const result = await runCli([ + NO_SERVER_SENTINEL, + "-e", + "INVALID_FORMAT", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + }); + + it("should handle environment variable with equals sign in value", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "-e", + "API_KEY=abc123=xyz789==", + "--cli", + "--method", + "resources/read", + "--uri", + "test://env", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + const envVars = JSON.parse(json.contents[0].text); + expect(envVars.API_KEY).toBe("abc123=xyz789=="); + }); + + it("should handle environment variable with base64-encoded value", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "-e", + "JWT_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=", + "--cli", + "--method", + "resources/read", + "--uri", + "test://env", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + const envVars = JSON.parse(json.contents[0].text); + expect(envVars.JWT_TOKEN).toBe( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=", + ); + }); + }); + + describe("Config File", () => { + it("should use config file with CLI mode", async () => { + const configPath = createSampleTestConfig(); + try { + const result = await runCli([ + "--config", + configPath, + "--server", + "test-stdio", + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + expect(json.tools.length).toBeGreaterThan(0); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should fail when using config file without server name", async () => { + const configPath = createSampleTestConfig(); + try { + const result = await runCli([ + "--config", + configPath, + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should fail when using server name without config file", async () => { + const result = await runCli([ + "--server", + "test-stdio", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + }); + + it("should fail with nonexistent config file", async () => { + const result = await runCli([ + "--config", + "./nonexistent-config.json", + "--server", + "test-stdio", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + }); + + it("should fail with invalid config file format", async () => { + // Create invalid config temporarily + const invalidConfigPath = createInvalidConfig(); + try { + const result = await runCli([ + "--config", + invalidConfigPath, + "--server", + "test-stdio", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + deleteConfigFile(invalidConfigPath); + } + }); + + it("should fail with nonexistent server in config", async () => { + const configPath = createSampleTestConfig(); + try { + const result = await runCli([ + "--config", + configPath, + "--server", + "nonexistent", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + deleteConfigFile(configPath); + } + }); + }); + + describe("Resource Options", () => { + it("should read resource with URI", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "resources/read", + "--uri", + "demo://resource/static/document/architecture.md", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("contents"); + expect(Array.isArray(json.contents)).toBe(true); + expect(json.contents.length).toBeGreaterThan(0); + expect(json.contents[0]).toHaveProperty( + "uri", + "demo://resource/static/document/architecture.md", + ); + expect(json.contents[0]).toHaveProperty("mimeType", "text/markdown"); + expect(json.contents[0]).toHaveProperty("text"); + expect(json.contents[0].text).toContain("Architecture Documentation"); + }); + + it("should fail when reading resource without URI", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "resources/read", + ]); + + expectCliFailure(result); + }); + }); + + describe("Prompt Options", () => { + it("should get prompt by name", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "prompts/get", + "--prompt-name", + "simple_prompt", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("messages"); + expect(Array.isArray(json.messages)).toBe(true); + expect(json.messages.length).toBeGreaterThan(0); + expect(json.messages[0]).toHaveProperty("role", "user"); + expect(json.messages[0]).toHaveProperty("content"); + expect(json.messages[0].content).toHaveProperty("type", "text"); + expect(json.messages[0].content.text).toBe( + "This is a simple prompt for testing purposes.", + ); + }); + + it("should get prompt with arguments", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "prompts/get", + "--prompt-name", + "args_prompt", + "--prompt-args", + "city=New York", + "state=NY", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("messages"); + expect(Array.isArray(json.messages)).toBe(true); + expect(json.messages.length).toBeGreaterThan(0); + expect(json.messages[0]).toHaveProperty("role", "user"); + expect(json.messages[0]).toHaveProperty("content"); + expect(json.messages[0].content).toHaveProperty("type", "text"); + // Verify that the arguments were actually used in the response + expect(json.messages[0].content.text).toContain("city=New York"); + expect(json.messages[0].content.text).toContain("state=NY"); + }); + + it("should fail when getting prompt without name", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "prompts/get", + ]); + + expectCliFailure(result); + }); + }); + + describe("Logging Options", () => { + it("should set log level", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + logging: true, + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "logging/setLevel", + "--log-level", + "debug", + "--transport", + "http", + ]); + + expectCliSuccess(result); + // Validate the response - logging/setLevel should return an empty result + const json = expectValidJson(result); + expect(json).toEqual({}); + + // Validate that the server actually received and recorded the log level + expect(server.getCurrentLogLevel()).toBe("debug"); + } finally { + await server.stop(); + } + }); + + it("should reject invalid log level", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "logging/setLevel", + "--log-level", + "invalid", + ]); + + expectCliFailure(result); + }); + }); + + describe("Combined Options", () => { + it("should handle config file with environment variables", async () => { + const configPath = createSampleTestConfig(); + try { + const result = await runCli([ + "--config", + configPath, + "--server", + "test-stdio", + "-e", + "CLI_ENV_VAR=cli_value", + "--cli", + "--method", + "resources/read", + "--uri", + "test://env", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("contents"); + expect(Array.isArray(json.contents)).toBe(true); + expect(json.contents.length).toBeGreaterThan(0); + + // Parse the env vars from the resource + const envVars = JSON.parse(json.contents[0].text); + expect(envVars).toHaveProperty("CLI_ENV_VAR"); + expect(envVars.CLI_ENV_VAR).toBe("cli_value"); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should handle all options together", async () => { + const configPath = createSampleTestConfig(); + try { + const result = await runCli([ + "--config", + configPath, + "--server", + "test-stdio", + "-e", + "CLI_ENV_VAR=cli_value", + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=Hello", + "--log-level", + "debug", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content.length).toBeGreaterThan(0); + expect(json.content[0]).toHaveProperty("type", "text"); + expect(json.content[0].text).toBe("Echo: Hello"); + } finally { + deleteConfigFile(configPath); + } + }); + }); + + describe("Config Transport Types", () => { + it("should work with stdio transport type", async () => { + const { command, args } = getTestMcpServerCommand(); + const configPath = createTestConfig({ + mcpServers: { + "test-stdio": { + type: "stdio", + command, + args, + env: { + TEST_ENV: "test-value", + }, + }, + }, + }); + try { + // First validate tools/list works + const toolsResult = await runCli([ + "--config", + configPath, + "--server", + "test-stdio", + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(toolsResult); + const toolsJson = expectValidJson(toolsResult); + expect(toolsJson).toHaveProperty("tools"); + expect(Array.isArray(toolsJson.tools)).toBe(true); + expect(toolsJson.tools.length).toBeGreaterThan(0); + + // Then validate env vars from config are passed to server + const envResult = await runCli([ + "--config", + configPath, + "--server", + "test-stdio", + "--cli", + "--method", + "resources/read", + "--uri", + "test://env", + ]); + + expectCliSuccess(envResult); + const envJson = expectValidJson(envResult); + const envVars = JSON.parse(envJson.contents[0].text); + expect(envVars).toHaveProperty("TEST_ENV"); + expect(envVars.TEST_ENV).toBe("test-value"); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should fail with SSE transport type in CLI mode (connection error)", async () => { + const configPath = createTestConfig({ + mcpServers: { + "test-sse": { + type: "sse", + url: "http://localhost:3000/sse", + }, + }, + }); + try { + const result = await runCli([ + "--config", + configPath, + "--server", + "test-sse", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should fail with HTTP transport type in CLI mode (connection error)", async () => { + const configPath = createTestConfig({ + mcpServers: { + "test-http": { + type: "streamable-http", + url: "http://localhost:3001/mcp", + }, + }, + }); + try { + const result = await runCli([ + "--config", + configPath, + "--server", + "test-http", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should work with legacy config without type field", async () => { + const { command, args } = getTestMcpServerCommand(); + const configPath = createTestConfig({ + mcpServers: { + "test-legacy": { + command, + args, + env: { + LEGACY_ENV: "legacy-value", + }, + }, + }, + }); + try { + // First validate tools/list works + const toolsResult = await runCli([ + "--config", + configPath, + "--server", + "test-legacy", + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(toolsResult); + const toolsJson = expectValidJson(toolsResult); + expect(toolsJson).toHaveProperty("tools"); + expect(Array.isArray(toolsJson.tools)).toBe(true); + expect(toolsJson.tools.length).toBeGreaterThan(0); + + // Then validate env vars from config are passed to server + const envResult = await runCli([ + "--config", + configPath, + "--server", + "test-legacy", + "--cli", + "--method", + "resources/read", + "--uri", + "test://env", + ]); + + expectCliSuccess(envResult); + const envJson = expectValidJson(envResult); + const envVars = JSON.parse(envJson.contents[0].text); + expect(envVars).toHaveProperty("LEGACY_ENV"); + expect(envVars.LEGACY_ENV).toBe("legacy-value"); + } finally { + deleteConfigFile(configPath); + } + }); + }); + + describe("Default Server Selection", () => { + it("should auto-select single server", async () => { + const { command, args } = getTestMcpServerCommand(); + const configPath = createTestConfig({ + mcpServers: { + "only-server": { + command, + args, + }, + }, + }); + try { + const result = await runCli([ + "--config", + configPath, + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + expect(json.tools.length).toBeGreaterThan(0); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should require explicit server selection even with default-server key (multiple servers)", async () => { + const { command, args } = getTestMcpServerCommand(); + const configPath = createTestConfig({ + mcpServers: { + "default-server": { + command, + args, + }, + "other-server": { + command: "node", + args: ["other.js"], + }, + }, + }); + try { + const result = await runCli([ + "--config", + configPath, + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + deleteConfigFile(configPath); + } + }); + + it("should require explicit server selection with multiple servers", async () => { + const { command, args } = getTestMcpServerCommand(); + const configPath = createTestConfig({ + mcpServers: { + server1: { + command, + args, + }, + server2: { + command: "node", + args: ["other.js"], + }, + }, + }); + try { + const result = await runCli([ + "--config", + configPath, + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + deleteConfigFile(configPath); + } + }); + }); + + describe("HTTP Transport", () => { + it("should infer HTTP transport from URL ending with /mcp", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + expect(json.tools.length).toBeGreaterThan(0); + } finally { + await server.stop(); + } + }); + + it("should work with explicit --transport http flag", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--transport", + "http", + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + expect(json.tools.length).toBeGreaterThan(0); + } finally { + await server.stop(); + } + }); + + it("should work with explicit transport flag and URL suffix", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--transport", + "http", + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + expect(json.tools.length).toBeGreaterThan(0); + } finally { + await server.stop(); + } + }); + + it("should fail when SSE transport is given to HTTP server", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--transport", + "sse", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + } finally { + await server.stop(); + } + }); + + it("should fail when HTTP transport is specified without URL", async () => { + const result = await runCli([ + "--transport", + "http", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + }); + + it("should fail when SSE transport is specified without URL", async () => { + const result = await runCli([ + "--transport", + "sse", + "--cli", + "--method", + "tools/list", + ]); + + expectCliFailure(result); + }); + }); +}); diff --git a/clients/cli/__tests__/headers.test.ts b/clients/cli/__tests__/headers.test.ts new file mode 100644 index 000000000..36d416d5c --- /dev/null +++ b/clients/cli/__tests__/headers.test.ts @@ -0,0 +1,202 @@ +import { describe, it, expect } from "vitest"; +import { runCli } from "./helpers/cli-runner.js"; +import { + expectCliFailure, + expectOutputContains, + expectCliSuccess, +} from "./helpers/assertions.js"; +import { + createTestServerHttp, + createEchoTool, + createTestServerInfo, +} from "@modelcontextprotocol/inspector-test-server"; + +describe("Header Parsing and Validation", () => { + describe("Valid Headers", () => { + it("should parse valid single header and send it to server", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--transport", + "http", + "--header", + "Authorization: Bearer token123", + ]); + + expectCliSuccess(result); + + // Check that the server received the request with the correct headers + const recordedRequests = server.getRecordedRequests(); + expect(recordedRequests.length).toBeGreaterThan(0); + + // Find the tools/list request (should be the last one) + const toolsListRequest = recordedRequests[recordedRequests.length - 1]; + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest.method).toBe("tools/list"); + + // Express normalizes headers to lowercase + expect(toolsListRequest.headers).toHaveProperty("authorization"); + expect(toolsListRequest.headers?.authorization).toBe("Bearer token123"); + } finally { + await server.stop(); + } + }); + + it("should parse multiple headers", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--transport", + "http", + "--header", + "Authorization: Bearer token123", + "--header", + "X-API-Key: secret123", + ]); + + expectCliSuccess(result); + + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests[recordedRequests.length - 1]; + expect(toolsListRequest.method).toBe("tools/list"); + expect(toolsListRequest.headers?.authorization).toBe("Bearer token123"); + expect(toolsListRequest.headers?.["x-api-key"]).toBe("secret123"); + } finally { + await server.stop(); + } + }); + + it("should handle header with colons in value", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--transport", + "http", + "--header", + "X-Time: 2023:12:25:10:30:45", + ]); + + expectCliSuccess(result); + + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests[recordedRequests.length - 1]; + expect(toolsListRequest.method).toBe("tools/list"); + expect(toolsListRequest.headers?.["x-time"]).toBe( + "2023:12:25:10:30:45", + ); + } finally { + await server.stop(); + } + }); + + it("should handle whitespace in headers", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--transport", + "http", + "--header", + " X-Header : value with spaces ", + ]); + + expectCliSuccess(result); + + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests[recordedRequests.length - 1]; + expect(toolsListRequest.method).toBe("tools/list"); + // Header values should be trimmed by the CLI parser + expect(toolsListRequest.headers?.["x-header"]).toBe( + "value with spaces", + ); + } finally { + await server.stop(); + } + }); + }); + + describe("Invalid Header Formats", () => { + it("should reject header format without colon", async () => { + const result = await runCli([ + "https://example.com", + "--cli", + "--method", + "tools/list", + "--transport", + "http", + "--header", + "InvalidHeader", + ]); + + expectCliFailure(result); + expectOutputContains(result, "Invalid header format"); + }); + + it("should reject header format with empty name", async () => { + const result = await runCli([ + "https://example.com", + "--cli", + "--method", + "tools/list", + "--transport", + "http", + "--header", + ": value", + ]); + + expectCliFailure(result); + expectOutputContains(result, "Invalid header format"); + }); + + it("should reject header format with empty value", async () => { + const result = await runCli([ + "https://example.com", + "--cli", + "--method", + "tools/list", + "--transport", + "http", + "--header", + "Header:", + ]); + + expectCliFailure(result); + expectOutputContains(result, "Invalid header format"); + }); + }); +}); diff --git a/clients/cli/__tests__/helpers/assertions.test.ts b/clients/cli/__tests__/helpers/assertions.test.ts new file mode 100644 index 000000000..90beb22c3 --- /dev/null +++ b/clients/cli/__tests__/helpers/assertions.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from "vitest"; +import { expectCliSuccess, expectCliFailure } from "./assertions.js"; +import type { CliResult } from "./cli-runner.js"; + +function assertionMessage(fn: () => void): string { + try { + fn(); + return ""; + } catch (error) { + return error instanceof Error ? error.message : String(error); + } +} + +describe("CLI assertion helpers", () => { + it("expectCliSuccess includes stdout and stderr on failure", () => { + const result: CliResult = { + exitCode: 1, + stdout: "tool output", + stderr: "connect error", + output: "tool outputconnect error", + }; + + const message = assertionMessage(() => expectCliSuccess(result)); + expect(message).toContain("CLI exited with code 1"); + expect(message).toContain("stdout: tool output"); + expect(message).toContain("stderr: connect error"); + }); + + it("expectCliFailure includes stdout and stderr on unexpected success", () => { + const result: CliResult = { + exitCode: 0, + stdout: "ok", + stderr: "", + output: "ok", + }; + + const message = assertionMessage(() => expectCliFailure(result)); + expect(message).toContain("CLI unexpectedly exited with code 0"); + expect(message).toContain("stdout: ok"); + expect(message).toContain("stderr: (empty)"); + }); +}); diff --git a/clients/cli/__tests__/helpers/assertions.ts b/clients/cli/__tests__/helpers/assertions.ts new file mode 100644 index 000000000..3631c00bf --- /dev/null +++ b/clients/cli/__tests__/helpers/assertions.ts @@ -0,0 +1,64 @@ +import { expect } from "vitest"; +import type { CliResult } from "./cli-runner.js"; + +function formatCliOutput(result: CliResult): string { + const out = result.stdout?.trim() || "(empty)"; + const err = result.stderr?.trim() || "(empty)"; + return `stdout: ${out}\nstderr: ${err}`; +} + +/** + * Assert that CLI command succeeded (exit code 0) + */ +export function expectCliSuccess(result: CliResult) { + expect( + result.exitCode, + `CLI exited with code ${result.exitCode}. ${formatCliOutput(result)}`, + ).toBe(0); +} + +/** + * Assert that CLI command failed (non-zero exit code) + */ +export function expectCliFailure(result: CliResult) { + expect( + result.exitCode, + `CLI unexpectedly exited with code ${result.exitCode}. ${formatCliOutput(result)}`, + ).not.toBe(0); +} + +/** + * Assert that output contains expected text + */ +export function expectOutputContains(result: CliResult, text: string) { + expect(result.output).toContain(text); +} + +/** + * Assert that output contains valid JSON + * Uses stdout (not stderr) since JSON is written to stdout and warnings go to stderr + */ +export function expectValidJson(result: CliResult) { + expect(() => JSON.parse(result.stdout)).not.toThrow(); + return JSON.parse(result.stdout); +} + +/** + * Assert that output contains JSON with error flag + */ +export function expectJsonError(result: CliResult) { + const json = expectValidJson(result); + expect(json.isError).toBe(true); + return json; +} + +/** + * Assert that output contains expected JSON structure + */ +export function expectJsonStructure(result: CliResult, expectedKeys: string[]) { + const json = expectValidJson(result); + expectedKeys.forEach((key) => { + expect(json).toHaveProperty(key); + }); + return json; +} diff --git a/clients/cli/__tests__/helpers/cli-runner.ts b/clients/cli/__tests__/helpers/cli-runner.ts new file mode 100644 index 000000000..02a3d58b7 --- /dev/null +++ b/clients/cli/__tests__/helpers/cli-runner.ts @@ -0,0 +1,97 @@ +import { spawn } from "child_process"; +import { resolve } from "path"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const CLI_PATH = resolve(__dirname, "../../build/index.js"); + +export interface CliResult { + exitCode: number | null; + stdout: string; + stderr: string; + output: string; // Combined stdout + stderr +} + +export interface CliOptions { + timeout?: number; + cwd?: string; + env?: Record; + signal?: AbortSignal; +} + +/** + * Run the CLI with given arguments and capture output + */ +export async function runCli( + args: string[], + options: CliOptions = {}, +): Promise { + return new Promise((resolve, reject) => { + const child = spawn("node", [CLI_PATH, ...args], { + stdio: ["pipe", "pipe", "pipe"], + cwd: options.cwd, + env: { ...process.env, ...options.env }, + signal: options.signal, + // Own process group on Unix so timeout can kill CLI + stdio grandchildren. + detached: process.platform !== "win32", + }); + + let stdout = ""; + let stderr = ""; + let resolved = false; + + // Default timeout of 10 seconds (less than vitest's 15s) + const timeoutMs = options.timeout ?? 10000; + const timeout = setTimeout(() => { + if (!resolved) { + resolved = true; + // Kill the process and all its children + try { + if (process.platform !== "win32" && child.pid != null) { + process.kill(-child.pid, "SIGTERM"); + } else { + child.kill("SIGTERM"); + } + } catch { + // Process might already be dead, try direct kill + try { + child.kill("SIGKILL"); + } catch { + // Process is definitely dead + } + } + reject(new Error(`CLI command timed out after ${timeoutMs}ms`)); + } + }, timeoutMs); + + child.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + child.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + child.on("close", (code) => { + if (!resolved) { + resolved = true; + clearTimeout(timeout); + resolve({ + exitCode: code, + stdout, + stderr, + output: stdout + stderr, + }); + } + }); + + child.on("error", (error) => { + if (!resolved) { + resolved = true; + clearTimeout(timeout); + reject(error); + } + }); + }); +} diff --git a/clients/cli/__tests__/helpers/fixtures.ts b/clients/cli/__tests__/helpers/fixtures.ts new file mode 100644 index 000000000..bfae186ac --- /dev/null +++ b/clients/cli/__tests__/helpers/fixtures.ts @@ -0,0 +1,90 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; +import * as crypto from "crypto"; +import { getTestMcpServerCommand } from "@modelcontextprotocol/inspector-test-server"; +import type { MCPServerConfig } from "@modelcontextprotocol/inspector-core/mcp/index.js"; + +/** + * Sentinel value for tests that don't need a real server + * (tests that expect failure before connecting) + */ +export const NO_SERVER_SENTINEL = "invalid-command-that-does-not-exist"; + +/** + * Create a sample test config with test-stdio and test-http servers + * Returns a temporary config file path that should be cleaned up with deleteConfigFile() + * @param httpUrl - Optional full URL (including /mcp path) for test-http server. + * If not provided, uses a placeholder URL. The test-http server exists + * to test server selection logic and may not actually be used. + */ +export function createSampleTestConfig(httpUrl?: string): string { + const { command, args } = getTestMcpServerCommand(); + return createTestConfig({ + mcpServers: { + "test-stdio": { + type: "stdio", + command, + args, + env: { + HELLO: "Hello MCP!", + }, + }, + "test-http": { + type: "streamable-http", + url: httpUrl || "http://localhost:3001/mcp", + }, + }, + }); +} + +/** + * Create a temporary directory for test files + * Uses crypto.randomUUID() to ensure uniqueness even when called in parallel + */ +function createTempDir(prefix: string = "mcp-inspector-test-"): string { + const uniqueId = crypto.randomUUID(); + const tempDir = path.join(os.tmpdir(), `${prefix}${uniqueId}`); + fs.mkdirSync(tempDir, { recursive: true }); + return tempDir; +} + +/** + * Clean up temporary directory + */ +function cleanupTempDir(dir: string) { + try { + fs.rmSync(dir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } +} + +/** + * Create a test config file + */ +export function createTestConfig(config: { + mcpServers: Record; +}): string { + const tempDir = createTempDir("mcp-inspector-config-"); + const configPath = path.join(tempDir, "config.json"); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + return configPath; +} + +/** + * Create an invalid config file (malformed JSON) + */ +export function createInvalidConfig(): string { + const tempDir = createTempDir("mcp-inspector-config-"); + const configPath = path.join(tempDir, "invalid-config.json"); + fs.writeFileSync(configPath, '{\n "mcpServers": {\n "invalid": {'); + return configPath; +} + +/** + * Delete a config file and its containing directory + */ +export function deleteConfigFile(configPath: string): void { + cleanupTempDir(path.dirname(configPath)); +} diff --git a/clients/cli/__tests__/metadata.test.ts b/clients/cli/__tests__/metadata.test.ts new file mode 100644 index 000000000..ac2876f6c --- /dev/null +++ b/clients/cli/__tests__/metadata.test.ts @@ -0,0 +1,934 @@ +import { describe, it, expect } from "vitest"; +import { runCli } from "./helpers/cli-runner.js"; +import { + expectCliSuccess, + expectCliFailure, + expectValidJson, +} from "./helpers/assertions.js"; +import { + createTestServerHttp, + createEchoTool, + createAddTool, + createTestServerInfo, +} from "@modelcontextprotocol/inspector-test-server"; +import { NO_SERVER_SENTINEL } from "./helpers/fixtures.js"; + +describe("Metadata Tests", () => { + describe("General Metadata", () => { + it("should work with tools/list", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + "client=test-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ client: "test-client" }); + } finally { + await server.stop(); + } + }); + + it("should work with resources/list", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + resources: [ + { + uri: "test://resource", + name: "test-resource", + text: "test content", + }, + ], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "resources/list", + "--metadata", + "client=test-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("resources"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const resourcesListRequest = recordedRequests.find( + (r) => r.method === "resources/list", + ); + expect(resourcesListRequest).toBeDefined(); + expect(resourcesListRequest?.metadata).toEqual({ + client: "test-client", + }); + } finally { + await server.stop(); + } + }); + + it("should work with prompts/list", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + prompts: [ + { + name: "test-prompt", + description: "A test prompt", + promptString: "test prompt", + }, + ], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "prompts/list", + "--metadata", + "client=test-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("prompts"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const promptsListRequest = recordedRequests.find( + (r) => r.method === "prompts/list", + ); + expect(promptsListRequest).toBeDefined(); + expect(promptsListRequest?.metadata).toEqual({ + client: "test-client", + }); + } finally { + await server.stop(); + } + }); + + it("should work with resources/read", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + resources: [ + { + uri: "test://resource", + name: "test-resource", + text: "test content", + }, + ], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "resources/read", + "--uri", + "test://resource", + "--metadata", + "client=test-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("contents"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const readRequest = recordedRequests.find( + (r) => r.method === "resources/read", + ); + expect(readRequest).toBeDefined(); + expect(readRequest?.metadata).toEqual({ client: "test-client" }); + } finally { + await server.stop(); + } + }); + + it("should work with prompts/get", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + prompts: [ + { + name: "test-prompt", + description: "A test prompt", + promptString: "test prompt", + }, + ], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "prompts/get", + "--prompt-name", + "test-prompt", + "--metadata", + "client=test-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("messages"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const getPromptRequest = recordedRequests.find( + (r) => r.method === "prompts/get", + ); + expect(getPromptRequest).toBeDefined(); + expect(getPromptRequest?.metadata).toEqual({ client: "test-client" }); + } finally { + await server.stop(); + } + }); + }); + + describe("Tool-Specific Metadata", () => { + it("should work with tools/call", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello world", + "--tool-metadata", + "client=test-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const toolCallRequest = recordedRequests.find( + (r) => r.method === "tools/call", + ); + expect(toolCallRequest).toBeDefined(); + expect(toolCallRequest?.metadata).toEqual({ client: "test-client" }); + } finally { + await server.stop(); + } + }); + + it("should work with complex tool", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createAddTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/call", + "--tool-name", + "add", + "--tool-arg", + "a=10", + "b=20", + "--tool-metadata", + "client=test-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const toolCallRequest = recordedRequests.find( + (r) => r.method === "tools/call", + ); + expect(toolCallRequest).toBeDefined(); + expect(toolCallRequest?.metadata).toEqual({ client: "test-client" }); + } finally { + await server.stop(); + } + }); + }); + + describe("Metadata Merging", () => { + it("should merge general and tool-specific metadata (tool-specific overrides)", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello world", + "--metadata", + "client=general-client", + "shared_key=shared_value", + "--tool-metadata", + "client=tool-specific-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate metadata was merged correctly (tool-specific overrides general) + const recordedRequests = server.getRecordedRequests(); + const toolCallRequest = recordedRequests.find( + (r) => r.method === "tools/call", + ); + expect(toolCallRequest).toBeDefined(); + expect(toolCallRequest?.metadata).toEqual({ + client: "tool-specific-client", // Tool-specific overrides general + shared_key: "shared_value", // General metadata is preserved + }); + } finally { + await server.stop(); + } + }); + }); + + describe("Metadata Parsing", () => { + it("should handle numeric values", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + "integer_value=42", + "decimal_value=3.14159", + "negative_value=-10", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate metadata values are sent as strings + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ + integer_value: "42", + decimal_value: "3.14159", + negative_value: "-10", + }); + } finally { + await server.stop(); + } + }); + + it("should handle JSON values", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + 'json_object="{\\"key\\":\\"value\\"}"', + 'json_array="[1,2,3]"', + 'json_string="\\"quoted\\""', + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate JSON values are sent as strings + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ + json_object: '{"key":"value"}', + json_array: "[1,2,3]", + json_string: '"quoted"', + }); + } finally { + await server.stop(); + } + }); + + it("should handle special characters", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + "unicode=🚀🎉✨", + "special_chars=!@#$%^&*()", + "spaces=hello world with spaces", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate special characters are preserved + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ + unicode: "🚀🎉✨", + special_chars: "!@#$%^&*()", + spaces: "hello world with spaces", + }); + } finally { + await server.stop(); + } + }); + }); + + describe("Metadata Edge Cases", () => { + it("should handle single metadata entry", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + "single_key=single_value", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate single metadata entry + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ + single_key: "single_value", + }); + } finally { + await server.stop(); + } + }); + + it("should handle many metadata entries", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + "key1=value1", + "key2=value2", + "key3=value3", + "key4=value4", + "key5=value5", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate all metadata entries + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ + key1: "value1", + key2: "value2", + key3: "value3", + key4: "value4", + key5: "value5", + }); + } finally { + await server.stop(); + } + }); + }); + + describe("Metadata Error Cases", () => { + it("should fail with invalid metadata format (missing equals)", async () => { + const result = await runCli([ + NO_SERVER_SENTINEL, + "--cli", + "--method", + "tools/list", + "--metadata", + "invalid_format_no_equals", + ]); + + expectCliFailure(result); + }); + + it("should fail with invalid tool-metadata format (missing equals)", async () => { + const result = await runCli([ + NO_SERVER_SENTINEL, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=test", + "--tool-metadata", + "invalid_format_no_equals", + ]); + + expectCliFailure(result); + }); + }); + + describe("Metadata Impact", () => { + it("should handle tool-specific metadata precedence over general", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=precedence test", + "--metadata", + "client=general-client", + "--tool-metadata", + "client=tool-specific-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate tool-specific metadata overrides general + const recordedRequests = server.getRecordedRequests(); + const toolCallRequest = recordedRequests.find( + (r) => r.method === "tools/call", + ); + expect(toolCallRequest).toBeDefined(); + expect(toolCallRequest?.metadata).toEqual({ + client: "tool-specific-client", + }); + } finally { + await server.stop(); + } + }); + + it("should work with resources methods", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + resources: [ + { + uri: "test://resource", + name: "test-resource", + text: "test content", + }, + ], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "resources/list", + "--metadata", + "resource_client=test-resource-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const resourcesListRequest = recordedRequests.find( + (r) => r.method === "resources/list", + ); + expect(resourcesListRequest).toBeDefined(); + expect(resourcesListRequest?.metadata).toEqual({ + resource_client: "test-resource-client", + }); + } finally { + await server.stop(); + } + }); + + it("should work with prompts methods", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + prompts: [ + { + name: "test-prompt", + description: "A test prompt", + promptString: "test prompt", + }, + ], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "prompts/get", + "--prompt-name", + "test-prompt", + "--metadata", + "prompt_client=test-prompt-client", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const getPromptRequest = recordedRequests.find( + (r) => r.method === "prompts/get", + ); + expect(getPromptRequest).toBeDefined(); + expect(getPromptRequest?.metadata).toEqual({ + prompt_client: "test-prompt-client", + }); + } finally { + await server.stop(); + } + }); + }); + + describe("Metadata Validation", () => { + it("should handle special characters in keys", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=special keys test", + "--metadata", + "key-with-dashes=value1", + "key_with_underscores=value2", + "key.with.dots=value3", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate special characters in keys are preserved + const recordedRequests = server.getRecordedRequests(); + const toolCallRequest = recordedRequests.find( + (r) => r.method === "tools/call", + ); + expect(toolCallRequest).toBeDefined(); + expect(toolCallRequest?.metadata).toEqual({ + "key-with-dashes": "value1", + key_with_underscores: "value2", + "key.with.dots": "value3", + }); + } finally { + await server.stop(); + } + }); + }); + + describe("Metadata Integration", () => { + it("should work with all MCP methods", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + "integration_test=true", + "test_phase=all_methods", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ + integration_test: "true", + test_phase: "all_methods", + }); + } finally { + await server.stop(); + } + }); + + it("should handle complex metadata scenario", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=complex test", + "--metadata", + "session_id=12345", + "user_id=67890", + "timestamp=2024-01-01T00:00:00Z", + "request_id=req-abc-123", + "--tool-metadata", + "tool_session=session-xyz-789", + "execution_context=test", + "priority=high", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate complex metadata merging + const recordedRequests = server.getRecordedRequests(); + const toolCallRequest = recordedRequests.find( + (r) => r.method === "tools/call", + ); + expect(toolCallRequest).toBeDefined(); + expect(toolCallRequest?.metadata).toEqual({ + session_id: "12345", + user_id: "67890", + timestamp: "2024-01-01T00:00:00Z", + request_id: "req-abc-123", + tool_session: "session-xyz-789", + execution_context: "test", + priority: "high", + }); + } finally { + await server.stop(); + } + }); + + it("should handle metadata parsing validation", async () => { + const server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=parsing validation test", + "--metadata", + "valid_key=valid_value", + "numeric_key=123", + "boolean_key=true", + 'json_key=\'{"test":"value"}\'', + "special_key=!@#$%^&*()", + "unicode_key=🚀🎉✨", + "--transport", + "http", + ]); + + expectCliSuccess(result); + + // Validate all value types are sent as strings + // Note: The CLI parses metadata values, so single-quoted JSON strings + // are preserved with their quotes + const recordedRequests = server.getRecordedRequests(); + const toolCallRequest = recordedRequests.find( + (r) => r.method === "tools/call", + ); + expect(toolCallRequest).toBeDefined(); + expect(toolCallRequest?.metadata).toEqual({ + valid_key: "valid_value", + numeric_key: "123", + boolean_key: "true", + json_key: '\'{"test":"value"}\'', // Single quotes are preserved + special_key: "!@#$%^&*()", + unicode_key: "🚀🎉✨", + }); + } finally { + await server.stop(); + } + }); + }); + + describe("SSE Transport Tests", () => { + it("should work with tools/list using SSE transport", async () => { + const server = createTestServerHttp({ + serverType: "sse", + serverInfo: createTestServerInfo(), + tools: [createEchoTool()], + }); + + try { + await server.start(); + const result = await runCli([ + server.url, + "--cli", + "--method", + "tools/list", + "--metadata", + "client=test-client", + "--transport", + "sse", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + + // Validate metadata was sent + const recordedRequests = server.getRecordedRequests(); + const toolsListRequest = recordedRequests.find( + (r) => r.method === "tools/list", + ); + expect(toolsListRequest).toBeDefined(); + expect(toolsListRequest?.metadata).toEqual({ client: "test-client" }); + } finally { + await server.stop(); + } + }); + }); +}); diff --git a/clients/cli/__tests__/tools.test.ts b/clients/cli/__tests__/tools.test.ts new file mode 100644 index 000000000..3a7ea6a17 --- /dev/null +++ b/clients/cli/__tests__/tools.test.ts @@ -0,0 +1,527 @@ +import { describe, it, expect } from "vitest"; +import { runCli } from "./helpers/cli-runner.js"; +import { + expectCliSuccess, + expectCliFailure, + expectValidJson, + expectJsonError, +} from "./helpers/assertions.js"; +import { getTestMcpServerCommand } from "@modelcontextprotocol/inspector-test-server"; + +describe("Tool Tests", () => { + describe("Tool Discovery", () => { + it("should list available tools", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/list", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("tools"); + expect(Array.isArray(json.tools)).toBe(true); + expect(json.tools.length).toBeGreaterThan(0); + // Validate that tools have required properties + expect(json.tools[0]).toHaveProperty("name"); + expect(json.tools[0]).toHaveProperty("description"); + // Validate expected tools from test-mcp-server + const toolNames = json.tools.map((tool: { name: string }) => tool.name); + expect(toolNames).toContain("echo"); + expect(toolNames).toContain("get_sum"); + expect(toolNames).toContain("get_annotated_message"); + }); + }); + + describe("JSON Argument Parsing", () => { + it("should handle string arguments (backward compatibility)", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello world", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content.length).toBeGreaterThan(0); + expect(json.content[0]).toHaveProperty("type", "text"); + expect(json.content[0].text).toBe("Echo: hello world"); + }); + + it("should handle integer number arguments", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "get_sum", + "--tool-arg", + "a=42", + "b=58", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content.length).toBeGreaterThan(0); + expect(json.content[0]).toHaveProperty("type", "text"); + // test-mcp-server returns JSON with {result: a+b} + const resultData = JSON.parse(json.content[0].text); + expect(resultData.result).toBe(100); + }); + + it("should handle decimal number arguments", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "get_sum", + "--tool-arg", + "a=19.99", + "b=20.01", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content.length).toBeGreaterThan(0); + expect(json.content[0]).toHaveProperty("type", "text"); + // test-mcp-server returns JSON with {result: a+b} + const resultData = JSON.parse(json.content[0].text); + expect(resultData.result).toBeCloseTo(40.0, 2); + }); + + it("should handle boolean arguments - true", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "get_annotated_message", + "--tool-arg", + "messageType=success", + "includeImage=true", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + // Should have both text and image content + expect(json.content.length).toBeGreaterThan(1); + const hasImage = json.content.some( + (item: { type?: string }) => item.type === "image", + ); + expect(hasImage).toBe(true); + }); + + it("should handle boolean arguments - false", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "get_annotated_message", + "--tool-arg", + "messageType=error", + "includeImage=false", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + // Should only have text content, no image + const hasImage = json.content.some( + (item: { type?: string }) => item.type === "image", + ); + expect(hasImage).toBe(false); + // test-mcp-server returns "This is a {messageType} message" + expect(json.content[0].text.toLowerCase()).toContain("error"); + }); + + it("should handle null arguments", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + 'message="null"', + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + // The string "null" should be passed through + expect(json.content[0].text).toBe("Echo: null"); + }); + + it("should handle multiple arguments with mixed types", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "get_sum", + "--tool-arg", + "a=42.5", + "b=57.5", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content.length).toBeGreaterThan(0); + expect(json.content[0]).toHaveProperty("type", "text"); + // test-mcp-server returns JSON with {result: a+b} + const resultData = JSON.parse(json.content[0].text); + expect(resultData.result).toBeCloseTo(100.0, 1); + }); + }); + + describe("JSON Parsing Edge Cases", () => { + it("should fall back to string for invalid JSON", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message={invalid json}", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + // Should treat invalid JSON as a string + expect(json.content[0].text).toBe("Echo: {invalid json}"); + }); + + it("should handle empty string value", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + 'message=""', + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + // Empty string should be preserved + expect(json.content[0].text).toBe("Echo: "); + }); + + it("should handle special characters in strings", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + 'message="C:\\\\Users\\\\test"', + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + // Special characters should be preserved + expect(json.content[0].text).toContain("C:"); + expect(json.content[0].text).toContain("Users"); + expect(json.content[0].text).toContain("test"); + }); + + it("should handle unicode characters", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + 'message="🚀🎉✨"', + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + // Unicode characters should be preserved + expect(json.content[0].text).toContain("🚀"); + expect(json.content[0].text).toContain("🎉"); + expect(json.content[0].text).toContain("✨"); + }); + + it("should handle arguments with equals signs in values", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=2+2=4", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + // Equals signs in values should be preserved + expect(json.content[0].text).toBe("Echo: 2+2=4"); + }); + + it("should handle base64-like strings", async () => { + const { command, args } = getTestMcpServerCommand(); + const base64String = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0="; + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + `message=${base64String}`, + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + // Base64-like strings should be preserved + expect(json.content[0].text).toBe(`Echo: ${base64String}`); + }); + }); + + describe("Tool Error Handling", () => { + it("should fail with nonexistent tool", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "nonexistent_tool", + "--tool-arg", + "message=test", + ]); + + // CLI returns exit code 0 but includes isError: true in JSON (server returns error) + expectJsonError(result); + }); + + it("should fail when tool name is missing", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-arg", + "message=test", + ]); + + expectCliFailure(result); + }); + + it("should fail with invalid tool argument format", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "invalid_format_no_equals", + ]); + + expectCliFailure(result); + }); + }); + + describe("Prompt JSON Arguments", () => { + it("should handle prompt with JSON arguments", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "prompts/get", + "--prompt-name", + "args_prompt", + "--prompt-args", + "city=New York", + "state=NY", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("messages"); + expect(Array.isArray(json.messages)).toBe(true); + expect(json.messages.length).toBeGreaterThan(0); + expect(json.messages[0]).toHaveProperty("content"); + expect(json.messages[0].content).toHaveProperty("type", "text"); + // Validate that the arguments were actually used in the response + // test-mcp-server formats it as "This is a prompt with arguments: city={city}, state={state}" + expect(json.messages[0].content.text).toContain("city=New York"); + expect(json.messages[0].content.text).toContain("state=NY"); + }); + + it("should handle prompt with simple arguments", async () => { + // Note: simple_prompt doesn't accept arguments, but the CLI should still + // accept the command and the server should ignore the arguments + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "prompts/get", + "--prompt-name", + "simple_prompt", + "--prompt-args", + "name=test", + "count=5", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("messages"); + expect(Array.isArray(json.messages)).toBe(true); + expect(json.messages.length).toBeGreaterThan(0); + expect(json.messages[0]).toHaveProperty("content"); + expect(json.messages[0].content).toHaveProperty("type", "text"); + // test-mcp-server's simple_prompt returns standard message (ignoring args) + expect(json.messages[0].content.text).toBe( + "This is a simple prompt for testing purposes.", + ); + }); + }); + + describe("Backward Compatibility", () => { + it("should support existing string-only usage", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "echo", + "--tool-arg", + "message=hello", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content[0]).toHaveProperty("type", "text"); + expect(json.content[0].text).toBe("Echo: hello"); + }); + + it("should support multiple string arguments", async () => { + const { command, args } = getTestMcpServerCommand(); + const result = await runCli([ + command, + ...args, + "--cli", + "--method", + "tools/call", + "--tool-name", + "get_sum", + "--tool-arg", + "a=10", + "b=20", + ]); + + expectCliSuccess(result); + const json = expectValidJson(result); + expect(json).toHaveProperty("content"); + expect(Array.isArray(json.content)).toBe(true); + expect(json.content.length).toBeGreaterThan(0); + expect(json.content[0]).toHaveProperty("type", "text"); + // test-mcp-server returns JSON with {result: a+b} + const resultData = JSON.parse(json.content[0].text); + expect(resultData.result).toBe(30); + }); + }); +}); diff --git a/clients/cli/package-lock.json b/clients/cli/package-lock.json new file mode 100644 index 000000000..66366bb6a --- /dev/null +++ b/clients/cli/package-lock.json @@ -0,0 +1,3954 @@ +{ + "name": "@modelcontextprotocol/inspector-cli", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@modelcontextprotocol/inspector-cli", + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "commander": "^13.1.0", + "pino": "^9.14.0", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "bin": { + "mcp-inspector-cli": "build/index.js" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "tsup": "^8.5.0", + "typescript": "~5.9.3", + "vitest": "^4.1.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "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/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", + "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "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 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "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/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "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/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "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/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "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/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "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/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "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": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "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.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "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.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "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.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/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/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.24", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.24.tgz", + "integrity": "sha512-I36D1s+HgQc55KbhEr4iybfxv/9o1zdpw+XEM6dJa91LqQD0HCoSGdxpRJCZE+aavs87j4V3Ls2OJzq8C/U4iw==", + "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.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-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.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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.2", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz", + "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "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.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/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.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "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.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "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/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.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/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "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/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "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": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "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/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "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": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.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.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "license": "MIT" + }, + "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": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/clients/cli/package.json b/clients/cli/package.json new file mode 100644 index 000000000..e63f80354 --- /dev/null +++ b/clients/cli/package.json @@ -0,0 +1,44 @@ +{ + "name": "@modelcontextprotocol/inspector-cli", + "version": "2.0.0", + "private": true, + "description": "CLI for the Model Context Protocol inspector", + "license": "MIT", + "type": "module", + "main": "build/index.js", + "exports": { + ".": "./build/index.js" + }, + "bin": { + "mcp-inspector-cli": "./build/index.js" + }, + "files": [ + "build" + ], + "scripts": { + "build": "tsup", + "pretest": "npm run test-servers:build && npm run build", + "test": "vitest run", + "test:watch": "vitest", + "test-servers:build": "tsc -p ../../test-servers --noCheck", + "test:cli": "vitest run cli.test.ts", + "test:cli-tools": "vitest run tools.test.ts", + "test:cli-headers": "vitest run headers.test.ts", + "test:cli-metadata": "vitest run metadata.test.ts" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "commander": "^13.1.0", + "pino": "^9.14.0", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "tsup": "^8.5.0", + "typescript": "~5.9.3", + "vitest": "^4.1.0" + } +} diff --git a/clients/cli/src/cli.ts b/clients/cli/src/cli.ts new file mode 100644 index 000000000..91f270c41 --- /dev/null +++ b/clients/cli/src/cli.ts @@ -0,0 +1,460 @@ +import { dirname, join } from "path"; +import { fileURLToPath, pathToFileURL } from "url"; +import { Command } from "commander"; +type McpResponse = Record; +import { handleError } from "./error-handler.js"; +import { awaitableLog } from "./utils/awaitable-log.js"; +import type { + InspectorServerSettings, + MCPServerConfig, +} from "@inspector/core/mcp/types.js"; +import { DEFAULT_TASK_TTL_MS } from "@inspector/core/mcp/types.js"; +import { InspectorClient } from "@inspector/core/mcp/index.js"; +import { + ManagedToolsState, + ManagedResourcesState, + ManagedResourceTemplatesState, + ManagedPromptsState, +} from "@inspector/core/mcp/state/index.js"; +import { + createTransportNode, + resolveServerConfigs, + parseKeyValuePair as parseEnvPair, + parseHeaderPair, +} from "@inspector/core/mcp/node/index.js"; +import type { JsonValue } from "@inspector/core/mcp/index.js"; +import { + LoggingLevelSchema, + type LoggingLevel, +} from "@modelcontextprotocol/sdk/types.js"; + +export const validLogLevels: LoggingLevel[] = Object.values( + LoggingLevelSchema.enum, +); + +type MethodArgs = { + method?: string; + promptName?: string; + promptArgs?: Record; + uri?: string; + logLevel?: LoggingLevel; + toolName?: string; + toolArg?: Record; + toolMeta?: Record; + metadata?: Record; +}; + +function headersToServerSettings( + headers?: Record, +): InspectorServerSettings | undefined { + if (!headers || Object.keys(headers).length === 0) { + return undefined; + } + return { + headers: Object.entries(headers).map(([key, value]) => ({ key, value })), + metadata: [], + connectionTimeout: 0, + requestTimeout: 0, + taskTtl: DEFAULT_TASK_TTL_MS, + autoRefreshOnListChanged: false, + roots: [], + }; +} + +async function callMethod( + serverConfig: MCPServerConfig, + serverSettings: InspectorServerSettings | undefined, + args: MethodArgs & { method: string }, +): Promise { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const packageJsonPath = join(__dirname, "../package.json"); + const packageJsonData = await import(pathToFileURL(packageJsonPath).href, { + with: { type: "json" }, + }); + const packageJson = packageJsonData.default as { + name: string; + version: string; + }; + + const [, name = packageJson.name] = packageJson.name.split("/"); + const version = packageJson.version; + const clientIdentity = { name, version }; + + const inspectorClient = new InspectorClient(serverConfig, { + environment: { + transport: createTransportNode, + }, + clientIdentity, + initialLoggingLevel: "debug", + progress: false, + sample: false, + elicit: false, + serverSettings, + }); + + let managedToolsState: ManagedToolsState | null = null; + let managedResourcesState: ManagedResourcesState | null = null; + let managedResourceTemplatesState: ManagedResourceTemplatesState | null = + null; + let managedPromptsState: ManagedPromptsState | null = null; + + try { + await inspectorClient.connect(); + + let result: McpResponse; + + if (args.method === "tools/list" || args.method === "tools/call") { + managedToolsState = new ManagedToolsState(inspectorClient); + managedToolsState.setMetadata(args.metadata); + await managedToolsState.refresh(); + } + + if (args.method === "resources/list") { + managedResourcesState = new ManagedResourcesState(inspectorClient); + managedResourcesState.setMetadata(args.metadata); + await managedResourcesState.refresh(); + } else if (args.method === "resources/templates/list") { + managedResourceTemplatesState = new ManagedResourceTemplatesState( + inspectorClient, + ); + managedResourceTemplatesState.setMetadata(args.metadata); + await managedResourceTemplatesState.refresh(); + } else if (args.method === "prompts/list") { + managedPromptsState = new ManagedPromptsState(inspectorClient); + managedPromptsState.setMetadata(args.metadata); + await managedPromptsState.refresh(); + } + + if (args.method === "tools/list") { + result = { tools: managedToolsState!.getTools() }; + } else if (args.method === "tools/call") { + if (!args.toolName) { + throw new Error( + "Tool name is required for tools/call method. Use --tool-name to specify the tool name.", + ); + } + + const tool = managedToolsState! + .getTools() + .find((t) => t.name === args.toolName); + if (!tool) { + result = { + content: [ + { + type: "text" as const, + text: `Tool '${args.toolName}' not found.`, + }, + ], + isError: true, + }; + } else { + const invocation = await inspectorClient.callTool( + tool, + args.toolArg || {}, + args.metadata, + args.toolMeta, + ); + if (invocation.result !== null) { + result = invocation.result; + } else { + result = { + content: [ + { + type: "text" as const, + text: invocation.error || "Tool call failed", + }, + ], + isError: true, + }; + } + } + } else if (args.method === "resources/list") { + result = { + resources: managedResourcesState!.getResources(), + }; + } else if (args.method === "resources/read") { + if (!args.uri) { + throw new Error( + "URI is required for resources/read method. Use --uri to specify the resource URI.", + ); + } + + const invocation = await inspectorClient.readResource( + args.uri, + args.metadata, + ); + result = invocation.result; + } else if (args.method === "resources/templates/list") { + result = { + resourceTemplates: + managedResourceTemplatesState!.getResourceTemplates(), + }; + } else if (args.method === "prompts/list") { + result = { prompts: managedPromptsState!.getPrompts() }; + } else if (args.method === "prompts/get") { + if (!args.promptName) { + throw new Error( + "Prompt name is required for prompts/get method. Use --prompt-name to specify the prompt name.", + ); + } + + const invocation = await inspectorClient.getPrompt( + args.promptName, + args.promptArgs || {}, + args.metadata, + ); + result = invocation.result; + } else if (args.method === "logging/setLevel") { + if (!args.logLevel) { + throw new Error( + "Log level is required for logging/setLevel method. Use --log-level to specify the log level.", + ); + } + + await inspectorClient.setLoggingLevel(args.logLevel); + result = {}; + } else { + throw new Error( + `Unsupported method: ${args.method}. Supported methods include: tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, logging/setLevel`, + ); + } + + await awaitableLog(JSON.stringify(result, null, 2)); + } finally { + managedToolsState?.destroy(); + managedResourcesState?.destroy(); + managedResourceTemplatesState?.destroy(); + managedPromptsState?.destroy(); + await inspectorClient.disconnect(); + } +} + +function parseKeyValuePair( + value: string, + previous: Record = {}, +): Record { + const parts = value.split("="); + const key = parts[0]; + const val = parts.slice(1).join("="); + + if (!key || val === undefined || val === "") { + throw new Error( + `Invalid parameter format: ${value}. Use key=value format.`, + ); + } + + let parsedValue: JsonValue; + try { + parsedValue = JSON.parse(val) as JsonValue; + } catch { + parsedValue = val; + } + + return { ...previous, [key as string]: parsedValue }; +} + +function parseArgs(argv?: string[]): { + serverConfig: MCPServerConfig; + serverSettings: InspectorServerSettings | undefined; + methodArgs: MethodArgs & { method: string }; +} { + const program = new Command(); + const rawArgs = argv ?? process.argv; + const scriptArgs = rawArgs.slice(2); + const dashDashIndex = scriptArgs.indexOf("--"); + let targetArgs: string[] = []; + let optionArgs: string[] = []; + if (dashDashIndex >= 0) { + targetArgs = scriptArgs.slice(0, dashDashIndex); + optionArgs = scriptArgs.slice(dashDashIndex + 1); + } else { + let i = 0; + while (i < scriptArgs.length && !scriptArgs[i]!.startsWith("-")) { + targetArgs.push(scriptArgs[i]!); + i++; + } + optionArgs = scriptArgs.slice(i); + } + const preArgs: string[] = [ + rawArgs[0] ?? "node", + rawArgs[1] ?? "inspector-cli", + ...optionArgs, + ]; + + program + .name("inspector-cli") + .allowUnknownOption() + .argument( + "[target...]", + "Command and arguments or URL of the MCP server (or use --config and --server)", + ) + .option("--config ", "Config file path") + .option("--server ", "Server name from config file") + .option( + "-e ", + "Environment variables for the server (KEY=VALUE)", + parseEnvPair, + {}, + ) + .option("--method ", "Method to invoke") + .option("--tool-name ", "Tool name (for tools/call method)") + .option( + "--tool-arg ", + "Tool argument as key=value pair", + parseKeyValuePair, + {}, + ) + .option("--uri ", "URI of the resource (for resources/read method)") + .option( + "--prompt-name ", + "Name of the prompt (for prompts/get method)", + ) + .option( + "--prompt-args ", + "Prompt arguments as key=value pairs", + parseKeyValuePair, + {}, + ) + .option( + "--log-level ", + "Logging level (for logging/setLevel method)", + (value: string) => { + if (!validLogLevels.includes(value as LoggingLevel)) { + throw new Error( + `Invalid log level: ${value}. Valid levels are: ${validLogLevels.join(", ")}`, + ); + } + return value as LoggingLevel; + }, + ) + .option("--cwd ", "Working directory for stdio server process") + .option( + "--transport ", + "Transport type (sse, http, or stdio). Auto-detected from URL: /mcp → http, /sse → sse, commands → stdio", + (value: string) => { + const validTransports = ["sse", "http", "stdio"]; + if (!validTransports.includes(value)) { + throw new Error( + `Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`, + ); + } + return value as "sse" | "http" | "stdio"; + }, + ) + .option("--server-url ", "Server URL for SSE/HTTP transport") + .option( + "--header ", + 'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)', + parseHeaderPair, + {}, + ) + .option( + "--metadata ", + "General metadata as key=value pairs (applied to all methods)", + parseKeyValuePair, + {}, + ) + .option( + "--tool-metadata ", + "Tool-specific metadata as key=value pairs (for tools/call method only)", + parseKeyValuePair, + {}, + ); + + program.parse(preArgs); + + const options = program.opts() as { + config?: string; + server?: string; + e?: Record; + method?: string; + toolName?: string; + toolArg?: Record; + uri?: string; + promptName?: string; + promptArgs?: Record; + logLevel?: LoggingLevel; + metadata?: Record; + toolMetadata?: Record; + cwd?: string; + transport?: "sse" | "http" | "stdio"; + serverUrl?: string; + header?: Record; + }; + + const serverOptions = { + configPath: options.config, + serverName: options.server, + target: targetArgs.length > 0 ? targetArgs : undefined, + transport: options.transport, + serverUrl: options.serverUrl, + cwd: options.cwd, + env: options.e, + }; + + const configs = resolveServerConfigs(serverOptions, "single"); + const serverConfig = configs[0]; + if (!serverConfig) { + throw new Error( + "Could not resolve server config. Specify a URL or command, or use --config and --server.", + ); + } + + if (!options.method) { + throw new Error( + "Method is required. Use --method to specify the method to invoke.", + ); + } + + const methodArgs: MethodArgs & { method: string } = { + method: options.method, + toolName: options.toolName, + toolArg: options.toolArg, + uri: options.uri, + promptName: options.promptName, + promptArgs: options.promptArgs, + logLevel: options.logLevel, + metadata: options.metadata + ? Object.fromEntries( + Object.entries(options.metadata).map(([key, value]) => [ + key, + String(value), + ]), + ) + : undefined, + toolMeta: options.toolMetadata + ? Object.fromEntries( + Object.entries(options.toolMetadata).map(([key, value]) => [ + key, + String(value), + ]), + ) + : undefined, + }; + + return { + serverConfig, + serverSettings: headersToServerSettings(options.header), + methodArgs, + }; +} + +export async function runCli(argv?: string[]): Promise { + const { serverConfig, serverSettings, methodArgs } = parseArgs( + argv ?? process.argv, + ); + await callMethod(serverConfig, serverSettings, methodArgs); +} + +export async function main(): Promise { + process.on("uncaughtException", (error) => { + handleError(error); + }); + + try { + await runCli(); + process.exit(0); + } catch (error) { + handleError(error); + } +} diff --git a/clients/cli/src/error-handler.ts b/clients/cli/src/error-handler.ts new file mode 100644 index 000000000..972577453 --- /dev/null +++ b/clients/cli/src/error-handler.ts @@ -0,0 +1,20 @@ +function formatError(error: unknown): string { + let message: string; + + if (error instanceof Error) { + message = error.message; + } else if (typeof error === "string") { + message = error; + } else { + message = "Unknown error"; + } + + return message; +} + +export function handleError(error: unknown): never { + const errorMessage = formatError(error); + console.error(errorMessage); + + process.exit(1); +} diff --git a/clients/cli/src/index.ts b/clients/cli/src/index.ts new file mode 100644 index 000000000..93c1fa228 --- /dev/null +++ b/clients/cli/src/index.ts @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +import { resolve } from "path"; +import { fileURLToPath } from "url"; +import { runCli, validLogLevels } from "./cli.js"; +import { handleError } from "./error-handler.js"; + +export { runCli, validLogLevels }; + +const __filename = fileURLToPath(import.meta.url); +const isMain = + process.argv[1] !== undefined && + resolve(process.argv[1]) === resolve(__filename); + +if (isMain) { + runCli(process.argv) + .then(() => process.exit(0)) + .catch(handleError); +} diff --git a/clients/cli/src/utils/awaitable-log.ts b/clients/cli/src/utils/awaitable-log.ts new file mode 100644 index 000000000..144f01123 --- /dev/null +++ b/clients/cli/src/utils/awaitable-log.ts @@ -0,0 +1,7 @@ +export function awaitableLog(logValue: string): Promise { + return new Promise((resolve) => { + process.stdout.write(logValue, () => { + resolve(); + }); + }); +} diff --git a/clients/cli/tsconfig.json b/clients/cli/tsconfig.json new file mode 100644 index 000000000..799b1fa9e --- /dev/null +++ b/clients/cli/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./build", + "rootDir": "./src", + "baseUrl": ".", + "paths": { + "@inspector/core/*": ["../../core/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/*.test.ts", "build"] +} diff --git a/clients/cli/tsup.config.ts b/clients/cli/tsup.config.ts new file mode 100644 index 000000000..2420a0220 --- /dev/null +++ b/clients/cli/tsup.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'tsup'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(dirname, '../..'); + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + outDir: 'build', + clean: true, + sourcemap: true, + target: 'node20', + platform: 'node', + // Bundle core source; leave npm deps external. + noExternal: [/^@inspector\/core/], + esbuildOptions(options) { + options.alias = { + '@inspector/core': path.join(repoRoot, 'core'), + }; + }, +}); diff --git a/clients/cli/vitest.config.ts b/clients/cli/vitest.config.ts new file mode 100644 index 000000000..cea47a3df --- /dev/null +++ b/clients/cli/vitest.config.ts @@ -0,0 +1,17 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vitest/config'; +import { vitestSharedPaths } from '../../vitest.shared.mts'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const { projectResolve } = vitestSharedPaths(dirname); + +export default defineConfig({ + resolve: projectResolve, + test: { + globals: false, + environment: 'node', + include: ['__tests__/**/*.test.ts'], + testTimeout: 15000, + }, +}); diff --git a/clients/launcher/README.md b/clients/launcher/README.md new file mode 100644 index 000000000..8f1150a7c --- /dev/null +++ b/clients/launcher/README.md @@ -0,0 +1,19 @@ +# MCP Inspector Launcher + +The launcher is the package that provides the global `mcp-inspector` binary (e.g. when users run `npx @modelcontextprotocol/inspector`). It is not a separate user-facing app—it is the single entrypoint that selects and runs one of the clients (web, CLI, or TUI). + +## Responsibility + +- Parse the mode flag: `--web` (default), `--cli`, or `--tui`. +- Forward the rest of `process.argv` to the chosen app. +- Dynamically import that app’s runner from `clients/{web,cli,tui}/build/index.js` (relative to the launcher build output) and call it **in-process** (no `spawn()`). + +All configuration parsing, config-file loading, and server setup are handled by the app runners and by **core**; the launcher does not interpret config or env vars. + +## Publishing + +The root `@modelcontextprotocol/inspector` package ships as one fat tarball: `npm run build` at the repo root builds all clients, then `prepack` runs before `npm publish`. Runtime dependencies are declared on the root `package.json`; client builds bundle `@inspector/core` and externalize npm packages resolved from the root install. + +## Architecture + +For how the launcher fits with the shared config processor and app runners, see the [Launcher and config consolidation](../../docs/launcher-config-consolidation-plan.md) document in the repo root `docs/` folder. diff --git a/clients/launcher/package-lock.json b/clients/launcher/package-lock.json new file mode 100644 index 000000000..feff55eba --- /dev/null +++ b/clients/launcher/package-lock.json @@ -0,0 +1,188 @@ +{ + "name": "@modelcontextprotocol/inspector-launcher", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@modelcontextprotocol/inspector-launcher", + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "commander": "^13.1.0" + }, + "bin": { + "mcp-inspector": "build/index.js" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "typescript": "~5.9.3" + } + }, + "../cli": { + "name": "@modelcontextprotocol/inspector-cli", + "version": "2.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "commander": "^13.1.0", + "pino": "^9.14.0", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "bin": { + "mcp-inspector-cli": "build/index.js" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "tsup": "^8.5.0", + "typescript": "~5.9.3", + "vitest": "^4.1.0" + } + }, + "../tui": { + "name": "@modelcontextprotocol/inspector-tui", + "version": "2.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "commander": "^13.1.0", + "ink": "^6.0.0", + "ink-form": "^2.0.1", + "ink-scroll-view": "^0.3.6", + "open": "^10.2.0", + "pino": "^9.14.0", + "react": "^19.2.4", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "bin": { + "mcp-inspector-tui": "build/index.js" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "@types/react": "^19.2.14", + "tsup": "^8.5.0", + "tsx": "^4.21.0", + "typescript": "~5.9.3", + "vite": "^8.0.16", + "vite-node": "^6.0.0", + "vitest": "^4.1.0" + } + }, + "../web": { + "name": "@modelcontextprotocol/inspector-web", + "version": "2.0.0", + "extraneous": true, + "dependencies": { + "@emotion/react": "^11.14.0", + "@hono/node-server": "^1.19.14", + "@mantine/core": "^8.3.17", + "@mantine/form": "^8.3.17", + "@mantine/hooks": "^8.3.17", + "@mantine/notifications": "^8.3.17", + "@modelcontextprotocol/ext-apps": "^1.7.1", + "@modelcontextprotocol/sdk": "^1.29.0", + "@napi-rs/keyring": "^1.3.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "chokidar": "^4.0.3", + "commander": "^13.1.0", + "hono": "^4.12.18", + "open": "^10.2.0", + "pino": "^9.14.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-icons": "^5.6.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "bin": { + "mcp-inspector-web": "build/index.js" + }, + "devDependencies": { + "@chromatic-com/storybook": "^5.0.1", + "@eslint/js": "^9.39.4", + "@storybook/addon-a11y": "^10.2.19", + "@storybook/addon-docs": "^10.2.19", + "@storybook/addon-onboarding": "^10.2.19", + "@storybook/addon-vitest": "^10.2.19", + "@storybook/react-vite": "^10.2.19", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/express": "^5.0.6", + "@types/node": "^24.12.4", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "@vitest/browser-playwright": "^4.1.0", + "@vitest/coverage-v8": "^4.1.0", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "eslint-plugin-storybook": "^10.2.19", + "express": "^5.2.1", + "globals": "^17.4.0", + "happy-dom": "^20.9.0", + "playwright": "^1.58.2", + "prettier": "^3.8.1", + "storybook": "^10.2.19", + "tsup": "^8.5.1", + "typescript": "~5.9.3", + "typescript-eslint": "^8.56.1", + "vite": "^8.0.0", + "vitest": "^4.1.0", + "yaml": "^2.9.0" + } + }, + "node_modules/@types/node": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", + "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/clients/launcher/package.json b/clients/launcher/package.json new file mode 100644 index 000000000..92c2962b3 --- /dev/null +++ b/clients/launcher/package.json @@ -0,0 +1,26 @@ +{ + "name": "@modelcontextprotocol/inspector-launcher", + "version": "2.0.0", + "private": true, + "description": "Launcher for MCP Inspector (web, CLI, TUI)", + "license": "MIT", + "type": "module", + "main": "build/index.js", + "bin": { + "mcp-inspector": "./build/index.js" + }, + "files": [ + "build" + ], + "scripts": { + "build": "tsc", + "postbuild": "node scripts/make-executable.js" + }, + "dependencies": { + "commander": "^13.1.0" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "typescript": "~5.9.3" + } +} diff --git a/clients/launcher/scripts/make-executable.js b/clients/launcher/scripts/make-executable.js new file mode 100644 index 000000000..0ddb70a41 --- /dev/null +++ b/clients/launcher/scripts/make-executable.js @@ -0,0 +1,26 @@ +/** + * Cross-platform script to make the launcher entrypoint executable + */ +import { promises as fs } from "fs"; +import { platform } from "os"; +import { execSync } from "child_process"; +import path from "path"; + +const TARGET_FILE = path.resolve("build/index.js"); + +async function makeExecutable() { + try { + if (platform() !== "win32") { + execSync(`chmod +x "${TARGET_FILE}"`); + console.log("Made file executable with chmod"); + } else { + await fs.access(TARGET_FILE); + console.log("File exists and is accessible on Windows"); + } + } catch (error) { + console.error("Error making file executable:", error); + process.exit(1); + } +} + +makeExecutable(); diff --git a/clients/launcher/src/index.ts b/clients/launcher/src/index.ts new file mode 100644 index 000000000..5b7e162f0 --- /dev/null +++ b/clients/launcher/src/index.ts @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +import { dirname, join } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { Command } from "commander"; + +const launcherDir = dirname(fileURLToPath(import.meta.url)); + +function clientEntry(client: "web" | "cli" | "tui"): string { + return pathToFileURL( + join(launcherDir, "..", "..", client, "build", "index.js"), + ).href; +} + +const program = new Command(); + +program + .name("mcp-inspector") + .description("MCP Inspector – run web UI, CLI, or TUI") + .option("--web", "Run web UI (default)") + .option("--cli", "Run CLI") + .option("--tui", "Run TUI") + .allowUnknownOption(); + +program.parseOptions(process.argv); +const opts = program.opts() as { web?: boolean; cli?: boolean; tui?: boolean }; + +const helpOnly = process.argv.includes("-h") || process.argv.includes("--help"); +const modeFlagSet = opts.web || opts.cli || opts.tui; + +if (helpOnly && !modeFlagSet) { + program.outputHelp(); + console.log( + "\nAll other arguments are forwarded to the selected app. Use --web, --cli, or --tui then pass app-specific options.", + ); + process.exit(0); +} + +const mode = opts.tui ? "tui" : opts.cli ? "cli" : "web"; +const modeFlag = opts.tui ? "--tui" : opts.cli ? "--cli" : "--web"; +// Forward argv without the launcher's mode flag so the app's Commander doesn't see unknown option +const forwardedArgv = process.argv.filter((arg) => arg !== modeFlag); + +async function run(): Promise { + if (mode === "web") { + const { runWeb } = await import(clientEntry("web")); + await runWeb(forwardedArgv); + } else if (mode === "cli") { + const { runCli } = await import(clientEntry("cli")); + await runCli(forwardedArgv); + } else { + const { runTui } = await import(clientEntry("tui")); + await runTui(forwardedArgv); + } +} + +run().catch((err: unknown) => { + console.error("Error running MCP Inspector:", err); + process.exit(1); +}); diff --git a/clients/launcher/tsconfig.json b/clients/launcher/tsconfig.json new file mode 100644 index 000000000..4b705d67e --- /dev/null +++ b/clients/launcher/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./build", + "rootDir": "./src", + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "build"] +} diff --git a/clients/tui/README.md b/clients/tui/README.md new file mode 100644 index 000000000..e050f9b46 --- /dev/null +++ b/clients/tui/README.md @@ -0,0 +1,57 @@ +# MCP Inspector TUI Client + +The Terminal User Interface (TUI) client brings the interactive exploration capabilities of the Web Client directly to your terminal. It is built using [Ink](https://github.com/vadimdemedes/ink) to provide a rich, React-like component experience in a command-line environment. + +![MCP Inspector TUI Screenshot](../../docs/images/tui-screenshot.png) + +## Running the TUI + +You can run the TUI client via `npx`: + +```bash +npx @modelcontextprotocol/inspector --tui node build/index.js +``` + +### With Configuration Files + +The TUI can load all servers from an MCP config file: + +```bash +npx @modelcontextprotocol/inspector --tui --config mcp.json +``` + +(It does not use `--server`; all servers in the file are available in the TUI.) + +## Options + +### MCP server (which server(s) to connect to) + +Options that specify the MCP server(s) (config file, ad-hoc command/URL, env vars, headers) are shared by the Web, CLI, and TUI and are documented in [MCP server configuration](../../docs/mcp-server-configuration.md): `--config`, `-e`, `--cwd`, `--header`, `--transport`, `--server-url`, and the positional `[target...]`. + +### TUI-specific (OAuth for HTTP servers) + +When connecting to SSE or Streamable HTTP servers that use OAuth, you can pass: + +| Option | Description | +| ----------------------------- | ------------------------------------------------------------------------------------ | +| `--client-id ` | OAuth client ID (static client). | +| `--client-secret ` | OAuth client secret (confidential clients). | +| `--client-metadata-url ` | OAuth Client ID Metadata Document URL (CIMD). | +| `--callback-url ` | OAuth redirect/callback listener URL (default: `http://127.0.0.1:0/oauth/callback`). | + +## Features + +The TUI provides terminal-native tabs and panes for interacting with your MCP server: + +- **Resources**: Browse and read resources exposed by the server. +- **Prompts**: List and test prompts. +- **Tools**: View available tools and execute them with form-like inputs. +- **History**: View the request and response history of your interactions. +- **Console**: View the direct stdout/stderr and diagnostic logging of the connected server. + +## Navigation + +- Use the **Arrow Keys** (Left/Right) or **Tab** to switch between the main tabs (Resources, Tools, Prompts, etc.). +- Use the **Arrow Keys** (Up/Down) to scroll through lists of items. +- Press **Enter** to select an item, execute a tool, or fetch a resource. +- Press **Escape** or `Ctrl+C` to exit the application. diff --git a/clients/tui/__tests__/tui.test.ts b/clients/tui/__tests__/tui.test.ts new file mode 100644 index 000000000..7e0072e08 --- /dev/null +++ b/clients/tui/__tests__/tui.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from "vitest"; +import { tabs } from "../src/components/tabsConfig.js"; + +describe("TUI", () => { + it("exports tabs with expected shape", () => { + expect(Array.isArray(tabs)).toBe(true); + expect(tabs.length).toBeGreaterThan(0); + for (const tab of tabs) { + expect(tab).toHaveProperty("id"); + expect(tab).toHaveProperty("label"); + expect(tab).toHaveProperty("accelerator"); + } + }); + + it("includes info tab", () => { + const info = tabs.find((t) => t.id === "info"); + expect(info).toBeDefined(); + expect(info?.label).toBe("Info"); + expect(info?.accelerator).toBe("i"); + }); +}); diff --git a/clients/tui/dev.ts b/clients/tui/dev.ts new file mode 100644 index 000000000..a5ba66b70 --- /dev/null +++ b/clients/tui/dev.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +import { runTui } from "./tui.js"; + +runTui(process.argv).catch((err: unknown) => { + console.error(err); + process.exit(1); +}); diff --git a/clients/tui/index.ts b/clients/tui/index.ts new file mode 100644 index 000000000..46f6d8fed --- /dev/null +++ b/clients/tui/index.ts @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +import { resolve } from "path"; +import { fileURLToPath } from "url"; +import { runTui } from "./tui.js"; + +export { runTui }; + +const __filename = fileURLToPath(import.meta.url); +const isMain = + process.argv[1] !== undefined && + resolve(process.argv[1]) === resolve(__filename); + +if (isMain) { + runTui(process.argv).catch((err: unknown) => { + console.error(err); + process.exit(1); + }); +} diff --git a/clients/tui/package-lock.json b/clients/tui/package-lock.json new file mode 100644 index 000000000..6515f7fa1 --- /dev/null +++ b/clients/tui/package-lock.json @@ -0,0 +1,5312 @@ +{ + "name": "@modelcontextprotocol/inspector-tui", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@modelcontextprotocol/inspector-tui", + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "commander": "^13.1.0", + "ink": "^6.0.0", + "ink-form": "^2.0.1", + "ink-scroll-view": "^0.3.6", + "open": "^10.2.0", + "pino": "^9.14.0", + "react": "^19.2.4", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "bin": { + "mcp-inspector-tui": "build/index.js" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "@types/react": "^19.2.14", + "tsup": "^8.5.0", + "tsx": "^4.21.0", + "typescript": "~5.9.3", + "vite": "^8.0.16", + "vite-node": "^6.0.0", + "vitest": "^4.1.0" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "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/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", + "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "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 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "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/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "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/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "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/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "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/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "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/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "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/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/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/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", + "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "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/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "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.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "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.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/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/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "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-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.24", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.24.tgz", + "integrity": "sha512-I36D1s+HgQc55KbhEr4iybfxv/9o1zdpw+XEM6dJa91LqQD0HCoSGdxpRJCZE+aavs87j4V3Ls2OJzq8C/U4iw==", + "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/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-form": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ink-form/-/ink-form-2.0.1.tgz", + "integrity": "sha512-vo0VMwHf+HOOJo7026K4vJEN8xm4sP9iWlQLx4bngNEEY5K8t30CUvVjQCCNAV6Mt2ODt2Aq+2crCuBONReJUg==", + "license": "MIT", + "dependencies": { + "ink-select-input": "^5.0.0", + "ink-text-input": "^6.0.0" + }, + "peerDependencies": { + "ink": ">=4", + "react": ">=18" + } + }, + "node_modules/ink-scroll-view": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/ink-scroll-view/-/ink-scroll-view-0.3.7.tgz", + "integrity": "sha512-lUBLxSbVry/+UtJhuvRu6wumP43+ScVp69J7e+hmYwz1kTkahWfkVwWOu7Mn1DMPb8AU8bnsmEsr6kvSJvnaRw==", + "license": "MIT", + "peerDependencies": { + "ink": "^5 || ^6 || ^7", + "react": "^18 || ^19" + } + }, + "node_modules/ink-select-input": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ink-select-input/-/ink-select-input-6.2.0.tgz", + "integrity": "sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ==", + "license": "MIT", + "dependencies": { + "figures": "^6.1.0", + "to-rotated": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ink-text-input": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz", + "integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5", + "react": ">=18" + } + }, + "node_modules/ink-text-input/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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.2", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz", + "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/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.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "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.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "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/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.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/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "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": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-rotated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-rotated/-/to-rotated-1.0.0.tgz", + "integrity": "sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/type-fest": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", + "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "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": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-6.0.0.tgz", + "integrity": "sha512-oj4PVrT+pDh6GYf5wfUXkcZyekYS8kKPfLPXVl8qe324Ec6l4K2DUKNadRbZ3LQl0qGcDz+PyOo7ZAh00Y+JjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^7.0.0", + "es-module-lexer": "^2.0.0", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "vite": "^8.0.0" + }, + "bin": { + "vite-node": "dist/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://opencollective.com/antfu" + } + }, + "node_modules/vite-node/node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/vitest": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.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.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "license": "MIT" + }, + "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/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/clients/tui/package.json b/clients/tui/package.json new file mode 100644 index 000000000..914891f2f --- /dev/null +++ b/clients/tui/package.json @@ -0,0 +1,51 @@ +{ + "name": "@modelcontextprotocol/inspector-tui", + "version": "2.0.0", + "private": true, + "description": "Terminal User Interface (TUI) for the Model Context Protocol inspector", + "license": "MIT", + "type": "module", + "main": "build/index.js", + "exports": { + ".": "./build/index.js" + }, + "bin": { + "mcp-inspector-tui": "./build/index.js" + }, + "files": [ + "build", + "README.md" + ], + "scripts": { + "build": "tsup", + "dev": "vite-node --config vitest.config.ts dev.ts", + "test": "vitest run" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "commander": "^13.1.0", + "ink": "^6.0.0", + "ink-form": "^2.0.1", + "ink-scroll-view": "^0.3.6", + "open": "^10.2.0", + "pino": "^9.14.0", + "react": "^19.2.4", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "overrides": { + "ink-select-input": "^6.2.0" + }, + "devDependencies": { + "@types/node": "^24.12.4", + "@types/react": "^19.2.14", + "tsup": "^8.5.0", + "tsx": "^4.21.0", + "typescript": "~5.9.3", + "vite": "^8.0.16", + "vite-node": "^6.0.0", + "vitest": "^4.1.0" + } +} diff --git a/clients/tui/src/App.tsx b/clients/tui/src/App.tsx new file mode 100644 index 000000000..7fcff5a7c --- /dev/null +++ b/clients/tui/src/App.tsx @@ -0,0 +1,1878 @@ +import React, { + useState, + useMemo, + useEffect, + useCallback, + useRef, +} from "react"; +import { Box, Text, useInput, useApp, type Key } from "ink"; +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import type { + MessageEntry, + FetchRequestEntry, + MCPServerConfig, + InspectorClientOptions, + InspectorClientEnvironment, +} from "@inspector/core/mcp/index.js"; +import type { + Tool, + Resource, + Prompt, + PromptArgument, + GetPromptResult, +} from "@modelcontextprotocol/sdk/types.js"; +import { InspectorClient } from "@inspector/core/mcp/index.js"; +import { + ManagedToolsState, + ManagedResourcesState, + ManagedResourceTemplatesState, + ManagedPromptsState, + MessageLogState, + FetchRequestLogState, + StderrLogState, +} from "@inspector/core/mcp/state/index.js"; +import { createTransportNode } from "@inspector/core/mcp/node/index.js"; +import { useInspectorClient } from "@inspector/core/react/useInspectorClient.js"; +import { useManagedTools } from "@inspector/core/react/useManagedTools.js"; +import { useManagedResources } from "@inspector/core/react/useManagedResources.js"; +import { useManagedResourceTemplates } from "@inspector/core/react/useManagedResourceTemplates.js"; +import { useManagedPrompts } from "@inspector/core/react/useManagedPrompts.js"; +import { useMessageLog } from "@inspector/core/react/useMessageLog.js"; +import { useFetchRequestLog } from "@inspector/core/react/useFetchRequestLog.js"; +import { useStderrLog } from "@inspector/core/react/useStderrLog.js"; +import { + CallbackNavigation, + MutableRedirectUrlProvider, +} from "@inspector/core/auth/index.js"; +import { + createOAuthCallbackServer, + type OAuthCallbackServer, + NodeOAuthStorage, +} from "@inspector/core/auth/node/index.js"; +import { getTuiLogger } from "./logger.js"; +import { openUrl } from "./utils/openUrl.js"; +import { Tabs } from "./components/Tabs.js"; +import { type TabType, tabs as tabList } from "./components/tabsConfig.js"; +import { InfoTab } from "./components/InfoTab.js"; +import { AuthTab } from "./components/AuthTab.js"; +import { ResourcesTab } from "./components/ResourcesTab.js"; +import { PromptsTab } from "./components/PromptsTab.js"; +import { ToolsTab } from "./components/ToolsTab.js"; +import { NotificationsTab } from "./components/NotificationsTab.js"; +import { HistoryTab } from "./components/HistoryTab.js"; +import { RequestsTab } from "./components/RequestsTab.js"; +import { ToolTestModal } from "./components/ToolTestModal.js"; +import { ResourceTestModal } from "./components/ResourceTestModal.js"; +import { PromptTestModal } from "./components/PromptTestModal.js"; +import { DetailsModal } from "./components/DetailsModal.js"; +import type { TuiServer } from "./tui-servers.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Read package.json to get project info +// Strategy: Try multiple paths to handle both local dev and global install +// - Local dev (tsx): __dirname = src/, package.json is one level up +// - Global install: __dirname = dist/src/, package.json is two levels up +let packagePath: string; +let packageJson: { name: string; description: string; version: string }; + +try { + // Try two levels up first (global install case) + packagePath = join(__dirname, "..", "..", "package.json"); + packageJson = JSON.parse(readFileSync(packagePath, "utf-8")) as { + name: string; + description: string; + version: string; + }; +} catch { + // Fall back to one level up (local dev case) + packagePath = join(__dirname, "..", "package.json"); + packageJson = JSON.parse(readFileSync(packagePath, "utf-8")) as { + name: string; + description: string; + version: string; + }; +} + +// Focus management types +type FocusArea = + | "serverList" + | "tabs" + // Used by Resources/Prompts/Tools - list pane + | "tabContentList" + // Used by Resources/Prompts/Tools - details pane + | "tabContentDetails" + // Used only when activeTab === 'messages' + | "messagesList" + | "messagesDetail" + // Used only when activeTab === 'requests' + | "requestsList" + | "requestsDetail"; + +interface AppProps { + mcpServers: Record; + clientId?: string; + clientSecret?: string; + clientMetadataUrl?: string; + callbackUrlConfig: { + hostname: string; + port: number; + pathname: string; + }; +} + +/** HTTP transports (SSE, streamable-http) can use OAuth. No config gate. */ +function isOAuthCapableServer(config: MCPServerConfig | null): boolean { + if (!config) return false; + const c = config as MCPServerConfig & { oauth?: unknown }; + return c.type === "sse" || c.type === "streamable-http"; +} + +function App({ + mcpServers, + clientId, + clientSecret, + clientMetadataUrl, + callbackUrlConfig, +}: AppProps) { + const { exit } = useApp(); + const callbackServerBaseOptions = useMemo( + () => ({ + port: callbackUrlConfig.port, + hostname: callbackUrlConfig.hostname, + path: callbackUrlConfig.pathname, + }), + [callbackUrlConfig], + ); + + useEffect(() => { + getTuiLogger().info({ serverNames: Object.keys(mcpServers) }, "TUI started"); + }, [mcpServers]); + + const [selectedServer, setSelectedServer] = useState(null); + const [activeTab, setActiveTab] = useState("info"); + const [focus, setFocus] = useState("serverList"); + const [tabCounts, setTabCounts] = useState<{ + info?: number; + resources?: number; + prompts?: number; + tools?: number; + messages?: number; + requests?: number; + logging?: number; + }>({}); + const [oauthStatus, setOauthStatus] = useState< + "idle" | "authenticating" | "success" | "error" + >("idle"); + const [oauthMessage, setOauthMessage] = useState(null); + const oauthInProgressRef = useRef(false); + const [selectedAuthAction, setSelectedAuthAction] = useState< + "guided" | "quick" | "clear" + >("guided"); + + // Tool test modal state + const [toolTestModal, setToolTestModal] = useState<{ + tool: Tool; + inspectorClient: InspectorClient | null; + } | null>(null); + + // Resource test modal state + const [resourceTestModal, setResourceTestModal] = useState<{ + template: { + name: string; + uriTemplate: string; + description?: string; + }; + inspectorClient: InspectorClient | null; + } | null>(null); + + // Prompt test modal state + const [promptTestModal, setPromptTestModal] = useState<{ + prompt: Prompt; + inspectorClient: InspectorClient | null; + } | null>(null); + + // Details modal state + const [detailsModal, setDetailsModal] = useState<{ + title: string; + content: React.ReactNode; + } | null>(null); + + // InspectorClient instances for each server + const [inspectorClients, setInspectorClients] = useState< + Record + >({}); + // ManagedToolsState per server (tools list from manager, not client) + const [managedToolsStates, setManagedToolsStates] = useState< + Record + >({}); + const [managedResourcesStates, setManagedResourcesStates] = useState< + Record + >({}); + const [managedResourceTemplatesStates, setManagedResourceTemplatesStates] = + useState>({}); + const [managedPromptsStates, setManagedPromptsStates] = useState< + Record + >({}); + const [messageLogStates, setMessageLogStates] = useState< + Record + >({}); + const [fetchRequestLogStates, setFetchRequestLogStates] = useState< + Record + >({}); + const [stderrLogStates, setStderrLogStates] = useState< + Record + >({}); + const [dimensions, setDimensions] = useState({ + width: process.stdout.columns || 80, + height: process.stdout.rows || 24, + }); + + useEffect(() => { + const updateDimensions = () => { + setDimensions({ + width: process.stdout.columns || 80, + height: process.stdout.rows || 24, + }); + }; + + process.stdout.on("resize", updateDimensions); + return () => { + process.stdout.off("resize", updateDimensions); + }; + }, []); + + const serverNames = Object.keys(mcpServers); + const selectedServerEntry = selectedServer + ? mcpServers[selectedServer] + : null; + const selectedServerConfig = selectedServerEntry?.config ?? null; + + // Mutable redirect URL providers, keyed by server name (populated before authenticate) + const redirectUrlProvidersRef = useRef< + Record + >({}); + + // Create InspectorClient and state managers for each server on mount + useEffect(() => { + const newClients: Record = {}; + const newManagers: Record = {}; + const newManagedResourcesStates: Record = {}; + const newManagedResourceTemplatesStates: Record< + string, + ManagedResourceTemplatesState + > = {}; + const newManagedPromptsStates: Record = {}; + const newMessageLogStates: Record = {}; + const newFetchRequestLogStates: Record = {}; + const newStderrLogStates: Record = {}; + for (const serverName of serverNames) { + if (!(serverName in inspectorClients)) { + const { config: serverConfig, settings: savedSettings } = + mcpServers[serverName]!; + const environment: InspectorClientEnvironment = { + transport: createTransportNode, + logger: getTuiLogger(), + }; + const defaultMetadata = savedSettings?.metadata + ? Object.fromEntries( + savedSettings.metadata + .filter((m) => m.key.trim() !== "") + .map((m) => [m.key, m.value]), + ) + : undefined; + const oauthFromSettings = + savedSettings && + (savedSettings.oauthClientId || + savedSettings.oauthClientSecret || + savedSettings.oauthScopes) + ? { + ...(savedSettings.oauthClientId && { + clientId: savedSettings.oauthClientId, + }), + ...(savedSettings.oauthClientSecret && { + clientSecret: savedSettings.oauthClientSecret, + }), + ...(savedSettings.oauthScopes && { + scope: savedSettings.oauthScopes, + }), + } + : undefined; + const opts: InspectorClientOptions = { + environment, + pipeStderr: true, + ...(savedSettings && + savedSettings.requestTimeout > 0 && { + timeout: savedSettings.requestTimeout, + }), + ...(defaultMetadata && + Object.keys(defaultMetadata).length > 0 && { + defaultMetadata, + }), + ...(savedSettings && { serverSettings: savedSettings }), + }; + if (isOAuthCapableServer(serverConfig)) { + const redirectUrlProvider = + redirectUrlProvidersRef.current[serverName] ?? + (redirectUrlProvidersRef.current[serverName] = + new MutableRedirectUrlProvider()); + environment.oauth = { + storage: new NodeOAuthStorage(), + navigation: new CallbackNavigation( + async (url) => await openUrl(url), + ), + redirectUrlProvider, + }; + opts.oauth = { + ...(oauthFromSettings ?? {}), + ...(clientId && { clientId }), + ...(clientSecret && { clientSecret }), + ...(clientMetadataUrl && { clientMetadataUrl }), + }; + } + const client = new InspectorClient(serverConfig, opts); + newClients[serverName] = client; + newManagers[serverName] = new ManagedToolsState(client); + newManagedResourcesStates[serverName] = new ManagedResourcesState( + client, + ); + newManagedResourceTemplatesStates[serverName] = + new ManagedResourceTemplatesState(client); + newManagedPromptsStates[serverName] = new ManagedPromptsState(client); + newMessageLogStates[serverName] = new MessageLogState(client); + newFetchRequestLogStates[serverName] = new FetchRequestLogState(client); + newStderrLogStates[serverName] = new StderrLogState(client); + } + } + if (Object.keys(newClients).length > 0) { + setInspectorClients((prev) => ({ ...prev, ...newClients })); + setManagedToolsStates((prev) => ({ ...prev, ...newManagers })); + setManagedResourcesStates((prev) => ({ + ...prev, + ...newManagedResourcesStates, + })); + setManagedResourceTemplatesStates((prev) => ({ + ...prev, + ...newManagedResourceTemplatesStates, + })); + setManagedPromptsStates((prev) => ({ + ...prev, + ...newManagedPromptsStates, + })); + setMessageLogStates((prev) => ({ ...prev, ...newMessageLogStates })); + setFetchRequestLogStates((prev) => ({ + ...prev, + ...newFetchRequestLogStates, + })); + setStderrLogStates((prev) => ({ ...prev, ...newStderrLogStates })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clientId, clientSecret, clientMetadataUrl]); + + // Cleanup: destroy managers and disconnect all clients on unmount + useEffect(() => { + return () => { + Object.values(managedToolsStates).forEach((manager) => { + manager.destroy(); + }); + Object.values(managedResourcesStates).forEach((manager) => { + manager.destroy(); + }); + Object.values(managedResourceTemplatesStates).forEach((manager) => { + manager.destroy(); + }); + Object.values(managedPromptsStates).forEach((manager) => { + manager.destroy(); + }); + Object.values(messageLogStates).forEach((manager) => { + manager.destroy(); + }); + Object.values(fetchRequestLogStates).forEach((manager) => { + manager.destroy(); + }); + Object.values(stderrLogStates).forEach((manager) => { + manager.destroy(); + }); + Object.values(inspectorClients).forEach((client) => { + client.disconnect().catch(() => { + // Ignore errors during cleanup + }); + }); + }; + }, [ + inspectorClients, + managedToolsStates, + managedResourcesStates, + managedResourceTemplatesStates, + managedPromptsStates, + messageLogStates, + fetchRequestLogStates, + stderrLogStates, + ]); + + // Preselect the first server on mount + useEffect(() => { + if (serverNames.length > 0 && selectedServer === null) { + setSelectedServer(serverNames[0]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Clear OAuth status when switching servers + useEffect(() => { + setOauthStatus("idle"); + setOauthMessage(null); + }, [selectedServer]); + + // Switch away from Auth tab when server is not OAuth-capable + useEffect(() => { + if ( + activeTab === "auth" && + selectedServerConfig && + !isOAuthCapableServer(selectedServerConfig) + ) { + setActiveTab("info"); + } + }, [activeTab, selectedServerConfig]); + + // Get InspectorClient for selected server + const selectedInspectorClient = useMemo( + () => (selectedServer ? inspectorClients[selectedServer] : null), + [selectedServer, inspectorClients], + ); + + // Use the hook to get reactive state from InspectorClient + const { + status: inspectorStatus, + capabilities: inspectorCapabilities, + serverInfo: inspectorServerInfo, + instructions: inspectorInstructions, + connect: connectInspector, + disconnect: disconnectInspector, + } = useInspectorClient(selectedInspectorClient); + + // Log state from managers (per-server) + const selectedMessageLogState = useMemo( + () => + selectedServer && messageLogStates[selectedServer] + ? messageLogStates[selectedServer] + : null, + [selectedServer, messageLogStates], + ); + const selectedFetchRequestLogState = useMemo( + () => + selectedServer && fetchRequestLogStates[selectedServer] + ? fetchRequestLogStates[selectedServer] + : null, + [selectedServer, fetchRequestLogStates], + ); + const selectedStderrLogState = useMemo( + () => + selectedServer && stderrLogStates[selectedServer] + ? stderrLogStates[selectedServer] + : null, + [selectedServer, stderrLogStates], + ); + const { messages: inspectorMessages } = useMessageLog( + selectedMessageLogState, + ); + const { fetchRequests: inspectorFetchRequests } = useFetchRequestLog( + selectedFetchRequestLogState, + ); + const { stderrLogs: inspectorStderrLogs } = useStderrLog( + selectedStderrLogState, + ); + + // Tools from ManagedToolsState (full list, auto-load on connect) + const selectedManagedToolsState = useMemo( + () => + selectedServer && managedToolsStates[selectedServer] + ? managedToolsStates[selectedServer] + : null, + [selectedServer, managedToolsStates], + ); + const { tools: managedTools } = useManagedTools( + selectedInspectorClient, + selectedManagedToolsState, + ); + + // Resources, resource templates, prompts from managed state managers + const selectedManagedResourcesState = useMemo( + () => + selectedServer && managedResourcesStates[selectedServer] + ? managedResourcesStates[selectedServer] + : null, + [selectedServer, managedResourcesStates], + ); + const selectedManagedResourceTemplatesState = useMemo( + () => + selectedServer && managedResourceTemplatesStates[selectedServer] + ? managedResourceTemplatesStates[selectedServer] + : null, + [selectedServer, managedResourceTemplatesStates], + ); + const selectedManagedPromptsState = useMemo( + () => + selectedServer && managedPromptsStates[selectedServer] + ? managedPromptsStates[selectedServer] + : null, + [selectedServer, managedPromptsStates], + ); + const { resources: managedResources } = useManagedResources( + selectedInspectorClient, + selectedManagedResourcesState, + ); + const { resourceTemplates: managedResourceTemplates } = + useManagedResourceTemplates( + selectedInspectorClient, + selectedManagedResourceTemplatesState, + ); + const { prompts: managedPrompts } = useManagedPrompts( + selectedInspectorClient, + selectedManagedPromptsState, + ); + + // Connect handler - InspectorClient now handles fetching server data automatically + const handleConnect = useCallback(async () => { + if (!selectedServer || !selectedInspectorClient) return; + + try { + await connectInspector(); + // InspectorClient automatically fetches server data (capabilities, tools, resources, resource templates, prompts, etc.) + // on connect, so we don't need to do anything here + } catch { + // Error handling is done by InspectorClient and will be reflected in status + } + }, [selectedServer, selectedInspectorClient, connectInspector]); + + // Disconnect handler + const handleDisconnect = useCallback(async () => { + if (!selectedServer) return; + await disconnectInspector(); + // InspectorClient will update status automatically, and data is preserved + }, [selectedServer, disconnectInspector]); + + // Shared ref for OAuth callback server; stop before starting new (avoids EADDRINUSE when prior auth failed without redirect) + const callbackServerRef = useRef(null); + + // OAuth Quick Auth (normal mode; callback server + open URL) + const handleQuickAuth = useCallback(async () => { + if ( + !selectedServer || + !selectedInspectorClient || + !selectedServerConfig || + !isOAuthCapableServer(selectedServerConfig) + ) { + return; + } + if (oauthInProgressRef.current) return; + oauthInProgressRef.current = true; + setOauthStatus("authenticating"); + setOauthMessage(null); + getTuiLogger().info( + { server: selectedServer }, + "OAuth authentication started (Quick Auth)", + ); + const existing = callbackServerRef.current; + if (existing) { + await existing.stop(); + callbackServerRef.current = null; + } + const callbackServer = createOAuthCallbackServer(); + callbackServerRef.current = callbackServer; + let flowResolve: () => void; + let flowReject: (err: Error) => void; + const flowDone = new Promise((resolve, reject) => { + flowResolve = resolve; + flowReject = reject; + }); + try { + const { redirectUrl } = await callbackServer.start({ + ...callbackServerBaseOptions, + onCallback: async (params) => { + try { + await selectedInspectorClient!.completeOAuthFlow(params.code); + flowResolve!(); + } catch (err) { + flowReject!(err instanceof Error ? err : new Error(String(err))); + } finally { + callbackServerRef.current = null; + } + }, + onError: (params) => { + flowReject!( + new Error( + params.error_description ?? params.error ?? "OAuth error", + ), + ); + void callbackServer.stop(); + callbackServerRef.current = null; + }, + }); + const redirectUrlProvider = + redirectUrlProvidersRef.current[selectedServer]; + if (redirectUrlProvider) { + redirectUrlProvider.redirectUrl = redirectUrl; + } + await selectedInspectorClient.authenticate(); + await flowDone; + setOauthStatus("success"); + setOauthMessage("OAuth complete. Press C to connect."); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + setOauthStatus("error"); + setOauthMessage(msg); + } finally { + oauthInProgressRef.current = false; + } + }, [ + selectedServer, + selectedInspectorClient, + selectedServerConfig, + callbackServerBaseOptions, + ]); + + // OAuth Guided Auth - step-by-step + const handleGuidedStart = useCallback(async () => { + if ( + !selectedServer || + !selectedInspectorClient || + !selectedServerConfig || + !isOAuthCapableServer(selectedServerConfig) + ) { + return; + } + if (oauthInProgressRef.current) return; + oauthInProgressRef.current = true; + setOauthStatus("authenticating"); + setOauthMessage(null); + getTuiLogger().info( + { server: selectedServer }, + "OAuth authentication started (Guided Auth)", + ); + // Stop any previous callback server (e.g. from failed auth where AS never redirected) + const existing = callbackServerRef.current; + if (existing) { + await existing.stop(); + callbackServerRef.current = null; + } + const callbackServer = createOAuthCallbackServer(); + callbackServerRef.current = callbackServer; + try { + const { redirectUrl } = await callbackServer.start({ + ...callbackServerBaseOptions, + onCallback: async (params) => { + try { + await selectedInspectorClient!.completeOAuthFlow(params.code); + setOauthStatus("success"); + setOauthMessage("OAuth complete. Press C to connect."); + } catch (err) { + setOauthStatus("error"); + setOauthMessage(err instanceof Error ? err.message : String(err)); + } finally { + callbackServerRef.current = null; + } + }, + onError: (params) => { + setOauthStatus("error"); + setOauthMessage( + params.error_description ?? params.error ?? "OAuth error", + ); + void callbackServer.stop(); + callbackServerRef.current = null; + }, + }); + const redirectUrlProvider = + redirectUrlProvidersRef.current[selectedServer]; + if (redirectUrlProvider) { + redirectUrlProvider.redirectUrl = redirectUrl; + } + await selectedInspectorClient.beginGuidedAuth(); + setOauthStatus("idle"); + } catch (err) { + setOauthStatus("error"); + setOauthMessage(err instanceof Error ? err.message : String(err)); + } finally { + oauthInProgressRef.current = false; + } + }, [ + selectedServer, + selectedInspectorClient, + selectedServerConfig, + callbackServerBaseOptions, + ]); + + const handleGuidedAdvance = useCallback(async () => { + if (!selectedInspectorClient) return; + if (oauthInProgressRef.current) return; + oauthInProgressRef.current = true; + setOauthStatus("authenticating"); + setOauthMessage(null); + getTuiLogger().info("OAuth authentication started (Guided Auth advance step)"); + try { + await selectedInspectorClient.proceedOAuthStep(); + const state = selectedInspectorClient.getOAuthState(); + if (state?.oauthStep === "authorization_code" && state.authorizationUrl) { + await openUrl(state.authorizationUrl); + } + setOauthStatus("idle"); + } catch (err) { + setOauthStatus("error"); + setOauthMessage(err instanceof Error ? err.message : String(err)); + } finally { + oauthInProgressRef.current = false; + } + }, [selectedInspectorClient]); + + const handleRunGuidedToCompletion = useCallback(async () => { + if ( + !selectedServer || + !selectedInspectorClient || + !selectedServerConfig || + !isOAuthCapableServer(selectedServerConfig) + ) { + return; + } + if (oauthInProgressRef.current) return; + oauthInProgressRef.current = true; + setOauthStatus("authenticating"); + setOauthMessage(null); + getTuiLogger().info( + { server: selectedServer }, + "OAuth authentication started (Run Guided Auth to completion)", + ); + + const ensureCallbackServer = async () => { + if (callbackServerRef.current) return; + const callbackServer = createOAuthCallbackServer(); + callbackServerRef.current = callbackServer; + const { redirectUrl } = await callbackServer.start({ + ...callbackServerBaseOptions, + onCallback: async (params) => { + try { + await selectedInspectorClient!.completeOAuthFlow(params.code); + setOauthStatus("success"); + setOauthMessage("OAuth complete. Press C to connect."); + } catch (err) { + setOauthStatus("error"); + setOauthMessage(err instanceof Error ? err.message : String(err)); + } finally { + callbackServerRef.current = null; + } + }, + onError: (params) => { + setOauthStatus("error"); + setOauthMessage( + params.error_description ?? params.error ?? "OAuth error", + ); + void callbackServer.stop(); + callbackServerRef.current = null; + }, + }); + const redirectUrlProvider = + redirectUrlProvidersRef.current[selectedServer]; + if (redirectUrlProvider) { + redirectUrlProvider.redirectUrl = redirectUrl; + } + }; + + try { + await ensureCallbackServer(); + const authUrl = await selectedInspectorClient.runGuidedAuth(); + if (authUrl) { + await openUrl(authUrl); + } + setOauthStatus("idle"); + } catch (err) { + setOauthStatus("error"); + setOauthMessage(err instanceof Error ? err.message : String(err)); + } finally { + oauthInProgressRef.current = false; + } + }, [ + selectedServer, + selectedInspectorClient, + selectedServerConfig, + callbackServerBaseOptions, + ]); + + const handleClearOAuth = useCallback(() => { + if (selectedInspectorClient) { + selectedInspectorClient.clearOAuthTokens(); + setOauthStatus("idle"); + setOauthMessage(null); + } + }, [selectedInspectorClient]); + + // Build current server state from InspectorClient data (tools from ManagedToolsState) + const currentServerState = useMemo(() => { + if (!selectedServer) return null; + return { + status: inspectorStatus, + error: null, // InspectorClient doesn't track error in state, only emits error events + capabilities: inspectorCapabilities, + serverInfo: inspectorServerInfo, + instructions: inspectorInstructions, + resources: managedResources, + resourceTemplates: managedResourceTemplates, + prompts: managedPrompts, + tools: managedTools, + stderrLogs: inspectorStderrLogs, // InspectorClient manages this + }; + }, [ + selectedServer, + inspectorStatus, + inspectorCapabilities, + inspectorServerInfo, + inspectorInstructions, + managedResources, + managedResourceTemplates, + managedPrompts, + managedTools, + inspectorStderrLogs, + ]); + + // 401 on connect → prompt to authenticate (HTTP servers). Hide during/after auth. + const show401AuthHint = useMemo(() => { + if (inspectorStatus !== "error") return false; + if (oauthStatus === "authenticating" || oauthStatus === "success") + return false; + if (!selectedServerConfig || !isOAuthCapableServer(selectedServerConfig)) + return false; + return inspectorFetchRequests.some((r) => r.responseStatus === 401); + }, [ + inspectorStatus, + oauthStatus, + selectedServerConfig, + inspectorFetchRequests, + ]); + + // Helper functions to render details modal content + const renderResourceDetails = ( + resource: + | Resource + | { + content: import("@modelcontextprotocol/sdk/types.js").ReadResourceResult; + }, + ) => ( + <> + {"uri" in resource && resource.description && ( + <> + {resource.description.split("\n").map((line: string, idx: number) => ( + + {line} + + ))} + + )} + {"uri" in resource && resource.uri && ( + + URI: + + {resource.uri} + + + )} + {"mimeType" in resource && resource.mimeType && ( + + MIME Type: + + {resource.mimeType} + + + )} + + Full JSON: + + {JSON.stringify(resource, null, 2)} + + + + ); + + const renderPromptDetails = ( + prompt: Prompt & { result?: GetPromptResult }, + ) => ( + <> + {prompt.description && ( + <> + {prompt.description.split("\n").map((line: string, idx: number) => ( + + {line} + + ))} + + )} + {prompt.arguments && prompt.arguments.length > 0 && ( + <> + + Arguments: + + {prompt.arguments.map((arg: PromptArgument, idx: number) => ( + + + - {arg.name}:{" "} + {arg.description ?? (arg as { type?: string }).type ?? "string"} + + + ))} + + )} + + Full JSON: + + {JSON.stringify(prompt, null, 2)} + + + + ); + + const renderToolDetails = (tool: Tool) => ( + <> + {tool.description && ( + <> + {tool.description.split("\n").map((line: string, idx: number) => ( + + {line} + + ))} + + )} + {tool.inputSchema && ( + + Input Schema: + + {JSON.stringify(tool.inputSchema, null, 2)} + + + )} + + Full JSON: + + {JSON.stringify(tool, null, 2)} + + + + ); + + const renderRequestDetails = (request: FetchRequestEntry) => ( + <> + + + {request.method} {request.url} + + + + + Category:{" "} + {request.category === "auth" ? "auth" : "transport"} + + + {request.responseStatus !== undefined ? ( + + + Status: {request.responseStatus} {request.responseStatusText || ""} + + + ) : request.error ? ( + + + Error: {request.error} + + + ) : null} + {request.duration !== undefined && ( + + + {request.timestamp.toLocaleTimeString()} ({request.duration}ms) + + + )} + + Request Headers: + {Object.entries(request.requestHeaders).map(([key, value]) => ( + + + {key}: {value} + + + ))} + + {request.requestBody && ( + <> + + Request Body: + + {(() => { + try { + const parsed = JSON.parse(request.requestBody); + return JSON.stringify(parsed, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + )); + } catch { + return ( + + {request.requestBody} + + ); + } + })()} + + )} + {request.responseHeaders && + Object.keys(request.responseHeaders).length > 0 && ( + <> + + Response Headers: + + {Object.entries(request.responseHeaders).map(([key, value]) => ( + + + {key}: {value} + + + ))} + + )} + {request.responseBody && ( + <> + + Response Body: + + {(() => { + try { + const parsed = JSON.parse(request.responseBody); + return JSON.stringify(parsed, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + )); + } catch { + return ( + + {request.responseBody} + + ); + } + })()} + + )} + + ); + + const renderMessageDetails = (message: MessageEntry) => ( + <> + + Direction: {message.direction} + + + + {message.timestamp.toLocaleTimeString()} + {message.duration !== undefined && ` (${message.duration}ms)`} + + + {message.direction === "request" ? ( + <> + + Request: + + {JSON.stringify(message.message, null, 2)} + + + {message.response && ( + + Response: + + + {JSON.stringify(message.response, null, 2)} + + + + )} + + ) : ( + + + {message.direction === "response" ? "Response:" : "Notification:"} + + + {JSON.stringify(message.message, null, 2)} + + + )} + + ); + + // Update tab counts when selected server changes or InspectorClient state changes + // Just reflect InspectorClient state - don't try to be clever + useEffect(() => { + if (!selectedServer) { + return; + } + + setTabCounts({ + resources: managedResources.length || 0, + prompts: managedPrompts.length || 0, + tools: managedTools.length || 0, + messages: inspectorMessages.length || 0, + requests: inspectorFetchRequests.length || 0, + logging: inspectorStderrLogs.length || 0, + }); + }, [ + selectedServer, + managedResources, + managedPrompts, + managedTools, + inspectorMessages, + inspectorFetchRequests, + inspectorStderrLogs, + ]); + + // Keep focus state consistent when switching tabs (only adjust if focus is already in tab content) + useEffect(() => { + if (activeTab === "messages") { + if (focus === "tabContentList" || focus === "tabContentDetails") { + setFocus("messagesList"); + } + } else if (activeTab === "requests") { + if (focus === "tabContentList" || focus === "tabContentDetails") { + setFocus("requestsList"); + } + } else { + if ( + focus === "messagesList" || + focus === "messagesDetail" || + focus === "requestsList" || + focus === "requestsDetail" + ) { + setFocus("tabContentList"); + } + } + // Intentionally not depending on focus to avoid loops + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeTab]); + + // Switch away from logging tab if server is not stdio + useEffect(() => { + if (activeTab === "logging" && selectedServer) { + const client = inspectorClients[selectedServer]; + if (client && client.getServerType() !== "stdio") { + setActiveTab("info"); + } + } + }, [selectedServer, activeTab, inspectorClients]); + + useInput((input: string, key: Key) => { + // Don't process input when modal is open + if (toolTestModal || resourceTestModal || promptTestModal || detailsModal) { + return; + } + + if (key.ctrl && input === "c") { + exit(); + } + + // Exit accelerators + if (key.escape) { + exit(); + } + + // G/Q/S: switch to Auth tab (if not already) and select Guided/Quick/Clear + const showAuthTabForAccel = + !!selectedServer && + !!selectedServerConfig && + isOAuthCapableServer(selectedServerConfig); + const lower = input.toLowerCase(); + if ( + showAuthTabForAccel && + (lower === "g" || lower === "q" || lower === "s") + ) { + setActiveTab("auth"); + setFocus("tabContentList"); + setSelectedAuthAction( + lower === "g" ? "guided" : lower === "q" ? "quick" : "clear", + ); + return; + } + + // Tab switching with accelerator keys (first character of tab name) + const showAuthTab = + !!selectedServer && + !!selectedServerConfig && + isOAuthCapableServer(selectedServerConfig); + const showLoggingTab = + !!selectedServer && + inspectorClients[selectedServer]?.getServerType() === "stdio"; + const showRequestsTab = + !!selectedServer && + (inspectorClients[selectedServer]?.getServerType() === "sse" || + inspectorClients[selectedServer]?.getServerType() === + "streamable-http"); + const tabAccelerators: Record = Object.fromEntries( + tabList + .filter((tab: { id: TabType }) => { + if (tab.id === "auth" && !showAuthTab) return false; + if (tab.id === "logging" && !showLoggingTab) return false; + if (tab.id === "requests" && !showRequestsTab) return false; + return true; + }) + .map((tab: { id: TabType; label: string; accelerator: string }) => [ + tab.accelerator, + tab.id, + ]), + ); + if (tabAccelerators[input.toLowerCase()]) { + setActiveTab(tabAccelerators[input.toLowerCase()]); + setFocus("tabs"); + } else if (key.tab && !key.shift) { + // Flat focus order: servers -> tabs -> list -> details -> wrap to servers + const focusOrder: FocusArea[] = + activeTab === "messages" + ? ["serverList", "tabs", "messagesList", "messagesDetail"] + : activeTab === "requests" + ? ["serverList", "tabs", "requestsList", "requestsDetail"] + : ["serverList", "tabs", "tabContentList", "tabContentDetails"]; + const currentIndex = focusOrder.indexOf(focus); + const nextIndex = (currentIndex + 1) % focusOrder.length; + setFocus(focusOrder[nextIndex]); + } else if (key.tab && key.shift) { + // Reverse order: servers <- tabs <- list <- details <- wrap to servers + const focusOrder: FocusArea[] = + activeTab === "messages" + ? ["serverList", "tabs", "messagesList", "messagesDetail"] + : activeTab === "requests" + ? ["serverList", "tabs", "requestsList", "requestsDetail"] + : ["serverList", "tabs", "tabContentList", "tabContentDetails"]; + const currentIndex = focusOrder.indexOf(focus); + const prevIndex = + currentIndex > 0 ? currentIndex - 1 : focusOrder.length - 1; + setFocus(focusOrder[prevIndex]); + } else if (key.upArrow || key.downArrow) { + // Arrow keys only work in the focused pane + if (focus === "serverList") { + // Arrow key navigation for server list + if (key.upArrow) { + if (selectedServer === null) { + setSelectedServer(serverNames[serverNames.length - 1] || null); + } else { + const currentIndex = serverNames.indexOf(selectedServer); + const newIndex = + currentIndex > 0 ? currentIndex - 1 : serverNames.length - 1; + setSelectedServer(serverNames[newIndex] || null); + } + } else if (key.downArrow) { + if (selectedServer === null) { + setSelectedServer(serverNames[0] || null); + } else { + const currentIndex = serverNames.indexOf(selectedServer); + const newIndex = + currentIndex < serverNames.length - 1 ? currentIndex + 1 : 0; + setSelectedServer(serverNames[newIndex] || null); + } + } + return; // Handled, don't let other handlers process + } + // If focus is on tabs, tabContentList, tabContentDetails, messagesList, or messagesDetail, + // arrow keys will be handled by those components - don't do anything here + } else if (focus === "tabs" && (key.leftArrow || key.rightArrow)) { + // Left/Right arrows switch tabs when tabs are focused + const showAuthTab = + !!selectedServer && + !!selectedServerConfig && + isOAuthCapableServer(selectedServerConfig); + const showLoggingTab = + !!selectedServer && + inspectorClients[selectedServer]?.getServerType() === "stdio"; + const showRequestsTab = + !!selectedServer && + (inspectorClients[selectedServer]?.getServerType() === "sse" || + inspectorClients[selectedServer]?.getServerType() === + "streamable-http"); + const allTabs: TabType[] = [ + "info", + "auth", + "resources", + "prompts", + "tools", + "messages", + "requests", + "logging", + ]; + const tabs = allTabs.filter((t) => { + if (t === "auth" && !showAuthTab) return false; + if (t === "logging" && !showLoggingTab) return false; + if (t === "requests" && !showRequestsTab) return false; + return true; + }); + const currentIndex = tabs.indexOf(activeTab); + if (key.leftArrow) { + const newIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1; + setActiveTab(tabs[newIndex]); + } else if (key.rightArrow) { + const newIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0; + setActiveTab(tabs[newIndex]); + } + } + + // Accelerator keys for connect/disconnect (work from anywhere) + // 'a' switches to Auth tab; use the Auth tab for Quick/Guided auth + if (selectedServer) { + if ( + input.toLowerCase() === "c" && + (inspectorStatus === "disconnected" || inspectorStatus === "error") + ) { + handleConnect(); + } else if ( + input.toLowerCase() === "d" && + (inspectorStatus === "connected" || inspectorStatus === "connecting") + ) { + handleDisconnect(); + } + } + }); + + // Calculate layout dimensions + const headerHeight = 1; + const tabsHeight = 1; + // Server details will be flexible - calculate remaining space for content + const availableHeight = dimensions.height - headerHeight - tabsHeight; + // Reserve space for server details (will grow as needed, but we'll use flexGrow) + const serverDetailsMinHeight = 3; + const contentHeight = availableHeight - serverDetailsMinHeight; + const serverListWidth = Math.floor(dimensions.width * 0.3); + const contentWidth = dimensions.width - serverListWidth; + + const getStatusColor = (status: string) => { + switch (status) { + case "connected": + return "green"; + case "connecting": + return "yellow"; + case "error": + return "red"; + default: + return "gray"; + } + }; + + const getStatusSymbol = (status: string) => { + switch (status) { + case "connected": + return "●"; + case "connecting": + return "◐"; + case "error": + return "✗"; + default: + return "○"; + } + }; + + return ( + + {/* Header row across the top */} + + + + {packageJson.name} + + - {packageJson.description} + + v{packageJson.version} + + + {/* Main content area */} + + {/* Left column - Server list */} + + + + MCP Servers + + + + {serverNames.map((serverName) => { + const isSelected = selectedServer === serverName; + return ( + + + {isSelected ? "▶ " : " "} + {serverName} + + + ); + })} + + + {/* Fixed footer */} + + + ESC to exit + + + + + {/* Right column - Server details, Tabs and content */} + + {/* Server Details - Flexible height */} + + + + + {selectedServer} + + + {currentServerState && ( + <> + + {getStatusSymbol(currentServerState.status)}{" "} + {currentServerState.status} + + + {(currentServerState?.status === "disconnected" || + currentServerState?.status === "error") && ( + + [Connect] + + )} + {(currentServerState?.status === "connected" || + currentServerState?.status === "connecting") && ( + + [Disconnect] + + )} + + )} + + + {show401AuthHint && ( + + + 401 Unauthorized. Press A to authenticate. + + + )} + {oauthStatus !== "idle" && ( + + {oauthStatus === "authenticating" && ( + OAuth: authenticating… + )} + {oauthStatus === "success" && oauthMessage && ( + {oauthMessage} + )} + {oauthStatus === "error" && oauthMessage && ( + OAuth: {oauthMessage} + )} + + )} + + + + {/* Tabs */} + { + const serverType = + inspectorClients[selectedServer].getServerType(); + return ( + serverType === "sse" || serverType === "streamable-http" + ); + })() + : false + } + /> + + {/* Tab Content */} + + {activeTab === "info" && ( + + )} + {activeTab === "auth" && + selectedServer && + selectedServerConfig && + isOAuthCapableServer(selectedServerConfig) ? ( + + ) : null} + {activeTab === "resources" && + currentServerState?.status === "connected" && + selectedInspectorClient ? ( + + setTabCounts((prev) => ({ ...prev, resources: count })) + } + focusedPane={ + focus === "tabContentDetails" + ? "details" + : focus === "tabContentList" + ? "list" + : null + } + onViewDetails={(resource) => + setDetailsModal({ + title: `Resource: ${"uri" in resource ? resource.name || resource.uri || "Unknown" : "Resource content"}`, + content: renderResourceDetails(resource), + }) + } + onFetchResource={() => { + // Resource fetching is handled internally by ResourcesTab + // This callback is just for triggering the fetch + }} + onFetchTemplate={(template) => { + setResourceTestModal({ + template, + inspectorClient: selectedInspectorClient, + }); + }} + modalOpen={ + !!(toolTestModal || resourceTestModal || detailsModal) + } + /> + ) : activeTab === "prompts" && + currentServerState?.status === "connected" && + selectedInspectorClient ? ( + + setTabCounts((prev) => ({ ...prev, prompts: count })) + } + focusedPane={ + focus === "tabContentDetails" + ? "details" + : focus === "tabContentList" + ? "list" + : null + } + onViewDetails={(prompt) => + setDetailsModal({ + title: `Prompt: ${prompt.name || "Unknown"}`, + content: renderPromptDetails(prompt), + }) + } + onFetchPrompt={(prompt) => { + setPromptTestModal({ + prompt, + inspectorClient: selectedInspectorClient, + }); + }} + modalOpen={ + !!( + toolTestModal || + resourceTestModal || + promptTestModal || + detailsModal + ) + } + /> + ) : activeTab === "tools" && + currentServerState?.status === "connected" && + selectedInspectorClient ? ( + + setTabCounts((prev) => ({ ...prev, tools: count })) + } + focusedPane={ + focus === "tabContentDetails" + ? "details" + : focus === "tabContentList" + ? "list" + : null + } + onTestTool={(tool) => + setToolTestModal({ + tool, + inspectorClient: selectedInspectorClient, + }) + } + onViewDetails={(tool) => + setDetailsModal({ + title: `Tool: ${tool.name || "Unknown"}`, + content: renderToolDetails(tool), + }) + } + modalOpen={!!(toolTestModal || detailsModal)} + /> + ) : activeTab === "messages" && selectedInspectorClient ? ( + + setTabCounts((prev) => ({ ...prev, messages: count })) + } + focusedPane={ + focus === "messagesDetail" + ? "details" + : focus === "messagesList" + ? "messages" + : null + } + modalOpen={!!(toolTestModal || detailsModal)} + onViewDetails={(message) => { + const label = + message.direction === "request" && + "method" in message.message + ? message.message.method + : message.direction === "response" + ? "Response" + : message.direction === "notification" && + "method" in message.message + ? message.message.method + : "Message"; + setDetailsModal({ + title: `Message: ${label}`, + content: renderMessageDetails(message), + }); + }} + /> + ) : activeTab === "requests" && + selectedInspectorClient && + (inspectorStatus === "connected" || + inspectorFetchRequests.length > 0) ? ( + + setTabCounts((prev) => ({ ...prev, requests: count })) + } + focusedPane={ + focus === "requestsDetail" + ? "details" + : focus === "requestsList" + ? "requests" + : null + } + modalOpen={!!(toolTestModal || detailsModal)} + onViewDetails={(request) => { + setDetailsModal({ + title: `Request: ${request.method} ${request.url}`, + content: renderRequestDetails(request), + }); + }} + /> + ) : activeTab === "logging" && selectedInspectorClient ? ( + + setTabCounts((prev) => ({ ...prev, logging: count })) + } + focused={ + focus === "tabContentList" || focus === "tabContentDetails" + } + /> + ) : null} + + + + + {/* Tool Test Modal - rendered at App level for full screen overlay */} + {toolTestModal && ( + setToolTestModal(null)} + /> + )} + + {/* Resource Test Modal - rendered at App level for full screen overlay */} + {resourceTestModal && ( + setResourceTestModal(null)} + /> + )} + + {promptTestModal && ( + setPromptTestModal(null)} + /> + )} + + {/* Details Modal - rendered at App level for full screen overlay */} + {detailsModal && ( + setDetailsModal(null)} + /> + )} + + ); +} + +export default App; diff --git a/clients/tui/src/components/AuthTab.tsx b/clients/tui/src/components/AuthTab.tsx new file mode 100644 index 000000000..b03af7708 --- /dev/null +++ b/clients/tui/src/components/AuthTab.tsx @@ -0,0 +1,432 @@ +import React, { useState, useEffect, useCallback, useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import { SelectableItem } from "./SelectableItem.js"; +import type { + MCPServerConfig, + InspectorClient, +} from "@inspector/core/mcp/index.js"; +import type { + AuthGuidedState, + OAuthStep, +} from "@inspector/core/auth/index.js"; + +const STEP_LABELS: Record = { + metadata_discovery: "Metadata Discovery", + client_registration: "Client Registration", + authorization_redirect: "Preparing Authorization", + authorization_code: "Request Authorization Code", + token_request: "Token Request", + complete: "Authentication Complete", +}; + +const STEP_ORDER: OAuthStep[] = [ + "metadata_discovery", + "client_registration", + "authorization_redirect", + "authorization_code", + "token_request", + "complete", +]; + +function stepIndex(step: OAuthStep): number { + const i = STEP_ORDER.indexOf(step); + return i >= 0 ? i : 0; +} + +interface AuthTabProps { + serverName: string | null; + serverConfig: MCPServerConfig | null; + inspectorClient: InspectorClient | null; + oauthStatus: "idle" | "authenticating" | "success" | "error"; + oauthMessage: string | null; + width: number; + height: number; + focused?: boolean; + selectedAction: "guided" | "quick" | "clear"; + onSelectedActionChange: (action: "guided" | "quick" | "clear") => void; + onQuickAuth: () => Promise; + onGuidedStart: () => Promise; + onGuidedAdvance: () => Promise; + onRunGuidedToCompletion: () => Promise; + onClearOAuth: () => void; + isOAuthCapable: boolean; +} + +export function AuthTab({ + serverName, + inspectorClient, + oauthStatus, + oauthMessage, + width, + height, + focused = false, + selectedAction, + onSelectedActionChange, + onQuickAuth, + onGuidedStart, + onGuidedAdvance, + onRunGuidedToCompletion, + onClearOAuth, + isOAuthCapable, +}: AuthTabProps) { + const scrollViewRef = useRef(null); + const [oauthState, setOauthState] = useState( + undefined, + ); + const [guidedStarted, setGuidedStarted] = useState(false); + const [clearedConfirmation, setClearedConfirmation] = useState(false); + + // Sync oauthState from InspectorClient + useEffect(() => { + if (!inspectorClient) { + setOauthState(undefined); + setGuidedStarted(false); + return; + } + + const update = () => setOauthState(inspectorClient.getOAuthState()); + update(); + + const onStepChange = () => update(); + inspectorClient.addEventListener("oauthStepChange", onStepChange); + inspectorClient.addEventListener("oauthComplete", onStepChange); + return () => { + inspectorClient.removeEventListener("oauthStepChange", onStepChange); + inspectorClient.removeEventListener("oauthComplete", onStepChange); + }; + }, [inspectorClient]); + + // Reset guided state when switching servers + useEffect(() => { + setGuidedStarted(false); + }, [serverName]); + + // Clear confirmation when switching away from Clear menu item + useEffect(() => { + if (selectedAction !== "clear") { + setClearedConfirmation(false); + } + }, [selectedAction]); + + const guidedFlowStarted = !!oauthState?.oauthStep; + const currentStep = oauthState?.oauthStep ?? "metadata_discovery"; + const needsAuthCode = + currentStep === "authorization_code" && oauthState?.authorizationUrl; + const isComplete = currentStep === "complete"; + + const handleContinue = useCallback(async () => { + if (!guidedStarted) { + await onGuidedStart(); + setGuidedStarted(true); + } else if (!needsAuthCode && !isComplete) { + await onGuidedAdvance(); + } + }, [ + guidedStarted, + needsAuthCode, + isComplete, + onGuidedStart, + onGuidedAdvance, + ]); + + // Keyboard: G/Q/S select menu item (handled by App when not focused), + // left/right select, Enter run, up/down scroll + useInput( + (input: string, key: Key) => { + if (!focused || !isOAuthCapable) return; + + const lower = input.toLowerCase(); + if (lower === "g") { + onSelectedActionChange("guided"); + return; + } + if (lower === "q") { + onSelectedActionChange("quick"); + return; + } + if (lower === "s") { + onSelectedActionChange("clear"); + return; + } + + if (key.leftArrow) { + onSelectedActionChange( + selectedAction === "guided" + ? "clear" + : selectedAction === "quick" + ? "guided" + : "quick", + ); + } else if (key.rightArrow) { + onSelectedActionChange( + selectedAction === "guided" + ? "quick" + : selectedAction === "quick" + ? "clear" + : "guided", + ); + } else if (key.upArrow && scrollViewRef.current) { + scrollViewRef.current.scrollBy(-1); + } else if (key.downArrow && scrollViewRef.current) { + scrollViewRef.current.scrollBy(1); + } else if (key.pageUp && scrollViewRef.current) { + const h = scrollViewRef.current.getViewportHeight() || 1; + scrollViewRef.current.scrollBy(-h); + } else if (key.pageDown && scrollViewRef.current) { + const h = scrollViewRef.current.getViewportHeight() || 1; + scrollViewRef.current.scrollBy(h); + } else if (key.return) { + if (selectedAction === "guided") onRunGuidedToCompletion(); + else if (selectedAction === "quick") onQuickAuth(); + else if (selectedAction === "clear") { + onClearOAuth(); + setClearedConfirmation(true); + } + } else if (input === " " && selectedAction === "guided") { + handleContinue(); + } + }, + { + isActive: focused, + }, + ); + + if (!serverName || !isOAuthCapable) { + return ( + + + Select an OAuth-capable server (SSE or Streamable HTTP) to configure + authentication. + + + ); + } + + return ( + + + + Authentication + + + + {/* Action bar and hint - single container for tight spacing */} + + + + Guided Auth + + + Quick Auth + + + Clear OAuth State + + + + {selectedAction === "guided" && ( + <> + + Press [Space] to advance one step through guided auth. + + + Press [Enter] to run guided auth to completion. + + + )} + {selectedAction === "quick" && ( + Press [Enter] to run quick auth. + )} + {selectedAction === "clear" && ( + Press [Enter] to clear OAuth state. + )} + + + + {selectedAction === "guided" && ( + + Guided OAuth Flow Progress + {STEP_ORDER.map((step) => { + const stepIdx = stepIndex(step); + const currentIdx = stepIndex(currentStep); + const completed = + guidedFlowStarted && + (stepIdx < currentIdx || + (step === currentStep && isComplete)); + const inProgress = + guidedFlowStarted && step === currentStep && !isComplete; + const details = oauthState + ? getStepDetails(oauthState, step) + : null; + + const icon = completed ? "✓" : inProgress ? "→" : "○"; + const color = completed + ? "green" + : inProgress + ? "cyan" + : "gray"; + + return ( + + + {icon} {STEP_LABELS[step]} + {inProgress && " (in progress)"} + + {completed && details && ( + + {details} + + )} + {inProgress && details && ( + + {details} + + )} + + ); + })} + + {/* Waiting for auth - URL was opened when we reached this step */} + {oauthState && needsAuthCode && oauthState?.authorizationUrl && ( + + Authorization URL opened in browser + + + {oauthState.authorizationUrl.toString()} + + + + + Complete authorization in the browser. You will be + redirected and the flow will complete automatically. + + + + )} + + )} + + {selectedAction === "quick" && ( + + {oauthStatus === "authenticating" && ( + Authenticating... + )} + {oauthStatus === "error" && oauthMessage && ( + {oauthMessage} + )} + {oauthStatus === "success" && + oauthState && + oauthState.authType === "normal" && + (oauthState.oauthTokens || oauthState.oauthClientInfo) && ( + <> + Quick Auth Results + {oauthState.oauthClientInfo && ( + + + Client:{" "} + {JSON.stringify(oauthState.oauthClientInfo, null, 2)} + + + )} + {oauthState.oauthTokens && ( + + + Access Token:{" "} + {oauthState.oauthTokens.access_token?.slice(0, 20)}... + + + )} + + )} + + )} + + {selectedAction === "clear" && clearedConfirmation && ( + + OAuth state cleared. + + )} + + + + {focused && ( + + + ←/→ select, G/Q/S or Enter run, ↑/↓ scroll + + + )} + + ); +} + +function getStepDetails( + state: AuthGuidedState, + step: OAuthStep, +): string | null { + switch (step) { + case "metadata_discovery": + if (state.resourceMetadata || state.oauthMetadata) { + const parts: string[] = []; + if (state.resourceMetadata) { + parts.push( + `Resource: ${JSON.stringify(state.resourceMetadata, null, 2)}`, + ); + } + if (state.oauthMetadata) { + parts.push(`OAuth: ${JSON.stringify(state.oauthMetadata, null, 2)}`); + } + return parts.join("\n"); + } + return null; + case "client_registration": + if (state.oauthClientInfo) { + return JSON.stringify(state.oauthClientInfo, null, 2); + } + return null; + case "authorization_redirect": + if (state.authorizationUrl) { + return `URL: ${state.authorizationUrl.toString()}`; + } + return null; + case "authorization_code": + return state.authorizationCode + ? `Code received: ${state.authorizationCode.slice(0, 10)}...` + : null; + case "token_request": + return "Exchanging code for tokens..."; + case "complete": + if (state.oauthTokens) { + return `Tokens: access_token=${state.oauthTokens.access_token?.slice(0, 15)}...`; + } + return null; + default: + return null; + } +} diff --git a/clients/tui/src/components/DetailsModal.tsx b/clients/tui/src/components/DetailsModal.tsx new file mode 100644 index 000000000..e01b555d3 --- /dev/null +++ b/clients/tui/src/components/DetailsModal.tsx @@ -0,0 +1,102 @@ +import React, { useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; + +interface DetailsModalProps { + title: string; + content: React.ReactNode; + width: number; + height: number; + onClose: () => void; +} + +export function DetailsModal({ + title, + content, + width, + height, + onClose, +}: DetailsModalProps) { + const scrollViewRef = useRef(null); + + // Use full terminal dimensions + const [terminalDimensions, setTerminalDimensions] = React.useState({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + + React.useEffect(() => { + const updateDimensions = () => { + setTerminalDimensions({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + }; + process.stdout.on("resize", updateDimensions); + updateDimensions(); + return () => { + process.stdout.off("resize", updateDimensions); + }; + }, [width, height]); + + // Handle escape to close and scrolling + useInput( + (input: string, key: Key) => { + if (key.escape) { + onClose(); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.pageDown) { + const viewportHeight = scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } else if (key.pageUp) { + const viewportHeight = scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } + }, + { isActive: true }, + ); + + // Calculate modal dimensions - use almost full screen + const modalWidth = terminalDimensions.width - 2; + const modalHeight = terminalDimensions.height - 2; + + return ( + + {/* Modal Content */} + + {/* Header */} + + + {title} + + + (Press ESC to close) + + + {/* Content Area */} + + {content} + + + + ); +} diff --git a/clients/tui/src/components/HistoryTab.tsx b/clients/tui/src/components/HistoryTab.tsx new file mode 100644 index 000000000..429e9623e --- /dev/null +++ b/clients/tui/src/components/HistoryTab.tsx @@ -0,0 +1,326 @@ +import React, { useEffect, useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import type { MessageEntry } from "@inspector/core/mcp/index.js"; +import { useSelectableList } from "../hooks/useSelectableList.js"; + +interface HistoryTabProps { + serverName: string | null; + messages: MessageEntry[]; + width: number; + height: number; + onCountChange?: (count: number) => void; + focusedPane?: "messages" | "details" | null; + onViewDetails?: (message: MessageEntry) => void; + modalOpen?: boolean; +} + +export function HistoryTab({ + messages, + width, + height, + onCountChange, + focusedPane = null, + onViewDetails, + modalOpen = false, +}: HistoryTabProps) { + const visibleCount = Math.max(1, height - 7); + const { selectedIndex, firstVisible, setSelection } = useSelectableList( + messages.length, + visibleCount, + ); + const scrollViewRef = useRef(null); + const selectedMessage = messages[selectedIndex] || null; + + // Handle arrow key navigation and scrolling when focused + useInput( + (input: string, key: Key) => { + if (focusedPane === "messages") { + if (key.upArrow && selectedIndex > 0) { + setSelection(selectedIndex - 1); + } else if (key.downArrow && selectedIndex < messages.length - 1) { + setSelection(selectedIndex + 1); + } else if (key.pageUp) { + setSelection(Math.max(0, selectedIndex - visibleCount)); + } else if (key.pageDown) { + setSelection( + Math.min(messages.length - 1, selectedIndex + visibleCount), + ); + } + return; + } + + // details scrolling (only when details pane is focused) + if (focusedPane === "details") { + // Handle '+' key to view in full screen modal + if (input === "+" && selectedMessage && onViewDetails) { + onViewDetails(selectedMessage); + return; + } + + if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } + } + }, + { isActive: !modalOpen && focusedPane !== undefined }, + ); + + // Update count when messages change + React.useEffect(() => { + onCountChange?.(messages.length); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [messages.length]); + + // Reset details scroll when message selection changes + useEffect(() => { + scrollViewRef.current?.scrollTo(0); + }, [selectedIndex]); + + const listWidth = Math.floor(width * 0.4); + const detailWidth = width - listWidth; + + return ( + + {/* Left column - Messages list */} + + + + Messages ({messages.length}) + + + + {/* Messages list */} + {messages.length === 0 ? ( + + No messages + + ) : ( + + {messages + .slice(firstVisible, firstVisible + visibleCount) + .map((msg, i) => { + const index = firstVisible + i; + const isSelected = index === selectedIndex; + let label: string; + if (msg.direction === "request" && "method" in msg.message) { + label = msg.message.method; + } else if (msg.direction === "response") { + if ("result" in msg.message) { + label = "Response (result)"; + } else if ("error" in msg.message) { + label = `Response (error: ${msg.message.error.code})`; + } else { + label = "Response"; + } + } else if ( + msg.direction === "notification" && + "method" in msg.message + ) { + label = msg.message.method; + } else { + label = "Unknown"; + } + const direction = + msg.direction === "request" + ? "→" + : msg.direction === "response" + ? "←" + : "•"; + const hasResponse = msg.response !== undefined; + + return ( + + + {isSelected ? "▶ " : " "} + {direction} {label} + {hasResponse + ? " ✓" + : msg.direction === "request" + ? " ..." + : ""} + + + ); + })} + + )} + + + {/* Right column - Message details */} + + {selectedMessage ? ( + <> + {/* Fixed method caption only */} + + + {selectedMessage.direction === "request" && + "method" in selectedMessage.message + ? selectedMessage.message.method + : selectedMessage.direction === "response" + ? "Response" + : selectedMessage.direction === "notification" && + "method" in selectedMessage.message + ? selectedMessage.message.method + : "Message"} + + + + {/* Scrollable content area */} + + {/* Metadata */} + + Direction: {selectedMessage.direction} + + + {selectedMessage.timestamp.toLocaleTimeString()} + {selectedMessage.duration !== undefined && + ` (${selectedMessage.duration}ms)`} + + + + + {selectedMessage.direction === "request" ? ( + <> + {/* Request label */} + + Request: + + + {/* Request content */} + {JSON.stringify(selectedMessage.message, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + {/* Response section */} + {selectedMessage.response ? ( + <> + + Response: + + {JSON.stringify(selectedMessage.response, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + ) : ( + + + Waiting for response... + + + )} + + ) : ( + <> + {/* Response or notification label */} + + + {selectedMessage.direction === "response" + ? "Response:" + : "Notification:"} + + + + {/* Message content */} + {JSON.stringify(selectedMessage.message, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + )} + + + {/* Fixed footer - only show when details pane is focused */} + {focusedPane === "details" && ( + + + ↑/↓ to scroll, + to zoom + + + )} + + ) : ( + + Select a message to view details + + )} + + + ); +} diff --git a/clients/tui/src/components/InfoTab.tsx b/clients/tui/src/components/InfoTab.tsx new file mode 100644 index 000000000..b99a293e1 --- /dev/null +++ b/clients/tui/src/components/InfoTab.tsx @@ -0,0 +1,226 @@ +import React, { useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import type { + MCPServerConfig, + ServerState, +} from "@inspector/core/mcp/index.js"; + +interface InfoTabProps { + serverName: string | null; + serverConfig: MCPServerConfig | null; + serverState: ServerState | null; + width: number; + height: number; + focused?: boolean; +} + +export function InfoTab({ + serverName, + serverConfig, + serverState, + width, + height, + focused = false, +}: InfoTabProps) { + const scrollViewRef = useRef(null); + + // Handle keyboard input for scrolling + useInput( + (input: string, key: Key) => { + if (focused) { + if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } + } + }, + { isActive: focused }, + ); + + return ( + + + + Info + + + + {serverName ? ( + <> + {/* Scrollable content area - takes remaining space */} + + + {/* Server Configuration */} + + Server Configuration + + {serverConfig ? ( + + {serverConfig.type === undefined || + serverConfig.type === "stdio" ? ( + <> + Type: stdio + Command: {serverConfig.command} + {serverConfig.args && serverConfig.args.length > 0 && ( + + Args: + {serverConfig.args.map((arg: string, idx: number) => ( + + {arg} + + ))} + + )} + {serverConfig.env && + Object.keys(serverConfig.env).length > 0 && ( + + + Env:{" "} + {Object.entries(serverConfig.env) + .map(([k, v]) => `${k}=${v}`) + .join(", ")} + + + )} + {serverConfig.cwd && ( + + CWD: {serverConfig.cwd} + + )} + + ) : serverConfig.type === "sse" ? ( + <> + Type: sse + URL: {serverConfig.url} + {serverConfig.headers && + Object.keys(serverConfig.headers).length > 0 && ( + + + Headers:{" "} + {Object.entries(serverConfig.headers) + .map(([k, v]) => `${k}=${v}`) + .join(", ")} + + + )} + + ) : serverConfig.type === "streamable-http" ? ( + <> + Type: streamable-http + URL: {serverConfig.url} + {serverConfig.headers && + Object.keys(serverConfig.headers).length > 0 && ( + + + Headers:{" "} + {Object.entries(serverConfig.headers) + .map(([k, v]) => `${k}=${v}`) + .join(", ")} + + + )} + + ) : null} + + ) : ( + + No configuration available + + )} + + {/* Server Info */} + {serverState && + serverState.status === "connected" && + serverState.serverInfo && ( + <> + + Server Information + + + {serverState.serverInfo.name && ( + + Name: {serverState.serverInfo.name} + + )} + {serverState.serverInfo.version && ( + + + Version: {serverState.serverInfo.version} + + + )} + {serverState.instructions && ( + + Instructions: + + {serverState.instructions} + + + )} + + + )} + + {serverState && serverState.status === "error" && ( + + + Error + + {serverState.error && ( + + {serverState.error} + + )} + + )} + + {serverState && serverState.status === "disconnected" && ( + + Server not connected + + )} + + + + {/* Fixed keyboard help footer at bottom - only show when focused */} + {focused && ( + + + ↑/↓ to scroll, + to zoom + + + )} + + ) : null} + + ); +} diff --git a/clients/tui/src/components/NotificationsTab.tsx b/clients/tui/src/components/NotificationsTab.tsx new file mode 100644 index 000000000..5ed35cbe2 --- /dev/null +++ b/clients/tui/src/components/NotificationsTab.tsx @@ -0,0 +1,83 @@ +import React, { useEffect, useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import type { StderrLogEntry } from "@inspector/core/mcp/index.js"; + +interface NotificationsTabProps { + stderrLogs: StderrLogEntry[]; + width: number; + height: number; + onCountChange?: (count: number) => void; + focused?: boolean; +} + +export function NotificationsTab({ + stderrLogs, + width, + height, + onCountChange, + focused = false, +}: NotificationsTabProps) { + const scrollViewRef = useRef(null); + const onCountChangeRef = useRef(onCountChange); + + // Update ref when callback changes + useEffect(() => { + onCountChangeRef.current = onCountChange; + }, [onCountChange]); + + useEffect(() => { + onCountChangeRef.current?.(stderrLogs.length); + }, [stderrLogs.length]); + + // Handle keyboard input for scrolling + useInput( + (input: string, key: Key) => { + if (focused) { + if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } + } + }, + { isActive: focused }, + ); + + return ( + + + + Logging ({stderrLogs.length}) + + + {stderrLogs.length === 0 ? ( + + No stderr output yet + + ) : ( + + {stderrLogs.map((log, index) => ( + + [{log.timestamp.toLocaleTimeString()}] + {log.message} + + ))} + + )} + + ); +} diff --git a/clients/tui/src/components/PromptTestModal.tsx b/clients/tui/src/components/PromptTestModal.tsx new file mode 100644 index 000000000..c10d03457 --- /dev/null +++ b/clients/tui/src/components/PromptTestModal.tsx @@ -0,0 +1,301 @@ +import React, { useState } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { Form } from "ink-form"; +import { InspectorClient } from "@inspector/core/mcp/index.js"; +import type { + Prompt, + GetPromptResult, +} from "@modelcontextprotocol/sdk/types.js"; +import { promptArgsToForm } from "../utils/promptArgsToForm.js"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; + +// Helper to extract error message from various error types +function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + if (typeof error === "string") { + return error; + } + if (error && typeof error === "object" && "message" in error) { + return String(error.message); + } + return "Unknown error"; +} + +interface PromptTestModalProps { + prompt: Prompt; + inspectorClient: InspectorClient | null; + width: number; + height: number; + onClose: () => void; +} + +type ModalState = "form" | "loading" | "results"; + +interface PromptResult { + input: Record; + output: GetPromptResult | null; + error?: string; + errorDetails?: unknown; + duration: number; +} + +export function PromptTestModal({ + prompt, + inspectorClient, + width, + height, + onClose, +}: PromptTestModalProps) { + const [state, setState] = useState("form"); + const [result, setResult] = useState(null); + const scrollViewRef = React.useRef(null); + + // Use full terminal dimensions instead of passed dimensions + const [terminalDimensions, setTerminalDimensions] = React.useState({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + + React.useEffect(() => { + const updateDimensions = () => { + setTerminalDimensions({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + }; + process.stdout.on("resize", updateDimensions); + updateDimensions(); + return () => { + process.stdout.off("resize", updateDimensions); + }; + }, [width, height]); + + const formStructure = promptArgsToForm( + prompt.arguments || [], + prompt.name || "Unknown Prompt", + ); + + // Reset state when modal closes + React.useEffect(() => { + return () => { + // Cleanup: reset state when component unmounts + setState("form"); + setResult(null); + }; + }, []); + + // Handle all input when modal is open - prevents input from reaching underlying components + // When in form mode, only handle escape (form handles its own input) + // When in results mode, handle scrolling keys + useInput( + (input: string, key: Key) => { + // Always handle escape to close modal + if (key.escape) { + setState("form"); + setResult(null); + onClose(); + return; + } + + if (state === "form") { + // In form mode, let the form handle all other input + // Don't process anything else - this prevents input from reaching underlying components + return; + } + + if (state === "results") { + // Allow scrolling in results view + if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } + } + }, + { isActive: true }, + ); + + const handleFormSubmit = async (values: Record) => { + if (!inspectorClient || !prompt) return; + + setState("loading"); + const startTime = Date.now(); + + try { + // Get the prompt using the provided arguments + const invocation = await inspectorClient.getPrompt(prompt.name, values); + + const duration = Date.now() - startTime; + + setResult({ + input: values, + output: invocation.result, + duration, + }); + setState("results"); + } catch (error) { + const duration = Date.now() - startTime; + const errorMessage = getErrorMessage(error); + + // Extract detailed error information + const errorObj: Record = { + message: errorMessage, + }; + if (error instanceof Error) { + errorObj.name = error.name; + errorObj.stack = error.stack; + } else if (error && typeof error === "object") { + // Try to extract more details from error object + Object.assign(errorObj, error); + } else { + errorObj.error = String(error); + } + + setResult({ + input: values, + output: null, + error: errorMessage, + errorDetails: errorObj, + duration, + }); + setState("results"); + } + }; + + // Calculate modal dimensions - use almost full screen + const modalWidth = terminalDimensions.width - 2; + const modalHeight = terminalDimensions.height - 2; + + return ( + + {/* Modal Content */} + + {/* Header */} + + + {formStructure.title} + + + (Press ESC to close) + + + {/* Content Area */} + + {state === "form" && ( + + {prompt.description && ( + + {prompt.description} + + )} +
+ handleFormSubmit(values as Record) + } + /> + + )} + + {state === "loading" && ( + + Getting prompt... + + )} + + {state === "results" && result && ( + + + {/* Timing */} + + + Duration: {result.duration}ms + + + + {/* Input */} + {Object.keys(result.input).length > 0 && ( + + + Arguments: + + + + {JSON.stringify(result.input, null, 2)} + + + + )} + + {/* Output or Error */} + {result.error ? ( + + + + Error: + + + + {String(result.error)} + + {result.errorDetails != null ? ( + <> + + + Error Details: + + + + + {JSON.stringify(result.errorDetails, null, 2)} + + + + ) : null} + + ) : ( + + + Prompt Messages: + + + + {JSON.stringify(result.output, null, 2)} + + + + )} + + + )} + + + + ); +} diff --git a/clients/tui/src/components/PromptsTab.tsx b/clients/tui/src/components/PromptsTab.tsx new file mode 100644 index 000000000..c41291a12 --- /dev/null +++ b/clients/tui/src/components/PromptsTab.tsx @@ -0,0 +1,276 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import type { InspectorClient } from "@inspector/core/mcp/index.js"; +import type { + Prompt, + PromptArgument, + GetPromptResult, +} from "@modelcontextprotocol/sdk/types.js"; +import { useSelectableList } from "../hooks/useSelectableList.js"; + +interface PromptsTabProps { + prompts: Prompt[]; + inspectorClient: InspectorClient | null; + width: number; + height: number; + onCountChange?: (count: number) => void; + focusedPane?: "list" | "details" | null; + onViewDetails?: (prompt: Prompt & { result?: GetPromptResult }) => void; + onFetchPrompt?: (prompt: Prompt) => void; + modalOpen?: boolean; +} + +export function PromptsTab({ + prompts, + inspectorClient, + width, + height, + focusedPane = null, + onViewDetails, + onFetchPrompt, + modalOpen = false, +}: PromptsTabProps) { + const visibleCount = Math.max(1, height - 7); + const { selectedIndex, firstVisible, setSelection } = useSelectableList( + prompts.length, + visibleCount, + { resetWhen: prompts }, + ); + const [error, setError] = useState(null); + const scrollViewRef = useRef(null); + + // Handle arrow key navigation when focused + useInput( + (input: string, key: Key) => { + // Handle Enter key to fetch prompt (works from both list and details) + if (key.return && selectedPrompt && inspectorClient && onFetchPrompt) { + // If prompt has arguments, open modal to collect them + // Otherwise, fetch directly + if (selectedPrompt.arguments && selectedPrompt.arguments.length > 0) { + onFetchPrompt(selectedPrompt); + } else { + // No arguments, fetch directly + (async () => { + try { + const invocation = await inspectorClient.getPrompt( + selectedPrompt.name, + ); + // Show result in details modal + if (onViewDetails) { + onViewDetails({ + ...selectedPrompt, + result: invocation.result, + }); + } + } catch (error) { + setError( + error instanceof Error ? error.message : "Failed to get prompt", + ); + } + })(); + } + return; + } + + if (focusedPane === "list") { + if (key.upArrow && selectedIndex > 0) { + setSelection(selectedIndex - 1); + } else if (key.downArrow && selectedIndex < prompts.length - 1) { + setSelection(selectedIndex + 1); + } + return; + } + + if (focusedPane === "details") { + // Handle '+' key to view in full screen modal + if (input === "+" && selectedPrompt && onViewDetails) { + onViewDetails(selectedPrompt); + return; + } + + // Scroll the details pane using ink-scroll-view + if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } + } + }, + { + isActive: + !modalOpen && (focusedPane === "list" || focusedPane === "details"), + }, + ); + + // Reset scroll when selection changes + useEffect(() => { + scrollViewRef.current?.scrollTo(0); + }, [selectedIndex]); + + const selectedPrompt = prompts[selectedIndex] || null; + + const listWidth = Math.floor(width * 0.4); + const detailWidth = width - listWidth; + + return ( + + {/* Prompts List */} + + + + Prompts ({prompts.length}) + + + {error ? ( + + {error} + + ) : prompts.length === 0 ? ( + + No prompts available + + ) : ( + + {prompts + .slice(firstVisible, firstVisible + visibleCount) + .map((prompt, i) => { + const index = firstVisible + i; + const isSelected = index === selectedIndex; + return ( + + + {isSelected ? "▶ " : " "} + {prompt.name || `Prompt ${index + 1}`} + + + ); + })} + + )} + + + {/* Prompt Details */} + + {selectedPrompt ? ( + <> + {/* Fixed header */} + + + {selectedPrompt.name} + + + + {/* Scrollable content area - direct ScrollView with height prop like NotificationsTab */} + + {/* Description */} + {selectedPrompt.description && ( + <> + {selectedPrompt.description + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + )} + + {/* Arguments */} + {selectedPrompt.arguments && + selectedPrompt.arguments.length > 0 && ( + <> + + Arguments: + + {selectedPrompt.arguments.map( + (arg: PromptArgument, idx: number) => ( + + + - {arg.name}:{" "} + {arg.description ?? + (arg as { type?: string }).type ?? + "string"} + + + ), + )} + + )} + + {/* Enter to Get Prompt message */} + + [Enter to Get Prompt] + + + + {/* Fixed footer - only show when details pane is focused */} + {focusedPane === "details" && ( + + + ↑/↓ to scroll, + to zoom + + + )} + + ) : ( + + Select a prompt to view details + + )} + + + ); +} diff --git a/clients/tui/src/components/RequestsTab.tsx b/clients/tui/src/components/RequestsTab.tsx new file mode 100644 index 000000000..e2e937a95 --- /dev/null +++ b/clients/tui/src/components/RequestsTab.tsx @@ -0,0 +1,370 @@ +import React, { useEffect, useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import type { FetchRequestEntry } from "@inspector/core/mcp/index.js"; +import { useSelectableList } from "../hooks/useSelectableList.js"; + +interface RequestsTabProps { + serverName: string | null; + requests: FetchRequestEntry[]; + width: number; + height: number; + onCountChange?: (count: number) => void; + focusedPane?: "requests" | "details" | null; + onViewDetails?: (request: FetchRequestEntry) => void; + modalOpen?: boolean; +} + +export function RequestsTab({ + requests, + width, + height, + onCountChange, + focusedPane = null, + onViewDetails, + modalOpen = false, +}: RequestsTabProps) { + const visibleCount = Math.max(1, height - 7); + const { selectedIndex, firstVisible, setSelection } = useSelectableList( + requests.length, + visibleCount, + ); + const scrollViewRef = useRef(null); + const selectedRequest = requests[selectedIndex] || null; + + // Handle arrow key navigation and scrolling when focused + useInput( + (input: string, key: Key) => { + if (focusedPane === "requests") { + if (key.upArrow && selectedIndex > 0) { + setSelection(selectedIndex - 1); + } else if (key.downArrow && selectedIndex < requests.length - 1) { + setSelection(selectedIndex + 1); + } else if (key.pageUp) { + setSelection(Math.max(0, selectedIndex - visibleCount)); + } else if (key.pageDown) { + setSelection( + Math.min(requests.length - 1, selectedIndex + visibleCount), + ); + } + return; + } + + // details scrolling (only when details pane is focused) + if (focusedPane === "details") { + // Handle '+' key to view in full screen modal + if (input === "+" && selectedRequest && onViewDetails) { + onViewDetails(selectedRequest); + return; + } + + if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } + } + }, + { isActive: !modalOpen && focusedPane !== undefined }, + ); + + // Update count when requests change + React.useEffect(() => { + onCountChange?.(requests.length); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [requests.length]); + + // Reset details scroll when request selection changes + useEffect(() => { + scrollViewRef.current?.scrollTo(0); + }, [selectedIndex]); + + const listWidth = Math.floor(width * 0.4); + const detailWidth = width - listWidth; + + const getStatusColor = (status?: number): string => { + if (!status) return "gray"; + if (status >= 200 && status < 300) return "green"; + if (status >= 300 && status < 400) return "yellow"; + if (status >= 400) return "red"; + return "gray"; + }; + + return ( + + {/* Left column - Requests list */} + + + + Requests ({requests.length}) + + + + {/* Requests list */} + {requests.length === 0 ? ( + + No requests + + ) : ( + + {requests + .slice(firstVisible, firstVisible + visibleCount) + .map((req, i) => { + const index = firstVisible + i; + const isSelected = index === selectedIndex; + const statusColor = getStatusColor(req.responseStatus); + const statusText = req.responseStatus + ? `${req.responseStatus}` + : req.error + ? "ERROR" + : "..."; + + const categoryLabel = req.category === "auth" ? "AUTH" : "MCP "; + const methodPadded = req.method === "GET" ? "GET " : req.method; + return ( + + + {isSelected ? "▶ " : " "} + {categoryLabel}{" "} + {methodPadded}{" "} + {statusText} + {req.duration !== undefined && ( + {req.duration}ms + )} + + + ); + })} + + )} + + + {/* Right column - Request details */} + + {selectedRequest ? ( + <> + {/* Fixed header */} + + + {selectedRequest.method} {selectedRequest.url} + + + + {/* Scrollable content area */} + + {/* Category */} + + + Category:{" "} + + {selectedRequest.category === "auth" ? "auth" : "transport"} + + + + + {/* Status */} + {selectedRequest.responseStatus !== undefined ? ( + + + Status:{" "} + + {selectedRequest.responseStatus}{" "} + {selectedRequest.responseStatusText || ""} + + + + ) : selectedRequest.error ? ( + + + Error: {selectedRequest.error} + + + ) : ( + + + Request in progress... + + + )} + + {/* Duration */} + {selectedRequest.duration !== undefined && ( + + + {selectedRequest.timestamp.toLocaleTimeString()} ( + {selectedRequest.duration}ms) + + + )} + + {/* Request Headers */} + + Request Headers: + + {Object.entries(selectedRequest.requestHeaders).map( + ([key, value]) => ( + + + {key}: {value} + + + ), + )} + + {/* Request Body */} + {selectedRequest.requestBody && ( + <> + + Request Body: + + {(() => { + try { + const parsed = JSON.parse(selectedRequest.requestBody); + return JSON.stringify(parsed, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + )); + } catch { + return ( + + {selectedRequest.requestBody} + + ); + } + })()} + + )} + + {/* Response Headers */} + {selectedRequest.responseHeaders && + Object.keys(selectedRequest.responseHeaders).length > 0 && ( + <> + + Response Headers: + + {Object.entries(selectedRequest.responseHeaders).map( + ([key, value]) => ( + + + {key}: {value} + + + ), + )} + + )} + + {/* Response Body */} + {selectedRequest.responseBody && ( + <> + + Response Body: + + {(() => { + try { + const parsed = JSON.parse(selectedRequest.responseBody); + return JSON.stringify(parsed, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + )); + } catch { + return ( + + {selectedRequest.responseBody} + + ); + } + })()} + + )} + + + {/* Fixed footer - only show when details pane is focused */} + {focusedPane === "details" && ( + + + ↑/↓ to scroll, + to zoom + + + )} + + ) : ( + + Select a request to view details + + )} + + + ); +} diff --git a/clients/tui/src/components/ResourceTestModal.tsx b/clients/tui/src/components/ResourceTestModal.tsx new file mode 100644 index 000000000..fe6dade72 --- /dev/null +++ b/clients/tui/src/components/ResourceTestModal.tsx @@ -0,0 +1,321 @@ +import React, { useState } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { Form } from "ink-form"; +import { InspectorClient } from "@inspector/core/mcp/index.js"; +import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { uriTemplateToForm } from "../utils/uriTemplateToForm.js"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; + +// Helper to extract error message from various error types +function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + if (typeof error === "string") { + return error; + } + if (error && typeof error === "object" && "message" in error) { + return String(error.message); + } + return "Unknown error"; +} + +interface ResourceTestModalProps { + template: { + name: string; + uriTemplate: string; + description?: string; + }; + inspectorClient: InspectorClient | null; + width: number; + height: number; + onClose: () => void; +} + +type ModalState = "form" | "loading" | "results"; + +interface ResourceResult { + input: Record; + output: ReadResourceResult | null; + error?: string; + errorDetails?: unknown; + duration: number; + uri: string; +} + +export function ResourceTestModal({ + template, + inspectorClient, + width, + height, + onClose, +}: ResourceTestModalProps) { + const [state, setState] = useState("form"); + const [result, setResult] = useState(null); + const scrollViewRef = React.useRef(null); + + // Use full terminal dimensions instead of passed dimensions + const [terminalDimensions, setTerminalDimensions] = React.useState({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + + React.useEffect(() => { + const updateDimensions = () => { + setTerminalDimensions({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + }; + process.stdout.on("resize", updateDimensions); + updateDimensions(); + return () => { + process.stdout.off("resize", updateDimensions); + }; + }, [width, height]); + + const formStructure = uriTemplateToForm( + template.uriTemplate, + template.name || "Unknown Template", + ); + + // Reset state when modal closes + React.useEffect(() => { + return () => { + // Cleanup: reset state when component unmounts + setState("form"); + setResult(null); + }; + }, []); + + // Handle all input when modal is open - prevents input from reaching underlying components + // When in form mode, only handle escape (form handles its own input) + // When in results mode, handle scrolling keys + useInput( + (input: string, key: Key) => { + // Always handle escape to close modal + if (key.escape) { + setState("form"); + setResult(null); + onClose(); + return; + } + + if (state === "form") { + // In form mode, let the form handle all other input + // Don't process anything else - this prevents input from reaching underlying components + return; + } + + if (state === "results") { + // Allow scrolling in results view + if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } + } + }, + { isActive: true }, + ); + + const handleFormSubmit = async (values: Record) => { + if (!inspectorClient || !template) return; + + setState("loading"); + const startTime = Date.now(); + + try { + // Use InspectorClient's readResourceFromTemplate method which encapsulates template expansion and resource reading + const invocation = await inspectorClient.readResourceFromTemplate( + template.uriTemplate, + values, + ); + + const duration = Date.now() - startTime; + + setResult({ + input: values, + output: invocation.result, // Extract the SDK result from the invocation + duration, + uri: invocation.expandedUri, // Use expandedUri instead of uri + }); + setState("results"); + } catch (error) { + const duration = Date.now() - startTime; + const errorMessage = getErrorMessage(error); + + // Try to get expanded URI from error if available, otherwise use template + let uri = template.uriTemplate; + // If the error response contains uri, use it + if (error && typeof error === "object" && "uri" in error) { + uri = (error as { uri: string }).uri; + } + + // Extract detailed error information + const errorObj: Record = { + message: errorMessage, + }; + if (error instanceof Error) { + errorObj.name = error.name; + errorObj.stack = error.stack; + } else if (error && typeof error === "object") { + // Try to extract more details from error object + Object.assign(errorObj, error); + } else { + errorObj.error = String(error); + } + + setResult({ + input: values, + output: null, + error: errorMessage, + errorDetails: errorObj, + duration, + uri, + }); + setState("results"); + } + }; + + // Calculate modal dimensions - use almost full screen + const modalWidth = terminalDimensions.width - 2; + const modalHeight = terminalDimensions.height - 2; + + return ( + + {/* Modal Content */} + + {/* Header */} + + + {formStructure.title} + + + (Press ESC to close) + + + {/* Content Area */} + + {state === "form" && ( + + {template.description && ( + + {template.description} + + )} + + handleFormSubmit(values as Record) + } + /> + + )} + + {state === "loading" && ( + + Reading resource... + + )} + + {state === "results" && result && ( + + + {/* Timing */} + + + Duration: {result.duration}ms + + + + {/* URI */} + + + URI:{" "} + + {result.uri} + + + {/* Input */} + + + Template Values: + + + + {JSON.stringify(result.input, null, 2)} + + + + + {/* Output or Error */} + {result.error ? ( + + + + Error: + + + + {String(result.error)} + + {result.errorDetails != null ? ( + <> + + + Error Details: + + + + + {JSON.stringify(result.errorDetails, null, 2)} + + + + ) : null} + + ) : ( + + + Resource Content: + + + + {JSON.stringify(result.output, null, 2)} + + + + )} + + + )} + + + + ); +} diff --git a/clients/tui/src/components/ResourcesTab.tsx b/clients/tui/src/components/ResourcesTab.tsx new file mode 100644 index 000000000..abcc6eaea --- /dev/null +++ b/clients/tui/src/components/ResourcesTab.tsx @@ -0,0 +1,430 @@ +import React, { useState, useEffect, useRef, useMemo } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import type { InspectorClient } from "@inspector/core/mcp/index.js"; +import type { + Resource, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; +import { useSelectableList } from "../hooks/useSelectableList.js"; + +interface ResourceTemplate { + name: string; + uriTemplate: string; + description?: string; +} + +interface ResourcesTabProps { + resources: Resource[]; + resourceTemplates?: ResourceTemplate[]; + inspectorClient: InspectorClient | null; + width: number; + height: number; + onCountChange?: (count: number) => void; + focusedPane?: "list" | "details" | null; + onViewDetails?: ( + resource: Resource | { content: ReadResourceResult }, + ) => void; + onFetchResource?: (resource: Resource) => void; + onFetchTemplate?: (template: ResourceTemplate) => void; + modalOpen?: boolean; +} + +export function ResourcesTab({ + resources, + resourceTemplates = [], + inspectorClient, + width, + height, + onCountChange, + focusedPane = null, + onViewDetails, + onFetchResource, + onFetchTemplate, + modalOpen = false, +}: ResourcesTabProps) { + const [error, setError] = useState(null); + const [resourceContent, setResourceContent] = + useState(null); + const [loading, setLoading] = useState(false); + const [shouldFetchResource, setShouldFetchResource] = useState( + null, + ); + const scrollViewRef = useRef(null); + + // Combined list: resources first, then templates - memoized to prevent unnecessary recalculations + const allItems = useMemo( + () => [ + ...resources.map((r) => ({ type: "resource" as const, data: r })), + ...resourceTemplates.map((t) => ({ type: "template" as const, data: t })), + ], + [resources, resourceTemplates], + ); + const totalCount = useMemo( + () => resources.length + resourceTemplates.length, + [resources.length, resourceTemplates.length], + ); + + const visibleCount = Math.max(1, height - 7); + const { selectedIndex, firstVisible, setSelection } = useSelectableList( + totalCount, + visibleCount, + { resetWhen: resources }, + ); + const selectedItem = useMemo( + () => allItems[selectedIndex] || null, + [allItems, selectedIndex], + ); + + // Handle arrow key navigation when focused + useInput( + (input: string, key: Key) => { + // Handle Enter key to fetch resource (works from both list and details) + if ( + key.return && + selectedItem && + inspectorClient && + (onFetchResource || onFetchTemplate) + ) { + if (selectedItem.type === "resource" && selectedItem.data.uri) { + // Trigger fetch for regular resource + setShouldFetchResource(selectedItem.data.uri); + if (onFetchResource) { + onFetchResource(selectedItem.data); + } + } else if (selectedItem.type === "template" && onFetchTemplate) { + // Open modal for template + onFetchTemplate(selectedItem.data); + } + return; + } + + if (focusedPane === "list") { + if (key.upArrow && selectedIndex > 0) { + setSelection(selectedIndex - 1); + } else if (key.downArrow && selectedIndex < totalCount - 1) { + setSelection(selectedIndex + 1); + } + return; + } + + if (focusedPane === "details") { + // Handle '+' key to view in full screen modal + if (input === "+" && resourceContent && onViewDetails) { + onViewDetails({ content: resourceContent }); + return; + } + + // Scroll the details pane using ink-scroll-view + if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } + } + }, + { + isActive: + !modalOpen && (focusedPane === "list" || focusedPane === "details"), + }, + ); + + // Reset scroll when selection changes + useEffect(() => { + scrollViewRef.current?.scrollTo(0); + }, [selectedIndex]); + + // Clear fetched content when resources change + const prevResourcesRef = useRef(resources); + useEffect(() => { + if (prevResourcesRef.current !== resources) { + setResourceContent(null); + setShouldFetchResource(null); + prevResourcesRef.current = resources; + } + }, [resources]); + + const isResource = selectedItem?.type === "resource"; + const isTemplate = selectedItem?.type === "template"; + const selectedResource = isResource ? selectedItem.data : null; + const selectedTemplate = isTemplate ? selectedItem.data : null; + + // Fetch resource content when shouldFetchResource is set + useEffect(() => { + if (!shouldFetchResource || !inspectorClient) return; + + const fetchContent = async () => { + setLoading(true); + setError(null); + try { + const invocation = + await inspectorClient.readResource(shouldFetchResource); + setResourceContent(invocation.result); + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to read resource", + ); + setResourceContent(null); + } finally { + setLoading(false); + setShouldFetchResource(null); + } + }; + + fetchContent(); + }, [shouldFetchResource, inspectorClient]); + + const listWidth = Math.floor(width * 0.4); + const detailWidth = width - listWidth; + + // Update count when items change - use ref to track previous count and only call when it actually changes + const prevCountRef = useRef(totalCount); + useEffect(() => { + if (prevCountRef.current !== totalCount) { + prevCountRef.current = totalCount; + onCountChange?.(totalCount); + } + }, [totalCount, onCountChange]); + + return ( + + {/* Resources and Templates List */} + + + + Resources ({totalCount}) + + + {error ? ( + + {error} + + ) : totalCount === 0 ? ( + + No resources available + + ) : ( + + {allItems + .slice(firstVisible, firstVisible + visibleCount) + .map((item, i) => { + const index = firstVisible + i; + const isSelected = index === selectedIndex; + const label = + item.type === "resource" + ? item.data.name || item.data.uri || `Resource ${index + 1}` + : item.data.name || + `Template ${index - resources.length + 1}`; + const key = + item.type === "resource" + ? item.data.uri || index + : item.data.uriTemplate || index; + return ( + + + {isSelected ? "▶ " : " "} + {label} + + + ); + })} + + )} + + + {/* Resource Details */} + + {selectedResource ? ( + <> + {/* Fixed header */} + + + {selectedResource.name || selectedResource.uri} + + + + {/* Scrollable content area */} + + {/* Description */} + {selectedResource.description && ( + <> + {selectedResource.description + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + )} + + {/* URI */} + {selectedResource.uri && ( + + URI: {selectedResource.uri} + + )} + + {/* MIME Type */} + {selectedResource.mimeType && ( + + MIME Type: {selectedResource.mimeType} + + )} + + {/* Resource Content */} + {loading && ( + + Loading resource content... + + )} + + {!loading && resourceContent && ( + <> + + Content: + + + + {JSON.stringify(resourceContent, null, 2)} + + + + )} + + {!loading && !resourceContent && selectedResource.uri && ( + + [Enter to Fetch Resource] + + )} + + + {/* Fixed footer - only show when details pane is focused */} + {focusedPane === "details" && ( + + + {resourceContent + ? "↑/↓ to scroll, + to zoom" + : "Enter to fetch, ↑/↓ to scroll"} + + + )} + + ) : selectedTemplate ? ( + <> + {/* Fixed header */} + + + {selectedTemplate.name} + + + + {/* Scrollable content area */} + + {/* Description */} + {selectedTemplate.description && ( + <> + {selectedTemplate.description + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + )} + + {/* URI Template */} + {selectedTemplate.uriTemplate && ( + + + URI Template: {selectedTemplate.uriTemplate} + + + )} + + + [Enter to Fetch Resource] + + + + {/* Fixed footer - only show when details pane is focused */} + {focusedPane === "details" && ( + + + Enter to fetch + + + )} + + ) : ( + + Select a resource or template to view details + + )} + + + ); +} diff --git a/clients/tui/src/components/SelectableItem.tsx b/clients/tui/src/components/SelectableItem.tsx new file mode 100644 index 000000000..a87315a34 --- /dev/null +++ b/clients/tui/src/components/SelectableItem.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { Box, Text } from "ink"; + +/** Renders a selectable item: ▶ + space when selected, space + space when not. Fixed width to prevent layout shift. */ +export function SelectableItem({ + isSelected, + bold, + children, +}: { + isSelected: boolean; + bold?: boolean; + children: React.ReactNode; +}) { + return ( + + + {isSelected ? "▶ " : " "} + + {children} + + ); +} diff --git a/clients/tui/src/components/Tabs.tsx b/clients/tui/src/components/Tabs.tsx new file mode 100644 index 000000000..59e972166 --- /dev/null +++ b/clients/tui/src/components/Tabs.tsx @@ -0,0 +1,85 @@ +import React from "react"; +import { Box, Text } from "ink"; +import { type TabType, tabs } from "./tabsConfig.js"; + +interface TabsProps { + activeTab: TabType; + onTabChange: (tab: TabType) => void; + width: number; + counts?: { + info?: number; + auth?: number; + resources?: number; + prompts?: number; + tools?: number; + messages?: number; + requests?: number; + logging?: number; + }; + focused?: boolean; + showAuth?: boolean; + showLogging?: boolean; + showRequests?: boolean; +} + +export function Tabs({ + activeTab, + width, + counts = {}, + focused = false, + showAuth = true, + showLogging = true, + showRequests = false, +}: TabsProps) { + let visibleTabs = tabs; + if (!showAuth) { + visibleTabs = visibleTabs.filter((tab) => tab.id !== "auth"); + } + if (!showLogging) { + visibleTabs = visibleTabs.filter((tab) => tab.id !== "logging"); + } + if (!showRequests) { + visibleTabs = visibleTabs.filter((tab) => tab.id !== "requests"); + } + + return ( + + {visibleTabs.map((tab) => { + const isActive = activeTab === tab.id; + const count = counts[tab.id]; + const countText = count !== undefined ? ` (${count})` : ""; + const firstChar = tab.label[0]; + const restOfLabel = tab.label.slice(1); + + return ( + + + {isActive ? "▶ " : " "} + {firstChar} + {restOfLabel} + {countText} + + + ); + })} + + ); +} diff --git a/clients/tui/src/components/ToolTestModal.tsx b/clients/tui/src/components/ToolTestModal.tsx new file mode 100644 index 000000000..dca992e4d --- /dev/null +++ b/clients/tui/src/components/ToolTestModal.tsx @@ -0,0 +1,287 @@ +import React, { useState } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { Form } from "ink-form"; +import { InspectorClient } from "@inspector/core/mcp/index.js"; +import type { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { JsonValue } from "@inspector/core/mcp/index.js"; +import { schemaToForm } from "../utils/schemaToForm.js"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; + +interface ToolTestModalProps { + tool: Tool; + inspectorClient: InspectorClient | null; + width: number; + height: number; + onClose: () => void; +} + +type ModalState = "form" | "loading" | "results"; + +interface ToolResult { + input: Record; + output: CallToolResult | null; + error?: string; + errorDetails?: unknown; + duration: number; +} + +export function ToolTestModal({ + tool, + inspectorClient, + width, + height, + onClose, +}: ToolTestModalProps) { + const [state, setState] = useState("form"); + const [result, setResult] = useState(null); + const scrollViewRef = React.useRef(null); + + // Use full terminal dimensions instead of passed dimensions + const [terminalDimensions, setTerminalDimensions] = React.useState({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + + React.useEffect(() => { + const updateDimensions = () => { + setTerminalDimensions({ + width: process.stdout.columns || width, + height: process.stdout.rows || height, + }); + }; + process.stdout.on("resize", updateDimensions); + updateDimensions(); + return () => { + process.stdout.off("resize", updateDimensions); + }; + }, [width, height]); + + const formStructure = tool?.inputSchema + ? schemaToForm(tool.inputSchema, tool.name || "Unknown Tool") + : { + title: `Test Tool: ${tool?.name || "Unknown"}`, + sections: [{ title: "Parameters", fields: [] }], + }; + + // Reset state when modal closes + React.useEffect(() => { + return () => { + // Cleanup: reset state when component unmounts + setState("form"); + setResult(null); + }; + }, []); + + // Handle all input when modal is open - prevents input from reaching underlying components + // When in form mode, only handle escape (form handles its own input) + // When in results mode, handle scrolling keys + useInput( + (input: string, key: Key) => { + // Always handle escape to close modal + if (key.escape) { + setState("form"); + setResult(null); + onClose(); + return; + } + + if (state === "form") { + // In form mode, let the form handle all other input + // Don't process anything else - this prevents input from reaching underlying components + return; + } + + if (state === "results") { + // Allow scrolling in results view + if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } + } + }, + { isActive: true }, + ); + + const handleFormSubmit = async (values: Record) => { + if (!inspectorClient || !tool) return; + + setState("loading"); + const startTime = Date.now(); + + try { + // Use InspectorClient.callTool() which handles parameter conversion and metadata + const invocation = await inspectorClient.callTool(tool, values); + + const duration = Date.now() - startTime; + + // InspectorClient.callTool() returns ToolCallInvocation + // Check if the call succeeded and extract the result + if (!invocation.success || invocation.result === null) { + // Error case: tool call failed + setResult({ + input: values, + output: null, + error: invocation.error || "Tool call failed", + errorDetails: invocation, + duration, + }); + } else { + // Success case: extract the result + const result = invocation.result; + // Check for error indicators in the result (SDK may return error in result) + const isError = "isError" in result && result.isError === true; + + setResult({ + input: values, + output: isError ? null : result, + error: isError ? "Tool returned an error" : undefined, + errorDetails: isError ? result : undefined, + duration, + }); + } + setState("results"); + } catch (error) { + const duration = Date.now() - startTime; + const errorObj = + error instanceof Error + ? { message: error.message, name: error.name, stack: error.stack } + : { error: String(error) }; + + setResult({ + input: values, + output: null, + error: error instanceof Error ? error.message : "Unknown error", + errorDetails: errorObj, + duration, + }); + setState("results"); + } + }; + + // Calculate modal dimensions - use almost full screen + const modalWidth = terminalDimensions.width - 2; + const modalHeight = terminalDimensions.height - 2; + + return ( + + {/* Modal Content */} + + {/* Header */} + + + {formStructure.title} + + + (Press ESC to close) + + + {/* Content Area */} + + {state === "form" && ( + + + void handleFormSubmit(value as Record) + } + /> + + )} + + {state === "loading" && ( + + Calling tool... + + )} + + {state === "results" && result && ( + + + {/* Timing */} + + + Duration: {result.duration}ms + + + + {/* Input */} + + + Input: + + + + {JSON.stringify(result.input, null, 2)} + + + + + {/* Output or Error */} + {result.error ? ( + + + Error: + + + {String(result.error)} + + {result.errorDetails != null ? ( + <> + + + Error Details: + + + + + {JSON.stringify(result.errorDetails, null, 2)} + + + + ) : null} + + ) : ( + + + Output: + + + + {JSON.stringify(result.output, null, 2)} + + + + )} + + + )} + + + + ); +} diff --git a/clients/tui/src/components/ToolsTab.tsx b/clients/tui/src/components/ToolsTab.tsx new file mode 100644 index 000000000..81dd15589 --- /dev/null +++ b/clients/tui/src/components/ToolsTab.tsx @@ -0,0 +1,247 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Box, Text, useInput, type Key } from "ink"; +import { ScrollView, type ScrollViewRef } from "ink-scroll-view"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; +import { useSelectableList } from "../hooks/useSelectableList.js"; + +interface ToolsTabProps { + tools: Tool[]; + isConnected: boolean; + width: number; + height: number; + onCountChange?: (count: number) => void; + focusedPane?: "list" | "details" | null; + onTestTool?: (tool: Tool) => void; + onViewDetails?: (tool: Tool) => void; + modalOpen?: boolean; +} + +export function ToolsTab({ + tools, + isConnected, + width, + height, + focusedPane = null, + onTestTool, + onViewDetails, + modalOpen = false, +}: ToolsTabProps) { + const visibleCount = Math.max(1, height - 7); + const { selectedIndex, firstVisible, setSelection } = useSelectableList( + tools.length, + visibleCount, + { resetWhen: tools }, + ); + const [error] = useState(null); + const scrollViewRef = useRef(null); + const listWidth = Math.floor(width * 0.4); + const detailWidth = width - listWidth; + + // Handle arrow key navigation when focused + useInput( + (input: string, key: Key) => { + // Handle Enter key to test tool (works from both list and details) + if (key.return && selectedTool && isConnected && onTestTool) { + onTestTool(selectedTool); + return; + } + + if (focusedPane === "list") { + if (key.upArrow && selectedIndex > 0) { + setSelection(selectedIndex - 1); + } else if (key.downArrow && selectedIndex < tools.length - 1) { + setSelection(selectedIndex + 1); + } + return; + } + + if (focusedPane === "details") { + // Handle '+' key to view in full screen modal + if (input === "+" && selectedTool && onViewDetails) { + onViewDetails(selectedTool); + return; + } + + // Scroll the details pane using ink-scroll-view + if (key.upArrow) { + scrollViewRef.current?.scrollBy(-1); + } else if (key.downArrow) { + scrollViewRef.current?.scrollBy(1); + } else if (key.pageUp) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(-viewportHeight); + } else if (key.pageDown) { + const viewportHeight = + scrollViewRef.current?.getViewportHeight() || 1; + scrollViewRef.current?.scrollBy(viewportHeight); + } + } + }, + { + isActive: + !modalOpen && (focusedPane === "list" || focusedPane === "details"), + }, + ); + + // Reset scroll when selection changes + useEffect(() => { + scrollViewRef.current?.scrollTo(0); + }, [selectedIndex]); + + const selectedTool = tools[selectedIndex] || null; + + return ( + + {/* Tools List */} + + + + Tools ({tools.length}) + + + {error ? ( + + {error} + + ) : tools.length === 0 ? ( + + No tools available + + ) : ( + + {tools + .slice(firstVisible, firstVisible + visibleCount) + .map((tool, i) => { + const index = firstVisible + i; + const isSelected = index === selectedIndex; + return ( + + + {isSelected ? "▶ " : " "} + {tool.name || `Tool ${index + 1}`} + + + ); + })} + + )} + + + {/* Tool Details */} + + {selectedTool ? ( + <> + {/* Fixed header */} + + + {selectedTool.name} + + {isConnected && ( + + + [Enter to Test] + + + )} + + + {/* Scrollable content area - direct ScrollView with height prop like NotificationsTab */} + + {/* Description */} + {selectedTool.description && ( + <> + {selectedTool.description + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + )} + + {/* Input Schema */} + {selectedTool.inputSchema && ( + <> + + Input Schema: + + {JSON.stringify(selectedTool.inputSchema, null, 2) + .split("\n") + .map((line: string, idx: number) => ( + + {line} + + ))} + + )} + + + {/* Fixed footer - only show when details pane is focused */} + {focusedPane === "details" && ( + + + ↑/↓ to scroll, + to zoom + + + )} + + ) : ( + + Select a tool to view details + + )} + + + ); +} diff --git a/clients/tui/src/components/tabsConfig.ts b/clients/tui/src/components/tabsConfig.ts new file mode 100644 index 000000000..1aae81095 --- /dev/null +++ b/clients/tui/src/components/tabsConfig.ts @@ -0,0 +1,20 @@ +export type TabType = + | "info" + | "auth" + | "resources" + | "prompts" + | "tools" + | "messages" + | "requests" + | "logging"; + +export const tabs: { id: TabType; label: string; accelerator: string }[] = [ + { id: "info", label: "Info", accelerator: "i" }, + { id: "auth", label: "Auth", accelerator: "a" }, + { id: "resources", label: "Resources", accelerator: "r" }, + { id: "prompts", label: "Prompts", accelerator: "p" }, + { id: "tools", label: "Tools", accelerator: "t" }, + { id: "messages", label: "Messages", accelerator: "m" }, + { id: "requests", label: "HTTP Requests", accelerator: "h" }, + { id: "logging", label: "Logging", accelerator: "l" }, +]; diff --git a/clients/tui/src/hooks/useSelectableList.ts b/clients/tui/src/hooks/useSelectableList.ts new file mode 100644 index 000000000..054624e9b --- /dev/null +++ b/clients/tui/src/hooks/useSelectableList.ts @@ -0,0 +1,61 @@ +import { useState, useEffect, useCallback } from "react"; + +function clampFirstVisible( + first: number, + selected: number, + visibleCount: number, +): number { + if (selected < first) return selected; + if (selected >= first + visibleCount) return selected - visibleCount + 1; + return first; +} + +export interface UseSelectableListOptions { + /** When this reference changes, reset selection to 0 (e.g. tools list when switching servers) */ + resetWhen?: unknown; +} + +/** + * Manages selection and scroll position for a virtualized list. + * Returns selection state and a setSelection that updates both + * selectedIndex and firstVisible so the selected item stays in view. + */ +export function useSelectableList( + itemCount: number, + visibleCount: number, + options?: UseSelectableListOptions, +) { + const [selectedIndex, setSelectedIndex] = useState(0); + const [firstVisible, setFirstVisible] = useState(0); + + const setSelection = useCallback( + (newIndex: number) => { + setSelectedIndex(newIndex); + setFirstVisible((prev) => + clampFirstVisible(prev, newIndex, visibleCount), + ); + }, + [visibleCount], + ); + + // Reset when the list reference changes (e.g. different server). + useEffect(() => { + if (options?.resetWhen !== undefined) { + setSelectedIndex(0); + setFirstVisible(0); + } + }, [options?.resetWhen]); + + // Clamp when list shrinks + useEffect(() => { + if (itemCount > 0 && selectedIndex >= itemCount) { + const newIndex = itemCount - 1; + setSelectedIndex(newIndex); + setFirstVisible((prev) => + clampFirstVisible(prev, newIndex, visibleCount), + ); + } + }, [itemCount, selectedIndex, visibleCount]); + + return { selectedIndex, firstVisible, setSelection }; +} diff --git a/clients/tui/src/logger.ts b/clients/tui/src/logger.ts new file mode 100644 index 000000000..0e9f63098 --- /dev/null +++ b/clients/tui/src/logger.ts @@ -0,0 +1,30 @@ +import path from "node:path"; +import pino from "pino"; +import type { Logger } from "pino"; + +let tuiLoggerInstance: Logger | undefined; + +/** + * TUI file logger for auth and InspectorClient events. + * Writes to ~/.mcp-inspector/auth.log so TUI console output is not corrupted. + * Lazy-initialized so --help and other early exits never open the log stream. + */ +export function getTuiLogger(): Logger { + if (!tuiLoggerInstance) { + const logDir = + process.env.MCP_INSPECTOR_LOG_DIR ?? + path.join( + process.env.HOME || process.env.USERPROFILE || ".", + ".mcp-inspector", + ); + const logPath = path.join(logDir, "auth.log"); + tuiLoggerInstance = pino( + { + name: "mcp-inspector-tui", + level: process.env.LOG_LEVEL ?? "info", + }, + pino.destination({ dest: logPath, append: true, mkdir: true }), + ); + } + return tuiLoggerInstance; +} diff --git a/clients/tui/src/tui-servers.ts b/clients/tui/src/tui-servers.ts new file mode 100644 index 000000000..8af17fdf7 --- /dev/null +++ b/clients/tui/src/tui-servers.ts @@ -0,0 +1,100 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import type { + InspectorServerSettings, + MCPServerConfig, + MCPConfig, + StdioServerConfig, +} from "@inspector/core/mcp/types.js"; +import { DEFAULT_TASK_TTL_MS } from "@inspector/core/mcp/types.js"; +import { mcpConfigToServerEntries } from "@inspector/core/mcp/serverList.js"; +import { + resolveServerConfigs, + withDefaultConfigPath, + type ServerConfigOptions, +} from "@inspector/core/mcp/node/config.js"; + +export type TuiServer = { + config: MCPServerConfig; + settings?: InspectorServerSettings; +}; + +export function headersToServerSettings( + headers?: Record, +): InspectorServerSettings | undefined { + if (!headers || Object.keys(headers).length === 0) { + return undefined; + } + return { + headers: Object.entries(headers).map(([key, value]) => ({ key, value })), + metadata: [], + connectionTimeout: 0, + requestTimeout: 0, + taskTtl: DEFAULT_TASK_TTL_MS, + autoRefreshOnListChanged: false, + roots: [], + }; +} + +function applyStdioOverrides( + config: MCPServerConfig, + overrides: { env?: Record; cwd?: string }, +): MCPServerConfig { + if (config.type !== "stdio") return config; + const c = { ...config } as StdioServerConfig; + if (overrides.env && Object.keys(overrides.env).length > 0) { + c.env = { ...(c.env ?? {}), ...overrides.env }; + } + if (overrides.cwd?.trim()) { + c.cwd = overrides.cwd.trim(); + } + return c; +} + +function mergeSettings( + base: InspectorServerSettings | undefined, + headers?: Record, +): InspectorServerSettings | undefined { + const fromHeaders = headersToServerSettings(headers); + if (!fromHeaders) return base; + if (!base) return fromHeaders; + return { ...base, headers: fromHeaders.headers }; +} + +export function loadTuiServers( + serverOptions: ServerConfigOptions & { headers?: Record }, +): Record { + serverOptions = withDefaultConfigPath(serverOptions); + const hasConfigPath = Boolean(serverOptions.configPath?.trim()); + + if (hasConfigPath) { + const configPath = serverOptions.configPath!; + const resolvedPath = resolve(process.cwd(), configPath); + const config = JSON.parse(readFileSync(resolvedPath, "utf-8")) as MCPConfig; + const entries = mcpConfigToServerEntries(config); + const result: Record = {}; + for (const entry of entries) { + result[entry.name] = { + config: applyStdioOverrides(entry.config, { + env: serverOptions.env, + cwd: serverOptions.cwd, + }), + settings: mergeSettings(entry.settings, serverOptions.headers), + }; + } + return result; + } + + const configs = resolveServerConfigs(serverOptions, "multi"); + if (configs.length === 0) { + throw new Error( + "At least one server is required. Use --config or ad-hoc target (command/URL).", + ); + } + return { + default: { + config: configs[0]!, + settings: headersToServerSettings(serverOptions.headers), + }, + }; +} diff --git a/clients/tui/src/utils/openUrl.ts b/clients/tui/src/utils/openUrl.ts new file mode 100644 index 000000000..a2a7d9639 --- /dev/null +++ b/clients/tui/src/utils/openUrl.ts @@ -0,0 +1,12 @@ +import open from "open"; + +/** + * Opens a URL in the user's default browser. + * Used when handling oauthAuthorizationRequired to launch the OAuth authorization page. + * + * @param url - URL to open (string or URL) + * @returns Promise that resolves when the opener completes (or rejects on error) + */ +export async function openUrl(url: string | URL): Promise { + await open(typeof url === "string" ? url : url.href); +} diff --git a/clients/tui/src/utils/promptArgsToForm.ts b/clients/tui/src/utils/promptArgsToForm.ts new file mode 100644 index 000000000..01da3d3e2 --- /dev/null +++ b/clients/tui/src/utils/promptArgsToForm.ts @@ -0,0 +1,47 @@ +/** + * Converts prompt arguments to ink-form format + */ + +import type { FormStructure, FormSection, FormField } from "ink-form"; +import type { PromptArgument } from "@modelcontextprotocol/sdk/types.js"; + +/** + * Converts prompt arguments array to ink-form structure + */ +export function promptArgsToForm( + promptArguments: PromptArgument[], + promptName: string, +): FormStructure { + const fields: FormField[] = []; + + if (!promptArguments || promptArguments.length === 0) { + return { + title: `Get Prompt: ${promptName}`, + sections: [{ title: "Parameters", fields: [] }], + }; + } + + for (const arg of promptArguments) { + const field: FormField = { + name: arg.name, + label: arg.name, + type: "string", // Prompt arguments are always strings + required: arg.required !== false, // Default to required unless explicitly false + description: arg.description, + }; + + fields.push(field); + } + + const sections: FormSection[] = [ + { + title: "Prompt Arguments", + fields, + }, + ]; + + return { + title: `Get Prompt: ${promptName}`, + sections, + }; +} diff --git a/clients/tui/src/utils/schemaToForm.ts b/clients/tui/src/utils/schemaToForm.ts new file mode 100644 index 000000000..c85bbff98 --- /dev/null +++ b/clients/tui/src/utils/schemaToForm.ts @@ -0,0 +1,137 @@ +/** + * Converts JSON Schema to ink-form format + */ + +import type { FormStructure, FormSection, FormField } from "ink-form"; + +/** Minimal JSON Schema property shape used when building tool parameter forms */ +interface JsonSchemaProperty { + type?: string; + title?: string; + enum?: unknown[]; + items?: { enum?: unknown[] }; + minimum?: number; + maximum?: number; + default?: unknown; +} + +/** Minimal JSON Schema object shape (properties + required) */ +interface JsonSchemaObject { + properties?: Record; + required?: string[]; +} + +/** + * Converts a JSON Schema to ink-form structure + */ +export function schemaToForm( + schema: JsonSchemaObject | null | undefined, + toolName: string, +): FormStructure { + const fields: FormField[] = []; + + if (!schema || !schema.properties) { + return { + title: `Test Tool: ${toolName}`, + sections: [{ title: "Parameters", fields: [] }], + }; + } + + const properties = schema.properties || {}; + const required = schema.required || []; + + for (const [key, prop] of Object.entries(properties)) { + const property = prop as JsonSchemaProperty; + const baseField = { + name: key, + label: property.title || key, + required: required.includes(key), + }; + + let field: FormField; + + // Handle enum -> select + if (property.enum) { + if (property.type === "array" && property.items?.enum) { + // For array of enums, we'll use select but handle it differently + // Note: ink-form doesn't have multiselect, so we'll use select + field = { + type: "select", + ...baseField, + options: property.items.enum.map((val: unknown) => ({ + label: String(val), + value: String(val), + })), + } as FormField; + } else { + // Single select + field = { + type: "select", + ...baseField, + options: property.enum.map((val: unknown) => ({ + label: String(val), + value: String(val), + })), + } as FormField; + } + } else { + // Map JSON Schema types to ink-form types + switch (property.type) { + case "string": + field = { + type: "string", + ...baseField, + } as FormField; + break; + case "integer": + field = { + type: "integer", + ...baseField, + ...(property.minimum !== undefined && { min: property.minimum }), + ...(property.maximum !== undefined && { max: property.maximum }), + } as FormField; + break; + case "number": + field = { + type: "float", + ...baseField, + ...(property.minimum !== undefined && { min: property.minimum }), + ...(property.maximum !== undefined && { max: property.maximum }), + } as FormField; + break; + case "boolean": + field = { + type: "boolean", + ...baseField, + } as FormField; + break; + default: + // Default to string for unknown types + field = { + type: "string", + ...baseField, + } as FormField; + } + } + + // Set initial value from default (ink-form FormField allows initialValue for some types) + if (property.default !== undefined) { + (field as FormField & { initialValue?: unknown }).initialValue = + property.default; + } + + fields.push(field); + } + + const sections: FormSection[] = [ + { + title: "Parameters", + fields, + }, + ]; + + return { + title: `Test Tool: ${toolName}`, + sections, + }; +} diff --git a/clients/tui/src/utils/uriTemplateToForm.ts b/clients/tui/src/utils/uriTemplateToForm.ts new file mode 100644 index 000000000..f8d2ee10b --- /dev/null +++ b/clients/tui/src/utils/uriTemplateToForm.ts @@ -0,0 +1,47 @@ +/** + * Converts URI Template to ink-form format for resource templates + */ + +import type { FormStructure, FormSection, FormField } from "ink-form"; +import { UriTemplate } from "@modelcontextprotocol/sdk/shared/uriTemplate.js"; + +/** + * Converts a URI Template to ink-form structure + */ +export function uriTemplateToForm( + uriTemplate: string, + templateName: string, +): FormStructure { + const fields: FormField[] = []; + + try { + const template = new UriTemplate(uriTemplate); + const variableNames = template.variableNames || []; + + for (const variableName of variableNames) { + const field: FormField = { + name: variableName, + label: variableName, + type: "string", + required: false, // URI template variables are typically optional + }; + + fields.push(field); + } + } catch (error) { + // If parsing fails, return empty form + console.error("Failed to parse URI template:", error); + } + + const sections: FormSection[] = [ + { + title: "Template Variables", + fields, + }, + ]; + + return { + title: `Read Resource: ${templateName}`, + sections, + }; +} diff --git a/clients/tui/test-config.json b/clients/tui/test-config.json new file mode 100644 index 000000000..0738f3328 --- /dev/null +++ b/clients/tui/test-config.json @@ -0,0 +1 @@ +{ "servers": [] } diff --git a/clients/tui/tsconfig.json b/clients/tui/tsconfig.json new file mode 100644 index 000000000..531a0ddef --- /dev/null +++ b/clients/tui/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "./build", + "rootDir": ".", + "baseUrl": ".", + "paths": { + "@inspector/core/*": ["../../core/*"] + } + }, + "include": ["index.ts", "tui.tsx", "src/**/*"], + "exclude": ["node_modules", "**/*.test.ts", "build"] +} diff --git a/clients/tui/tsup.config.ts b/clients/tui/tsup.config.ts new file mode 100644 index 000000000..964e5b2bf --- /dev/null +++ b/clients/tui/tsup.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from 'tsup'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(dirname, '../..'); + +export default defineConfig({ + entry: ['index.ts'], + format: ['esm'], + outDir: 'build', + clean: true, + sourcemap: true, + target: 'node20', + platform: 'node', + noExternal: [/^@inspector\/core/], + external: [ + 'react', + 'ink', + 'ink-form', + 'ink-scroll-view', + 'open', + 'commander', + 'pino', + 'zustand', + '@modelcontextprotocol/sdk', + ], + esbuildOptions(options) { + options.alias = { + '@inspector/core': path.join(repoRoot, 'core'), + }; + options.jsx = 'automatic'; + }, +}); diff --git a/clients/tui/tui.tsx b/clients/tui/tui.tsx new file mode 100644 index 000000000..78e6d9e0e --- /dev/null +++ b/clients/tui/tui.tsx @@ -0,0 +1,206 @@ +#!/usr/bin/env node + +import { Command } from "commander"; +import { render } from "ink"; +import { + parseKeyValuePair, + parseHeaderPair, +} from "@inspector/core/mcp/node/index.js"; +import App from "./src/App.js"; +import { loadTuiServers } from "./src/tui-servers.js"; + +export async function runTui(args?: string[]): Promise { + const program = new Command(); + + program + .name("mcp-inspector-tui") + .description("Terminal UI for MCP Inspector") + .option( + "--config ", + "Path to MCP servers config file (or use ad-hoc server options below)", + ) + .option( + "-e ", + "Environment variables for stdio servers", + parseKeyValuePair, + {}, + ) + .option("--cwd ", "Working directory for stdio servers") + .option( + "--header ", + 'HTTP headers as "Name: Value"', + parseHeaderPair, + {}, + ) + .option( + "--client-id ", + "OAuth client ID (static client) for HTTP servers", + ) + .option( + "--client-secret ", + "OAuth client secret (for confidential clients)", + ) + .option( + "--client-metadata-url ", + "OAuth Client ID Metadata Document URL (CIMD) for HTTP servers", + ) + .option( + "--callback-url ", + "OAuth redirect/callback listener URL (default: http://127.0.0.1:0/oauth/callback)", + ) + .argument( + "[target...]", + "Command and args or URL for a single ad-hoc server (when not using --config)", + ) + .option( + "--transport ", + "Transport: stdio, sse, or http (ad-hoc only)", + ) + .option("--server-url ", "Server URL (ad-hoc only)") + .parse(args ?? process.argv); + + const options = program.opts() as { + config?: string; + e?: Record; + cwd?: string; + header?: Record; + clientId?: string; + clientSecret?: string; + clientMetadataUrl?: string; + callbackUrl?: string; + transport?: "stdio" | "sse" | "http"; + serverUrl?: string; + }; + const targetArgs = program.args as string[]; + + const serverOptions = { + configPath: options.config?.trim() || undefined, + target: targetArgs.length > 0 ? targetArgs : undefined, + cwd: options.cwd?.trim() || undefined, + env: options.e, + headers: options.header, + transport: options.transport, + serverUrl: options.serverUrl?.trim() || undefined, + }; + + const mcpServers = loadTuiServers(serverOptions); + + interface CallbackUrlConfig { + hostname: string; + port: number; + pathname: string; + } + + function parseCallbackUrl(raw?: string): CallbackUrlConfig { + if (!raw) { + return { hostname: "127.0.0.1", port: 0, pathname: "/oauth/callback" }; + } + let url: URL; + try { + url = new URL(raw); + } catch (err) { + throw new Error( + `Invalid callback URL: ${(err as Error)?.message ?? String(err)}`, + ); + } + if (url.protocol !== "http:") { + throw new Error("Callback URL must use http scheme"); + } + const hostname = url.hostname; + if (!hostname) { + throw new Error("Callback URL must include a hostname"); + } + const pathname = url.pathname || "/"; + let port: number; + if (url.port === "") { + port = 80; + } else { + port = Number(url.port); + if ( + !Number.isFinite(port) || + !Number.isInteger(port) || + port < 0 || + port > 65535 + ) { + throw new Error("Callback URL port must be between 0 and 65535"); + } + } + return { hostname, port, pathname }; + } + + let callbackUrlConfig: CallbackUrlConfig; + try { + callbackUrlConfig = parseCallbackUrl(options.callbackUrl); + } catch (err) { + if (err instanceof Error) { + throw err; + } + throw err; + } + + const ansiEraseSavedLines = new RegExp( + String.fromCharCode(0x1b) + "\\[3J", + "g", + ); + const originalWrite = process.stdout.write.bind(process.stdout); + process.stdout.write = function ( + chunk: string | Buffer, + encoding?: BufferEncoding | ((err?: Error) => void), + cb?: (err?: Error) => void, + ): boolean { + if (typeof chunk === "string") { + if (chunk.includes("\x1b[3J")) { + chunk = chunk.replace(ansiEraseSavedLines, ""); + } + } else if (Buffer.isBuffer(chunk)) { + if (chunk.includes("\x1b[3J")) { + let str = chunk.toString("utf8"); + str = str.replace(ansiEraseSavedLines, ""); + chunk = Buffer.from(str, "utf8"); + } + } + if (typeof encoding === "function") { + return ( + originalWrite as ( + chunk: string | Buffer, + cb?: (err?: Error) => void, + ) => boolean + )(chunk, encoding); + } + return ( + originalWrite as ( + chunk: string | Buffer, + encoding?: BufferEncoding, + cb?: (err?: Error) => void, + ) => boolean + )(chunk, encoding, cb); + }; + + if (process.stdout.isTTY) { + process.stdout.write("\x1b[?1049h"); + } + + const instance = render( + , + ); + + try { + await instance.waitUntilExit(); + if (process.stdout.isTTY) { + process.stdout.write("\x1b[?1049l"); + } + process.exit(0); + } catch (error: unknown) { + if (process.stdout.isTTY) { + process.stdout.write("\x1b[?1049l"); + } + console.error("TUI Error:", error); + process.exit(1); + } +} diff --git a/clients/tui/vitest.config.ts b/clients/tui/vitest.config.ts new file mode 100644 index 000000000..a54650553 --- /dev/null +++ b/clients/tui/vitest.config.ts @@ -0,0 +1,16 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vitest/config'; +import { vitestSharedPaths } from '../../vitest.shared.mts'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const { projectResolve } = vitestSharedPaths(dirname); + +export default defineConfig({ + resolve: projectResolve, + test: { + globals: false, + environment: 'node', + include: ['__tests__/**/*.test.ts'], + }, +}); diff --git a/clients/web/.gitignore b/clients/web/.gitignore index 2fc34ad64..9a08ebd62 100644 --- a/clients/web/.gitignore +++ b/clients/web/.gitignore @@ -8,7 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules -dist +build dist-ssr storybook-static coverage diff --git a/clients/web/package-lock.json b/clients/web/package-lock.json index 02ec7f3ba..a14dc7d55 100644 --- a/clients/web/package-lock.json +++ b/clients/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/inspector-web", - "version": "0.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector-web", - "version": "0.0.0", + "version": "2.0.0", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^8.0.0", @@ -23,6 +23,7 @@ "ajv": "^8.17.1", "atomically": "^2.1.1", "chokidar": "^4.0.3", + "commander": "^13.1.0", "hono": "^4.12.18", "open": "^10.2.0", "pino": "^9.14.0", @@ -34,6 +35,9 @@ "zod": "^4.3.6", "zustand": "^5.0.13" }, + "bin": { + "mcp-inspector-web": "build/index.js" + }, "devDependencies": { "@chromatic-com/storybook": "^5.0.1", "@eslint/js": "^9.39.4", @@ -62,6 +66,7 @@ "playwright": "^1.58.2", "prettier": "^3.8.1", "storybook": "^10.2.19", + "tsup": "^8.5.1", "typescript": "~5.9.3", "typescript-eslint": "^8.56.1", "vite": "^8.0.0", @@ -1597,9 +1602,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1616,9 +1618,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1635,9 +1634,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1654,9 +1650,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1673,9 +1666,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1883,9 +1873,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1903,9 +1890,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1923,9 +1907,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1943,9 +1924,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1963,9 +1941,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1983,9 +1958,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2093,6 +2065,356 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "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", @@ -2563,9 +2885,9 @@ "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==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -3421,6 +3743,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3648,6 +3977,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3657,6 +4002,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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", @@ -3888,6 +4243,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3895,6 +4259,23 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -4832,6 +5213,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -5518,6 +5911,16 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5783,9 +6186,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5807,9 +6207,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5831,9 +6228,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5855,9 +6249,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5913,12 +6304,35 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6996,6 +7410,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -7012,6 +7439,18 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -7392,6 +7831,16 @@ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", "license": "MIT" }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -7401,6 +7850,18 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/playwright": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", @@ -7487,6 +7948,49 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8084,6 +8588,51 @@ "dev": true, "license": "MIT" }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -8559,6 +9108,39 @@ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "license": "MIT" }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8590,6 +9172,29 @@ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/thread-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", @@ -8679,6 +9284,16 @@ "node": ">=6" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -8722,6 +9337,13 @@ "node": ">=6.10" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -8743,6 +9365,86 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tsup/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/tsup/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8820,6 +9522,13 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", diff --git a/clients/web/package.json b/clients/web/package.json index fd13fb1bc..d3f6c4594 100644 --- a/clients/web/package.json +++ b/clients/web/package.json @@ -1,11 +1,19 @@ { "name": "@modelcontextprotocol/inspector-web", "private": true, - "version": "0.0.0", + "version": "2.0.0", "type": "module", + "main": "build/index.js", + "bin": { + "mcp-inspector-web": "./build/index.js" + }, + "exports": { + ".": "./build/index.js" + }, "scripts": { "dev": "node --import ./server/quiet-config-warnings.mjs ./node_modules/vite/bin/vite.js", - "build": "tsc -b && vite build", + "build": "tsc -b && vite build && npm run build:runner", + "build:runner": "tsup --config tsup.runner.config.ts", "lint": "eslint .", "format": "prettier --write src", "format:check": "prettier --check src", @@ -38,6 +46,7 @@ "ajv": "^8.17.1", "atomically": "^2.1.1", "chokidar": "^4.0.3", + "commander": "^13.1.0", "hono": "^4.12.18", "open": "^10.2.0", "pino": "^9.14.0", @@ -77,6 +86,7 @@ "playwright": "^1.58.2", "prettier": "^3.8.1", "storybook": "^10.2.19", + "tsup": "^8.5.1", "typescript": "~5.9.3", "typescript-eslint": "^8.56.1", "vite": "^8.0.0", diff --git a/clients/web/server/run-web.ts b/clients/web/server/run-web.ts new file mode 100644 index 000000000..ed43ae204 --- /dev/null +++ b/clients/web/server/run-web.ts @@ -0,0 +1,161 @@ +/** + * Programmatic entry for the launcher and published `mcp-inspector --web`. + * Day-to-day dev still uses `npm run dev` (Vite CLI + buildWebServerConfigFromEnv). + * Standalone prod uses `node dist/server.js` (env-only, no argv). + */ + +import { resolve, join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { Command } from "commander"; +import type { MCPServerConfig } from "../../../core/mcp/types.ts"; +import { + resolveServerConfigs, + parseKeyValuePair, + parseHeaderPair, + type ServerConfigOptions, +} from "../../../core/mcp/node/config.ts"; +import { buildWebServerConfig } from "./web-server-config.js"; +import { startViteDevServer } from "./start-vite-dev-server.js"; +import { startHonoServer } from "./server.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +function ensureStdioCwd(config: MCPServerConfig): MCPServerConfig { + if ( + (config.type === "stdio" || config.type === undefined) && + config.command && + !config.cwd + ) { + return { ...config, cwd: resolve(process.cwd()) }; + } + return config; +} + +export async function runWeb(argv: string[]): Promise { + const program = new Command(); + + const argSeparatorIndex = argv.indexOf("--"); + let preArgs = argv; + let postArgs: string[] = []; + if (argSeparatorIndex !== -1) { + preArgs = argv.slice(0, argSeparatorIndex); + postArgs = argv.slice(argSeparatorIndex + 1); + } + + program + .name("mcp-inspector-web") + .description("Web UI for MCP Inspector") + .allowExcessArguments() + .allowUnknownOption() + .option( + "-e ", + "environment variables in KEY=VALUE format", + parseKeyValuePair, + {}, + ) + .option("--config ", "config file path") + .option("--server ", "server name from config file") + .option("--transport ", "transport type (stdio, sse, http)") + .option("--server-url ", "server URL for SSE/HTTP transport") + .option("--cwd ", "working directory for stdio server process") + .option( + "--header ", + 'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)', + parseHeaderPair, + {}, + ) + .option("--dev", "run in development mode (Vite)") + .parse(preArgs); + + const opts = program.opts() as { + config?: string; + server?: string; + e?: Record; + transport?: string; + serverUrl?: string; + cwd?: string; + header?: Record; + dev?: boolean; + }; + + const args = program.args; + const target = [...args, ...postArgs]; + const isDev = !!opts.dev; + + const hasServerInput = + opts.config || + target.length > 0 || + opts.serverUrl || + (opts.transport && opts.transport !== "stdio"); + + let initialMcpConfig: MCPServerConfig | null = null; + + if (hasServerInput) { + const serverOptions: ServerConfigOptions = { + configPath: opts.config, + serverName: opts.server, + target: target.length > 0 ? target : undefined, + transport: opts.transport as "stdio" | "sse" | "http" | undefined, + serverUrl: opts.serverUrl, + cwd: opts.cwd, + env: opts.e, + }; + + if (opts.header && Object.keys(opts.header).length > 0) { + console.warn( + "Warning: --header is accepted but initial HTTP headers are configured via server settings in the web UI (post-#1358).", + ); + } + + try { + const configs = resolveServerConfigs(serverOptions, "single"); + const config = configs[0]; + if (!config) { + console.error( + "Error: Could not resolve server config. Use --config and --server, or pass a command/URL.", + ); + process.exit(1); + } + initialMcpConfig = ensureStdioCwd(config); + } catch (err) { + const message = + err instanceof Error ? err.message : "Could not resolve server config."; + console.error("Error:", message); + process.exit(1); + } + } + + const webConfig = buildWebServerConfig({ initialMcpConfig }); + if (!isDev) { + webConfig.staticRoot = join(__dirname, "..", "dist"); + } + + console.log( + isDev + ? "Starting MCP inspector in development mode..." + : "Starting MCP inspector...", + ); + + let handle: { close(): Promise }; + + try { + if (isDev) { + handle = await startViteDevServer(webConfig); + } else { + handle = await startHonoServer(webConfig); + } + } catch (err) { + const message = + err instanceof Error ? err.message : "Web client failed to start."; + console.error("Error:", message); + process.exit(1); + } + + const shutdown = () => { + void handle.close().then(() => process.exit(0)); + }; + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); + + return new Promise(() => {}); +} diff --git a/clients/web/server/web-server-config.ts b/clients/web/server/web-server-config.ts index f3e834fdb..0c7184caf 100644 --- a/clients/web/server/web-server-config.ts +++ b/clients/web/server/web-server-config.ts @@ -149,10 +149,18 @@ export function printServerBanner( return url; } +export interface BuildWebServerConfigOptions { + initialMcpConfig?: MCPServerConfig | null; +} + /** - * Build WebServerConfig from process.env. Used when running server as standalone (e.g. node dist/server.js). + * Build WebServerConfig from process.env and optional initial MCP server config. + * Used by the launcher runner, Vite dev (`buildWebServerConfigFromEnv`), and prod standalone. */ -export function buildWebServerConfigFromEnv(): WebServerConfig { +export function buildWebServerConfig( + options: BuildWebServerConfigOptions = {}, +): WebServerConfig { + const { initialMcpConfig = null } = options; const port = parseInt(process.env.CLIENT_PORT ?? "6274", 10); const hostname = process.env.HOST ?? "localhost"; const baseUrl = `http://${hostname}:${port}`; @@ -163,8 +171,6 @@ export function buildWebServerConfigFromEnv(): WebServerConfig { (process.env[LEGACY_AUTH_TOKEN_ENV] as string | undefined) ?? ""); - const initialMcpConfig: MCPServerConfig | null = null; - const sandboxPort = resolveSandboxPort(); let logger: Logger | undefined; @@ -196,6 +202,13 @@ export function buildWebServerConfigFromEnv(): WebServerConfig { }; } +/** + * Build WebServerConfig from process.env only. Used when running server as standalone (e.g. node dist/server.js) or via `vite dev`. + */ +export function buildWebServerConfigFromEnv(): WebServerConfig { + return buildWebServerConfig({ initialMcpConfig: null }); +} + /** * Three-way resolution for `autoOpen`: * - explicit `MCP_AUTO_OPEN_ENABLED=true` → always open (overrides VITEST) diff --git a/clients/web/src/index.ts b/clients/web/src/index.ts new file mode 100644 index 000000000..dfb6c8cce --- /dev/null +++ b/clients/web/src/index.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node + +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { runWeb } from "../server/run-web.js"; + +export { runWeb }; + +const __filename = fileURLToPath(import.meta.url); +const isMain = + process.argv[1] !== undefined && + resolve(process.argv[1]) === resolve(__filename); + +if (isMain) { + runWeb(process.argv) + .then((code) => process.exit(code ?? 0)) + .catch((e) => { + console.error(e); + process.exit(1); + }); +} diff --git a/clients/web/src/test/core/logging/logger.test.ts b/clients/web/src/test/core/logging/logger.test.ts new file mode 100644 index 000000000..6a855215a --- /dev/null +++ b/clients/web/src/test/core/logging/logger.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from "vitest"; +import { silentLogger } from "../../../../../../core/logging/logger.js"; + +describe("silentLogger", () => { + it("reports silent level", () => { + expect(silentLogger.level).toBe("silent"); + }); + + it("accepts log calls at every level without throwing", () => { + expect(() => { + silentLogger.fatal("fatal"); + silentLogger.error("error"); + silentLogger.warn("warn"); + silentLogger.info("info"); + silentLogger.debug("debug"); + silentLogger.trace("trace"); + silentLogger.silent("silent"); + }).not.toThrow(); + }); + + it("child returns a logger that remains silent", () => { + const child = silentLogger.child({ component: "test" }); + expect(child.level).toBe("silent"); + expect(() => child.info("nested")).not.toThrow(); + expect(child.child()).toBe(child); + }); +}); diff --git a/clients/web/src/test/core/mcp/node/config.test.ts b/clients/web/src/test/core/mcp/node/config.test.ts index 8ecab6da0..baf04aca0 100644 --- a/clients/web/src/test/core/mcp/node/config.test.ts +++ b/clients/web/src/test/core/mcp/node/config.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { @@ -170,10 +170,16 @@ describe("resolveServerConfigs — single mode", () => { ).toThrow(/Only stdio transport can be used with local commands/); }); - it("rejects an empty target with no URL", () => { - expect(() => resolveServerConfigs({ target: [] }, "single")).toThrow( - /Target is required/, - ); + it("uses the default config path when no target or --config is given", () => { + const prevHome = process.env.HOME; + process.env.HOME = tempDir; + try { + expect(() => resolveServerConfigs({ target: [] }, "single")).toThrow( + /Config file not found/, + ); + } finally { + process.env.HOME = prevHome; + } }); it("loads a named server from config", () => { @@ -358,6 +364,26 @@ describe("resolveServerConfigs — multi mode", () => { expect(configs[0]?.type).toBe("stdio"); }); + it("loads the default config path when no args are given", () => { + const prevHome = process.env.HOME; + const homeDir = join(tempDir, "home"); + mkdirSync(homeDir, { recursive: true }); + const defaultConfig = join(homeDir, ".mcp-inspector", "mcp.json"); + mkdirSync(join(homeDir, ".mcp-inspector"), { recursive: true }); + writeFileSync( + defaultConfig, + JSON.stringify({ mcpServers: { a: { command: "a" } } }), + ); + process.env.HOME = homeDir; + try { + const configs = resolveServerConfigs({}, "multi"); + expect(configs).toHaveLength(1); + expect(configs[0]).toMatchObject({ type: "stdio", command: "a" }); + } finally { + process.env.HOME = prevHome; + } + }); + it("rejects ad-hoc flags alongside a config path", () => { writeFileSync( configPath, diff --git a/clients/web/src/test/integration/server/run-web.test.ts b/clients/web/src/test/integration/server/run-web.test.ts new file mode 100644 index 000000000..138458e4b --- /dev/null +++ b/clients/web/src/test/integration/server/run-web.test.ts @@ -0,0 +1,323 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { mkdtemp, writeFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const { mockClose, startViteDevServer, startHonoServer } = vi.hoisted(() => { + const mockClose = vi.fn().mockResolvedValue(undefined); + return { + mockClose, + startViteDevServer: vi.fn().mockResolvedValue({ close: mockClose }), + startHonoServer: vi.fn().mockResolvedValue({ close: mockClose }), + }; +}); + +vi.mock("../../../../server/start-vite-dev-server.js", () => ({ + startViteDevServer, +})); + +vi.mock("../../../../server/server.js", () => ({ + startHonoServer, +})); + +import * as nodeConfig from "../../../../../../core/mcp/node/config.js"; +import { runWeb } from "../../../../server/run-web.js"; + +describe("runWeb", () => { + let logLines: string[]; + let warnLines: string[]; + let errorLines: string[]; + let originalLog: typeof console.log; + let originalWarn: typeof console.warn; + let originalError: typeof console.error; + let exitSpy: ReturnType; + let exitCode: number | undefined; + const signalHandlers = new Map void>(); + + beforeEach(() => { + startViteDevServer.mockClear(); + startHonoServer.mockClear(); + mockClose.mockClear(); + signalHandlers.clear(); + exitCode = undefined; + + logLines = []; + warnLines = []; + errorLines = []; + originalLog = console.log; + originalWarn = console.warn; + originalError = console.error; + console.log = (...args: unknown[]) => { + logLines.push(args.map(String).join(" ")); + }; + console.warn = (...args: unknown[]) => { + warnLines.push(args.map(String).join(" ")); + }; + console.error = (...args: unknown[]) => { + errorLines.push(args.map(String).join(" ")); + }; + + vi.spyOn(process, "on").mockImplementation((event, handler) => { + if (event === "SIGINT" || event === "SIGTERM") { + signalHandlers.set(event, handler as () => void); + } + return process; + }); + + exitSpy = vi.spyOn(process, "exit").mockImplementation((code) => { + exitCode = Number(code); + if (code !== 0) { + throw new Error(`process.exit:${String(code)}`); + } + return undefined as never; + }); + }); + + afterEach(() => { + console.log = originalLog; + console.warn = originalWarn; + console.error = originalError; + exitSpy.mockRestore(); + vi.restoreAllMocks(); + }); + + async function expectServerStarted( + starter: typeof startHonoServer | typeof startViteDevServer, + ) { + await vi.waitFor(() => expect(starter).toHaveBeenCalledTimes(1)); + } + + it("starts Hono in production mode with no server args", async () => { + void runWeb(["node", "run-web"]); + await expectServerStarted(startHonoServer); + + expect(startHonoServer).toHaveBeenCalledWith( + expect.objectContaining({ initialMcpConfig: null }), + ); + expect(startHonoServer.mock.calls[0]?.[0]?.staticRoot).toMatch(/dist$/); + expect(logLines.some((l) => l.includes("Starting MCP inspector..."))).toBe( + true, + ); + }); + + it("starts Vite when --dev is set", async () => { + void runWeb(["node", "run-web", "--dev"]); + await expectServerStarted(startViteDevServer); + + expect(startViteDevServer).toHaveBeenCalledWith( + expect.objectContaining({ initialMcpConfig: null }), + ); + expect(logLines.some((l) => l.includes("development mode"))).toBe(true); + }); + + it("resolves an ad-hoc HTTP URL into initialMcpConfig", async () => { + void runWeb([ + "node", + "run-web", + "https://example.com/mcp", + "--transport", + "http", + ]); + await expectServerStarted(startHonoServer); + + expect(startHonoServer).toHaveBeenCalledWith( + expect.objectContaining({ + initialMcpConfig: expect.objectContaining({ + type: "streamable-http", + url: "https://example.com/mcp", + }), + }), + ); + }); + + it("appends positional args after -- to the ad-hoc target", async () => { + void runWeb([ + "node", + "run-web", + "--transport", + "http", + "--", + "https://example.com/mcp", + ]); + await expectServerStarted(startHonoServer); + + expect(startHonoServer).toHaveBeenCalledWith( + expect.objectContaining({ + initialMcpConfig: expect.objectContaining({ + url: "https://example.com/mcp", + }), + }), + ); + }); + + it("fills stdio cwd when omitted", async () => { + void runWeb(["node", "run-web", "node", "server.js"]); + await expectServerStarted(startHonoServer); + + const config = startHonoServer.mock.calls[0]?.[0]?.initialMcpConfig; + expect(config).toMatchObject({ + type: "stdio", + command: "node", + args: ["server.js"], + cwd: process.cwd(), + }); + }); + + it("warns when --header is passed", async () => { + void runWeb([ + "node", + "run-web", + "https://example.com/mcp", + "--transport", + "http", + "--header", + "Authorization: Bearer x", + ]); + await expectServerStarted(startHonoServer); + + expect( + warnLines.some((l) => l.includes("Warning: --header is accepted")), + ).toBe(true); + }); + + it("loads a named server from a config file", async () => { + const dir = await mkdtemp(join(tmpdir(), "run-web-test-")); + const configPath = join(dir, "mcp.json"); + await writeFile( + configPath, + JSON.stringify({ + mcpServers: { + api: { + type: "streamable-http", + url: "https://example.com/mcp", + }, + }, + }), + ); + + try { + void runWeb([ + "node", + "run-web", + "--config", + configPath, + "--server", + "api", + ]); + await expectServerStarted(startHonoServer); + + expect(startHonoServer).toHaveBeenCalledWith( + expect.objectContaining({ + initialMcpConfig: expect.objectContaining({ + type: "streamable-http", + url: "https://example.com/mcp", + }), + }), + ); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it("exits when server config resolution fails", async () => { + await expect( + runWeb(["node", "run-web", "--config", "/no/such/file.json"]), + ).rejects.toThrow("process.exit:1"); + expect(errorLines.some((l) => l.startsWith("Error:"))).toBe(true); + expect(startHonoServer).not.toHaveBeenCalled(); + }); + + it("exits when the web server fails to start", async () => { + startHonoServer.mockRejectedValueOnce(new Error("bind failed")); + + await expect(runWeb(["node", "run-web"])).rejects.toThrow("process.exit:1"); + expect(errorLines.some((l) => l.includes("bind failed"))).toBe(true); + }); + + it("shuts down via SIGINT after a successful start", async () => { + void runWeb(["node", "run-web"]); + await expectServerStarted(startHonoServer); + + const shutdown = signalHandlers.get("SIGINT"); + expect(shutdown).toBeDefined(); + expect(signalHandlers.get("SIGTERM")).toBeDefined(); + shutdown!(); + await vi.waitFor(() => expect(mockClose).toHaveBeenCalled()); + await vi.waitFor(() => expect(exitCode).toBe(0)); + }); + + it("preserves an existing stdio cwd", async () => { + const dir = await mkdtemp(join(tmpdir(), "run-web-cwd-")); + const configPath = join(dir, "mcp.json"); + await writeFile( + configPath, + JSON.stringify({ + mcpServers: { + local: { + type: "stdio", + command: "node", + args: ["server.js"], + cwd: "/custom/cwd", + }, + }, + }), + ); + + try { + void runWeb([ + "node", + "run-web", + "--config", + configPath, + "--server", + "local", + ]); + await expectServerStarted(startHonoServer); + + expect( + startHonoServer.mock.calls[0]?.[0]?.initialMcpConfig, + ).toMatchObject({ + cwd: "/custom/cwd", + }); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it("resolves --server-url without a positional target", async () => { + void runWeb([ + "node", + "run-web", + "--server-url", + "https://example.com/mcp", + "--transport", + "http", + ]); + await expectServerStarted(startHonoServer); + + expect(startHonoServer).toHaveBeenCalledWith( + expect.objectContaining({ + initialMcpConfig: expect.objectContaining({ + url: "https://example.com/mcp", + }), + }), + ); + }); + + it("exits when resolveServerConfigs returns no entries", async () => { + vi.spyOn(nodeConfig, "resolveServerConfigs").mockReturnValueOnce([]); + + await expect( + runWeb([ + "node", + "run-web", + "https://example.com/mcp", + "--transport", + "http", + ]), + ).rejects.toThrow("process.exit:1"); + expect( + errorLines.some((l) => l.includes("Could not resolve server config")), + ).toBe(true); + }); +}); diff --git a/clients/web/src/test/integration/server/web-server-config.test.ts b/clients/web/src/test/integration/server/web-server-config.test.ts index 295d1dabe..71d7aecf4 100644 --- a/clients/web/src/test/integration/server/web-server-config.test.ts +++ b/clients/web/src/test/integration/server/web-server-config.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { + buildWebServerConfig, buildWebServerConfigFromEnv, printServerBanner, webServerConfigToInitialPayload, @@ -192,6 +193,39 @@ describe("buildWebServerConfigFromEnv", () => { }); }); +describe("buildWebServerConfig", () => { + it("matches buildWebServerConfigFromEnv when initialMcpConfig is omitted", () => { + process.env[API_SERVER_ENV_VARS.AUTH_TOKEN] = "shared"; + expect(buildWebServerConfig()).toEqual(buildWebServerConfigFromEnv()); + expect(buildWebServerConfig({ initialMcpConfig: null })).toEqual( + buildWebServerConfigFromEnv(), + ); + }); + + it("preserves a stdio initialMcpConfig while applying shared env defaults", () => { + process.env.CLIENT_PORT = "7000"; + const initialMcpConfig = { + type: "stdio" as const, + command: "node", + args: ["server.js"], + cwd: "/srv", + }; + const cfg = buildWebServerConfig({ initialMcpConfig }); + expect(cfg.port).toBe(7000); + expect(cfg.initialMcpConfig).toEqual(initialMcpConfig); + expect(cfg.allowedOrigins).toEqual(["http://localhost:7000"]); + }); + + it("preserves remote transport initialMcpConfig", () => { + const initialMcpConfig = { + type: "streamable-http" as const, + url: "https://example.com/mcp", + }; + const cfg = buildWebServerConfig({ initialMcpConfig }); + expect(cfg.initialMcpConfig).toEqual(initialMcpConfig); + }); +}); + describe("webServerConfigToInitialPayload", () => { it("returns only defaultEnvironment when initialMcpConfig is null", () => { const payload = webServerConfigToInitialPayload(baseConfig()); diff --git a/clients/web/tsconfig.node.json b/clients/web/tsconfig.node.json index 68fbf6621..a2ff33462 100644 --- a/clients/web/tsconfig.node.json +++ b/clients/web/tsconfig.node.json @@ -46,5 +46,5 @@ "@napi-rs/keyring": ["./node_modules/@napi-rs/keyring"] } }, - "include": ["vite.config.ts", "server/**/*.ts"] + "include": ["vite.config.ts", "tsup.runner.config.ts", "server/**/*.ts"] } diff --git a/clients/web/tsup.runner.config.ts b/clients/web/tsup.runner.config.ts new file mode 100644 index 000000000..67bd81dc6 --- /dev/null +++ b/clients/web/tsup.runner.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from 'tsup'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(dirname, '../..'); + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + outDir: 'build', + clean: true, + sourcemap: true, + target: 'node20', + platform: 'node', + noExternal: [/^@inspector\/core/], + external: [ + 'commander', + 'open', + 'pino', + 'hono', + '@hono/node-server', + 'vite', + '@vitejs/plugin-react', + 'zustand', + 'atomically', + 'chokidar', + '@napi-rs/keyring', + '@modelcontextprotocol/sdk', + ], + esbuildOptions(options) { + options.alias = { + '@inspector/core': path.join(repoRoot, 'core'), + }; + }, +}); diff --git a/clients/web/vite.config.ts b/clients/web/vite.config.ts index e702b2669..511b6ed4f 100644 --- a/clients/web/vite.config.ts +++ b/clients/web/vite.config.ts @@ -10,70 +10,10 @@ import { playwright } from '@vitest/browser-playwright'; import { honoMiddlewarePlugin } from './server/vite-hono-plugin'; import { getViteBaseConfig } from './server/vite-base-config'; import { buildWebServerConfigFromEnv } from './server/web-server-config'; +import { vitestSharedPaths } from '../../vitest.shared.mts'; const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); -const repoRoot = path.resolve(dirname, '../..'); - -// Aliases shared between the top-level resolve and the vitest projects. -// Vitest projects don't inherit `resolve` from the parent, so the unit and -// integration projects redeclare them — keeping a single source here prevents -// them from drifting (e.g. if a new core/* alias is added). -const sharedAliases = { - '@inspector/core': path.resolve(dirname, '../../core'), - // Point at the BUILT test-servers entry, not src/, so that any test which - // calls `getTestMcpServerPath()` (via `fileURLToPath(import.meta.url)`) - // resolves to a `.js` path Node can spawn directly as a subprocess. The - // integration tests build test-servers via `npm run test:integration` - // (or `npm run test-servers:build`) before running. - '@modelcontextprotocol/inspector-test-server': path.resolve(dirname, '../../test-servers/build/index.js'), -}; -const sharedDedupe = [ - 'react', - 'react-dom', - // The SDK is installed under both clients/web/node_modules and the repo - // root's node_modules (hoisted by npm). Without dedupe, source files in - // core/ (no local node_modules) resolve to the root copy while test files - // resolve to the clients/web copy — splitting class identity and breaking - // vi.mock() / instanceof checks (see #1307). - '@modelcontextprotocol/sdk', -]; - -// Bare-module aliases needed when running tests from repoRoot (which has no -// node_modules of its own). Shared between the unit and integration projects. -// Use anchored regex `find` patterns so each package's own `exports` field -// handles subpath resolution. -const nodeModulesAliases = [ - { find: /^react$/, replacement: path.resolve(dirname, 'node_modules/react') }, - { find: /^pino$/, replacement: path.resolve(dirname, 'node_modules/pino') }, - { find: /^pino\/browser\.js$/, replacement: path.resolve(dirname, 'node_modules/pino/browser.js') }, - { find: /^zustand$/, replacement: path.resolve(dirname, 'node_modules/zustand') }, - { find: /^zustand\/middleware$/, replacement: path.resolve(dirname, 'node_modules/zustand/middleware.js') }, - { find: /^zustand\/vanilla$/, replacement: path.resolve(dirname, 'node_modules/zustand/vanilla.js') }, - { find: /^hono$/, replacement: path.resolve(dirname, 'node_modules/hono/dist/index.js') }, - { find: /^hono\/streaming$/, replacement: path.resolve(dirname, 'node_modules/hono/dist/helper/streaming/index.js') }, - { find: /^@hono\/node-server$/, replacement: path.resolve(dirname, 'node_modules/@hono/node-server') }, - { find: /^atomically$/, replacement: path.resolve(dirname, 'node_modules/atomically') }, - { find: /^chokidar$/, replacement: path.resolve(dirname, 'node_modules/chokidar') }, - { find: /^@napi-rs\/keyring$/, replacement: path.resolve(dirname, 'node_modules/@napi-rs/keyring') }, - { find: /^express$/, replacement: path.resolve(dirname, 'node_modules/express') }, - { find: /^yaml$/, replacement: path.resolve(dirname, 'node_modules/yaml') }, - // Pin the SDK auth subpath so test and source resolve to the exact same - // module ID. Without this, the source's `import` from core/auth/*.ts and - // the test's `vi.mock(...)` can resolve through different cache keys in - // Vitest's transformer pipeline — the mock then fails to intercept the - // source-side import (see #1307). - { find: /^@modelcontextprotocol\/sdk\/client\/auth\.js$/, replacement: path.resolve(dirname, 'node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js') }, -]; - -// Project resolve config shared between the unit and integration projects. -// sharedAliases come first as exact-match entries, then the bare-module -// regex aliases that node-style imports from core/ rely on. -const projectResolve = { - alias: [ - ...Object.entries(sharedAliases).map(([find, replacement]) => ({ find, replacement })), - ...nodeModulesAliases, - ], - dedupe: sharedDedupe, -}; +const { repoRoot, sharedDedupe, nodeModulesAliases, projectResolve, sharedAliases } = + vitestSharedPaths(dirname); // Integration tests live under clients/web/src/test/integration/ and run in // the node-env vitest project below. The folder is the manifest: anything diff --git a/core/logging/index.ts b/core/logging/index.ts index 6abcce77a..907bfedff 100644 --- a/core/logging/index.ts +++ b/core/logging/index.ts @@ -1 +1 @@ -export { silentLogger } from "./logger.js"; +export { silentLogger, type InspectorLogger } from "./logger.js"; diff --git a/core/logging/logger.ts b/core/logging/logger.ts index 3b39a4361..e95ac93cc 100644 --- a/core/logging/logger.ts +++ b/core/logging/logger.ts @@ -1,7 +1,39 @@ -import pino from "pino"; +import type { Bindings, LevelWithSilentOrString, LogFn } from "pino"; /** - * Silent logger for use when no logger is injected. Satisfies pino.Logger, - * does not output anything. InspectorClient uses this as the default. + * Logging surface InspectorClient uses. Real pino loggers satisfy this; + * the default silent logger implements it without opening a stream. */ -export const silentLogger = pino({ level: "silent" }); +export interface InspectorLogger { + level: LevelWithSilentOrString; + fatal: LogFn; + error: LogFn; + warn: LogFn; + info: LogFn; + debug: LogFn; + trace: LogFn; + silent: LogFn; + child(bindings?: Bindings): InspectorLogger; +} + +const noop: LogFn = () => {}; + +function createSilentLogger(): InspectorLogger { + const logger: InspectorLogger = { + level: "silent", + fatal: noop, + error: noop, + warn: noop, + info: noop, + debug: noop, + trace: noop, + silent: noop, + child: () => logger, + }; + return logger; +} + +/** + * Default logger when none is injected. No-op at all levels; no SonicBoom stream. + */ +export const silentLogger: InspectorLogger = createSilentLogger(); diff --git a/core/mcp/inspectorClient.ts b/core/mcp/inspectorClient.ts index 0243761ec..34ad6650e 100644 --- a/core/mcp/inspectorClient.ts +++ b/core/mcp/inspectorClient.ts @@ -119,8 +119,7 @@ import { } from "./urlElicitation.js"; import type { AuthGuidedState, OAuthStep } from "../auth/types.js"; import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; -import type pino from "pino"; -import { silentLogger } from "../logging/logger.js"; +import { silentLogger, type InspectorLogger } from "../logging/logger.js"; import { createFetchTracker } from "./fetchTracking.js"; import { OAuthManager, type OAuthManagerConfig } from "./oauthManager.js"; @@ -197,7 +196,7 @@ export class InspectorClient extends InspectorClientEventTarget { private receiverTaskRecords: Map = new Map(); // OAuth support (config owned by oauthManager; client delegates and uses !!oauthManager for "is OAuth configured") private oauthManager: OAuthManager | null = null; - private logger: pino.Logger; + private logger: InspectorLogger; private transportClientFactory: CreateTransport; private fetchFn?: typeof fetch; private effectiveAuthFetch: typeof fetch; diff --git a/core/mcp/node/config.ts b/core/mcp/node/config.ts index 3a0899419..9c854e57f 100644 --- a/core/mcp/node/config.ts +++ b/core/mcp/node/config.ts @@ -1,5 +1,6 @@ import { existsSync, readFileSync } from "fs"; import { resolve } from "path"; +import { getDefaultMcpConfigPath } from "../../storage/store-io.js"; import type { MCPConfig, MCPServerConfig, @@ -211,6 +212,24 @@ function applyOverrides( export type ResolveServerConfigsMode = "single" | "multi"; +export function hasAdHocServerOptions(options: ServerConfigOptions): boolean { + return ( + (options.target != null && options.target.length > 0) || + Boolean(options.transport) || + Boolean(options.serverUrl?.trim()) + ); +} + +/** When no --config and no ad-hoc target, use ~/.mcp-inspector/mcp.json (same as web). */ +export function withDefaultConfigPath( + options: ServerConfigOptions, +): ServerConfigOptions { + if (options.configPath?.trim() || hasAdHocServerOptions(options)) { + return options; + } + return { ...options, configPath: getDefaultMcpConfigPath() }; +} + /** * Resolves server config(s) from options and mode. Used by all runners. * Single mode: one config (from file + overrides, or from args). @@ -220,11 +239,9 @@ export function resolveServerConfigs( options: ServerConfigOptions, mode: ResolveServerConfigsMode, ): MCPServerConfig[] { + options = withDefaultConfigPath(options); const hasConfigPath = Boolean(options.configPath?.trim()); - const hasAdHoc = - (options.target && options.target.length > 0) || - Boolean(options.transport) || - Boolean(options.serverUrl); + const hasAdHoc = hasAdHocServerOptions(options); if (mode === "single") { if (hasConfigPath && options.serverName) { diff --git a/core/mcp/types.ts b/core/mcp/types.ts index a432f3998..caeec1ba4 100644 --- a/core/mcp/types.ts +++ b/core/mcp/types.ts @@ -21,7 +21,7 @@ import type { import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; -import type pino from "pino"; +import type { InspectorLogger } from "../logging/logger.js"; import type { JsonValue } from "../json/jsonUtils.js"; import type { OAuthNavigation, @@ -524,7 +524,7 @@ export interface InspectorClientEnvironment { * - Node: pino file logger * - Browser: createRemoteLogger */ - logger?: pino.Logger; + logger?: InspectorLogger; /** * OAuth environment components diff --git a/package-lock.json b/package-lock.json index 9eeefeac7..0c77598c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,75 @@ "version": "2.0.0", "license": "MIT", "dependencies": { + "@hono/node-server": "^1.19.14", "@modelcontextprotocol/ext-apps": "^1.7.1", "@modelcontextprotocol/sdk": "^1.29.0", - "zod": "^4.3.6" + "@napi-rs/keyring": "^1.3.0", + "@vitejs/plugin-react": "^6.0.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "chokidar": "^4.0.3", + "commander": "^13.1.0", + "hono": "^4.12.18", + "ink": "^6.0.0", + "ink-form": "^2.0.1", + "ink-scroll-view": "^0.3.6", + "open": "^10.2.0", + "pino": "^9.14.0", + "react": "^19.2.4", + "vite": "^8.0.0", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "bin": { + "mcp-inspector": "clients/launcher/build/index.js" + }, + "engines": { + "node": ">=22.7.5" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@hono/node-server": { @@ -95,12 +161,547 @@ } } }, + "node_modules/@napi-rs/keyring": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring/-/keyring-1.3.0.tgz", + "integrity": "sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/keyring-darwin-arm64": "1.3.0", + "@napi-rs/keyring-darwin-x64": "1.3.0", + "@napi-rs/keyring-freebsd-x64": "1.3.0", + "@napi-rs/keyring-linux-arm-gnueabihf": "1.3.0", + "@napi-rs/keyring-linux-arm64-gnu": "1.3.0", + "@napi-rs/keyring-linux-arm64-musl": "1.3.0", + "@napi-rs/keyring-linux-riscv64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-musl": "1.3.0", + "@napi-rs/keyring-win32-arm64-msvc": "1.3.0", + "@napi-rs/keyring-win32-ia32-msvc": "1.3.0", + "@napi-rs/keyring-win32-x64-msvc": "1.3.0" + } + }, + "node_modules/@napi-rs/keyring-darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-arm64/-/keyring-darwin-arm64-1.3.0.tgz", + "integrity": "sha512-pl76hJvdYUBn6I24bXiOBMA9nbDapo3I5B+f3OorjDU4dUMSypXeKbOVehJe8fhgTiH24flMyTS3aAIy43xegQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-x64/-/keyring-darwin-x64-1.3.0.tgz", + "integrity": "sha512-YcJtEV5LA3cvA4z3BurgxH5IhTsW1JfIvcAAcqcecwk06Si9F9NqkxbZVIfDwQ8oRHgaBmT3zZJnLAotCrVahw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-freebsd-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-freebsd-x64/-/keyring-freebsd-x64-1.3.0.tgz", + "integrity": "sha512-vlLf31TGhfRAaxLDBhg8b89ss0HHD/lyNmL5F3UjSaz5CUXElsJmKYq9fqA/B+cZKUEUcLHHGhF0I/CqcFdaVw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm-gnueabihf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm-gnueabihf/-/keyring-linux-arm-gnueabihf-1.3.0.tgz", + "integrity": "sha512-KiWdMMu/Inz/bHHIAGrnF7r54FZDYXuHO6UFF/rhIrshUsxbMG1Rl9lEymNtqqsVo927G0VYcb02FzWQ3iBQRQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-gnu/-/keyring-linux-arm64-gnu-1.3.0.tgz", + "integrity": "sha512-eyKGpY40lm9Jvs1aD294XRH4y7+TlJM0YVAryZeXA6TX0mb4gMkxVXwSQv7MCwgah7raeUd0dKUb4BPAYIgcMg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-musl/-/keyring-linux-arm64-musl-1.3.0.tgz", + "integrity": "sha512-iIK6JWHXAJqDrEyLY3TmswwloVyt2vj+04TZnew+uSJ9gnDO8EwRbp3/iw3LpWaXiDO7VomGO6y8I0Id8uBZSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-riscv64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-riscv64-gnu/-/keyring-linux-riscv64-gnu-1.3.0.tgz", + "integrity": "sha512-/PGqrwn6EwgtK6vccASSXJRfOSP4vN1F4ASsIQ+7MdrK6hNvAJ1FZPrIuD5gGGdxezo3F++To2Wq7DbuGIeuNQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-gnu/-/keyring-linux-x64-gnu-1.3.0.tgz", + "integrity": "sha512-2PDK1WKWTu9lBGq9VvNEkSlQD3O7YwVpmnyN2M3cy4v7NJ/8gDMd9GXv3G+FVXN13uhp4gnnPBS+ScefmEeD2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-musl/-/keyring-linux-x64-musl-1.3.0.tgz", + "integrity": "sha512-oJ2HkX8YUo46QBkn0pG+HuIKQNqr523q6vBobCn+P95s4C4K6/kLBqHY/1bg5J4ap31DzsznhnFKcfBNBsjCnw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-arm64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-arm64-msvc/-/keyring-win32-arm64-msvc-1.3.0.tgz", + "integrity": "sha512-tOd3c/uAaeoE4ycVlmAdSvygz0Zt3zdca6Y7gokBeIbaRDWpjDIUOpU3MvML59XAaqyuKGsVVu0F/DZb1lHPmw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-ia32-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-ia32-msvc/-/keyring-win32-ia32-msvc-1.3.0.tgz", + "integrity": "sha512-sPSqeAFZMGqP1R++M2JTza7GQJJ/TpCo6JU6Vcd4jnebvOaEDs9b7eipakU1PJdSvhpC2yXMCNRk9gXfrhuwHQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-x64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-x64-msvc/-/keyring-win32-x64-msvc-1.3.0.tgz", + "integrity": "sha512-4DnCWXwDc0HRKwyRlG5y0VhKZW2tNRQfKKfyj6IX/KWfDNyq9hn4n+GL1auyDcOO/v8PwnhmYo2+rOOqCkvvOg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, "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==", "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -147,6 +748,76 @@ } } }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -164,49 +835,155 @@ "type-is": "^2.0.1" }, "engines": { - "node": ">=18" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, "engines": { - "node": ">= 0.8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" }, "engines": { - "node": ">= 0.4" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "convert-to-spaces": "^2.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/content-disposition": { @@ -231,6 +1008,15 @@ "node": ">= 0.6" } }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -297,6 +1083,46 @@ } } }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -306,6 +1132,15 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -326,6 +1161,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -335,6 +1176,18 @@ "node": ">= 0.8" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -365,12 +1218,31 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", + "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "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/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -484,6 +1356,38 @@ ], "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==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -523,6 +1427,20 @@ "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==", + "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", @@ -532,6 +1450,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -606,9 +1536,9 @@ } }, "node_modules/hono": { - "version": "4.12.12", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", - "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -640,46 +1570,267 @@ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-form": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ink-form/-/ink-form-2.0.1.tgz", + "integrity": "sha512-vo0VMwHf+HOOJo7026K4vJEN8xm4sP9iWlQLx4bngNEEY5K8t30CUvVjQCCNAV6Mt2ODt2Aq+2crCuBONReJUg==", + "license": "MIT", + "dependencies": { + "ink-select-input": "^5.0.0", + "ink-text-input": "^6.0.0" + }, + "peerDependencies": { + "ink": ">=4", + "react": ">=18" + } + }, + "node_modules/ink-scroll-view": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/ink-scroll-view/-/ink-scroll-view-0.3.7.tgz", + "integrity": "sha512-lUBLxSbVry/+UtJhuvRu6wumP43+ScVp69J7e+hmYwz1kTkahWfkVwWOu7Mn1DMPb8AU8bnsmEsr6kvSJvnaRw==", + "license": "MIT", + "peerDependencies": { + "ink": "^5 || ^6 || ^7", + "react": "^18 || ^19" + } + }, + "node_modules/ink-select-input": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ink-select-input/-/ink-select-input-6.2.0.tgz", + "integrity": "sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ==", + "license": "MIT", + "dependencies": { + "figures": "^6.1.0", + "to-rotated": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ink-text-input": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz", + "integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5", + "react": ">=18" + } + }, + "node_modules/ink-text-input/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "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-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, "engines": { - "node": ">= 0.10" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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", @@ -707,6 +1858,255 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -762,12 +2162,39 @@ "url": "https://opencollective.com/express" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "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.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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", @@ -798,6 +2225,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -819,6 +2255,39 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -828,6 +2297,15 @@ "node": ">= 0.8" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -847,6 +2325,61 @@ "url": "https://opencollective.com/express" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -856,6 +2389,50 @@ "node": ">=16.20.0" } }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -884,6 +2461,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -905,7 +2488,53 @@ "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" } }, "node_modules/require-from-string": { @@ -917,6 +2546,55 @@ "node": ">=0.10.0" } }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -933,12 +2611,39 @@ "node": ">= 18" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -1083,6 +2788,67 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -1092,6 +2858,113 @@ "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-rotated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-rotated/-/to-rotated-1.0.0.tgz", + "integrity": "sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1101,6 +2974,28 @@ "node": ">=0.6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-fest": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", + "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -1133,6 +3028,89 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1148,12 +3126,103 @@ "node": ">= 8" } }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", @@ -1171,6 +3240,35 @@ "peerDependencies": { "zod": "^3.25.28 || ^4" } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index ceb784e65..891c75ac3 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,58 @@ "license": "MIT", "author": "The MCP Maintainers and Community", "type": "module", - "main": "./build/index.js", + "bin": { + "mcp-inspector": "./clients/launcher/build/index.js" + }, + "files": [ + "clients/launcher/build", + "clients/web/build", + "clients/web/dist", + "clients/cli/build", + "clients/tui/build" + ], "scripts": { + "build": "npm run build:web && npm run build:cli && npm run build:tui && npm run build:launcher", + "build:web": "cd clients/web && npm run build", + "build:cli": "cd clients/cli && npm run build", + "build:tui": "cd clients/tui && npm run build", + "build:launcher": "cd clients/launcher && npm run build", + "prepack": "npm run build", + "test:cli": "cd clients/cli && npm run test", + "test:tui": "cd clients/tui && npm run test", "validate": "cd ./clients/web/ && npm run validate", - "test": "cd ./clients/web/ && npm run test", + "test": "cd ./clients/web/ && npm run test && cd ../cli && npm run test && cd ../tui && npm run test", "test:coverage": "cd ./clients/web/ && npm run test:coverage", - "test:storybook": "cd ./clients/web/ && npm run test:storybook" + "test:storybook": "cd ./clients/web/ && npm run test:storybook", + "inspector": "node clients/launcher/build/index.js", + "web": "node clients/launcher/build/index.js --web", + "web:dev": "node clients/launcher/build/index.js --web --dev" }, "dependencies": { + "@hono/node-server": "^1.19.14", "@modelcontextprotocol/ext-apps": "^1.7.1", "@modelcontextprotocol/sdk": "^1.29.0", - "zod": "^4.3.6" + "@napi-rs/keyring": "^1.3.0", + "@vitejs/plugin-react": "^6.0.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "chokidar": "^4.0.3", + "commander": "^13.1.0", + "hono": "^4.12.18", + "ink": "^6.0.0", + "ink-form": "^2.0.1", + "ink-scroll-view": "^0.3.6", + "open": "^10.2.0", + "pino": "^9.14.0", + "react": "^19.2.4", + "vite": "^8.0.0", + "zod": "^4.3.6", + "zustand": "^5.0.13" + }, + "overrides": { + "ink-select-input": "^6.2.0" + }, + "engines": { + "node": ">=22.7.5" } } diff --git a/specification/v2_catalog_launch_config.md b/specification/v2_catalog_launch_config.md new file mode 100644 index 000000000..bf451286e --- /dev/null +++ b/specification/v2_catalog_launch_config.md @@ -0,0 +1,520 @@ +# Inspector V2 Tech Stack - Storage - Catalog and Launch Configuration + +### [Brief](README.md) | [V1 Problems](v1_problems.md) | [V2 Scope](v2_scope.md) | V2 Tech Stack | [V2 UX](v2_ux.md) + +#### [Web Client](v2_web_client.md) | [Server](v2_server.md) | [Storage](v2_storage.md) +##### [Overview](v2_storage.md) | [Server List File](v2_servers_file.md) | Catalog and Launch + +## Table of Contents + + * [Summary](#summary) + * [Goals](#goals) + * [Non-goals](#non-goals) + * [Related specifications](#related-specifications) + * [Terminology](#terminology) + * [User intents (UC1–UC5)](#user-intents-uc1uc5) + * [Flag reference](#flag-reference) + * [Background (v1.5)](#background-v15) + * [Normalization](#normalization) + * [Cross-client list contract](#cross-client-list-contract) + * [Launch behavior by client](#launch-behavior-by-client) + * [Import (UC2)](#import-uc2) + * [Write-back policy](#write-back-policy) + * [Web launch behavior](#web-launch-behavior) + * [Target API (core)](#target-api-core) + * [Open decisions](#open-decisions) + * [Known gaps](#known-gaps) + * [Decision matrix](#decision-matrix) + * [Engineering follow-ups](#engineering-follow-ups) + * [Related GitHub issues](#related-github-issues) + * [References](#references) + +## Summary + +v2 treats `~/.mcp-inspector/mcp.json` as the default **catalog** — a persistent, Inspector-owned grouping of MCP servers. The web client loads and mutates it via `/api/servers`. CLI and TUI resolve servers from that path when launch args omit both `--config` and ad-hoc targets (`withDefaultConfigPath()` in `core/mcp/node/config.ts`). + +This specification defines **launch-time configuration** across web, CLI, and TUI: how `--catalog`, `--config`, `--server`, and ad-hoc flags select a server list; which paths are writable; and how import differs from session launch. On-disk catalog format and web CRUD are specified in [Server List File](v2_servers_file.md). CLI/TUI/launcher build and per-client implementation gaps are in [CLI, TUI, and Launcher](v2_cli_tui_launcher.md). + +## Goals + +- One **catalog** model shared by web, CLI, and TUI, with a clear split between **catalog modes** (writable, UC1/UC4) and **session mode** (read-only, UC3). +- **`--catalog`** selects the active writable catalog; **`--config`** without catalog intent supplies a read-only session file — not overloaded. +- All reads **parse → normalize → lift** into canonical `ServerEntry[]`; catalog writes serialize through the same contract as [Server List File](v2_servers_file.md). +- **List clients** (web, TUI) consume the same resolved list plus `writable`, `defaultServerId`, and optional `autoConnect` — resolution in **core**, not the launcher. +- **Import** is an explicit catalog mutation (`servers/import` on CLI; web Import menu) — never a side effect of launch. +- **CLI** resolves a single server through the same normalizer as list clients, including disk **settings** lift. +- **Web** follows **catalog-first** launch: full catalog visible; `--server` selects an entry; ad-hoc launches do not auto-persist. + +## Non-goals + +- Syncing with Claude Desktop, Cursor, or other tools' config paths (symlinking is the user's choice — see [Server List File](v2_servers_file.md)). +- Silent write-back of launch overrides (`-e`, `--cwd`, `--header`) to catalog or session files. +- Launcher as config arbitrator — it routes mode and forwards argv only. +- Requiring import support on all three clients at startup (UC2 is CLI + web UI). +- Auto-importing ad-hoc launches into the catalog. +- File watching or two-way sync with external MCP client configs. + +## Related specifications + +| Document | Owns | +|----------|------| +| [v2_servers_file.md](v2_servers_file.md) | Catalog path, on-disk shape, web CRUD, `/api/servers`, per-server settings | +| [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md) | Bins, tsup, tests, `runWeb`, CLI config-file settings gap (G1) | +| [v2_ux.md](v2_ux.md) | Import config vs Import server.json menu UX | +| v1.5 `docs/mcp-server-configuration.md` | Baseline shared launch flags (port to v2 pending, G6) | + +--- + +## Terminology + +| Term | Meaning | +|------|---------| +| **Catalog** | Inspector-owned `mcp.json` used as a persistent server grouping. Default: `~/.mcp-inspector/mcp.json`. Active catalog selected by `--catalog ` or default (UC1/UC4). CRUD when `writable: true`. | +| **`--catalog`** | Selects the **active catalog** for this run (`writable: true`). Omitted → default path (UC1). | +| **Alternate config file** | Any JSON passed via `--config ` without catalog intent. **Read-only, run-scoped** server set (UC3). Inspector does not write to this path. | +| **Canonical catalog format** | Normalized `MCPConfig` / `ServerEntry[]` after `normalizeServerType`, `normalizeMcpServers`, and `mcpConfigToServerEntries` — the only shape written to a catalog. | +| **Catalog lookup** | `--server ` with no `--config` and no ad-hoc target. `withDefaultConfigPath()` injects the catalog path, then the named entry is resolved. | +| **Ad-hoc / inline config** | Positional command/URL and/or `--transport`, `--server-url`, `-e`, `--cwd`, `--header` without `--config`. Ephemeral unless the user persists via CRUD or import. | +| **Launch-time initial config (web)** | `initialMcpConfig` from `runWeb(argv)` → `GET /api/config`. Legacy v1.5 channel; largely unused by the v2 web UI today (G2). | + +--- + +## User intents (UC1–UC5) + +Five distinct user goals. They share flags (`--config`, `--server`, `--catalog`) but differ in **whether the list is the catalog, read-only, or mutated**, and **which client** owns each action. + +### UC1 — Default catalog: run and manage servers in place + +**Intent:** Use the usual server list at `~/.mcp-inspector/mcp.json` — the default active catalog (UC4 with default path). Add, edit, remove, and configure settings through the UX; CLI invokes methods against a named catalog entry. + +| | Today | Target | +|---|--------|--------| +| **Web** | Works — `useServers` + `/api/servers` CRUD | Same | +| **TUI** | Loads default catalog via `withDefaultConfigPath` in `loadTuiServers`; **no CRUD UX yet** | Catalog CRUD in TUI (planned) | +| **CLI** | `--server foo --method …` with no `--config` resolves against default catalog | Same; lift settings from catalog entries (G1) | + +```bash +mcp-inspector --web +mcp-inspector --cli --server my-api --method tools/list +mcp-inspector --tui +``` + +The catalog is both the **data source** and the **write target** (`writable: true`). + +### UC2 — Import into a catalog (automation / one-off CLI) + +**Intent:** Add servers **into** an Inspector catalog (default or `--catalog` target). Distinct from “launch and use for this session” (UC3). Import is a **catalog write**, not a launch mode. + +| Source | CLI shape | Stored | +|--------|-----------|--------| +| **A — `mcpServers` file** | `--config /path/to/source.json` [ `--server ` ] `--method servers/import` | One or all entries, canonicalized | +| **B — Ad-hoc parts** | Same argv as ad-hoc launch (`[target...]`, `--transport`, `--server-url`, `-e`, `--cwd`, `--header`, …); no `--config`. Method → `servers/import`. | One entry; **`--server `** = catalog map key (required for B) | + +```bash +# A: import all from a foreign mcp.json +mcp-inspector --cli --config /path/to/claude.json --method servers/import + +# A: one named entry from source file (--server = name in source) +mcp-inspector --cli --config /path/to/claude.json --server acme --method servers/import + +# B: stdio — launch vs import differ only by --method and --server (catalog id) +mcp-inspector --cli \ + -e API_KEY=dev --cwd /path/to/server \ + node ./build/index.js --method tools/list + +mcp-inspector --cli \ + --server my-local \ + -e API_KEY=dev --cwd /path/to/server \ + node ./build/index.js --method servers/import + +# B: streamable HTTP +mcp-inspector --cli \ + --transport streamable-http \ + --server-url https://api.example.com/mcp \ + --header "Authorization: Bearer xxx" \ + --method tools/list + +mcp-inspector --cli \ + --server staging-api \ + --transport streamable-http \ + --server-url https://api.example.com/mcp \ + --header "Authorization: Bearer xxx" \ + --method servers/import + +# Into a non-default catalog (UC4) +mcp-inspector --cli --catalog ./project/.mcp-inspector/mcp.json \ + --config ./vendor.json --method servers/import +``` + +**Not UC2:** `mcp-inspector --cli node ./server.js --method tools/list` without `servers/import` is UC3/UC1 (use now), not import. + +| | Today | Target | +|---|--------|--------| +| **CLI** | Not implemented | `servers/import` for A and B via `importServersIntoCatalog` in core | +| **Web** | Import menu items stubbed | Interactive import per [v2_ux.md](v2_ux.md); [#1348](https://github.com/modelcontextprotocol/inspector/issues/1348), [#1435](https://github.com/modelcontextprotocol/inspector/issues/1435) | +| **TUI** | N/A | No requirement | + +See [Import (UC2)](#import-uc2) for behavior rules. + +### UC3 — Launch with a specific config (session list, not the catalog) + +**Intent:** Run against **another** server set for **this session only** — ad-hoc (one server from command/URL) or file (all servers in TUI; one selected by `--server` for CLI). User sees those servers in list UI; Add/Edit/Remove must not mutate the file (`writable: false`). + +| | Today | Target | +|---|--------|--------| +| **TUI** | Works for `--config ` and ad-hoc via `loadTuiServers` | Same + explicit `writable: false` for non-catalog paths | +| **Web** | UI always shows default catalog; `runWeb --config` only sets legacy `initialMcpConfig` | Session `mcpConfigPath` → `/api/servers` serves that file; `writable: false`; disable CRUD | +| **CLI** | Single resolved server (file + `--server`, ad-hoc, or default catalog) | Same; shared normalizer + settings lift | + +```bash +mcp-inspector --tui --config ~/Library/Application\ Support/Claude/claude_desktop_config.json +mcp-inspector --cli --config ./project/mcp.json --server local --method tools/list +mcp-inspector --cli node ./server.js --method tools/list +``` + +Launching with `--config` does **not** import unless the user separately runs UC2. + +### UC4 — Choose which catalog (grouping), not only the default + +**Intent:** Catalogs **group** MCP configs for reuse. Most users only use the default (UC1). Power users switch catalogs — per-project files, work vs personal, repo-local `.mcp-inspector/mcp.json`. + +**`--catalog `** selects the **active catalog** for this run. Omitting it → default (UC1). + +```bash +mcp-inspector --web +mcp-inspector --web --catalog ./.mcp-inspector/mcp.json +mcp-inspector --tui --catalog ~/work/mcp.json +mcp-inspector --cli --catalog ~/work/mcp.json --server api --method tools/list +``` + +**Distinction from UC3:** UC3 uses **`--config`** (or ad-hoc) for a foreign/session file — show servers, **do not write**. UC4 uses **`--catalog`** — this file **is** the Inspector catalog for this run. + +| | Today | Target | +|---|--------|--------| +| **Web** | Only default catalog — backend always `getDefaultMcpConfigPath()` | `runWeb --catalog ` → `mcpConfigPath`; CRUD on active catalog | +| **TUI** | `--config` loads a file; no catalog vs session distinction | `--catalog` = writable active catalog; `--config` without `--catalog` → UC3 | +| **CLI** | `--config` for one-shot reads; default path when only `--server` | `--catalog` scopes UC1/UC2/UC5; `--config` remains session-only | + +**Env alternative:** `MCP_CATALOG_PATH` — same semantics as `--catalog` for scripts/CI. + +### UC5 — Default selection within any list + +**Intent:** Among loaded servers (catalog or session), focus one entry — required for CLI (one server), useful for web/TUI. + +| | Today | Target | +|---|--------|--------| +| **CLI** | `--server` with catalog (UC1) or `--config` file (UC3) | Same | +| **TUI** | All servers shown; no `--server` flag | Optional highlight / initial selection | +| **Web** | Ignores `--server` for list/selection | `defaultServerId` from launch + optional auto-connect ([#1183](https://github.com/modelcontextprotocol/inspector/issues/1183)) | + +### Use-case map (quick reference) + +| UC | User goal | How selected | Writable? | Primary clients | Import? | +|----|-----------|--------------|-----------|-----------------|---------| +| **1** | Manage default catalog | (no `--catalog`) | Yes | Web (now), TUI (CRUD planned), CLI | No | +| **2** | Add servers to a catalog | `--method servers/import` | Writes **active catalog** only | CLI; web UI | Yes | +| **3** | Session with external config | `--config` or ad-hoc | **No** | TUI (now), Web (gap), CLI | No | +| **4** | Switch / use another catalog | `--catalog ` | Yes (that path) | Web/TUI (gaps) | Via UC2 | +| **5** | Pick one server by name | `--server` | — | CLI (required), Web/TUI (hint) | — | + +**Design principle:** UC1 is UC4 with the default path. Catalog modes (UC1/UC4): `writable: true`. Session mode (UC3): `writable: false`. UC2 mutates the active catalog only — not a launch side-effect on all clients. + +--- + +## Flag reference + +Shared launch flags (full detail in v1.5 `docs/mcp-server-configuration.md`, port pending): + +| Flag | UC1/UC4 (catalog) | UC3 (session) | UC2 (import) | +|------|-------------------|---------------|--------------| +| `--catalog ` | Active writable catalog | — | Target catalog for write | +| `--config ` | — | Read-only session file | **Source** file (A only) | +| `--server ` | Catalog entry id (UC5) | Entry in session file | Source name (A) or new catalog id (B) | +| `[target...]`, `-e`, `--cwd`, `--transport`, `--server-url`, `--header` | Ad-hoc overrides on connect | Ad-hoc session server | Same argv as launch (B) | + +**`--server` overload (by context — no new flags):** + +| Context | `--server` means | +|---------|------------------| +| `servers/import` + `--config` | Entry to copy **from source file** | +| `servers/import` + ad-hoc (no `--config`) | Catalog **id** for new entry | +| `tools/list` (etc.) + no `--config` | Lookup **from active catalog** | +| `tools/list` + `--config` | Lookup **from session file** | + +Optional import flags (target): `--on-conflict skip|error|replace`. + +--- + +## Background (v1.5) + +v1.5 (`docs/mcp-server-configuration.md`) had no default catalog: omitting `--config` and positional args failed resolution. Config-file `headers` lived on `MCPServerConfig`. Web had no file-backed list — launcher argv became `initialMcpConfig` for form pre-fill only. v2 adds the persistent catalog ([Server List File](v2_servers_file.md)), flat on-disk settings (post-#1358), and `withDefaultConfigPath()` for CLI/TUI/web runners. + +--- + +## Normalization + +External config files (Claude Desktop, Cursor, hand-edited JSON) are not reliably conformant. **Read path:** always parse → normalize → lift; never connect from raw file bytes. + +| Stage | Where | What | +|-------|-------|------| +| Parse | `loadMcpServersConfig` / backend JSON parse | Structural check; tolerate unknown keys | +| Normalize transport | `normalizeServerType` (`core/mcp/serverList.ts`) | Missing type → `stdio`; `http` → `streamable-http` | +| Normalize catalog entries | `normalizeMcpServers` (`core/mcp/remote/node/server.ts`) | Legacy `settings` hard-cutover; secret migration | +| Lift to runtime | `mcpConfigToServerEntries` | Map key → `id`; flat disk fields → `InspectorServerSettings` | + +**Write path (catalog only):** `serverEntriesToMcpConfig` / `buildStoredEntry` / `writeStoreFile` per [Server List File](v2_servers_file.md). + +**UC3 session files:** normalize on read; no silent writes. Persistence into a catalog requires UC2, web CRUD, or manual edit outside Inspector. + +**Ad-hoc inline config:** ephemeral; never written by the runner. + +--- + +## Cross-client list contract + +Web and TUI are **list clients**: display servers, pick/connect, edit only when `writable`. They need not distinguish catalog path from session path in the UI — only the resolved list and flags. + +Required inputs: + +1. **Canonical list** — `ServerEntry[]` after normalize + `mcpConfigToServerEntries`. +2. **`writable`** — if false, hide/disable Add, Edit, Remove, settings-save-to-disk. +3. **`defaultServerId`** — from `--server` (UC5). +4. Optional **`autoConnect`** — [#1183](https://github.com/modelcontextprotocol/inspector/issues/1183). + +Centralization belongs in **core**, not the launcher. Today: + +| Piece | Today | Target | +|-------|--------|--------| +| **Canonical conversion** | `mcpConfigToServerEntries` | Same | +| **TUI** | `loadTuiServers` — full settings; no `writable` | `resolveServerList`; gate UI on `writable` | +| **Web** | `useServers` → always default catalog; CRUD always on | `/api/servers` returns session or catalog list + `writable` | +| **CLI** | Single server via `resolveServerConfigs` | `resolveSingleServer` from same normalizer | +| **Launcher** | Forwards argv | Unchanged | + +Import is not a list-client concern — catalog mutation via UC2 or web CRUD when `writable`. + +--- + +## Launch behavior by client + +### Web + +| Launch | Catalog (`/api/servers`) | `initialMcpConfig` → `/api/config` | Connect | +|--------|--------------------------|--------------------------------------|---------| +| No server args | Default catalog via `useServers` | Empty | User selects catalog server | +| Ad-hoc server flags | **Still full default catalog** | Populated (transport fields) | **Launch config not consumed** — no auto-connect, no form pre-fill (G2) | +| `--config` + `--server` | **Still default catalog** | Populated from resolved config (settings stripped) | User sees catalog, not launch file | + +**Target:** `/api/servers` serves the **resolved** list for the session (catalog or UC3 file) with `writable` and `defaultServerId`. See [Web launch behavior](#web-launch-behavior). + +### CLI and TUI + +CLI connects to exactly one resolved config per invocation; TUI shows all entries from the resolved list. Resolution paths, settings lift, and port gaps are documented in [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md). Key catalog-specific gaps: **G1** (CLI drops disk settings on catalog/file lookup), **G4** (web `runWeb --header` warns only). + +### Shared resolver (`resolveServerConfigs`) + +``` +withDefaultConfigPath(options) + → if no --config and no ad-hoc target: configPath = ~/.mcp-inspector/mcp.json + +single mode (CLI): + configPath + serverName → loadServerFromConfig (MCPServerConfig only — G1) + configPath, no serverName → sole entry or error if multiple + ad-hoc → buildConfigFromOptions + +multi mode (TUI): + configPath → all entries via mcpConfigToServerEntries (settings lifted) + ad-hoc → one config +``` + +`loadServerFromConfig` returns bare `MCPServerConfig`. Only `mcpConfigToServerEntries()` lifts flat disk fields into `InspectorServerSettings`. + +--- + +## Import (UC2) + +**`servers/import`** on CLI only (plus web Import menu for interactive use). Targets the **active catalog** (default or `--catalog`). No launcher `--import` flag — `mcp-inspector --cli … --method servers/import` is sufficient. + +| Approach | Rationale | +|----------|-----------| +| **CLI method** | Explicit, scriptable, reuses `--config` / ad-hoc argv parsing; same normalization as `POST /api/servers` | +| **Not launcher flag** | Launcher routes only; catalog mutation blurs responsibilities | +| **Not all clients at startup** | TUI/web import is UI concern (paste/upload) | + +**Behavior:** + +1. **UC2-A:** read `--config` source → canonicalize → one or all entries (`--server` optional). +2. **UC2-B:** build one entry from ad-hoc argv (same resolver as UC3 launch) → `--server ` names catalog entry → canonicalize. +3. Write to **active catalog** via `importServersIntoCatalog` — never to the UC3 source file. +4. Collision policy per entry: skip, replace, or error (default TBD). +5. Exit 0 with summary JSON (imported ids, skipped duplicates). + +Web **Import config** vs **Import server.json** menu semantics: [v2_ux.md](v2_ux.md) and [Server List File](v2_servers_file.md). Registry conversion: [#922](https://github.com/modelcontextprotocol/inspector/issues/922), [#1435](https://github.com/modelcontextprotocol/inspector/issues/1435). + +--- + +## Write-back policy + +No silent write-back to **any** file on launch. Catalog mutations go through `/api/servers`, `servers/import`, or explicit user CRUD. + +| Action | Writes catalog? | Writes session `--config` file? | +|--------|-----------------|--------------------------------| +| Launch ad-hoc command/URL | No | No | +| Launch `--server` + overrides (`-e`, `--cwd`, `--header`) | No | No | +| Launch `--config /other.json` | No | **No** (read-only) | +| User Add/Edit/Settings (web UI) | Yes (canonical serialize) | No | +| Import config / server.json (web UI, future) | Yes | No | +| `--cli --method servers/import` | Yes (active catalog) | No | +| Future explicit `--save-as ` | Opt-in only | No | + +**Runtime overlay model (CLI/TUI):** + +``` +catalog entry (config + settings) + + override flags for this process only + → effective connect payload + → never persisted unless explicit CRUD or import +``` + +--- + +## Web launch behavior + +**Target model: catalog-first (B1).** The catalog always exists in the UI. Launch args influence selection and connect ergonomics; they do not replace the catalog or auto-persist ad-hoc servers. + +| Behavior | Rule | +|----------|------| +| Catalog UI | Always show full resolved catalog (or UC3 session list when wired) | +| `--server ` | Set `activeServerId` / `defaultServerId` to matching catalog entry | +| Ad-hoc inline config | Temporary “session server” or “Quick connect” — **do not** auto-`POST /api/servers` | +| Auto-connect | Optional `--connect` or `?autoConnect=true` ([#1183](https://github.com/modelcontextprotocol/inspector/issues/1183)) | + +**Rejected alternatives:** launch-only UI hiding catalog (B2); v1.5 form pre-fill without catalog (B3). + +**Implementation sketch:** + +1. Extend `GET /api/config` with `initialServerId` and/or settings reference. +2. `App.tsx` on load: if `initialServerId` matches an entry, set selection; optional auto-connect behind flag. +3. Ad-hoc without catalog id: non-persisted session card or “Save to catalog?” prompt. + +--- + +## Target API (core) + +Evolve `loadTuiServers` / `resolveServerConfigs` in core (not launcher): + +```ts +interface ResolvedServerList { + servers: ServerEntry[]; + writable: boolean; + source: "catalog" | "alternate-file" | "adhoc"; + configPath?: string; + defaultServerId?: string; +} + +resolveServerList(options: ServerConfigOptions): ResolvedServerList; +resolveSingleServer(options: ServerConfigOptions): { entry: ServerEntry; ... }; // CLI + +importServersIntoCatalog( + entries: ServerEntry[], + opts?: { onConflict: "skip" | "error" | "replace" }, +): ImportResult; +``` + +**Rules:** + +1. Ad-hoc (no `--config`, no `--catalog`) → one entry, `writable: false` (UC3). +2. `--catalog` or default → active catalog, `writable: true` (UC1/UC4). +3. `--config` without catalog intent → canonicalize → `ServerEntry[]`, `writable: false` (UC3). +4. `defaultServerId` from `--server`. +5. Runtime overlays on connect, not on disk. +6. Web `/api/servers` and TUI use `resolveServerList`; CLI uses `resolveSingleServer`. + +--- + +## Open decisions + +| Topic | Status | Notes | +|-------|--------|-------| +| Import collision default | TBD | `skip` vs `error` vs `replace` for duplicate catalog ids | +| `MCP_CATALOG_PATH` env name | Proposed | Alias for `--catalog` | +| Ad-hoc web UX detail | Partially decided | B1 catalog-first; exact “Quick connect” vs banner TBD | +| TUI `--server` initial selection | Open | Optional highlight when UC5 used from launcher | + +Resolved (documented above): catalog vs session flag split; no launch write-back; CLI-only `servers/import`; core-owned `resolveServerList`; web catalog-first (B1). + +--- + +## Known gaps + +| # | Gap | Severity | Clients | +|---|-----|----------|---------| +| G1 | CLI catalog/file lookup drops settings — `loadServerFromConfig` not `mcpConfigToServerEntries` | Functional | CLI | +| G2 | Web ignores launch-time `initialMcpConfig` for list/selection/connect | Functional / UX | Web | +| G3 | `InitialConfigPayload` has no settings — cannot pass headers/oauth at web launch | Functional | Web + launcher | +| G4 | `runWeb --header` warns only | Functional | Web launcher | +| G5 | Two config pipelines — `config.ts` vs `serverList.ts` | Design debt | CLI vs TUI vs web | +| G6 | v1.5 `docs/mcp-server-configuration.md` not ported to v2 | Docs | All | +| G7 | CLI test expects `--server` without `--config` to fail — predates default catalog | Tests | CLI | +| G8 | No auto-connect ([#1183](https://github.com/modelcontextprotocol/inspector/issues/1183)) | Enhancement | Web | +| G9 | Import config / Import server.json menu stubbed | UX | Web | +| G10 | CLI thin parse path — no full `normalizeMcpServers` / settings lift on catalog GET parity | Consistency | CLI | + +G1, G4, and launcher details: [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md). + +--- + +## Decision matrix + +| User launches with… | File read | Canonicalize on read? | Catalog UI (web) | Writable? | +|---------------------|-----------|----------------------|------------------|-----------| +| Nothing (defaults) | Default catalog | Yes | Full list | Catalog only | +| `--server foo` | Default catalog | Yes | Full list; should select `foo` | Catalog only | +| `--config other.json` | Alternate file (UC3) | Yes | Session list (target); today default catalog | **No** | +| `--catalog ./proj/mcp.json` | Project catalog | Yes | That catalog (target) | That path | +| Ad-hoc `node srv.js` | None | N/A | Full catalog + session hint (target) | Catalog only if user saves | +| Web Add / Edit / Import | N/A | Yes before write | Full list | Catalog only | +| `servers/import` | Source (A) or ad-hoc (B) | Yes before write | N/A (CLI) | Active catalog | + +--- + +## Engineering follow-ups + +1. **Core:** `resolveServerList`, `resolveSingleServer`, `importServersIntoCatalog`. +2. **CLI:** resolve through shared normalizer; implement `--method servers/import` (G1, G10). +3. **Web:** `/api/servers` serves resolved session or catalog list + `writable` + `defaultServerId` (G2). +4. **TUI:** `resolveServerList`; respect `writable` (no CRUD on UC3 files). +5. **Launcher / web:** extend `InitialConfigPayload` for settings at launch (G3, G4). +6. **Tests:** CLI default-catalog success with tmp catalog; import method tests (G7). +7. **Docs:** port `docs/mcp-server-configuration.md` to v2 (G6); close/update [#1347](https://github.com/modelcontextprotocol/inspector/issues/1347). + +--- + +## Related GitHub issues + +| Issue | Status | Relevance | +|-------|--------|-----------| +| [#1347](https://github.com/modelcontextprotocol/inspector/issues/1347) — default `--config` | Open (likely closable) | Implemented via `withDefaultConfigPath()` | +| [#1246](https://github.com/modelcontextprotocol/inspector/issues/1246) — port CLI/TUI/launcher | Done in worktree | Parent of default-catalog behavior | +| [#1183](https://github.com/modelcontextprotocol/inspector/issues/1183) — auto-connect | Open | UC5 web ergonomics | +| [#1348](https://github.com/modelcontextprotocol/inspector/issues/1348) — import from other clients | Open | UC2 web UI | +| [#1435](https://github.com/modelcontextprotocol/inspector/issues/1435) — registry import | Open | UC2 registry path | +| [#1432](https://github.com/modelcontextprotocol/inspector/issues/1432) — CLI v2 | Open | Session CLI umbrella | +| [#1352](https://github.com/modelcontextprotocol/inspector/pull/1352) / [#1358](https://github.com/modelcontextprotocol/inspector/pull/1358) | Merged | Flat settings on disk | +| [#1356](https://github.com/modelcontextprotocol/inspector/pull/1356) | Merged | Secrets in keychain | + +--- + +## References + +- [v2_servers_file.md](v2_servers_file.md) — catalog format, CRUD, settings on disk +- [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md) — CLI/TUI/launcher architecture and port gaps +- [v2_ux.md](v2_ux.md) — Import config vs Import server.json UX +- `core/mcp/node/config.ts` — `withDefaultConfigPath`, `resolveServerConfigs` +- `core/mcp/serverList.ts` — `mcpConfigToServerEntries` +- `clients/tui/src/tui-servers.ts` — TUI catalog + settings load path +- `clients/cli/src/cli.ts` — single-config resolve; settings gap (G1) +- `clients/web/server/run-web.ts` — launcher argv → `initialMcpConfig` +- `clients/web/src/App.tsx` — catalog via `useServers` +- `clients/web/src/components/groups/ImportServerJsonPanel/` — registry panel (unwired) +- `clients/web/src/components/groups/ServerAddMenu/ServerAddMenu.tsx` — Add menu labels diff --git a/specification/v2_cli_tui_launcher.md b/specification/v2_cli_tui_launcher.md new file mode 100644 index 000000000..24647eb1a --- /dev/null +++ b/specification/v2_cli_tui_launcher.md @@ -0,0 +1,234 @@ +# Inspector V2 Tech Stack - CLI, TUI, and Launcher + +### [Brief](README.md) | [V1 Problems](v1_problems.md) | [V2 Scope](v2_scope.md) | V2 Tech Stack | [V2 UX](v2_ux.md) + +#### [Web Client](v2_web_client.md) | CLI, TUI, Launcher | [Server](v2_server.md) | [Storage](v2_storage.md) + +## Summary + +v2 ships three non-web Inspector incarnations alongside the web client: a **one-shot CLI**, an **interactive TUI**, and a **launcher** that routes to web, CLI, or TUI from a single `mcp-inspector` binary. All three consume the same `core/` source as the web client via the `@inspector/core` path alias and run on the shared `InspectorClient` stack ported from v1.5/main. + +This document describes how those clients are built, wired, and tested today, and records known gaps. For catalog vs launch-time config semantics (`--config`, `--catalog`, import), see [Catalog and Launch Configuration](v2_catalog_launch_config.md). + +## Goals + +- One published entry binary (`mcp-inspector`) that forwards argv to the selected client. +- CLI and TUI use the same `InspectorClient`, managed-state classes, and `resolveServerConfigs()` path as v1.5 — adapted to v2's `InspectorServerSettings` model (post-#1358). +- Bundle `core/` into CLI/TUI Node artifacts with **tsup** (analogous to Vite bundling core for the browser). +- Port v1.5 CLI integration tests (86 cases) and TUI smoke tests without duplicating `core/` unit tests (web suite remains canonical for core). +- CI runs CLI and TUI tests plus a launcher `--help` smoke step on every PR. + +## Non-goals + +- **CLI v2 sessions** (connect once, many subcommands) — tracked separately in [#1432](https://github.com/modelcontextprotocol/inspector/issues/1432). +- **npm workspaces** — v2 uses a fat root package plus per-client `package.json` for dev dependencies; launcher resolves sibling `build/` outputs via relative paths, not workspace hoisting. +- **Per-client coverage gates** — `core/` coverage stays on the web suite; CLI/TUI source gates are follow-up work (see [Known gaps](#known-gaps)). +- **Catalog CRUD in TUI** — TUI loads and connects; persistent catalog editing remains web-first today. + +--- + +## Packaging and entrypoints + +| Artifact | Path | Build | Published bin | +|----------|------|-------|---------------| +| Launcher | `clients/launcher/` | `tsc` → `build/index.js` | Root `mcp-inspector` → `clients/launcher/build/index.js` | +| CLI | `clients/cli/` | `tsup` → `build/index.js` | `mcp-inspector-cli` (client package only) | +| TUI | `clients/tui/` | `tsup` → `build/index.js` | `mcp-inspector-tui` (client package only) | +| Web runner | `clients/web/server/run-web.ts` | `tsup` (`build:runner`) → `clients/web/build/index.js` | `mcp-inspector-web` (client package only) | + +Root `package.json` (`@modelcontextprotocol/inspector` v2) publishes a **fat package**: merged runtime `dependencies`, `files` manifest listing each client's `build/` (and web `dist/`), and `prepack` → full `npm run build`. The launcher does not declare `file:` sibling dependencies; it dynamically imports `../web/build/index.js`, `../cli/build/index.js`, or `../tui/build/index.js` relative to its own `build/` directory. + +**Typical invocations:** + +```bash +mcp-inspector # web (prod Hono server) +mcp-inspector --web --dev # web (Vite dev server) +mcp-inspector --cli --config mcp.json --server my-api --method tools/list +mcp-inspector --tui --config mcp.json +``` + +Root scripts `inspector`, `web`, and `web:dev` are thin wrappers around the launcher binary. + +--- + +## Launcher + +`clients/launcher/src/index.ts` parses only mode flags (`--web`, `--cli`, `--tui`; default **web**), strips the mode flag from `process.argv`, and dynamically imports the selected client's `runWeb`, `runCli`, or `runTui`. + +- **No core imports** — plain TypeScript compile is sufficient. +- **Argv forwarding** — all other flags pass through unchanged so each client's Commander parser owns server and method options. +- **Help** — `mcp-inspector --help` shows launcher help; `mcp-inspector --cli --help` forwards to CLI help. + +**Production web caveat:** launcher `prebuild` chains `build:runner` for web, not a full `vite build`. Running `mcp-inspector --web` (without `--dev`) requires `clients/web/dist/` from a prior `cd clients/web && npm run build`. CI builds TUI and launcher but does not currently validate prod web startup end-to-end. + +--- + +## Shared core consumption + +All three clients import from `@inspector/core/...` (mapped to `../../core/` source). + +| Concern | Web | CLI / TUI | Launcher | +|---------|-----|-----------|----------| +| Dev typecheck | `tsconfig.app.json` paths | per-client `tsconfig.json` paths | `tsconfig.json` (no core) | +| Runtime bundle | Vite alias | tsup `noExternal: [/^@inspector\/core/]` + esbuild alias | n/a | +| Tests | Vitest projects in `clients/web/` | Vitest + `vitest.shared.mts` aliases | none | + +`vitest.shared.mts` at repo root centralizes `@inspector/core` and test-server aliases plus bare-module pins (`react`, `pino`, SDK, etc.) so CLI/TUI Vitest configs stay aligned with web. + +**Resolved design choices:** + +| Topic | Decision | +|-------|----------| +| Core package | No separate `inspector-core` npm package; source-only `core/` | +| CLI/TUI build | tsup bundles `@inspector/core` into `build/index.js` | +| Core tests | Not duplicated under cli/tui; web unit + integration suites cover `core/` | +| Default config path | `resolveServerConfigs()` calls `withDefaultConfigPath()` → `~/.mcp-inspector/mcp.json` when no `--config` and no ad-hoc target | + +--- + +## CLI + +**Model:** one-shot — each invocation connects, runs a single `--method`, prints JSON to stdout, disconnects, exits. Same surface as v1.5; session-oriented CLI v2 is future work ([#1432](https://github.com/modelcontextprotocol/inspector/issues/1432)). + +**Entry:** `clients/cli/src/index.ts` exports `runCli(argv)`; `src/cli.ts` owns Commander parsing and `InspectorClient` orchestration. + +**Server resolution:** + +1. Positional `[target...]` (command/URL) and/or `--config` + `--server`, plus `-e`, `--cwd`, `--transport`, `--server-url`. +2. `resolveServerConfigs(serverOptions, "single")` in `core/mcp/node/config.ts` — applies default catalog path when appropriate. +3. Ad-hoc `--header` pairs map to `InspectorServerSettings` and pass into `InspectorClient` via `serverSettings` (not `MCPServerConfig.headers`). + +**Gap — config-file settings:** when loading from a config file, CLI uses `loadServerFromConfig()` / bare `MCPServerConfig` only. Persisted `headers`, timeouts, and OAuth fields on disk (post-#1358 flat entry shape) are **not** lifted into `serverSettings`. TUI uses `mcpConfigToServerEntries()` and does lift them. Ad-hoc `--header` on CLI works; file-backed settings do not. + +**Tests:** 86 cases in `clients/cli/__tests__/` spawn `node build/index.js` via `cli-runner.ts` (`pretest` builds test-servers + CLI). Core behavior is exercised; Vitest coverage on `src/cli.ts` is invisible to the coverage instrumenter because tests run out-of-process (see [Known gaps](#known-gaps)). + +--- + +## TUI + +**Model:** interactive terminal UI (Ink + React 19) mirroring web information architecture: Servers, Tools, Prompts, Resources, Logs, OAuth, etc. + +**Entry:** `clients/tui/index.ts` exports `runTui(argv)`; `tui.tsx` parses argv and renders `App`. + +**Server resolution:** `loadTuiServers()` in `clients/tui/src/tui-servers.ts`: + +- **Config file path:** reads JSON, runs `mcpConfigToServerEntries()` — returns `{ config, settings }` per server (correct post-#1358 path; matches web `useServers`). +- **Ad-hoc:** `resolveServerConfigs(..., "multi")` for a single inline server; merges launch-time `--header` into `settings`. +- Applies `withDefaultConfigPath()` before resolution. + +**Core hooks:** TUI uses the same managed-state and `useInspectorClient` patterns as web (`ManagedToolsState`, etc.) inside Ink components. + +**Behavior note:** v2 core only auto-refreshes tool/resource lists when `autoRefreshOnListChanged` is true in server settings; v1.5 TUI always auto-refreshed. Port accepts v2 core behavior; UI indicator parity with web is open ([#1402](https://github.com/modelcontextprotocol/inspector/issues/1402)). + +**Tests:** 2 cases in `clients/tui/__tests__/tui.test.ts` assert `tabsConfig` shape only. Config parsing, OAuth, and Ink component flows are untested. + +**Dev:** `npm run dev` in `clients/tui` uses `vite-node` + `dev.ts` for fast iteration without a full tsup rebuild. + +--- + +## Web runner (`runWeb`) + +The launcher `--web` path does not start Vite via the `npm run dev` script. It calls `runWeb(argv)` from `clients/web/server/run-web.ts`, which: + +1. Parses the same server-selection flags as CLI/TUI (`--config`, `--server`, positional target, `-e`, `--cwd`, `--transport`, `--server-url`, `--header`, `--dev`). +2. Resolves one `MCPServerConfig` via `resolveServerConfigs()` when server input is present. +3. Passes `initialMcpConfig` into `buildWebServerConfig()` → `GET /api/config` legacy channel. +4. Starts `startViteDevServer()` (`--dev`) or `startHonoServer()` (prod). + +**Gap — launch-time headers:** `--header` is accepted but logs a warning; headers are not applied to the web session. The v2 web UI configures HTTP headers via per-server settings in the catalog (`ServerSettingsForm`), not via `initialMcpConfig`. Extending `InitialConfigPayload` (or equivalent) is required for launcher-passed headers to reach the UI. + +Day-to-day web development still uses `cd clients/web && npm run dev` (Vite CLI + env-based `buildWebServerConfigFromEnv`). + +--- + +## Server configuration flags (shared) + +CLI, TUI, and `runWeb` share launch-time server options documented in v1.5's `docs/mcp-server-configuration.md` (port to v2 pending). Common flags: + +| Flag | Purpose | +|------|---------| +| `--config ` | MCP servers JSON file | +| `--server ` | Named entry within config (required when file has multiple servers) | +| `[target...]` | Ad-hoc stdio command/args or HTTP URL | +| `-e KEY=VALUE` | Stdio environment overrides | +| `--cwd ` | Stdio working directory | +| `--transport` | `stdio`, `sse`, or `http` | +| `--server-url ` | HTTP/SSE endpoint | +| `--header "Name: Value"` | Launch-time HTTP headers (CLI/TUI ad-hoc → `serverSettings`; web → warning only) | + +CLI additionally requires `--method` and method-specific args (`--tool-name`, `--uri`, etc.). Semantics for catalog vs session config, `--catalog`, and `servers/import` are in [v2_catalog_launch_config.md](v2_catalog_launch_config.md). + +--- + +## Testing and CI + +| Suite | Location | Count | How | +|-------|----------|-------|-----| +| CLI | `clients/cli/__tests__/` | 86 | Subprocess `node build/index.js` | +| TUI | `clients/tui/__tests__/` | 2 | In-process imports | +| Launcher | CI smoke only | 3 | `--help`, `--cli --help`, `--tui --help` | +| Core | `clients/web/src/test/` | — | Canonical; not duplicated in cli/tui | + +**Root orchestration:** `npm run test` chains web unit tests, then `clients/cli` and `clients/tui` tests. `npm run test:coverage` runs web coverage only. + +**CI** (`.github/workflows/main.yml`): after web validate + coverage, installs and tests CLI and TUI, builds TUI + launcher, runs launcher help smoke. + +**Prerequisites:** CLI `pretest` runs `test-servers:build` (`tsc -p test-servers`) so stdio integration tests can spawn `test-servers/build/test-server-stdio.js`. + +--- + +## Developer setup + +v2 does not use npm workspaces. Each client maintains its own `package.json` and `node_modules`: + +```bash +npm install # root (launcher runtime deps) +cd clients/web && npm install # required for web dev/test +cd clients/cli && npm install # required for CLI build/test +cd clients/tui && npm install # required for TUI build/test +cd clients/launcher && npm install +``` + +**Build order for a full local smoke:** + +```bash +npm run build:web # includes build:runner for runWeb +npm run build:cli +npm run build:tui +npm run build:launcher +``` + +Root `npm run validate` currently runs web validate only; extending it to CLI/TUI build + test is follow-up work. + +--- + +## Known gaps + +| Area | Gap | Tracking | +|------|-----|----------| +| **CLI config-file settings** | Disk `headers` / timeouts / OAuth not lifted when `--config` is used | This spec; [catalog doc](v2_catalog_launch_config.md) G1 | +| **Web launch headers** | `runWeb --header` warns but does not apply settings to UI | This spec; [#1246](https://github.com/modelcontextprotocol/inspector/issues/1246) | +| **TUI list-changed UX** | No stale-list indicator when `autoRefreshOnListChanged` is false | [#1402](https://github.com/modelcontextprotocol/inspector/issues/1402) | +| **TUI test coverage** | Only `tabsConfig` shape tested | Expand `tui-servers.ts`, Ink flows | +| **CLI coverage gates** | Subprocess tests don't instrument `src/cli.ts` | In-process `runCli()` runner + thin binary E2E suite | +| **Prod web via launcher** | Requires pre-built `clients/web/dist/` | Document or wire into launcher prebuild | +| **Import / `--catalog`** | `servers/import`, `--catalog` not implemented | [catalog doc](v2_catalog_launch_config.md); [#1348](https://github.com/modelcontextprotocol/inspector/issues/1348) | +| **README refresh** | Client READMEs may lag v2 install/build commands | `clients/*/README.md`, `AGENTS.md` | +| **Root validate** | Does not build/test CLI/TUI | Root `package.json` | +| **Root postinstall** | Optional one-shot `npm install` in all clients | Declined workspaces; lighter alternative | + +--- + +## References + +- `clients/launcher/src/index.ts` — mode routing and dynamic import +- `clients/cli/src/cli.ts` — CLI parsing, `InspectorClient` one-shot flow +- `clients/tui/src/tui-servers.ts` — config + settings load path (correct for v2) +- `clients/web/server/run-web.ts` — launcher web entry +- `core/mcp/node/config.ts` — `resolveServerConfigs`, `withDefaultConfigPath` +- `core/mcp/serverList.ts` — `mcpConfigToServerEntries` +- `vitest.shared.mts` — shared Vitest aliases +- [v2_catalog_launch_config.md](v2_catalog_launch_config.md) — catalog and launch config, import, UC1–UC5 +- [v2_servers_file.md](v2_servers_file.md) — catalog file format and web CRUD +- [#1246](https://github.com/modelcontextprotocol/inspector/issues/1246) — port tracking issue +- v1.5 sources: `v1.5/main/clients/{cli,tui,launcher}` diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 000000000..a3718b946 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "noUncheckedIndexedAccess": true, + "baseUrl": ".", + "paths": { + "@inspector/core/*": ["core/*"] + } + } +} diff --git a/vitest.shared.mts b/vitest.shared.mts new file mode 100644 index 000000000..b63fe4361 --- /dev/null +++ b/vitest.shared.mts @@ -0,0 +1,100 @@ +/** + * Vitest/Vite resolve aliases shared between clients/web and node clients + * (cli, tui). Pass each client's directory so bare-module pins resolve against + * that client's node_modules. + */ + +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +export function vitestSharedPaths(clientDir: string) { + const dirname = path.resolve(clientDir); + const repoRoot = path.resolve(dirname, '../..'); + + const sharedAliases = { + '@inspector/core': path.resolve(repoRoot, 'core'), + '@modelcontextprotocol/inspector-test-server': path.resolve( + repoRoot, + 'test-servers/build/index.js', + ), + }; + + const sharedDedupe = ['react', 'react-dom', '@modelcontextprotocol/sdk']; + + const nodeModulesAliases = [ + { find: /^react$/, replacement: path.resolve(dirname, 'node_modules/react') }, + { + find: /^react\/jsx-runtime$/, + replacement: path.resolve(dirname, 'node_modules/react/jsx-runtime.js'), + }, + { + find: /^react\/jsx-dev-runtime$/, + replacement: path.resolve(dirname, 'node_modules/react/jsx-dev-runtime.js'), + }, + { + find: /^react-dom$/, + replacement: path.resolve(dirname, 'node_modules/react-dom'), + }, + { + find: /^react-dom\/client$/, + replacement: path.resolve(dirname, 'node_modules/react-dom/client.js'), + }, + { find: /^pino$/, replacement: path.resolve(dirname, 'node_modules/pino') }, + { + find: /^pino\/browser\.js$/, + replacement: path.resolve(dirname, 'node_modules/pino/browser.js'), + }, + { find: /^zustand$/, replacement: path.resolve(dirname, 'node_modules/zustand') }, + { + find: /^zustand\/middleware$/, + replacement: path.resolve(dirname, 'node_modules/zustand/middleware.js'), + }, + { + find: /^zustand\/vanilla$/, + replacement: path.resolve(dirname, 'node_modules/zustand/vanilla.js'), + }, + { + find: /^hono$/, + replacement: path.resolve(dirname, 'node_modules/hono/dist/index.js'), + }, + { + find: /^hono\/streaming$/, + replacement: path.resolve(dirname, 'node_modules/hono/dist/helper/streaming/index.js'), + }, + { + find: /^@hono\/node-server$/, + replacement: path.resolve(dirname, 'node_modules/@hono/node-server'), + }, + { find: /^atomically$/, replacement: path.resolve(dirname, 'node_modules/atomically') }, + { find: /^chokidar$/, replacement: path.resolve(dirname, 'node_modules/chokidar') }, + { + find: /^@napi-rs\/keyring$/, + replacement: path.resolve(dirname, 'node_modules/@napi-rs/keyring'), + }, + { find: /^express$/, replacement: path.resolve(dirname, 'node_modules/express') }, + { find: /^yaml$/, replacement: path.resolve(dirname, 'node_modules/yaml') }, + { + find: /^@modelcontextprotocol\/sdk\/client\/auth\.js$/, + replacement: path.resolve( + dirname, + 'node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js', + ), + }, + ]; + + const projectResolve = { + alias: [ + ...Object.entries(sharedAliases).map(([find, replacement]) => ({ find, replacement })), + ...nodeModulesAliases, + ], + dedupe: sharedDedupe, + }; + + return { repoRoot, sharedAliases, sharedDedupe, nodeModulesAliases, projectResolve }; +} + +/** Convenience for importers that only have import.meta.url. */ +export function vitestSharedPathsFromMetaUrl(metaUrl: string) { + const clientDir = path.dirname(fileURLToPath(metaUrl)); + return vitestSharedPaths(clientDir); +}