From 72423b5c4ed3f97293fb00a39bfff7cf5272c4c8 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 13:32:24 +0800 Subject: [PATCH 1/9] refactor!: migrate codebase from JavaScript to TypeScript Port all src/, test/, and fixtures/ files to TypeScript with strict mode enabled. Compiled output now lives in dist/ (tsc); bin/agentcli.js and test paths point at dist; shared types are centralized in src/types.ts. Behavior changes bundled with the migration: - server remove now also deletes the stale tools cache file for that server - tool help output lists the --no-daemon global flag - client version reported to MCP servers is the real package version (was hardcoded "0.0.1") Build and publish hardening: - engines: ">=20" -> ">=20.10" (import attributes require Node 20.10+) - files narrowed to bin, dist/src, dist/package.json, skills, README.md with negations for *.d.ts and *.map: package shrinks from 56 to 18 files (184 kB -> 67 kB unpacked); tests and fixtures no longer ship - tsconfig: noEmitOnError true, so type errors cannot produce a bad dist - saveConfig writes ~/.agentcli/config.json with 0600 permissions and also tightens pre-existing files (config may hold bearer tokens) Verified: build and 48/48 tests pass; installed from the real packed tarball and exercised stdio/HTTP MCP calls, daemon mode, help discovery, and error surfaces end to end. BREAKING CHANGE: Node.js >= 20.10 is now required (was >= 20). --- bin/agentcli.js | 2 +- fixtures/{echo-server.mjs => echo-server.ts} | 16 +-- package-lock.json | 24 +++- package.json | 23 +++- src/{client.js => client.ts} | 113 +++++++++++------- src/{config.js => config.ts} | 41 ++++--- src/daemon/{child.js => child.ts} | 9 +- src/daemon/{lifecycle.js => lifecycle.ts} | 39 ++++--- src/daemon/{paths.js => paths.ts} | 12 +- src/daemon/{server.js => server.ts} | 99 +++++++++------- src/{dispatch.js => dispatch.ts} | 56 ++++++--- src/{errors.js => errors.ts} | 39 +++++-- src/{flags.js => flags.ts} | 67 ++++++----- src/{fuzzy.js => fuzzy.ts} | 8 +- src/{index.js => index.ts} | 64 +++++----- src/jsonout.js | 17 --- src/jsonout.ts | 19 +++ src/types.ts | 117 +++++++++++++++++++ test/{daemon.test.js => daemon.test.ts} | 44 +++---- test/{e2e.test.js => e2e.test.ts} | 90 +++++++------- test/{skill.test.js => skill.test.ts} | 4 +- tsconfig.json | 21 ++++ 22 files changed, 603 insertions(+), 321 deletions(-) rename fixtures/{echo-server.mjs => echo-server.ts} (84%) rename src/{client.js => client.ts} (65%) rename src/{config.js => config.ts} (65%) rename src/daemon/{child.js => child.ts} (51%) rename src/daemon/{lifecycle.js => lifecycle.ts} (61%) rename src/daemon/{paths.js => paths.ts} (72%) rename src/daemon/{server.js => server.ts} (68%) rename src/{dispatch.js => dispatch.ts} (76%) rename src/{errors.js => errors.ts} (61%) rename src/{flags.js => flags.ts} (79%) rename src/{fuzzy.js => fuzzy.ts} (82%) rename src/{index.js => index.ts} (77%) delete mode 100644 src/jsonout.js create mode 100644 src/jsonout.ts create mode 100644 src/types.ts rename test/{daemon.test.js => daemon.test.ts} (74%) rename test/{e2e.test.js => e2e.test.ts} (76%) rename test/{skill.test.js => skill.test.ts} (97%) create mode 100644 tsconfig.json diff --git a/bin/agentcli.js b/bin/agentcli.js index 637fb16..3fe1851 100755 --- a/bin/agentcli.js +++ b/bin/agentcli.js @@ -1,4 +1,4 @@ #!/usr/bin/env node -import { run } from "../src/index.js"; +import { run } from "../dist/src/index.js"; run(process.argv.slice(2)); diff --git a/fixtures/echo-server.mjs b/fixtures/echo-server.ts similarity index 84% rename from fixtures/echo-server.mjs rename to fixtures/echo-server.ts index 5c6941e..cba8591 100644 --- a/fixtures/echo-server.mjs +++ b/fixtures/echo-server.ts @@ -52,13 +52,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ })); server.setRequestHandler(CallToolRequestSchema, async (req) => { - const a = req.params.arguments || {}; - switch (req.params.name) { + const a = (req.params as { arguments?: Record }).arguments || {}; + switch ((req.params as { name: string }).name) { case "echo": { - let m = a.message; + let m = a.message as string; if (a.upper) m = m.toUpperCase(); if (a.mode === "shout") m = m.toUpperCase() + "!!!"; - const times = Math.max(1, a.times ?? 1); + const times = Math.max(1, (a.times as number) ?? 1); return { content: [{ type: "text", text: Array(times).fill(m).join(" ") }] }; } case "complex": @@ -66,15 +66,15 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => { case "fail": return { isError: true, content: [{ type: "text", text: "boom: intentional failure" }] }; case "slow": - await new Promise((r) => setTimeout(r, a.ms ?? 1000)); + await new Promise((r) => setTimeout(r, (a.ms as number) ?? 1000)); return { content: [{ type: "text", text: "done" }] }; case "double": { - const payload = [{ id: a.n ?? 1 }, { id: (a.n ?? 1) + 1 }]; + const payload = [{ id: (a.n as number) ?? 1 }, { id: ((a.n as number) ?? 1) + 1 }]; return { content: [{ type: "text", text: JSON.stringify(JSON.stringify(payload)) }] }; } default: - return { isError: true, content: [{ type: "text", text: "unknown tool: " + req.params.name }] }; + return { isError: true, content: [{ type: "text", text: "unknown tool: " + (req.params as { name: string }).name }] }; } }); -await server.connect(new StdioServerTransport()); \ No newline at end of file +await server.connect(new StdioServerTransport()); diff --git a/package-lock.json b/package-lock.json index f99fb69..106325f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@agenticbro/agentcli", + "name": "@happyvibing/agentcli", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@agenticbro/agentcli", + "name": "@happyvibing/agentcli", "version": "0.1.0", "license": "MIT", "dependencies": { @@ -15,8 +15,12 @@ "bin": { "agentcli": "bin/agentcli.js" }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/.pnpm/commander@12.1.0/node_modules/commander": { @@ -43,6 +47,16 @@ "node": ">=18" } }, + "node_modules/.pnpm/commander@12.1.0/node_modules/commander/node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/.pnpm/commander@12.1.0/node_modules/commander/node_modules/handlebars": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", @@ -1339,7 +1353,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.43", + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e754d55..d45f6c9 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,12 @@ }, "files": [ "bin", - "src", + "dist/src", + "dist/package.json", "skills", - "README.md" + "README.md", + "!dist/src/**/*.d.ts", + "!dist/src/**/*.map" ], "keywords": [ "cli", @@ -26,20 +29,28 @@ "tool-runtime" ], "license": "MIT", - "author": "", + "author": "happyvibing", "publishConfig": { "access": "public" }, "engines": { - "node": ">=20" + "node": ">=20.10" }, "scripts": { + "build": "tsc", + "clean": "rm -rf dist", "start": "node bin/agentcli.js", - "prepublishOnly": "npm test", - "test": "node --test" + "prebuild": "npm run clean", + "prepublishOnly": "npm run build && npm test", + "pretest": "npm run build", + "test": "node --test dist/test/*.test.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", "commander": "^12.1.0" + }, + "devDependencies": { + "typescript": "^5.7.0", + "@types/node": "^22.0.0" } } diff --git a/src/client.js b/src/client.ts similarity index 65% rename from src/client.js rename to src/client.ts index a1107cc..c6eb487 100644 --- a/src/client.js +++ b/src/client.ts @@ -4,19 +4,21 @@ // it, which keeps CLI startup fast for the common agent path. import net from "node:net"; import { readToolsCache, writeToolsCache } from "./config.js"; -import { errors, reviveError } from "./errors.js"; +import { errors, reviveError, AgentCliError } from "./errors.js"; import { socketPath } from "./daemon/paths.js"; +import type { AgentCliConfig, ServerSpec, McpTool, ListToolsResultEnvelope, CallToolResultEnvelope, DaemonResponse } from "./types.js"; +import pkg from "../package.json" with { type: "json" }; -const CLIENT_INFO = { name: "agentcli", version: "0.0.1" }; +const CLIENT_INFO = { name: "agentcli", version: pkg.version }; const DEFAULT_TTL_MS = 10 * 60 * 1000; const DEFAULT_TIMEOUT_MS = 60 * 1000; -export function ttlFromEnv() { +export function ttlFromEnv(): number { const n = Number(process.env.AGENTCLI_TTL_MS); return Number.isFinite(n) && n > 0 ? n : DEFAULT_TTL_MS; } -export function timeoutFromEnv() { +export function timeoutFromEnv(): number { const n = Number(process.env.AGENTCLI_TIMEOUT_MS); return Number.isFinite(n) && n > 0 ? n : DEFAULT_TIMEOUT_MS; } @@ -32,7 +34,11 @@ export function timeoutFromEnv() { // Override with AGENTCLI_PROTOCOL_VERSIONS (comma-separated). Dedup-safe once // the SDK ships these versions itself. -let sdkPromise = null; +let sdkPromise: Promise<{ + Client: typeof import("@modelcontextprotocol/sdk/client/index.js").Client; + StdioClientTransport: typeof import("@modelcontextprotocol/sdk/client/stdio.js").StdioClientTransport; + StreamableHTTPClientTransport: typeof import("@modelcontextprotocol/sdk/client/streamableHttp.js").StreamableHTTPClientTransport; +}> | null = null; export function sdk() { if (!sdkPromise) { @@ -60,25 +66,25 @@ export function sdk() { // whatever else sits in the agent's environment never reaches the server process). const ENV_WHITELIST = ["PATH", "PATHEXT", "HOME", "USERPROFILE", "TEMP", "TMP", "TMPDIR", "LANG", "LC_ALL", "SYSTEMROOT", "COMSPEC"]; -export function buildEnv(specEnv) { - const base = {}; +export function buildEnv(specEnv?: Record): Record { + const base: Record = {}; for (const k of ENV_WHITELIST) { - if (process.env[k] !== undefined) base[k] = process.env[k]; + if (process.env[k] !== undefined) base[k] = process.env[k] as string; } return { ...base, ...(specEnv || {}) }; } // Accepts both config shapes: {Name: value} (stored config) and // ["Name: value", ...] (raw --header flags). -function parseHeaders(headerList) { +function parseHeaders(headerList: unknown): Record { if (!headerList) return {}; if (!Array.isArray(headerList)) { if (typeof headerList !== "object") { - throw errors.invalidArgument("invalid headers: expected an object or array of \"Name: value\" strings"); + throw errors.invalidArgument('invalid headers: expected an object or array of "Name: value" strings'); } - return { ...headerList }; + return { ...(headerList as Record) }; } - const headers = {}; + const headers: Record = {}; for (const h of headerList) { const idx = h.indexOf(":"); if (idx <= 0) throw errors.invalidArgument('invalid header "' + h + '"', 'Expected "Name: value"'); @@ -87,43 +93,54 @@ function parseHeaders(headerList) { return headers; } -export async function createTransport(spec) { +type Transport = InstanceType; + +export async function createTransport(spec: ServerSpec): Promise { const { StdioClientTransport, StreamableHTTPClientTransport } = await sdk(); if (spec.type === "http") { return new StreamableHTTPClientTransport(new URL(spec.url), { requestInit: { headers: parseHeaders(spec.headers) }, - }); + }) as unknown as Transport; } return new StdioClientTransport({ command: spec.command, args: spec.args || [], env: buildEnv(spec.env), stderr: "pipe", - }); + }) as unknown as Transport; } -export function mapError(e, serverName, stderrTail) { - const msg = String((e && e.message) || e); +export function mapError(e: unknown, serverName: string, stderrTail?: string): AgentCliError { + const err = e as Error & { status?: number; code?: string | number }; + const msg = String((err && err.message) || e); if (/timed?\s*out/i.test(msg)) return errors.timeout(serverName + ": " + msg); - const status = e && (e.status ?? e.code); + const status = err && (err.status ?? err.code); if (status === 401 || status === 403) return errors.auth(serverName + ": " + msg); const details = stderrTail ? { stderr: stderrTail } : undefined; return errors.connect(serverName + ": " + msg, details); } -async function withClient(spec, serverName, fn, timeoutMs) { +type McpClient = InstanceType; + +async function withClient( + spec: ServerSpec, + serverName: string, + fn: (client: McpClient) => Promise, + timeoutMs?: number +): Promise { const { Client } = await sdk(); const transport = await createTransport(spec); const client = new Client(CLIENT_INFO); let stderrTail = ""; - if (transport.stderr && typeof transport.stderr.on === "function") { - transport.stderr.on("data", (d) => { + const stdioTransport = transport as unknown as { stderr?: { on?: (event: string, cb: (d: Buffer) => void) => void } }; + if (stdioTransport.stderr && typeof stdioTransport.stderr.on === "function") { + stdioTransport.stderr.on("data", (d: Buffer) => { stderrTail = (stderrTail + d.toString()).split("\n").slice(-20).join("\n"); }); } try { try { - await client.connect(transport); + await client.connect(transport as unknown as Parameters[0]); } catch (e) { throw mapError(e, serverName, stderrTail); } @@ -137,7 +154,7 @@ async function withClient(spec, serverName, fn, timeoutMs) { } } -function requireServer(cfg, serverName) { +function requireServer(cfg: AgentCliConfig, serverName: string): ServerSpec { const spec = cfg.servers[serverName]; if (!spec) { throw errors.notFound( @@ -151,14 +168,15 @@ function requireServer(cfg, serverName) { // --- daemon (persistent connections) --- export class DaemonUnavailable extends Error { - constructor(message) { + unavailable = true; + + constructor(message: string) { super(message); this.name = "DaemonUnavailable"; - this.unavailable = true; } } -export function daemonEnabled(pref = true) { +export function daemonEnabled(pref = true): boolean { if (!pref) return false; if (process.env.AGENTCLI_NO_DAEMON) return false; return process.platform !== "win32"; @@ -167,7 +185,7 @@ export function daemonEnabled(pref = true) { let daemonSeq = 0; // One request per connection: simple, robust against daemon restarts. -export async function daemonRequest(op, payload = {}, { timeoutMs = 65000 } = {}) { +export async function daemonRequest(op: string, payload: Record = {}, { timeoutMs = 65000 }: { timeoutMs?: number } = {}): Promise { return new Promise((resolve, reject) => { let buf = ""; let connected = false; @@ -176,30 +194,31 @@ export async function daemonRequest(op, payload = {}, { timeoutMs = 65000 } = {} sock.destroy(); reject(connected ? errors.timeout("daemon request timed out: " + op) : new DaemonUnavailable("daemon timeout")); }, timeoutMs); - const fail = (e) => { + const fail = (e: unknown) => { clearTimeout(timer); - reject(connected ? e : new DaemonUnavailable(String((e && e.message) || e))); + reject(connected ? (e as Error) : new DaemonUnavailable(String(((e as Error)?.message) || e))); }; sock.on("error", fail); sock.on("connect", () => { connected = true; sock.write(JSON.stringify({ id: ++daemonSeq, op, ...payload }) + "\n"); }); - sock.on("data", (d) => { + sock.on("data", (d: Buffer) => { buf += d.toString(); const nl = buf.indexOf("\n"); if (nl < 0) return; clearTimeout(timer); sock.end(); - let msg; + let msg: DaemonResponse; try { msg = JSON.parse(buf.slice(0, nl)); } catch (e) { - reject(errors.connect("daemon sent invalid response: " + e.message)); + const err = e as Error; + reject(errors.connect("daemon sent invalid response: " + err.message)); return; } if (msg.ok) resolve(msg.result); - else reject(reviveError(msg.error)); + else reject(reviveError(msg.error!)); }); }); } @@ -211,14 +230,18 @@ export async function daemonRequest(op, payload = {}, { timeoutMs = 65000 } = {} // are surfaced as-is — never retried against a fresh process, or a non-idempotent // tool could run twice. -export async function listTools(cfg, serverName, { refresh = false, ttlMs = ttlFromEnv(), timeoutMs, daemon = true } = {}) { +export async function listTools( + cfg: AgentCliConfig, + serverName: string, + { refresh = false, ttlMs = ttlFromEnv(), timeoutMs, daemon = true }: { refresh?: boolean; ttlMs?: number; timeoutMs?: number; daemon?: boolean } = {} +): Promise { const spec = requireServer(cfg, serverName); if (daemonEnabled(daemon)) { try { - const r = await daemonRequest("listTools", { server: serverName, refresh }, { timeoutMs: timeoutMs ?? 30000 }); + const r = (await daemonRequest("listTools", { server: serverName, refresh }, { timeoutMs: timeoutMs ?? 30000 })) as { tools: McpTool[]; cached: boolean }; return { tools: r.tools || [], cached: !!r.cached, via: "daemon" }; } catch (e) { - if (!(e instanceof DaemonUnavailable) && !e.unavailable) throw e; + if (!(e instanceof DaemonUnavailable) && !(e as DaemonUnavailable)?.unavailable) throw e; } } if (!refresh) { @@ -226,9 +249,9 @@ export async function listTools(cfg, serverName, { refresh = false, ttlMs = ttlF if (cached && cached.fresh) return { tools: cached.tools, cached: true, via: "direct" }; } try { - const tools = await withClient(spec, serverName, async (client) => { + const tools = await withClient(spec, serverName, async (client: McpClient) => { const res = await client.listTools(undefined, { timeout: timeoutMs }); - return res.tools || []; + return (res.tools as McpTool[]) || []; }, timeoutMs); writeToolsCache(serverName, tools); return { tools, cached: false, via: "direct" }; @@ -237,22 +260,28 @@ export async function listTools(cfg, serverName, { refresh = false, ttlMs = ttlF } } -export async function callTool(cfg, serverName, toolName, args, { timeoutMs, daemon = true } = {}) { +export async function callTool( + cfg: AgentCliConfig, + serverName: string, + toolName: string, + args: Record, + { timeoutMs, daemon = true }: { timeoutMs?: number; daemon?: boolean } = {} +): Promise { const spec = requireServer(cfg, serverName); if (daemonEnabled(daemon)) { try { const result = await daemonRequest("callTool", { server: serverName, tool: toolName, args: args || {}, timeoutMs: timeoutMs ?? timeoutFromEnv() }, { timeoutMs: (timeoutMs ?? timeoutFromEnv()) + 10000 }); return { result, via: "daemon" }; } catch (e) { - if (!(e instanceof DaemonUnavailable) && !e.unavailable) throw e; + if (!(e instanceof DaemonUnavailable) && !(e as DaemonUnavailable)?.unavailable) throw e; } } try { - const result = await withClient(spec, serverName, async (client) => { + const result = await withClient(spec, serverName, async (client: McpClient) => { return client.callTool({ name: toolName, arguments: args || {} }, undefined, { timeout: timeoutMs ?? timeoutFromEnv() }); }, timeoutMs); return { result, via: "direct" }; } catch (e) { throw mapError(e, serverName); } -} \ No newline at end of file +} diff --git a/src/config.js b/src/config.ts similarity index 65% rename from src/config.js rename to src/config.ts index dd95d8e..9be8683 100644 --- a/src/config.js +++ b/src/config.ts @@ -3,25 +3,27 @@ import fs from "node:fs"; import path from "node:path"; import os from "node:os"; import { errors } from "./errors.js"; +import type { AgentCliConfig, ServerSpec, McpTool, ToolsCache } from "./types.js"; export const RESERVED_NAMES = new Set(["server", "call", "help", "version", "config", "doctor", "completion", "daemon"]); -export function configPath() { +export function configPath(): string { return process.env.AGENTCLI_CONFIG || path.join(os.homedir(), ".agentcli", "config.json"); } -export function cacheDir() { +export function cacheDir(): string { return process.env.AGENTCLI_CACHE_DIR || path.join(path.dirname(configPath()), "cache"); } -export function loadConfig() { +export function loadConfig(): AgentCliConfig { const p = configPath(); if (!fs.existsSync(p)) return { version: 1, servers: {} }; - let cfg; + let cfg: AgentCliConfig; try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch (e) { - throw errors.invalidArgument("config file is not valid JSON: " + p, "Fix or remove the file: " + e.message); + const err = e as Error; + throw errors.invalidArgument("config file is not valid JSON: " + p, "Fix or remove the file: " + err.message); } if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) { throw errors.invalidArgument("config file must contain a JSON object: " + p); @@ -31,13 +33,15 @@ export function loadConfig() { return cfg; } -export function saveConfig(cfg) { +export function saveConfig(cfg: AgentCliConfig): void { const p = configPath(); fs.mkdirSync(path.dirname(p), { recursive: true }); - fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n"); + fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n", { mode: 0o600 }); + // mode only applies at creation — tighten pre-existing files too (config may hold tokens). + try { fs.chmodSync(p, 0o600); } catch { /* best effort */ } } -export function validateServerName(name) { +export function validateServerName(name: string): void { if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) { throw errors.invalidArgument('invalid server name "' + name + '"', "Must start with a letter; only [a-zA-Z0-9_-] allowed"); } @@ -46,7 +50,7 @@ export function validateServerName(name) { } } -export function addServer(cfg, name, spec) { +export function addServer(cfg: AgentCliConfig, name: string, spec: ServerSpec): void { validateServerName(name); if (cfg.servers[name]) { throw errors.invalidArgument('server "' + name + '" already exists', "Remove it first: agentcli server remove " + name); @@ -55,22 +59,29 @@ export function addServer(cfg, name, spec) { saveConfig(cfg); } -export function removeServer(cfg, name) { +export function removeServer(cfg: AgentCliConfig, name: string): void { if (!cfg.servers[name]) { throw errors.notFound('server "' + name + '" is not configured', "List configured servers: agentcli server list"); } delete cfg.servers[name]; saveConfig(cfg); + + // Clean up the stale tools cache file for the removed server. + try { + fs.rmSync(path.join(cacheDir(), name + ".tools.json"), { force: true }); + } catch { + // ignore + } } // --- tools-list cache --- -function cacheFile(server) { +function cacheFile(server: string): string { return path.join(cacheDir(), server + ".tools.json"); } -export function readToolsCache(server, ttlMs) { - let raw; +export function readToolsCache(server: string, ttlMs: number): { tools: McpTool[]; fresh: boolean } | null { + let raw: ToolsCache; try { raw = JSON.parse(fs.readFileSync(cacheFile(server), "utf8")); } catch { @@ -80,7 +91,7 @@ export function readToolsCache(server, ttlMs) { return { tools: raw.tools, fresh: Date.now() - raw.fetchedAt < ttlMs }; } -export function writeToolsCache(server, tools) { +export function writeToolsCache(server: string, tools: McpTool[]): void { fs.mkdirSync(cacheDir(), { recursive: true }); fs.writeFileSync(cacheFile(server), JSON.stringify({ fetchedAt: Date.now(), tools }, null, 2)); -} \ No newline at end of file +} diff --git a/src/daemon/child.js b/src/daemon/child.ts similarity index 51% rename from src/daemon/child.js rename to src/daemon/child.ts index dc71456..b9eb46d 100644 --- a/src/daemon/child.js +++ b/src/daemon/child.ts @@ -1,4 +1,4 @@ -// Detached daemon entrypoint: node src/daemon/child.js +// Detached daemon entrypoint: node dist/src/daemon/child.js import { runDaemon } from "./server.js"; runDaemon() @@ -7,7 +7,8 @@ runDaemon() // otherwise resolved on shutdown process.exit(0); }) - .catch((e) => { - process.stderr.write("agentcli daemon failed: " + String((e && e.stack) || e) + "\n"); + .catch((e: unknown) => { + const err = e as Error; + process.stderr.write("agentcli daemon failed: " + String((err && err.stack) || e) + "\n"); process.exit(1); - }); \ No newline at end of file + }); diff --git a/src/daemon/lifecycle.js b/src/daemon/lifecycle.ts similarity index 61% rename from src/daemon/lifecycle.js rename to src/daemon/lifecycle.ts index 2ce0d8a..9040b80 100644 --- a/src/daemon/lifecycle.js +++ b/src/daemon/lifecycle.ts @@ -4,32 +4,43 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { errors } from "../errors.js"; +import { DaemonUnavailable } from "../client.js"; import { socketPath, pidPath, logPath, ensureDaemonDir } from "./paths.js"; import { runDaemon } from "./server.js"; import { daemonRequest } from "../client.js"; +import type { DaemonStatusData } from "../types.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); -function requireUnix() { +function requireUnix(): void { if (process.platform === "win32") { throw errors.invalidArgument("daemon mode requires unix sockets and is not supported on Windows yet", "calls still work without the daemon (direct mode)"); } } -async function isAlive() { +async function isAlive(): Promise { try { - const r = await daemonRequest("ping", {}, { timeoutMs: 1500 }); + const r = (await daemonRequest("ping", {}, { timeoutMs: 1500 })) as { pong?: boolean }; return !!(r && r.pong); } catch { return false; } } +interface StartResult { + ok: boolean; + alreadyRunning?: boolean; + started?: boolean; + stopped?: boolean; + running?: boolean; + data?: DaemonStatusData | null; +} + // `agentcli daemon start --foreground` runs it inside this process (debugging); // default spawns a detached child that survives the CLI process. -export async function startDaemon({ foreground = false } = {}) { +export async function startDaemon({ foreground = false }: { foreground?: boolean } = {}): Promise { requireUnix(); if (foreground) { const r = await runDaemon(); @@ -56,10 +67,10 @@ export async function startDaemon({ foreground = false } = {}) { throw errors.connect("daemon did not come up in time", { log: logPath() }); } -export async function stopDaemon() { +export async function stopDaemon(): Promise<{ ok: boolean; stopped: boolean; running?: boolean; pid?: number }> { requireUnix(); try { - const status = await daemonRequest("status", {}, { timeoutMs: 3000 }); + const status = (await daemonRequest("status", {}, { timeoutMs: 3000 })) as DaemonStatusData | null; await daemonRequest("shutdown", {}, { timeoutMs: 5000 }); // give the daemon a beat to unlink the socket for (let i = 0; i < 20; i++) { @@ -68,30 +79,30 @@ export async function stopDaemon() { } return { ok: true, stopped: true, pid: status ? status.pid : undefined }; } catch (e) { - if (e && e.unavailable) return { ok: true, stopped: false, running: false }; + if (e instanceof DaemonUnavailable || (e as { unavailable?: boolean })?.unavailable) return { ok: true, stopped: false, running: false }; throw e; } } -async function statusPayload() { +async function statusPayload(): Promise<{ running: boolean; data: DaemonStatusData | null }> { try { - const data = await daemonRequest("status", {}, { timeoutMs: 3000 }); + const data = (await daemonRequest("status", {}, { timeoutMs: 3000 })) as DaemonStatusData; return { running: true, data }; } catch (e) { - if (e && e.unavailable) return { running: false, data: null }; + if (e instanceof DaemonUnavailable || (e as { unavailable?: boolean })?.unavailable) return { running: false, data: null }; throw e; } } -export async function daemonStatus() { +export async function daemonStatus(): Promise<{ ok: boolean; running: boolean; data: DaemonStatusData | null }> { const payload = await statusPayload(); return { ok: true, ...payload }; } -export async function readDaemonPid() { +export async function readDaemonPid(): Promise { try { return Number(fs.readFileSync(pidPath(), "utf8").trim()); } catch { return null; } -} \ No newline at end of file +} diff --git a/src/daemon/paths.js b/src/daemon/paths.ts similarity index 72% rename from src/daemon/paths.js rename to src/daemon/paths.ts index cb5c140..0f1c481 100644 --- a/src/daemon/paths.js +++ b/src/daemon/paths.ts @@ -4,24 +4,24 @@ import fs from "node:fs"; import path from "node:path"; import { configPath } from "../config.js"; -export function daemonDir() { +export function daemonDir(): string { return path.dirname(configPath()); } -export function ensureDaemonDir() { +export function ensureDaemonDir(): string { const dir = daemonDir(); fs.mkdirSync(dir, { recursive: true }); return dir; } -export function socketPath() { +export function socketPath(): string { return path.join(daemonDir(), "daemon.sock"); } -export function pidPath() { +export function pidPath(): string { return path.join(daemonDir(), "daemon.pid"); } -export function logPath() { +export function logPath(): string { return path.join(daemonDir(), "daemon.log"); -} \ No newline at end of file +} diff --git a/src/daemon/server.js b/src/daemon/server.ts similarity index 68% rename from src/daemon/server.js rename to src/daemon/server.ts index de22899..e905ae0 100644 --- a/src/daemon/server.js +++ b/src/daemon/server.ts @@ -2,37 +2,38 @@ // per-call spawn + handshake. Newline-delimited JSON over a unix socket. import net from "node:net"; import fs from "node:fs"; -import path from "node:path"; import readline from "node:readline"; import { createTransport, mapError, sdk, ttlFromEnv } from "../client.js"; import { loadConfig } from "../config.js"; -import { errors, serializeError } from "../errors.js"; -import { socketPath, pidPath, logPath, ensureDaemonDir } from "./paths.js"; +import { errors, serializeError, AgentCliError } from "../errors.js"; +import { socketPath, pidPath, ensureDaemonDir } from "./paths.js"; +import type { AgentCliConfig, ServerSpec, McpTool, DaemonStatusData, DaemonResponse } from "../types.js"; +import pkg from "../../package.json" with { type: "json" }; -const CLIENT_INFO = { name: "agentcli-daemon", version: "0.0.1" }; +const CLIENT_INFO = { name: "agentcli-daemon", version: pkg.version }; const DEFAULT_IDLE_MS = 30 * 60 * 1000; -function idleMsFromEnv() { +function idleMsFromEnv(): number { const n = Number(process.env.AGENTCLI_DAEMON_IDLE_MS); return Number.isFinite(n) && n > 0 ? n : DEFAULT_IDLE_MS; } +type McpClient = InstanceType; + class DaemonState { - constructor() { - // Persistent MCP clients keyed by server name. - this.clients = new Map(); // name -> {client, specJson} - // In-memory tools cache (direct mode keeps its own on-disk cache). - this.tools = new Map(); // name -> {tools, fetchedAt} - this.stats = { startedAt: Date.now(), requests: 0, toolCalls: 0 }; - this.shuttingDown = false; - this.idleTimer = null; - } + clients: Map = new Map(); + tools: Map = new Map(); + stats = { startedAt: Date.now(), requests: 0, toolCalls: 0 }; + shuttingDown = false; + idleTimer: ReturnType | null = null; + server: net.Server | null = null; + onStopped: ((value: { alreadyRunning?: boolean; stopped?: boolean; reason?: string }) => void) | null = null; - freshConfig() { + freshConfig(): AgentCliConfig { return loadConfig(); } - armIdleTimer() { + armIdleTimer(): void { if (this.idleTimer) clearTimeout(this.idleTimer); this.idleTimer = setTimeout(() => { this.stop("idle timeout").catch(() => process.exit(0)); @@ -40,7 +41,7 @@ class DaemonState { this.idleTimer.unref(); } - async getClient(serverName, spec) { + async getClient(serverName: string, spec: ServerSpec): Promise { const specJson = JSON.stringify(spec); const existing = this.clients.get(serverName); if (existing) { @@ -53,12 +54,14 @@ class DaemonState { // ignore } } - let transport, Client, client; + let transport: unknown; + let Client: typeof import("@modelcontextprotocol/sdk/client/index.js").Client; + let client: McpClient; try { transport = await createTransport(spec); ({ Client } = await sdk()); client = new Client(CLIENT_INFO); - await client.connect(transport); + await client.connect(transport as Parameters[0]); } catch (e) { throw mapError(e, serverName); } @@ -66,7 +69,7 @@ class DaemonState { return client; } - async listTools({ server, refresh }) { + async listTools({ server, refresh }: { server: string; refresh?: boolean }): Promise<{ tools: McpTool[]; cached: boolean }> { const cfg = this.freshConfig(); const spec = cfg.servers[server]; if (!spec) { @@ -77,18 +80,18 @@ class DaemonState { return { tools: cached.tools, cached: true }; } const client = await this.getClient(server, spec); - let res; + let res: { tools?: unknown[] }; try { res = await client.listTools(undefined, { timeout: 30000 }); } catch (e) { throw mapError(e, server); } - const tools = res.tools || []; + const tools = (res.tools as McpTool[]) || []; this.tools.set(server, { tools, fetchedAt: Date.now() }); return { tools, cached: false }; } - async callTool({ server, tool, args, timeoutMs }) { + async callTool({ server, tool, args, timeoutMs }: { server: string; tool: string; args?: Record; timeoutMs?: number }): Promise { const cfg = this.freshConfig(); const spec = cfg.servers[server]; if (!spec) { @@ -103,7 +106,7 @@ class DaemonState { } } - status() { + status(): DaemonStatusData { return { pid: process.pid, startedAt: this.stats.startedAt, @@ -115,7 +118,7 @@ class DaemonState { }; } - async stop(reason = "shutdown") { + async stop(reason = "shutdown"): Promise { if (this.shuttingDown) return; this.shuttingDown = true; if (this.idleTimer) clearTimeout(this.idleTimer); @@ -147,8 +150,18 @@ class DaemonState { } } -async function handleLine(state, sock, line) { - let req; +interface DaemonRequest { + id: number | null; + op: string; + server?: string; + tool?: string; + args?: Record; + refresh?: boolean; + timeoutMs?: number; +} + +async function handleLine(state: DaemonState, sock: net.Socket, line: string): Promise { + let req: DaemonRequest; try { req = JSON.parse(line); } catch { @@ -158,7 +171,7 @@ async function handleLine(state, sock, line) { const { id, op } = req; try { state.stats.requests++; - let result; + let result: unknown; switch (op) { case "ping": result = { pong: true, pid: process.pid }; @@ -167,10 +180,10 @@ async function handleLine(state, sock, line) { result = state.status(); break; case "listTools": - result = await state.listTools(req); + result = await state.listTools({ server: req.server!, refresh: req.refresh }); break; case "callTool": - result = await withWatchdog(state.callTool(req), (req.timeoutMs ?? 60000) + 10000); + result = await withWatchdog(state.callTool({ server: req.server!, tool: req.tool!, args: req.args, timeoutMs: req.timeoutMs }), (req.timeoutMs ?? 60000) + 10000); break; case "shutdown": sock.write(JSON.stringify({ id, ok: true, result: { stopping: true } }) + "\n"); @@ -186,15 +199,15 @@ async function handleLine(state, sock, line) { state.armIdleTimer(); } -function withWatchdog(promise, ms) { +function withWatchdog(promise: Promise, ms: number): Promise { return Promise.race([ promise, - new Promise((_, reject) => setTimeout(() => reject(errors.timeout("daemon operation timed out")), ms).unref()), + new Promise((_, reject) => setTimeout(() => reject(errors.timeout("daemon operation timed out")), ms).unref()), ]); } // Public: boot the daemon in this process. Resolves when the daemon stops. -export async function runDaemon() { +export async function runDaemon(): Promise<{ alreadyRunning?: boolean; stopped?: boolean; reason?: string }> { ensureDaemonDir(); // Already running? Exit quietly (the CLI layer reports the live daemon). @@ -207,9 +220,9 @@ export async function runDaemon() { const state = new DaemonState(); const server = net.createServer((sock) => { const rl = readline.createInterface({ input: sock }); - rl.on("line", (line) => { + rl.on("line", (line: string) => { if (!line.trim()) return; - handleLine(state, sock, line).catch((e) => { + handleLine(state, sock, line).catch((e: unknown) => { // last-resort: never leave a request unanswered try { sock.write(JSON.stringify({ id: null, ok: false, error: serializeError(e) }) + "\n"); @@ -221,7 +234,7 @@ export async function runDaemon() { sock.on("error", () => {}); }); - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { server.once("error", reject); server.listen(socketPath(), resolve); }); @@ -240,18 +253,18 @@ export async function runDaemon() { return new Promise((resolve) => { state.onStopped = resolve; const wrapStop = state.stop.bind(state); - state.stop = async (reason) => { + state.stop = async (reason: string) => { await wrapStop(reason); resolve({ stopped: true, reason }); }; }); } -export async function pingSocket(timeoutMs = 1000) { +export async function pingSocket(timeoutMs = 1000): Promise { return new Promise((resolve) => { let buf = ""; const sock = net.connect(socketPath()); - const done = (r) => { + const done = (r: boolean) => { try { sock.destroy(); } catch { @@ -267,16 +280,16 @@ export async function pingSocket(timeoutMs = 1000) { sock.on("connect", () => { sock.write(JSON.stringify({ id: 0, op: "ping" }) + "\n"); }); - sock.on("data", (d) => { + sock.on("data", (d: Buffer) => { buf += d.toString(); if (!buf.includes("\n")) return; clearTimeout(timer); try { - const msg = JSON.parse(buf.slice(0, buf.indexOf("\n"))); - done(!!(msg && msg.ok && msg.result && msg.result.pong)); + const msg = JSON.parse(buf.slice(0, buf.indexOf("\n"))) as DaemonResponse; + done(!!(msg && msg.ok && msg.result && (msg.result as { pong?: boolean }).pong)); } catch { done(false); } }); }); -} \ No newline at end of file +} diff --git a/src/dispatch.js b/src/dispatch.ts similarity index 76% rename from src/dispatch.js rename to src/dispatch.ts index f3e8b59..0a9d99d 100644 --- a/src/dispatch.js +++ b/src/dispatch.ts @@ -5,22 +5,36 @@ import { suggest } from "./fuzzy.js"; import { buildFlagPlan, parseToolArgs, readInputJson, mergeArgs, renderToolHelp, validateRequired } from "./flags.js"; import { errors, EXIT } from "./errors.js"; import { printJson } from "./jsonout.js"; +import type { AgentCliConfig, McpTool } from "./types.js"; -function firstLine(text) { +function firstLine(text: string | undefined): string { return String(text || "").split("\n")[0]; } -function extractText(result) { +interface McpContentItem { + type: string; + text?: string; + [key: string]: unknown; +} + +interface McpCallToolResult { + content?: McpContentItem[]; + isError?: boolean; + structuredContent?: unknown; + [key: string]: unknown; +} + +function extractText(result: McpCallToolResult): string { return (result.content || []) .filter((c) => c && c.type === "text") - .map((c) => c.text) + .map((c) => c.text || "") .join("\n"); } // Some servers return JSON.stringify(JSON.stringify(payload)) — text content that // is still a JSON string after one parse. Unwrap while it stays a string (bounded). -function parseMaybeEncoded(text, maxDepth = 3) { - let v = text; +function parseMaybeEncoded(text: string, maxDepth = 3): unknown { + let v: unknown = text; for (let i = 0; typeof v === "string" && i <= maxDepth; i++) { try { v = JSON.parse(v); @@ -36,25 +50,27 @@ function parseMaybeEncoded(text, maxDepth = 3) { // else if all items are text: parsed JSON (double-encoded strings unwrapped), // joined text when it is not JSON; // else pass the content items through. -function extractData(result) { +function extractData(result: McpCallToolResult): unknown { if (result.structuredContent !== undefined) return result.structuredContent; const items = result.content || []; if (items.length === 0) return null; if (items.every((c) => c && c.type === "text")) { - return parseMaybeEncoded(items.map((c) => c.text).join("\n")); + return parseMaybeEncoded(items.map((c) => c.text || "").join("\n")); } return { content: items }; } -function toolNotFound(serverName, toolName, tools) { +function toolNotFound(serverName: string, toolName: string, tools: McpTool[]): AgentCliError { const similar = suggest(toolName, tools.map((t) => t.name)).slice(0, 5); - const hints = []; + const hints: string[] = []; if (similar.length) hints.push("Similar tools: " + similar.join(", ")); hints.push("List tools: agentcli " + serverName + " --help"); return errors.notFound('tool "' + toolName + '" not found on server "' + serverName + '"', hints.join(" | ")); } -async function printServerHelp(cfg, serverName, refresh) { +import type { AgentCliError } from "./errors.js"; + +async function printServerHelp(cfg: AgentCliConfig, serverName: string, refresh: boolean): Promise { const { tools } = await listTools(cfg, serverName, { refresh }); const lines = [serverName + " — MCP server (" + tools.length + " tools)", "", "Usage:", " agentcli " + serverName + " [flags]", "", "Tools:"]; for (const t of tools) { @@ -67,7 +83,7 @@ async function printServerHelp(cfg, serverName, refresh) { return EXIT.OK; } -export async function runServerCommand(cfg, serverName, tail) { +export async function runServerCommand(cfg: AgentCliConfig, serverName: string, tail: string[]): Promise { // 1. server-level help: `agentcli ` or `agentcli --help` if (tail.length === 0 || tail[0] === "--help" || tail[0] === "-h") { return printServerHelp(cfg, serverName, tail.includes("--refresh")); @@ -101,10 +117,10 @@ export async function runServerCommand(cfg, serverName, tail) { const { args: flagArgs, opts } = parseToolArgs(plan, rest); let args = flagArgs; if (opts.input !== undefined) { - args = mergeArgs(readInputJson(opts.input), flagArgs); + args = mergeArgs(readInputJson(opts.input as string), flagArgs); } validateRequired(plan, args); - const output = opts.output || "json"; + const output = (opts.output as string) || "json"; if (output !== "json" && output !== "text") { throw errors.invalidArgument('invalid --output "' + output + '"', "Supported: json, text"); } @@ -118,16 +134,18 @@ export async function runServerCommand(cfg, serverName, tail) { const started = Date.now(); const { result, via } = await callTool(cfg, serverName, toolName, args, { timeoutMs, daemon: !noDaemon }); - if (result.isError) { - throw errors.execution(extractText(result) || "tool reported an error without a message"); + const mcpResult = result as McpCallToolResult; + + if (mcpResult.isError) { + throw errors.execution(extractText(mcpResult) || "tool reported an error without a message"); } // 5. observe if (output === "text") { - const text = extractText(result); + const text = extractText(mcpResult); if (text === "") { // Non-text content items: fall back to the machine-readable form. - process.stdout.write(JSON.stringify(extractData(result)) + "\n"); + process.stdout.write(JSON.stringify(extractData(mcpResult)) + "\n"); } else { // Double-encoded JSON pretty-prints; genuine prose passes through raw. const v = parseMaybeEncoded(text); @@ -140,8 +158,8 @@ export async function runServerCommand(cfg, serverName, tail) { ok: true, server: serverName, tool: toolName, - data: extractData(result), + data: extractData(mcpResult), meta: { durationMs: Date.now() - started, schemaCached: cached, via }, }); return EXIT.OK; -} \ No newline at end of file +} diff --git a/src/errors.js b/src/errors.ts similarity index 61% rename from src/errors.js rename to src/errors.ts index 580ba04..e1f053c 100644 --- a/src/errors.js +++ b/src/errors.ts @@ -5,10 +5,19 @@ export const EXIT = { OK: 0, FAILURE: 1, -}; +} as const; + +export interface ErrorOptions { + hint?: string; + details?: unknown; +} export class AgentCliError extends Error { - constructor(code, message, { hint, details } = {}) { + code: string; + hint?: string; + details?: unknown; + + constructor(code: string, message: string, { hint, details }: ErrorOptions = {}) { super(message); this.name = "AgentCliError"; this.code = code; @@ -18,34 +27,42 @@ export class AgentCliError extends Error { } export const errors = { - invalidArgument: (message, hint) => + invalidArgument: (message: string, hint?: string): AgentCliError => new AgentCliError("INVALID_ARGUMENT", message, { hint }), - notFound: (message, hint) => + notFound: (message: string, hint?: string): AgentCliError => new AgentCliError("NOT_FOUND", message, { hint }), - execution: (message, details) => + execution: (message: string, details?: unknown): AgentCliError => new AgentCliError("EXECUTION_ERROR", message, { details }), - connect: (message, details) => + connect: (message: string, details?: unknown): AgentCliError => new AgentCliError("CONNECT_FAILED", message, { details, hint: "check server config/env or `agentcli daemon` state; do not blind-retry", }), - auth: (message) => + auth: (message: string): AgentCliError => new AgentCliError("AUTH_REQUIRED", message, { hint: "ask the user for credentials; do not retry with the same token", }), - timeout: (message) => + timeout: (message: string): AgentCliError => new AgentCliError("TIMEOUT", message, { hint: "retry, or raise --timeout-ms" }), }; // Wire format for daemon <-> CLI error transport. -export function serializeError(e) { +export interface SerializedError { + code: string; + message: string; + hint?: string; + details?: unknown; +} + +export function serializeError(e: unknown): SerializedError { if (e instanceof AgentCliError) { return { code: e.code, message: e.message, hint: e.hint, details: e.details }; } - return { code: "INTERNAL", message: String((e && e.message) || e), hint: "agentcli bug — please report it" }; + const err = e as Error; + return { code: "INTERNAL", message: String((err && err.message) || e), hint: "agentcli bug — please report it" }; } -export function reviveError(raw) { +export function reviveError(raw: SerializedError): AgentCliError { if (raw && typeof raw.code === "string") { return new AgentCliError(raw.code, raw.message || "unknown error", { hint: raw.hint, diff --git a/src/flags.js b/src/flags.ts similarity index 79% rename from src/flags.js rename to src/flags.ts index 905ca1d..93cc6fa 100644 --- a/src/flags.js +++ b/src/flags.ts @@ -3,13 +3,14 @@ // --input accepts inline JSON, @file, or - (stdin). Flags override --input keys. import fs from "node:fs"; import { errors } from "./errors.js"; +import type { ToolInputSchema, ToolPropertySchema, FlagPlan, FlagSpec, McpTool } from "./types.js"; const CONTROL_FLAGS = new Set(["input", "output", "schema", "refresh", "help", "timeout-ms", "no-daemon"]); -export function buildFlagPlan(inputSchema) { +export function buildFlagPlan(inputSchema: ToolInputSchema | undefined): FlagPlan { const schema = inputSchema || {}; const required = new Set(schema.required || []); - const plan = { flags: new Map(), complex: [], required: schema.required || [] }; + const plan: FlagPlan = { flags: new Map(), complex: [], required: schema.required || [] }; const props = schema.properties || {}; for (const name of Object.keys(props)) { const ps = props[name] || {}; @@ -17,13 +18,16 @@ export function buildFlagPlan(inputSchema) { !ps.type || ps.type === "object" || (ps.type === "array" && (!ps.items || !ps.items.type || ps.items.type === "object")) || - ps.anyOf || ps.oneOf || ps.allOf || ps.$ref; + ps.anyOf || + ps.oneOf || + ps.allOf || + ps.$ref; if (complex) { plan.complex.push({ name, description: ps.description || "" }); } else { plan.flags.set(name, { name, - type: ps.type, + type: ps.type as string, itemType: ps.type === "array" ? (ps.items && ps.items.type) || "string" : undefined, enum: ps.enum, default: ps.default, @@ -35,7 +39,7 @@ export function buildFlagPlan(inputSchema) { return plan; } -function coerceValue(spec, raw) { +function coerceValue(spec: FlagSpec, raw: string): unknown { const type = spec.type === "array" ? spec.itemType : spec.type; if (type === "boolean") { if (raw === "true") return true; @@ -55,17 +59,22 @@ function coerceValue(spec, raw) { return raw; } +interface ParsedArgs { + args: Record; + opts: Record; +} + // tokens: everything after `agentcli ` -export function parseToolArgs(plan, tokens) { - const args = {}; - const opts = {}; +export function parseToolArgs(plan: FlagPlan, tokens: string[]): ParsedArgs { + const args: Record = {}; + const opts: Record = {}; for (let i = 0; i < tokens.length; i++) { const tok = tokens[i]; if (!tok.startsWith("--")) { throw errors.invalidArgument('unexpected positional argument "' + tok + '"', "Pass values as flags, or the whole object via --input ''"); } let body = tok.slice(2); - let value; + let value: string | undefined; let hasValue = false; const eq = body.indexOf("="); if (eq >= 0) { @@ -81,13 +90,13 @@ export function parseToolArgs(plan, tokens) { value = tokens[++i]; if (value === undefined) throw errors.invalidArgument("--" + body + " requires a value"); } - opts[body] = value; + opts[body] = value as string; } continue; } const spec = plan.flags.get(body); if (!spec) { - const hints = []; + const hints: string[] = []; const available = [...plan.flags.keys()].map((k) => "--" + k).join(", "); if (available) hints.push("Available flags: " + available); if (plan.complex.length) hints.push("Complex parameters (use --input): " + plan.complex.map((c) => c.name).join(", ")); @@ -111,9 +120,10 @@ export function parseToolArgs(plan, tokens) { throw errors.invalidArgument("--" + body + " requires a value", "Use --" + body + "= when the value itself starts with --"); } } - const parsed = coerceValue(spec, raw); + const parsed = coerceValue(spec, raw as string); if (spec.type === "array") { - (args[body] ||= []).push(parsed); + const arr = (args[body] as unknown[]) ||= []; + arr.push(parsed); } else { args[body] = parsed; } @@ -121,39 +131,41 @@ export function parseToolArgs(plan, tokens) { return { args, opts }; } -export function readInputJson(spec) { - let rawText; +export function readInputJson(spec: string): Record { + let rawText: string; if (spec === "-") { rawText = fs.readFileSync(0, "utf8"); } else if (spec.startsWith("@")) { try { rawText = fs.readFileSync(spec.slice(1), "utf8"); } catch (e) { - throw errors.invalidArgument("cannot read --input file: " + spec.slice(1), e.message); + const err = e as Error; + throw errors.invalidArgument("cannot read --input file: " + spec.slice(1), err.message); } } else { rawText = spec; } - let value; + let value: unknown; try { value = JSON.parse(rawText); } catch (e) { - throw errors.invalidArgument("--input is not valid JSON", e.message); + const err = e as Error; + throw errors.invalidArgument("--input is not valid JSON", err.message); } if (value === null || typeof value !== "object" || Array.isArray(value)) { throw errors.invalidArgument("--input must be a JSON object"); } - return value; + return value as Record; } -export function mergeArgs(base, overrides) { +export function mergeArgs(base: Record | undefined, overrides: Record | undefined): Record { return { ...(base || {}), ...(overrides || {}) }; } // Required parameters must be present after flags + --input merge. A required // property with a schema default is left to the server. Throws INVALID_ARGUMENT. -export function validateRequired(plan, args) { - const missing = []; +export function validateRequired(plan: FlagPlan, args: Record): void { + const missing: string[] = []; for (const name of plan.required || []) { if (args[name] !== undefined) continue; const spec = plan.flags.get(name); @@ -168,9 +180,9 @@ export function validateRequired(plan, args) { ); } -export function renderToolHelp(serverName, tool) { +export function renderToolHelp(serverName: string, tool: McpTool): string { const plan = buildFlagPlan(tool.inputSchema); - const lines = []; + const lines: string[] = []; lines.push(serverName + " " + tool.name); if (tool.description) lines.push("", tool.description); lines.push("", "Usage:", " agentcli " + serverName + " " + tool.name + " [flags]"); @@ -184,7 +196,7 @@ export function renderToolHelp(serverName, tool) { if (opt.length || plan.complex.length) { lines.push("", "Optional:"); for (const f of opt) { - const parts = [f.description]; + const parts: string[] = [f.description]; if (f.enum) parts.push("choices: " + f.enum.join("|")); if (f.default !== undefined) parts.push("default: " + JSON.stringify(f.default)); const d = parts.filter(Boolean).join("; "); @@ -201,7 +213,8 @@ export function renderToolHelp(serverName, tool) { " --output Output format (default: json)", " --schema Print the raw tool input schema", " --refresh Bypass the cached tool list", - " --timeout-ms Request timeout in milliseconds" + " --timeout-ms Request timeout in milliseconds", + " --no-daemon Force direct mode (skip the daemon)" ); return lines.join("\n"); -} \ No newline at end of file +} diff --git a/src/fuzzy.js b/src/fuzzy.ts similarity index 82% rename from src/fuzzy.js rename to src/fuzzy.ts index 7d4e04d..c50b740 100644 --- a/src/fuzzy.js +++ b/src/fuzzy.ts @@ -1,6 +1,6 @@ // Typo-tolerant matching used for "did you mean" suggestions (no deps). -export function levenshtein(a, b) { +export function levenshtein(a: string, b: string): number { const m = a.length; const n = b.length; if (Math.abs(m - n) > 3) return Infinity; @@ -15,13 +15,13 @@ export function levenshtein(a, b) { return prev[n]; } -export function fuzzyMatch(typed, candidate) { +export function fuzzyMatch(typed: string, candidate: string): boolean { if (candidate.includes(typed)) return true; return levenshtein(typed, candidate) <= Math.max(1, Math.floor(candidate.length / 3)); } // Closest candidates for a typo'd name, best (lowest distance) first. -export function suggest(typed, candidates) { +export function suggest(typed: string, candidates: string[]): string[] { const lower = String(typed || "").toLowerCase(); if (!lower) return []; return candidates @@ -29,4 +29,4 @@ export function suggest(typed, candidates) { .filter(({ lower: cl }) => fuzzyMatch(lower, cl)) .sort((x, y) => levenshtein(lower, x.lower) - levenshtein(lower, y.lower)) .map(({ c }) => c); -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.ts similarity index 77% rename from src/index.js rename to src/index.ts index dbce1a4..bff55e0 100644 --- a/src/index.js +++ b/src/index.ts @@ -7,6 +7,7 @@ import { runServerCommand } from "./dispatch.js"; import { listTools } from "./client.js"; import { startDaemon, stopDaemon, daemonStatus } from "./daemon/lifecycle.js"; import { suggest } from "./fuzzy.js"; +import type { AgentCliConfig, ServerSpec, StdioServerSpec, HttpServerSpec } from "./types.js"; const { version } = pkg; @@ -14,34 +15,34 @@ const { version } = pkg; // server name is dispatched dynamically). const KNOWN_BUILTINS = new Set(["server", "daemon", "help", "version"]); -function exitWithError(e) { +function exitWithError(e: unknown): void { if (e instanceof AgentCliError) { printError(e); process.exitCode = EXIT.FAILURE; return; } - if (e && typeof e.code === "string" && e.code.startsWith("commander.")) { + const err = e as Error & { code?: string }; + if (err && typeof err.code === "string" && err.code.startsWith("commander.")) { // help/version output has already been written by commander - if (e.code === "commander.help" || e.code === "commander.helpDisplayed" || e.code === "commander.version") { + if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") { process.exitCode = EXIT.OK; return; } - const isUnknownCommand = e.code === "commander.unknownCommand"; + const isUnknownCommand = err.code === "commander.unknownCommand"; printError( - new AgentCliError(isUnknownCommand ? "NOT_FOUND" : "INVALID_ARGUMENT", e.message.replace(/^error:\s*/, ""), { - hint: "agentcli --help", + new AgentCliError(isUnknownCommand ? "NOT_FOUND" : "INVALID_ARGUMENT", err.message.replace(/^error:\s*/, ""), { hint: "agentcli --help", }) ); process.exitCode = EXIT.FAILURE; return; } - printError(new AgentCliError("INTERNAL", (e && e.stack) || String(e))); + printError(new AgentCliError("INTERNAL", (err && err.stack) || String(e))); process.exitCode = EXIT.FAILURE; } -function parseKeyValueList(list, flagName, expected, sep = "=") { - const out = {}; +function parseKeyValueList(list: string[] | undefined, flagName: string, expected: string, sep = "="): Record { + const out: Record = {}; for (const kv of list || []) { const idx = kv.indexOf(sep); if (idx <= 0) throw errors.invalidArgument("invalid --" + flagName + ' "' + kv + '"', "Expected " + expected); @@ -50,9 +51,9 @@ function parseKeyValueList(list, flagName, expected, sep = "=") { return out; } -function stripGlobalFlags(argv) { - let configPath; - const rest = []; +function stripGlobalFlags(argv: string[]): { rest: string[]; configPath: string | undefined } { + let configPath: string | undefined; + const rest: string[] = []; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a === "--config" || a === "-c") { @@ -72,7 +73,7 @@ function stripGlobalFlags(argv) { // The dynamic `agentcli ` surface is invisible to commander's // generated help, so surface configured servers explicitly -- top-level help is // the discovery entry point for agents and humans alike. -function serversHelpSection(cfg) { +function serversHelpSection(cfg: AgentCliConfig): string { const entries = Object.entries(cfg.servers); if (entries.length === 0) { return [ @@ -83,7 +84,7 @@ function serversHelpSection(cfg) { "", ].join("\n"); } - const lines = entries.map(([name, spec]) => { + const lines = entries.map(([name, spec]: [string, ServerSpec]) => { const detail = spec.type === "http" ? spec.url : [spec.command, ...(spec.args || [])].join(" "); return " " + name.padEnd(16) + (spec.type === "http" ? "http - " : "stdio - ") + detail; }); @@ -96,7 +97,7 @@ function serversHelpSection(cfg) { ].join("\n"); } -function buildBuiltins(cfg) { +function buildBuiltins(cfg: AgentCliConfig): Command { const program = new Command(); program .name("agentcli") @@ -120,11 +121,12 @@ function buildBuiltins(cfg) { .option("--header ", "HTTP header sent on every request (repeatable)") .option("--env ", "Extra env vars for a stdio server (repeatable)") .argument("[cmd...]", "stdio server command and args (after --)") - .action(async (name, cmd, opts) => { + .action(async (name: string, cmd: string[], opts: { url?: string; header?: string[]; env?: string[] }) => { const current = loadConfig(); if (opts.url) { if (cmd && cmd.length) throw errors.invalidArgument("--url and a command are mutually exclusive"); - addServer(current, name, { type: "http", url: opts.url, headers: parseKeyValueList(opts.header, "header", '"Name: value"', ":") }); + const spec: HttpServerSpec = { type: "http", url: opts.url, headers: parseKeyValueList(opts.header, "header", '"Name: value"', ":") }; + addServer(current, name, spec); } else { if (!cmd || cmd.length === 0) { throw errors.invalidArgument( @@ -133,7 +135,8 @@ function buildBuiltins(cfg) { ); } const [command, ...args] = cmd; - addServer(current, name, { type: "stdio", command, args, env: parseKeyValueList(opts.env, "env", "KEY=value") }); + const spec: StdioServerSpec = { type: "stdio", command, args, env: parseKeyValueList(opts.env, "env", "KEY=value") }; + addServer(current, name, spec); } printJson({ ok: true, server: name, config: process.env.AGENTCLI_CONFIG }); }); @@ -142,13 +145,12 @@ function buildBuiltins(cfg) { .command("list") .description("List configured servers.") .option("-o, --output ", "json | text (default: json)") - .action((opts) => { + .action((opts: { output?: string }) => { const current = loadConfig(); - const rows = Object.entries(current.servers).map(([name, spec]) => ({ - name, - type: spec.type, - ...(spec.type === "http" ? { url: spec.url } : { command: [spec.command, ...(spec.args || [])].join(" ") }), - })); + const rows: Array<{ name: string; type: string; command?: string; url?: string }> = Object.entries(current.servers).map(([name, spec]: [string, ServerSpec]) => { + if (spec.type === "http") return { name, type: spec.type, url: spec.url }; + return { name, type: spec.type, command: [spec.command, ...(spec.args || [])].join(" ") }; + }); if (opts.output === "text") { for (const r of rows) console.log(r.name.padEnd(16) + r.type.padEnd(8) + (r.command || r.url || "")); return; @@ -159,7 +161,7 @@ function buildBuiltins(cfg) { server .command("remove ") .description("Remove a configured server.") - .action((name) => { + .action((name: string) => { removeServer(loadConfig(), name); printJson({ ok: true, removed: name }); }); @@ -169,7 +171,7 @@ function buildBuiltins(cfg) { .description("List the tools a server exposes.") .option("--refresh", "Bypass the tools cache") .option("-o, --output ", "json | text (default: json)") - .action(async (name, opts) => { + .action(async (name: string, opts: { refresh?: boolean; output?: string }) => { const { tools } = await listTools(loadConfig(), name, { refresh: !!opts.refresh }); if (opts.output === "text") { for (const t of tools) console.log(String(t.name).padEnd(28) + String(t.description || "").split("\n")[0]); @@ -184,13 +186,13 @@ function buildBuiltins(cfg) { const daemon = program .command("daemon") .description("Manage the background daemon (persistent MCP connections, fast repeated calls).") - .action((opts, cmd) => cmd.help()); + .action((_opts: unknown, cmd: Command) => cmd.help()); daemon .command("start") .description("Start the daemon in the background (persists after this command exits).") .option("-f, --foreground", "Run in the foreground (logs to console; Ctrl-C stops it)") - .action(async (opts) => { + .action(async (opts: { foreground?: boolean }) => { const r = await startDaemon({ foreground: !!opts.foreground }); printJson(r); }); @@ -220,7 +222,7 @@ function buildBuiltins(cfg) { return program; } -export async function run(argv) { +export async function run(argv: string[]): Promise { try { const { rest, configPath } = stripGlobalFlags(argv); if (configPath) process.env.AGENTCLI_CONFIG = configPath; @@ -246,7 +248,7 @@ export async function run(argv) { if (first && !first.startsWith("-") && !cfg.servers[first] && !KNOWN_BUILTINS.has(first)) { const names = Object.keys(cfg.servers); const near = suggest(first, names); - const hints = []; + const hints: string[] = []; if (near.length) hints.push("Did you mean: " + near.join(", ") + "?"); hints.push( names.length @@ -262,4 +264,4 @@ export async function run(argv) { } catch (e) { exitWithError(e); } -} \ No newline at end of file +} diff --git a/src/jsonout.js b/src/jsonout.js deleted file mode 100644 index 7351137..0000000 --- a/src/jsonout.js +++ /dev/null @@ -1,17 +0,0 @@ -// stdout = machine-readable result, stderr = one-line JSON error (grep-friendly). -export function printJson(obj) { - process.stdout.write(JSON.stringify(obj, null, 2) + "\n"); -} - -export function printError(err) { - const payload = { - ok: false, - error: { - code: err.code || "INTERNAL", - message: err.message, - ...(err.hint ? { hint: err.hint } : {}), - ...(err.details ? { details: err.details } : {}), - }, - }; - process.stderr.write(JSON.stringify(payload) + "\n"); -} \ No newline at end of file diff --git a/src/jsonout.ts b/src/jsonout.ts new file mode 100644 index 0000000..133aa14 --- /dev/null +++ b/src/jsonout.ts @@ -0,0 +1,19 @@ +// stdout = machine-readable result, stderr = one-line JSON error (grep-friendly). +import type { AgentCliError } from "./errors.js"; + +export function printJson(obj: unknown): void { + process.stdout.write(JSON.stringify(obj, null, 2) + "\n"); +} + +export function printError(err: AgentCliError): void { + const payload: Record = { + ok: false, + error: { + code: err.code || "INTERNAL", + message: err.message, + }, + }; + if (err.hint) (payload.error as Record).hint = err.hint; + if (err.details) (payload.error as Record).details = err.details; + process.stderr.write(JSON.stringify(payload) + "\n"); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ed38a40 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,117 @@ +// Shared types used across the codebase. + +export interface StdioServerSpec { + type: "stdio"; + command: string; + args?: string[]; + env?: Record; +} + +export interface HttpServerSpec { + type: "http"; + url: string; + headers?: Record | string[]; +} + +export type ServerSpec = StdioServerSpec | HttpServerSpec; + +export interface AgentCliConfig { + version: 1; + servers: Record; +} + +export interface ToolInputSchema { + type?: string; + properties?: Record; + required?: string[]; + [key: string]: unknown; +} + +export interface ToolPropertySchema { + type?: string; + description?: string; + enum?: string[]; + default?: unknown; + items?: { type?: string }; + anyOf?: unknown[]; + oneOf?: unknown[]; + allOf?: unknown[]; + $ref?: string; + [key: string]: unknown; +} + +export interface McpTool { + name: string; + description?: string; + inputSchema?: ToolInputSchema; + [key: string]: unknown; +} + +export interface FlagSpec { + name: string; + type: string; + itemType?: string; + enum?: string[]; + default?: unknown; + description: string; + required: boolean; +} + +export interface ComplexParam { + name: string; + description: string; +} + +export interface FlagPlan { + flags: Map; + complex: ComplexParam[]; + required: string[]; +} + +export interface ToolsCache { + tools: McpTool[]; + fetchedAt: number; +} + +export interface DaemonRequestPayload { + id: number; + op: string; + server?: string; + tool?: string; + args?: Record; + refresh?: boolean; + timeoutMs?: number; +} + +export interface DaemonResponse { + id: number | null; + ok: boolean; + result?: unknown; + error?: { + code: string; + message: string; + hint?: string; + details?: unknown; + }; +} + +export interface CallToolResultEnvelope { + result: unknown; + via: "daemon" | "direct"; +} + +export interface ListToolsResultEnvelope { + tools: McpTool[]; + cached: boolean; + via: "daemon" | "direct"; +} + +export interface DaemonStatusData { + pid: number; + startedAt: number; + uptimeMs: number; + requests: number; + toolCalls: number; + connectedServers: string[]; + socket: string; +} diff --git a/test/daemon.test.js b/test/daemon.test.ts similarity index 74% rename from test/daemon.test.js rename to test/daemon.test.ts index c2714ac..6309552 100644 --- a/test/daemon.test.js +++ b/test/daemon.test.ts @@ -1,21 +1,21 @@ // Daemon mode: persistent MCP connections + transparent routing + fallback. import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -import { spawn, spawnSync } from "node:child_process"; +import { spawnSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.join(__dirname, ".."); +const ROOT = path.join(__dirname, "..", ".."); const BIN = path.join(ROOT, "bin", "agentcli.js"); -const FIXTURE = path.join(ROOT, "fixtures", "echo-server.mjs"); +const FIXTURE = path.join(ROOT, "dist", "fixtures", "echo-server.js"); -let tmp; -let configFile; +let tmp: string; +let configFile: string; -function cli(args, env = {}) { +function cli(args: string[], env: Record = {}) { return spawnSync(process.execPath, [BIN, ...args], { cwd: ROOT, encoding: "utf8", @@ -38,14 +38,14 @@ after(() => { test("daemon status before start: running=false, exit 0", () => { const r = cli(["daemon", "status"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.equal(out.running, false); }); test("daemon start returns a live status", () => { const r = cli(["daemon", "start"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.equal(out.ok, true); assert.equal(out.running, true); assert.ok(Number.isInteger(out.data.pid)); @@ -54,13 +54,13 @@ test("daemon start returns a live status", () => { test("daemon start is idempotent", () => { const r = cli(["daemon", "start"]); assert.equal(r.status, 0, r.stderr); - assert.equal(JSON.parse(r.stdout).alreadyRunning, true); + assert.equal(JSON.parse(r.stdout as string).alreadyRunning, true); }); test("tool calls route through the daemon", () => { const r = cli(["demo", "echo", "--message", "hi"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.equal(out.data, "hi"); assert.equal(out.meta.via, "daemon"); }); @@ -68,36 +68,36 @@ test("tool calls route through the daemon", () => { test("server tools lists via daemon", () => { const r = cli(["server", "tools", "demo"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.ok(out.data.length === 5); }); test("tool errors keep their exit codes through the daemon", () => { const r = cli(["demo", "fail"]); assert.equal(r.status, 1); - assert.equal(JSON.parse(r.stderr).error.code, "EXECUTION_ERROR"); + assert.equal(JSON.parse(r.stderr as string).error.code, "EXECUTION_ERROR"); const nf = cli(["demo", "nope"]); assert.equal(nf.status, 1); - assert.equal(JSON.parse(nf.stderr).error.code, "NOT_FOUND"); + assert.equal(JSON.parse(nf.stderr as string).error.code, "NOT_FOUND"); }); test("--no-daemon flag forces the direct path", () => { const r = cli(["demo", "echo", "--message", "x", "--no-daemon"]); assert.equal(r.status, 0, r.stderr); - assert.equal(JSON.parse(r.stdout).meta.via, "direct"); + assert.equal(JSON.parse(r.stdout as string).meta.via, "direct"); }); test("AGENTCLI_NO_DAEMON env forces the direct path", () => { const r = cli(["demo", "echo", "--message", "x"], { AGENTCLI_NO_DAEMON: "1" }); assert.equal(r.status, 0, r.stderr); - assert.equal(JSON.parse(r.stdout).meta.via, "direct"); + assert.equal(JSON.parse(r.stdout as string).meta.via, "direct"); }); test("daemon status shows the connected server", () => { const r = cli(["daemon", "status"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.equal(out.running, true); assert.ok(out.data.connectedServers.includes("demo")); }); @@ -105,24 +105,24 @@ test("daemon status shows the connected server", () => { test("after daemon stop, calls transparently fall back to direct", () => { const stopped = cli(["daemon", "stop"]); assert.equal(stopped.status, 0, stopped.stderr); - assert.equal(JSON.parse(stopped.stdout).stopped, true); + assert.equal(JSON.parse(stopped.stdout as string).stopped, true); const status = cli(["daemon", "status"]); - assert.equal(JSON.parse(status.stdout).running, false); + assert.equal(JSON.parse(status.stdout as string).running, false); const r = cli(["demo", "echo", "--message", "after-stop"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.equal(out.data, "after-stop"); assert.equal(out.meta.via, "direct"); }); -test("daemon restarts cleanly", async () => { +test("daemon restarts cleanly", () => { const started = cli(["daemon", "start"]); assert.equal(started.status, 0, started.stderr); const r = cli(["demo", "echo", "--message", "restarted"]); assert.equal(r.status, 0, r.stderr); - assert.equal(JSON.parse(r.stdout).meta.via, "daemon"); + assert.equal(JSON.parse(r.stdout as string).meta.via, "daemon"); const stopped = cli(["daemon", "stop"]); assert.equal(stopped.status, 0, stopped.stderr); -}); \ No newline at end of file +}); diff --git a/test/e2e.test.js b/test/e2e.test.ts similarity index 76% rename from test/e2e.test.js rename to test/e2e.test.ts index fa66fcc..758892d 100644 --- a/test/e2e.test.js +++ b/test/e2e.test.ts @@ -8,14 +8,14 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.join(__dirname, ".."); +const ROOT = path.join(__dirname, "..", ".."); const BIN = path.join(ROOT, "bin", "agentcli.js"); -const FIXTURE = path.join(ROOT, "fixtures", "echo-server.mjs"); +const FIXTURE = path.join(ROOT, "dist", "fixtures", "echo-server.js"); -let tmp; -let configFile; +let tmp: string; +let configFile: string; -function cli(args, env = {}) { +function cli(args: string[], env: Record = {}) { return spawnSync(process.execPath, [BIN, ...args], { cwd: ROOT, encoding: "utf8", @@ -43,22 +43,22 @@ test("server add (stdio) writes config", () => { test("server list shows the server (json default)", () => { const r = cli(["server", "list"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.equal(out.data[0].name, "demo"); - assert.match(out.data[0].command, /echo-server\.mjs$/); + assert.match(out.data[0].command, /echo-server\.js$/); }); test("server tools lists tools (json default)", () => { const r = cli(["server", "tools", "demo"]); assert.equal(r.status, 0, r.stderr); - const names = JSON.parse(r.stdout).data.map((t) => t.name).sort(); + const names = JSON.parse(r.stdout as string).data.map((t: { name: string }) => t.name).sort(); assert.deepEqual(names, ["complex", "double", "echo", "fail", "slow"]); }); test("tool call with flags", () => { const r = cli(["demo", "echo", "--message", "hi", "--times", "2"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.equal(out.ok, true); assert.equal(out.data, "hi hi"); assert.equal(out.server, "demo"); @@ -68,20 +68,20 @@ test("tool call with flags", () => { test("tool call with --flag=value and boolean flag", () => { const r = cli(["demo", "echo", "--message=hey", "--upper"]); assert.equal(r.status, 0, r.stderr); - assert.equal(JSON.parse(r.stdout).data, "HEY"); + assert.equal(JSON.parse(r.stdout as string).data, "HEY"); }); test("invalid integer value -> exits 1 INVALID_ARGUMENT", () => { const r = cli(["demo", "echo", "--message", "x", "--times", "bad"]); assert.equal(r.status, 1); - const err = JSON.parse(r.stderr); + const err = JSON.parse(r.stderr as string); assert.equal(err.error.code, "INVALID_ARGUMENT"); }); test("unknown flag -> exits 1 with hint listing flags", () => { const r = cli(["demo", "echo", "--nope", "1"]); assert.equal(r.status, 1); - const err = JSON.parse(r.stderr); + const err = JSON.parse(r.stderr as string); assert.equal(err.error.code, "INVALID_ARGUMENT"); assert.match(err.error.hint, /--message/); }); @@ -89,13 +89,13 @@ test("unknown flag -> exits 1 with hint listing flags", () => { test("positional argument -> exits 1", () => { const r = cli(["demo", "echo", "positional"]); assert.equal(r.status, 1); - assert.equal(JSON.parse(r.stderr).error.code, "INVALID_ARGUMENT"); + assert.equal(JSON.parse(r.stderr as string).error.code, "INVALID_ARGUMENT"); }); test("enum rejects invalid choice -> exits 1 with allowed values", () => { const r = cli(["demo", "echo", "--message", "hi", "--mode", "loud"]); assert.equal(r.status, 1); - const err = JSON.parse(r.stderr); + const err = JSON.parse(r.stderr as string); assert.equal(err.error.code, "INVALID_ARGUMENT"); assert.match(err.error.hint, /Allowed values: plain, shout/); }); @@ -103,13 +103,13 @@ test("enum rejects invalid choice -> exits 1 with allowed values", () => { test("enum accepts a valid choice", () => { const r = cli(["demo", "echo", "--message", "hi", "--mode", "shout"]); assert.equal(r.status, 0, r.stderr); - assert.equal(JSON.parse(r.stdout).data, "HI!!!"); + assert.equal(JSON.parse(r.stdout as string).data, "HI!!!"); }); test("missing required flag -> exits 1 before hitting the server", () => { const r = cli(["demo", "echo"]); assert.equal(r.status, 1); - const err = JSON.parse(r.stderr); + const err = JSON.parse(r.stderr as string); assert.equal(err.error.code, "INVALID_ARGUMENT"); assert.match(err.error.message, /missing required parameter: message/); assert.match(err.error.hint, /--message/); @@ -120,13 +120,13 @@ test("required complex param satisfied via --input is accepted", () => { assert.equal(r.status, 0, r.stderr); const r2 = cli(["demo", "complex"]); assert.equal(r2.status, 1); - assert.match(r2.stderr, /spec \(via --input\)/); + assert.match(r2.stderr as string, /spec \(via --input\)/); }); test("unknown tool -> exits 1 NOT_FOUND", () => { const r = cli(["demo", "nope"]); assert.equal(r.status, 1); - const err = JSON.parse(r.stderr); + const err = JSON.parse(r.stderr as string); assert.equal(err.error.code, "NOT_FOUND"); assert.match(err.error.hint, /agentcli demo --help/); }); @@ -134,50 +134,50 @@ test("unknown tool -> exits 1 NOT_FOUND", () => { test("unknown server -> exits 1 NOT_FOUND", () => { const r = cli(["ghost", "whatever"]); assert.equal(r.status, 1); - assert.equal(JSON.parse(r.stderr).error.code, "NOT_FOUND"); + assert.equal(JSON.parse(r.stderr as string).error.code, "NOT_FOUND"); }); test("typo in server name suggests the closest configured server", () => { const r = cli(["demoa", "whatever"]); assert.equal(r.status, 1); - const err = JSON.parse(r.stderr); + const err = JSON.parse(r.stderr as string); assert.match(err.error.hint, /Did you mean: demo/); }); test("top-level --help lists configured servers; empty config shows onboarding", () => { const r = cli(["--help"]); assert.equal(r.status, 0, r.stderr); - assert.match(r.stdout, /Configured servers/); - assert.match(r.stdout, /demo/); + assert.match(r.stdout as string, /Configured servers/); + assert.match(r.stdout as string, /demo/); const emptyConfig = path.join(tmp, "empty.json"); const empty = cli(["--help"], { AGENTCLI_CONFIG: emptyConfig }); assert.equal(empty.status, 0, empty.stderr); - assert.match(empty.stdout, /No servers configured yet/); - assert.match(empty.stdout, /server add/); + assert.match(empty.stdout as string, /No servers configured yet/); + assert.match(empty.stdout as string, /server add/); }); test("server -h lists configured servers", () => { const r = cli(["server", "-h"]); assert.equal(r.status, 0, r.stderr); - assert.match(r.stdout, /Configured servers: demo/); + assert.match(r.stdout as string, /Configured servers: demo/); }); test("object parameter cannot be a flag; --input works and flags merge", () => { const bad = cli(["demo", "complex", "--spec", "x"]); assert.equal(bad.status, 1); - assert.match(bad.stderr, /--input/); + assert.match(bad.stderr as string, /--input/); const ok = cli(["demo", "complex", "--input", '{"spec":{"a":1}}']); assert.equal(ok.status, 0, ok.stderr); - const out = JSON.parse(ok.stdout); + const out = JSON.parse(ok.stdout as string); assert.deepEqual(out.data, { received: { a: 1 } }); }); test("array-of-primitives via repeated flag, merged over --input", () => { const r = cli(["demo", "complex", "--input", '{"spec":{}}', "--tags", "a", "--tags", "b"]); assert.equal(r.status, 0, r.stderr); - assert.deepEqual(JSON.parse(r.stdout).data.receivedTags, ["a", "b"]); + assert.deepEqual(JSON.parse(r.stdout as string).data.receivedTags, ["a", "b"]); }); test("--input from file", () => { @@ -185,13 +185,13 @@ test("--input from file", () => { fs.writeFileSync(file, JSON.stringify({ spec: { via: "file" } })); const r = cli(["demo", "complex", "--input", "@" + file]); assert.equal(r.status, 0, r.stderr); - assert.deepEqual(JSON.parse(r.stdout).data.received, { via: "file" }); + assert.deepEqual(JSON.parse(r.stdout as string).data.received, { via: "file" }); }); test("tool error -> exit 1 EXECUTION_ERROR", () => { const r = cli(["demo", "fail"]); assert.equal(r.status, 1); - const err = JSON.parse(r.stderr); + const err = JSON.parse(r.stderr as string); assert.equal(err.error.code, "EXECUTION_ERROR"); assert.match(err.error.message, /boom/); }); @@ -199,33 +199,33 @@ test("tool error -> exit 1 EXECUTION_ERROR", () => { test("--output text prints raw content", () => { const r = cli(["demo", "echo", "--message", "plain", "--output", "text"]); assert.equal(r.status, 0, r.stderr); - assert.equal(r.stdout.trim(), "plain"); + assert.equal((r.stdout as string).trim(), "plain"); }); test("--schema prints the raw input schema", () => { const r = cli(["demo", "echo", "--schema"]); assert.equal(r.status, 0, r.stderr); - const schema = JSON.parse(r.stdout); + const schema = JSON.parse(r.stdout as string); assert.equal(schema.properties.message.type, "string"); }); test("--help renders generated usage (tool and server level)", () => { const tool = cli(["demo", "echo", "--help"]); assert.equal(tool.status, 0, tool.stderr); - assert.match(tool.stdout, /Required:/); - assert.match(tool.stdout, /--message /); - assert.match(tool.stdout, /default: 1/); + assert.match(tool.stdout as string, /Required:/); + assert.match(tool.stdout as string, /--message /); + assert.match(tool.stdout as string, /default: 1/); const srv = cli(["demo", "--help"]); assert.equal(srv.status, 0, srv.stderr); - assert.match(srv.stdout, /echo/); - assert.match(srv.stdout, /complex/); + assert.match(srv.stdout as string, /echo/); + assert.match(srv.stdout as string, /complex/); }); test("timeout -> exits 1 TIMEOUT", () => { const r = cli(["demo", "slow", "--ms", "5000"], { AGENTCLI_TIMEOUT_MS: "400" }); assert.equal(r.status, 1, r.stderr); - assert.equal(JSON.parse(r.stderr).error.code, "TIMEOUT"); + assert.equal(JSON.parse(r.stderr as string).error.code, "TIMEOUT"); }); test("tools cache file is written and reused", () => { @@ -238,37 +238,37 @@ test("tools cache file is written and reused", () => { test("reserved names are rejected", () => { const r = cli(["server", "add", "server", "--", process.execPath, FIXTURE]); assert.equal(r.status, 1); - assert.match(r.stderr, /reserved/); + assert.match(r.stderr as string, /reserved/); }); test("duplicate server is rejected", () => { const r = cli(["server", "add", "demo", "--", process.execPath, FIXTURE]); assert.equal(r.status, 1); - assert.match(r.stderr, /already exists/); + assert.match(r.stderr as string, /already exists/); }); test("double-encoded text content is unwrapped into data (json mode)", () => { const r = cli(["demo", "double", "--n", "7"]); assert.equal(r.status, 0, r.stderr); - const out = JSON.parse(r.stdout); + const out = JSON.parse(r.stdout as string); assert.deepEqual(out.data, [{ id: 7 }, { id: 8 }]); }); test("double-encoded text content pretty-prints in --output text (jq-ready)", () => { const r = cli(["demo", "double", "--n", "7", "--output", "text"]); assert.equal(r.status, 0, r.stderr); - assert.deepEqual(JSON.parse(r.stdout), [{ id: 7 }, { id: 8 }]); - assert.match(r.stdout, /\n\s+"id"/); + assert.deepEqual(JSON.parse(r.stdout as string), [{ id: 7 }, { id: 8 }]); + assert.match(r.stdout as string, /\n\s+"id"/); }); test("prose text passes through raw in --output text", () => { const r = cli(["demo", "echo", "--message", "hi", "--output", "text"]); assert.equal(r.status, 0, r.stderr); - assert.equal(r.stdout.trim(), "hi"); + assert.equal((r.stdout as string).trim(), "hi"); }); test("server remove", () => { const r = cli(["server", "remove", "demo"]); assert.equal(r.status, 0, r.stderr); const cfg = JSON.parse(fs.readFileSync(configFile, "utf8")); assert.ok(!cfg.servers.demo); -}); \ No newline at end of file +}); diff --git a/test/skill.test.js b/test/skill.test.ts similarity index 97% rename from test/skill.test.js rename to test/skill.test.ts index 0500780..4f36d71 100644 --- a/test/skill.test.js +++ b/test/skill.test.ts @@ -7,7 +7,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.join(__dirname, ".."); +const ROOT = path.join(__dirname, "..", ".."); const SKILL = path.join(ROOT, "skills", "agentcli", "SKILL.md"); test("skill file exists with valid frontmatter", () => { @@ -47,4 +47,4 @@ test("skill stays thin: no tool catalogs", () => { const raw = fs.readFileSync(SKILL, "utf8"); // The skill must teach a method, not enumerate tools from any specific server. assert.ok(raw.length < 6000, "skill is drifting toward a tool catalog; keep it under 6KB, currently " + raw.length); -}); \ No newline at end of file +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e038ee6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "rootDir": ".", + "sourceMap": true, + "declaration": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"], + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": false, + "noEmitOnError": true + }, + "include": ["src/**/*.ts", "test/**/*.ts", "fixtures/**/*.ts"], + "exclude": ["node_modules", "dist"] +} From 08c40b3c35049f836c2f6d1c1910406af10de1c4 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 13:37:16 +0800 Subject: [PATCH 2/9] chore: release v0.1.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 106325f..a0f9474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@happyvibing/agentcli", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@happyvibing/agentcli", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", diff --git a/package.json b/package.json index d45f6c9..c18d4d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@happyvibing/agentcli", - "version": "0.1.0", + "version": "0.1.1", "repository": { "type": "git", "url": "git+ssh://git@github.com/happyvibing/agentcli.git" From 92baed6c1c73b8ea123e4d18336dcf1474605109 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 13:41:52 +0800 Subject: [PATCH 3/9] ci: add GitHub Actions (CI, npm publish on release, PR title lint) - CI: build + test on push to main/ts and PRs to main, Node 20/22 matrix to cover the >=20.10 engines floor (import attributes) - Release: publish to npm automatically when a GitHub release is published (NPM_TOKEN secret, NODE_AUTH_TOKEN via registry-url) - Lint PR: validate conventional-commit PR titles --- .github/workflows/ci.yaml | 29 +++++++++++++++++++++++++++++ .github/workflows/lint-pr.yaml | 22 ++++++++++++++++++++++ .github/workflows/release.yaml | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/lint-pr.yaml create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9cc5dca --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [main, ts] + pull_request: + branches: [main] + +jobs: + test: + name: Test (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + strategy: + matrix: + # 20 = engines floor (>=20.10, import attributes), 22 = current LTS + node-version: [20, 22] + steps: + - uses: actions/checkout@v5 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Run tests + run: npm test diff --git a/.github/workflows/lint-pr.yaml b/.github/workflows/lint-pr.yaml new file mode 100644 index 0000000..98020b8 --- /dev/null +++ b/.github/workflows/lint-pr.yaml @@ -0,0 +1,22 @@ +name: "Lint PR" + +on: + pull_request_target: + types: + - opened + - edited + - reopened + +jobs: + lint-pr: + name: Validate PR title + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + subjectPattern: ^[a-zA-Z0-9!-_@#=*`.|+,\s]+$ + subjectPatternError: "The subject \"{subject}\" found in the pr title \"{title}\" didn't match the configured pattern. Can only chars, numbers and symbols !-_@#=*`|+." diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..aad1e0a --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,34 @@ +name: Release + +on: + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read + id-token: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Use Node.js 22 + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Run tests + run: npm test + - name: Publish to npm + # prepublishOnly re-runs build + test as a final gate inside publish. + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 480c701e914c09730eb24c151c61eba7100c4a1d Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 13:44:06 +0800 Subject: [PATCH 4/9] fix: regenerate package-lock.json (was polluted by pnpm link entries) The lock file contained pnpm-style node_modules/.pnpm link entries, so npm ci on CI resolved commander to a broken local path and tsc failed with 'Cannot find module commander'. Regenerate the lock with npm and drop the stale pnpm-lock.yaml; the project is npm-only. --- package-lock.json | 7365 +++++---------------------------------------- pnpm-lock.yaml | 23 - 2 files changed, 736 insertions(+), 6652 deletions(-) delete mode 100644 pnpm-lock.yaml diff --git a/package-lock.json b/package-lock.json index a0f9474..543d3ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6162 +20,567 @@ "typescript": "^5.7.0" }, "engines": { - "node": ">=20" - } - }, - "node_modules/.pnpm/commander@12.1.0/node_modules/commander": { - "version": "12.1.0", - "license": "MIT", - "devDependencies": { - "@eslint/js": "^8.56.0", - "@types/jest": "^29.2.4", - "@types/node": "^20.2.5", - "eslint": "^8.30.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-jest": "^28.3.0", - "eslint-plugin-jsdoc": "^48.1.0", - "globals": "^13.24.0", - "jest": "^29.3.1", - "prettier": "^3.2.5", - "prettier-plugin-jsdoc": "^1.3.0", - "ts-jest": "^29.0.3", - "tsd": "^0.31.0", - "typescript": "^5.0.4", - "typescript-eslint": "^7.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/.pnpm/commander@12.1.0/node_modules/commander/node_modules/@types/node": { - "version": "20.19.43", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", - "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "node": ">=20.10" } }, - "node_modules/.pnpm/commander@12.1.0/node_modules/commander/node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", - "dev": true, + "node_modules/@hono/node-server": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", + "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, "engines": { - "node": ">=0.4.7" + "node": ">=20" }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "peerDependencies": { + "hono": "^4" } }, - "node_modules/.pnpm/commander@12.1.0/node_modules/commander/node_modules/ts-jest": { - "version": "29.4.12", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz", - "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==", - "dev": true, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", "license": "MIT", "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.9", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.8.5", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + "node": ">=18" }, "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <7" + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { + "@cfworker/json-schema": { "optional": true }, - "jest-util": { - "optional": true + "zod": { + "optional": false } } }, - "node_modules/.pnpm/commander@12.1.0/node_modules/commander/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" + "undici-types": "~6.21.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "dev": true, + "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": ">=6.9.0" + "node": ">= 0.6" } }, - "node_modules/@babel/core": { - "version": "7.29.7", - "dev": true, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" + "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": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "dev": true, + "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": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "ajv": "^8.0.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "dev": true, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "dev": true, + "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": ">=6.9.0" + "node": ">= 0.8" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "dev": true, + "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": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "dev": true, + "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": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "dev": true, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "dev": true, + "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": ">=6.9.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "dev": true, + "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": ">=6.9.0" + "node": ">= 0.6" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "dev": true, + "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": ">=6.9.0" + "node": ">= 0.6" } }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "dev": true, + "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", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">=6.6.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "dev": true, + "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": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "node": ">= 0.10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "dev": true, + "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": { - "@babel/helper-plugin-utils": "^7.12.13" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 8" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "dev": true, + "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": { - "@babel/helper-plugin-utils": "^7.14.5" + "ms": "^2.1.3" }, "engines": { - "node": ">=6.9.0" + "node": ">=6.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.29.7", - "dev": 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", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 0.8" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, + "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": { - "@babel/helper-plugin-utils": "^7.10.4" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "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/@babel/plugin-syntax-jsx": { - "version": "7.29.7", - "dev": true, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 0.8" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, + "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", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, + "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", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, + "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": { - "@babel/helper-plugin-utils": "^7.10.4" + "es-errors": "^1.3.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "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/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, + "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", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, + "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": { - "@babel/helper-plugin-utils": "^7.8.0" + "eventsource-parser": "^3.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "dev": true, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "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": ">=6.9.0" + "node": ">= 18" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.29.7", - "dev": true, + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "debug": "^4.4.3", + "ip-address": "^10.2.0" }, "engines": { - "node": ">=6.9.0" + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "express": ">= 4.11" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "dev": true, + "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.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "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": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" + "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": ">=6.9.0" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "dev": true, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.6" } }, - "node_modules/@babel/types": { - "version": "7.29.8", - "dev": true, + "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", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.8" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.46.0", - "dev": true, + "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", - "dependencies": { - "comment-parser": "1.4.1", - "esquery": "^1.6.0", - "jsdoc-type-pratt-parser": "~4.0.0" - }, - "engines": { - "node": ">=16" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.10.1", - "dev": true, + "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": { - "eslint-visitor-keys": "^3.4.3" + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, + "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": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">= 0.4" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@hono/node-server": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", - "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.15.2", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgr/core": { - "version": "0.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@tsd/typescript": { - "version": "5.4.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/debug": { - "version": "4.1.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/eslint": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.20.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", - "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { - "version": "1.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/minimatch": { - "version": "9.0.9", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/ts-api-utils": { - "dev": true - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.69.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.69.0", - "@typescript-eslint/types": "^8.69.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.69.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/visitor-keys": "8.69.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.69.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { - "version": "9.0.9", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": { - "version": "1.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.69.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.69.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.69.0", - "@typescript-eslint/tsconfig-utils": "8.69.0", - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/visitor-keys": "8.69.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.6", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.69.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.69.0", - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/typescript-estree": "8.69.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.69.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.69.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.4.0", - "dev": true, - "license": "ISC" - }, - "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.18.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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": "4.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.21", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/binary-searching": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.18", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.9", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.11.20", - "caniuse-lite": "^1.0.30001810", - "electron-to-chromium": "^1.5.420", - "node-releases": "^2.0.54", - "update-browserslist-db": "^1.3.2" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-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/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "resolved": "node_modules/.pnpm/commander@12.1.0/node_modules/commander", - "link": true - }, - "node_modules/comment-parser": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "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", - "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/create-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "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", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dedent": { - "version": "1.7.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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/dequal": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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/electron-to-chromium": { - "version": "1.5.422", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "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/error-ex": { - "version": "1.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "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", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "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/escalade": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "9.1.2", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-formatter-pretty": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "^7.2.13", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "eslint-rule-docs": "^1.1.5", - "log-symbols": "^4.0.0", - "plur": "^4.0.0", - "string-width": "^4.2.0", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-jest": { - "version": "28.14.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "engines": { - "node": "^16.10.0 || ^18.12.0 || >=20.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsdoc": { - "version": "48.11.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.46.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.1", - "debug": "^4.3.5", - "escape-string-regexp": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.6.0", - "parse-imports": "^2.1.1", - "semver": "^7.6.3", - "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/espree": { - "version": "10.4.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-rule-docs": { - "version": "1.1.235", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "9.6.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", - "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", - "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.3", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.4.4", - "dev": true, - "license": "ISC" - }, - "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/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "dev": 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", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "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-package-type": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "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/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/graceful-fs": { - "version": "4.2.11", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hono": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", - "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "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/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", - "license": "MIT", - "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/irregular-plurals": { - "version": "3.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.2", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jose": { - "version": "6.2.12", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", - "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.3.2", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "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/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-obj": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/mdast-util-from-markdown": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/meow": { - "version": "9.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "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/merge-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "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", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", - "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/negotiator/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.54", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/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", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-imports": { - "version": "2.2.1", - "dev": true, - "license": "Apache-2.0 AND MIT", - "dependencies": { - "es-module-lexer": "^1.5.3", - "slashes": "^3.0.12" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "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/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "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/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "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-dir": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/plur": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "irregular-plurals": "^3.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.9.6", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-jsdoc": { - "version": "1.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-searching": "^2.0.5", - "comment-parser": "^1.4.0", - "mdast-util-from-markdown": "^2.0.0" - }, - "engines": { - "node": ">=14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "prettier": "^3.0.0" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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-is": { - "version": "18.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.12", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, + "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" }, @@ -6183,641 +588,553 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { + "node_modules/has-symbols": { "version": "1.1.0", - "dev": true, + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "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": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 18" + "node": ">= 0.4" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/hono": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "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/semver": { - "version": "7.8.5", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": ">=16.9.0" } }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "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": { - "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" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">= 18" + "node": ">= 0.8" }, "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==", + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 18" + "node": ">=0.10.0" }, "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==", + "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/shebang-command": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", + "node_modules/ip-address": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 12" } }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "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", - "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": ">= 0.10" } }, - "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/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/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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, - "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==", + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "license": "MIT", - "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" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "dev": true, + "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/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slashes": { - "version": "3.0.12", - "dev": true, - "license": "ISC" + "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/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", + "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.10.0" + "node": ">= 0.4" } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "dev": true, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "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/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, + "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", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "dev": true, + "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": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" + "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/stack-utils": { - "version": "2.0.6", - "dev": true, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", "dependencies": { - "escape-string-regexp": "^2.0.0" + "content-type": "^2.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "dev": true, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "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.8" + "node": ">=0.10.0" } }, - "node_modules/string-length": { - "version": "4.0.2", - "dev": true, + "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", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-width": { - "version": "4.2.3", - "dev": true, + "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": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ee-first": "1.1.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", + "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": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "wrappy": "1" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "dev": true, + "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": ">=8" + "node": ">= 0.8" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "dev": true, + "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", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, + "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": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16.20.0" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "dev": true, + "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": { - "has-flag": "^4.0.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "dev": true, - "license": "MIT", + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 0.6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/synckit": { - "version": "0.9.3", - "dev": true, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "node": ">= 0.10" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "dev": true, - "license": "ISC", + "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/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": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "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": ">=8" + "node": ">= 18" } }, - "node_modules/text-table": { - "version": "0.2.0", - "dev": true, + "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/tinyglobby": { - "version": "0.2.17", - "dev": true, + "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": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "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": ">=12.0.0" + "node": ">= 18" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "dev": true, + "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", - "engines": { - "node": ">=12.0.0" + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "engines": { + "node": ">= 18" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "dev": true, - "license": "BSD-3-Clause" + "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/to-regex-range": { - "version": "5.0.1", - "dev": true, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=8.0" + "node": ">=8" } }, - "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==", + "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": ">=0.6" + "node": ">=8" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "dev": true, + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "dev": true, + "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": ">=18.12" + "node": ">= 0.4" }, - "peerDependencies": { - "typescript": ">=4.8.4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tsd": { - "version": "0.31.2", - "dev": true, + "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": { - "@tsd/typescript": "~5.4.3", - "eslint-formatter-pretty": "^4.1.0", - "globby": "^11.0.1", - "jest-diff": "^29.0.3", - "meow": "^9.0.0", - "path-exists": "^4.0.0", - "read-pkg-up": "^7.0.0" - }, - "bin": { - "tsd": "dist/cli.js" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=14.16" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, + "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": { - "prelude-ls": "^1.2.1" + "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.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "dev": true, + "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": ">=4" + "node": ">= 0.8" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "dev": true, - "license": "(MIT OR CC0-1.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": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.6" } }, "node_modules/type-is": { @@ -6853,6 +1170,8 @@ }, "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": { @@ -6863,63 +1182,13 @@ "node": ">=14.17" } }, - "node_modules/typescript-eslint": { - "version": "7.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "7.18.0", - "@typescript-eslint/parser": "7.18.0", - "@typescript-eslint/utils": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { - "dev": true - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/undici-types": { "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6929,74 +1198,6 @@ "node": ">= 0.8" } }, - "node_modules/update-browserslist-db": { - "version": "1.3.2", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -7006,16 +1207,10 @@ "node": ">= 0.8" } }, - "node_modules/walker": { - "version": "1.0.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, "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" @@ -7027,100 +1222,12 @@ "node": ">= 8" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "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/write-file-atomic": { - "version": "4.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.3", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index c6c6294..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,23 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - commander: - specifier: ^12.1.0 - version: 12.1.0 - -packages: - - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - -snapshots: - - commander@12.1.0: {} From 2b6d1ea5dd299a9f25320f8bcaddfefa80e464a4 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 13:47:32 +0800 Subject: [PATCH 5/9] chore: switch the project to pnpm - add packageManager: pnpm@10.28.0 (corepack pins the toolchain) - regenerate the lockfile as pnpm-lock.yaml, drop package-lock.json - make scripts self-contained (build inlines the clean step) because pnpm does not run pre/post hooks by default; prepublishOnly now runs clean + tsc + tests directly - CI and Release workflows install with pnpm (pnpm/action-setup reads the version from packageManager; frozen lockfile; pnpm cache) - publishing stays on npm publish: the pnpm packer does not honor the negation patterns in the files field, so a pnpm publish would ship 44 files including .d.ts and source maps instead of the intended 18-file tarball - README: install and from-source instructions use pnpm; engines note updated to >= 20.10 Verified locally: pnpm install/build/test pass (48/48); npm publish --dry-run runs prepublishOnly end to end and packs the 18-file tarball. --- .github/workflows/ci.yaml | 11 +- .github/workflows/release.yaml | 16 +- README.md | 4 +- package-lock.json | 1250 -------------------------------- package.json | 13 +- pnpm-lock.yaml | 821 +++++++++++++++++++++ 6 files changed, 847 insertions(+), 1268 deletions(-) delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9cc5dca..cbe1859 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,14 +16,17 @@ jobs: node-version: [20, 22] steps: - uses: actions/checkout@v5 + - name: Set up pnpm + uses: pnpm/action-setup@v4 + # version is taken from the packageManager field in package.json - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} - cache: 'npm' + cache: 'pnpm' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Build - run: npm run build + run: pnpm build - name: Run tests - run: npm test + run: pnpm test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index aad1e0a..ad3dba7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -15,20 +15,26 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + - name: Set up pnpm + uses: pnpm/action-setup@v4 + # version is taken from the packageManager field in package.json - name: Use Node.js 22 uses: actions/setup-node@v5 with: node-version: 22 - cache: 'npm' + cache: 'pnpm' registry-url: 'https://registry.npmjs.org' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Build - run: npm run build + run: pnpm build - name: Run tests - run: npm test + run: pnpm test - name: Publish to npm - # prepublishOnly re-runs build + test as a final gate inside publish. + # npm (not pnpm) publish: pnpm's packer does not honor the "!" + # negation patterns in the files field, which keeps .d.ts and + # source maps out of the tarball. prepublishOnly re-runs clean, + # build, and tests as a final gate inside publish. run: npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 07351d7..57364e0 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ Agents that register every MCP tool upfront pay for it in context: hundreds of s ## Install ```bash -npm install -g @happyvibing/agentcli +pnpm add -g @happyvibing/agentcli # or: npm install -g @happyvibing/agentcli ``` -Requires Node >= 20. Or run from source: `git clone && npm install && npm link`. +Requires Node >= 20.10. Or run from source: `git clone && pnpm install && pnpm build && pnpm link --global`. ## Quick start diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 543d3ba..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1250 +0,0 @@ -{ - "name": "@happyvibing/agentcli", - "version": "0.1.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@happyvibing/agentcli", - "version": "0.1.1", - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "commander": "^12.1.0" - }, - "bin": { - "agentcli": "bin/agentcli.js" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" - }, - "engines": { - "node": ">=20.10" - } - }, - "node_modules/@hono/node-server": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", - "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@types/node": { - "version": "22.20.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", - "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ajv": { - "version": "8.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/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "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/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-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/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/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.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", - "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", - "license": "MIT", - "engines": { - "node": ">=18.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.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", - "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/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.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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.13.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", - "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", - "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.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "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.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", - "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.12", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", - "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, - "node_modules/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.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", - "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/negotiator/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/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/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/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.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.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/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/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "2.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.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "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/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "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/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/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.5.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", - "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", - "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" - } - } - } -} diff --git a/package.json b/package.json index c18d4d6..6179cd6 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git+ssh://git@github.com/happyvibing/agentcli.git" }, - "description": "AgentCLI — call MCP servers from the command line. One CLI, any MCP server.", + "description": "AgentCLI \u2014 call MCP servers from the command line. One CLI, any MCP server.", "type": "module", "bin": { "agentcli": "bin/agentcli.js" @@ -37,13 +37,11 @@ "node": ">=20.10" }, "scripts": { - "build": "tsc", "clean": "rm -rf dist", + "build": "rm -rf dist && tsc", "start": "node bin/agentcli.js", - "prebuild": "npm run clean", - "prepublishOnly": "npm run build && npm test", - "pretest": "npm run build", - "test": "node --test dist/test/*.test.js" + "test": "node --test dist/test/*.test.js", + "prepublishOnly": "rm -rf dist && tsc && node --test dist/test/*.test.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", @@ -52,5 +50,6 @@ "devDependencies": { "typescript": "^5.7.0", "@types/node": "^22.0.0" - } + }, + "packageManager": "pnpm@10.28.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..2dd5378 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,821 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.30.0 + version: 1.30.0(zod@4.5.4) + commander: + specifier: ^12.1.0 + version: 12.1.0 + devDependencies: + '@types/node': + specifier: ^22.0.0 + version: 22.20.1 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@hono/node-server@2.1.1': + resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 + + '@modelcontextprotocol/sdk@1.30.0': + resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@types/node@22.20.1': + resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@8.7.0: + resolution: {integrity: sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hono@4.13.7: + resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.7.0: + resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.2.12: + resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} + engines: {node: '>=0.6'} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.5.4: + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} + +snapshots: + + '@hono/node-server@2.1.1(hono@4.13.7)': + dependencies: + hono: 4.13.7 + + '@modelcontextprotocol/sdk@1.30.0(zod@4.5.4)': + dependencies: + '@hono/node-server': 2.1.1(hono@4.13.7) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.2.1 + express-rate-limit: 8.7.0(express@5.2.1) + hono: 4.13.7 + jose: 6.2.12 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.5.4 + zod-to-json-schema: 3.25.2(zod@4.5.4) + transitivePeerDependencies: + - supports-color + + '@types/node@22.20.1': + dependencies: + undici-types: 6.21.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.7 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.16.0 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + commander@12.1.0: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + + express-rate-limit@8.7.0(express@5.2.1): + dependencies: + debug: 4.4.3 + express: 5.2.1 + ip-address: 10.7.0 + transitivePeerDependencies: + - supports-color + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.16.0 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.7: {} + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hono@4.13.7: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ip-address@10.7.0: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.2.12: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + math-intrinsics@1.1.0: {} + + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + ms@2.1.3: {} + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@8.4.2: {} + + pkce-challenge@5.0.1: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.16.0: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + require-from-string@2.0.2: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + + send@1.2.1: + 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.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + 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 + + statuses@2.0.2: {} + + toidentifier@1.0.1: {} + + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrappy@1.0.2: {} + + zod-to-json-schema@3.25.2(zod@4.5.4): + dependencies: + zod: 4.5.4 + + zod@4.5.4: {} From bc88c6178e4be1750ff115b4abbf27a9a753891c Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 14:00:44 +0800 Subject: [PATCH 6/9] docs: OpenAPI unified dynamic-loading adapter design --- docs/design-openapi-adapter.md | 248 +++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 docs/design-openapi-adapter.md diff --git a/docs/design-openapi-adapter.md b/docs/design-openapi-adapter.md new file mode 100644 index 0000000..3edbf22 --- /dev/null +++ b/docs/design-openapi-adapter.md @@ -0,0 +1,248 @@ +# OpenAPI 统一动态加载适配器 — 设计文档 + +> 状态: 设计稿 (openapi-adapter-design 分支) +> 前置: AgentCLI v0.1.1 已实现 MCP → CLI 的动态转换。本文设计把 OpenAPI (3.x JSON) 纳入同一运行时。 + +## 0. 一句话 + +**CLI 核心只认识 "工具 + JSON Schema";后端负责生产它们。** +MCP 后端从协议里拿工具列表;OpenAPI 后端把 spec 编译成工具列表。往下 (flag 编译 / 帮助 / 执行 / 缓存 / 错误 / 退出码) 全部复用,零分叉。 + +``` + agentcli --flags + │ + dispatch.ts (backend 无关) + │ ToolDef { name, description, inputSchema, tags? } + ┌────────────┴─────────────┐ + McpBackend OpenApiBackend + stdio/http + daemon snapshot → compile → fetch + tools cache (TTL) tools cache (同一套) +``` + +统一的四个锚点: + +| 锚点 | 含义 | +|---|---| +| ToolDef | 同一种工具定义 (JSON Schema inputSchema) → 同一个 flag 编译器 (`flags.ts` 不动) | +| Result envelope | callTool 返回同一种 MCP 形状结果 → 同一个 `extractData` / 输出路径 | +| Cache | 同一个 `.tools.json` TTL/refresh 语义 | +| Errors | 同一张错误码表 → 同一套 exit code 协议 | + +## 1. 目标 / 非目标 + +**目标** +1. `agentcli server add petstore --openapi ` 之后,`agentcli petstore -h` 列出全部 operation,`agentcli petstore getPetById --petId 1` 直接执行。 +2. 对 dispatch / flags / help / 输出层 **零改动或近零改动** — "统一"由一个 Backend 接口完成,不是新写一个平行 CLI。 +3. 离线友好: spec 快照落盘,`--refresh` 才回源。 +4. 凭据与 MCP 同等隔离: header 值支持 `${ENV_VAR}` 展开,永不落盘。 + +**非目标 (明确不做)** +- Swagger 2.0 (报错并提示升级到 3.x) +- YAML spec (P2; JSON 先行,检测到 `.yaml` 报错给出明确 hint) +- OAuth2 流程、token 刷新 — 只做 header 注入,凭据由用户提供 +- multipart / form body (报错带 hint) +- 响应体按 schema 校验、分页自动翻页 (`--all` 留作 P3) +- OpenAPI → MCP 反向桥接 (架构上已具备 — Backend 产 ToolDef,未来可包一层 MCP server;本文不含) + +## 2. Backend 接口 (统一点) + +```ts +// src/backend/types.ts +export interface Backend { + readonly kind: "mcp" | "openapi"; + listTools(opts: { refresh?: boolean; timeoutMs?: number }): Promise; + callTool(tool: string, args: Record, opts: { timeoutMs?: number; daemon?: boolean }): Promise; +} + +export function openBackend(cfg: AgentCliConfig, name: string): Backend; +// 按 cfg.servers[name].type 分发: "mcp 类" (stdio/http) → McpBackend, "openapi" → OpenApiBackend +``` + +- `McpBackend`: 薄封装现有 `client.ts` 的 `listTools/callTool`,逻辑一行不改。 +- `OpenApiBackend`: 新增。忽略 `daemon` 选项 (无状态 HTTP,无持久连接需求),`via` 恒为 `"direct"`。 +- **改造点**: `dispatch.ts` 与 `index.ts` 的 `server tools` 改调 `openBackend(...)`;`client.ts` 保持 MCP 专用。 +- `ToolDef` = 现有 `McpTool` 更名 (纯重命名,字段不变,新增可选 `tags?: string[]` 供 help 分组)。 + +OpenAPI 的 callTool 返回值伪装成 MCP 形状 — 这是"统一"的实惠: + +```ts +{ + content: [{ type: "text", text: rawBody }], + structuredContent: parsedJson ?? undefined, + isError: status >= 400 +} +``` + +dispatch 的 `extractData` / `extractText` / `--output text` 全部直接工作。 + +## 3. 配置层 + +### 3.1 ServerSpec 新变体 + +```ts +export interface OpenApiServerSpec { + type: "openapi"; + spec: string; // 本地快照路径 (add 时已下载/复制),相对 dataDir + origin: string; // 原始来源: URL 或绝对文件路径 (--refresh 回源用) + baseUrl?: string; // 覆盖 spec.servers[0].url + headers?: Record; // 可含 ${ENV_VAR} 占位 +} +``` + +### 3.2 注册: server add + +```bash +agentcli server add petstore --openapi https://petstore3.swagger.io/api/v3/openapi.json \ + [--base-url https://...] [--header "Authorization: Bearer ${PETSTORE_TOKEN}"] +agentcli server add myapi --openapi ./myapi.json # 本地文件同样快照 +``` + +add 时的动作 (fail-fast,注册即验证): +1. 拉/读 spec → 校验 `openapi: "3.x"` → **快照落盘** `~/.agentcli/specs/.json` (0600) +2. 试编译一遍: operation 数、命名冲突、致命 $ref 问题在 add 时就报,不留到调用时 +3. 写 config (`spec` 指向快照,`origin` 记录来源) → 输出 `{ ok, server, operations: N }` + +`--refresh` 语义: 有 origin URL → 重新拉取 + 重新快照 + 重新编译;origin 是本地文件 → 重新复制 + 重编译 (方便用户改了本地 spec)。 + +### 3.3 凭据 + +- header 值里的 `${VAR}` 在 **请求时** 展开;引用了未定义变量 → `AUTH_REQUIRED` (hint: 设置环境变量)。 +- 与 MCP stdio 的 env whitelist 哲学一致: 凭据不进 config 文件明文,不进日志。 +- P2 增强: 读 `components.securitySchemes`,发现 spec 需要 bearer 而配置没给 Authorization → 调用时给一条 helpful hint。 + +## 4. OpenAPI → ToolDef 编译规则 (src/openapi/compile.ts) + +### 4.1 operation → 工具名 + +| 优先级 | 规则 | 例子 | +|---|---|---| +| 1 | `operationId` (做 slug 清洗: 非 `[a-zA-Z0-9_.-]` → `_`) | `getPetById` | +| 2 | 兜底 `METHOD + "_" + pathSlug`: `{param}` → 参数名, `/` → `_` | `GET /pets/{petId}` → `get_pets_petId` | + +- 重名: 确定性自动加后缀 `_2`, `_3`;工具 description 首行标注 `METHOD /path — origin: `,保证 agent 可追溯。 +- description = `operation.summary` + `operation.description`。 + +### 4.2 参数 → inputSchema.properties + +把 path / query / header 三类参数**打平**到同一个顶层 properties (cookie 忽略): + +```jsonc +// GET /pets/{petId}?verbose= → inputSchema: +{ "type": "object", + "required": ["petId"], + "properties": { + "petId": { "type": "integer", "description": "…" }, + "verbose":{ "type": "boolean" } } } +``` + +- path 参数强制 required (spec 没写也补上)。 +- OpenAPI param schema 支持 `schema.$ref` → 解引用后内联 (见 4.4)。 +- query 数组: flag 重复传值 → 序列化为 repeat (`?tags=a&tags=b`,form/explode 默认);`style: csv` 的 spec 少见,P1 按单值字符串透传 (spec 里写了 enum 的照常生成 choices)。 + +### 4.3 requestBody → body 参数打平 + +仅处理 `application/json`: + +| body schema 形状 | 编译结果 | +|---|---| +| object,顶层属性是简单类型 | **属性全部提升为顶层 flags** — `POST /pets` 的 `{name, tag}` 直接 `--name x --tag y` | +| object,含嵌套/复杂属性 | 简单属性照常 flag;复杂属性进 `plan.complex` (→ `--input`),与 MCP 路径完全一致 | +| 顶层 `allOf` | 合并各段的 properties/required 后按上两行处理 (allOf-merge 常见,值得做) | +| array / string / 无 schema | 单个复杂参数 `--input '{"body": [...]}'` 形态,body 键名固定 | +| 其他 content-type | 编译期不报错;调用时若真传了 body → `INVALID_ARGUMENT` + hint | + +命名冲突 (如 path 有 `id`,body 也有 `id`): 冲突键以 body 侧加前缀 `body_` 消解,并在 description 标注原名。编译期不再因冲突失败 — 自动消解优于报错 (真实 spec 里同名不罕见)。 + +### 4.4 $ref 解析 (src/openapi/ref.ts) + +- 只支持 **本地 ref** `#/components/...`;内部 `$ref` 递归内联,**循环检测**: 二次命中同一 ref 路径 → 替换为 `{type: "object", description: "(circular ref)"}` 并视为 complex。 +- 内联深度上限 (如 8) 防深递归炸弹。 +- 外部 ref (`file://`, `http://`) → 编译期 `INVALID_ARGUMENT`,hint 指明哪个 operation 引用了它 (常见于拆分的多文件 spec,P2 再考虑 bundle)。 + +### 4.5 tags + +`operation.tags` 收集进 `ToolDef.tags`。服务级 help (`agentcli petstore -h`) 有 tag 就按 tag 分组显示 (无 tag 的进 `—` 组);工具名保持扁平。解决"一个 spec 500 个 operation 刷屏"的发现问题,命令面不引入层级 (与 MCP 侧一致)。 + +## 5. 执行层 (src/openapi/exec.ts) + +``` +callTool(tool, args) → + 1. 查编译产物 (operation 元数据: method, pathTemplate, paramLocations, bodyInfo, securityHint) + 2. URL = baseUrl 解析 + path 参数替换 ({petId} → String(args.petId)) + 3. query: 非 path/header/body 的参数 → URLSearchParams (数组 repeat) + 4. header params → 请求头; config headers (env 展开) 合并 + 5. body 参数存在 → JSON.stringify + content-type: application/json + 6. fetch (AbortSignal.timeout(--timeout-ms, 默认 60s)) + 7. 包成 MCP 形状 envelope (见 §2) +``` + +### 5.1 HTTP → 错误码映射 + +| 情况 | code | 附加 | +|---|---|---| +| DNS/网络失败 | `CONNECT_FAILED` | — | +| 超时 | `TIMEOUT` | hint 同现有 | +| 401 / 403 | `AUTH_REQUIRED` | — | +| 404 | `NOT_FOUND` | — | +| 429 | `EXECUTION_ERROR` | details 带 `retryAfter` (读 header) | +| 其余 4xx / 5xx | `EXECUTION_ERROR` | details: `{ httpStatus, body }` (body 截断 2KB) | +| 响应非 JSON | data 走 text 路径 | `--output text` 原样输出 | + +错误响应体里若能解出 `message/error` 字段,提升为错误 message 首行 — agent 第一眼看到 API 自己的话术。 + +### 5.2 servers/variables + +`spec.servers[0]` 为基准;URL 模板变量用 variables 默认值展开;`--base-url` (config `baseUrl`) 覆盖一切。多个 server entry 只取第一个 (P2: `server add --server-label` 选择)。 + +## 6. 缓存与 daemon + +- 编译产物写现有 tools cache (`.tools.json`,同一 TTL 10min / `AGENTCLI_TTL_MS` / `--refresh`)。cache 里存的是 **编译后的 ToolDef[] + 每工具的 operation 元数据** (method/path/paramLocations/bodyInfo),调用时不需要重新碰 spec。 +- 快照 (specs/.json) 与编译缓存 (cache/.tools.json) 分层: 快照是"源",编译缓存是"产物"。`--refresh`: TTL 之外重编译;快照损坏 → 自动回源重拉。 +- daemon 不参与 openapi (无状态);`meta.via: "direct"`。daemon status 里也不计入。 +- `server remove` 同步删快照与编译缓存 (现有删 cache 的地方加一行)。 + +## 7. 模块布局与改造点 + +``` +src/backend/types.ts 新 Backend 接口 + ToolDef (自 types.ts 迁移更名) +src/backend/index.ts 新 openBackend(cfg, name) 工厂 +src/backend/mcp.ts 新 薄封装 client.ts (零逻辑) +src/openapi/specstore.ts 新 快照下载/复制/回源刷新 (fetch + fs, 0600) +src/openapi/ref.ts 新 $ref 内联解析 (循环/深度保护) +src/openapi/compile.ts 新 spec → ToolDef[] + operation 元数据 +src/openapi/exec.ts 新 args → HTTP 请求 → envelope + 错误映射 +src/dispatch.ts 改 listTools/callTool → openBackend(...) (≈5 行) +src/index.ts 改 server add --openapi/--base-url;server tools 走 backend +src/types.ts 改 +OpenApiServerSpec;McpTool → ToolDef (tags?) +src/config.ts 改 removeServer 清快照 +src/flags.ts / jsonout / errors / fuzzy / daemon/* 不动 +``` + +外部依赖: **零新增** (fetch 是 Node ≥18 内建)。 + +## 8. 测试计划 + +| 层 | 内容 | +|---|---| +| 单元: compile | petstore 迷你 fixture: 命名 (operationId/兜底/冲突后缀)、path 强制 required、query/header 参数、body 打平 (简单/复杂/allOf/array)、$ref 内联与循环、tags 收集 | +| 单元: exec | URL 构建 (path 替换/query 数组 repeat)、header env 展开 (含未定义 → AUTH_REQUIRED)、错误码映射表全分支 | +| e2e | test 内起一个真 http server (node:http): GET+path、POST+JSON body、401→AUTH_REQUIRED、500→EXECUTION_ERROR、非 JSON 响应 → text 输出;快照 add → 断网 (删 origin 指向) 仍可执行 | +| 回归 | 现有 MCP 全部测试不动通过 (证明"统一"没破坏旧路径) | + +## 9. 分阶段落地 + +- **P1 (本分支目标)**: Backend 接口 + openapi 全链路 (JSON/3.x、参数打平、body 打平、本地 $ref、快照、错误映射、tag 分组 help) + 全部测试 +- **P2**: securitySchemes 自动提示、YAML、query style (csv/spaceDelimited)、外部 $ref bundle、`--server-label` +- **P3**: 响应 schema 感知输出、`--all` 分页、OpenAPI→MCP 反向桥接 (Backend 已产 ToolDef,包一层 stdio server 即可) + +## 10. 关键决策记录 (ADR 摘要) + +| # | 决策 | 备选与理由 | +|---|---|---| +| 1 | 统一点放在 Backend 接口而非"编译期生成 CLI 代码" | 动态运行时与现有 MCP 路径同构;不引入代码生成、构建步骤 | +| 2 | callTool 返回 MCP 形状 envelope | dispatch/输出层零改动;代价是 openapi 侧一次包装,可控 | +| 3 | add 时快照落盘 | 离线可用、可审计、spec 变更不破坏已注册 CLI;live 模式 (每次调用回源拉 spec) 被否 — 慢且不稳 | +| 4 | body 属性打平为顶层 flags | agent 体验优先 (不用每次 --input);冲突用 `body_` 前缀消解而非报错 | +| 5 | operationId 缺失兜底为机械 slug | 语义化改名 (如 list_issues) 需要启发式,不可预测;机械规则 agent 可推理 | +| 6 | daemon 不服务 openapi | 无状态 HTTP 无持久连接收益;少一条守护进程职责,复杂度换不来性能 | +| 7 | 零新增依赖 | fetch 内建;$ref 自写解析器 (<100 行) 比引入 api-ref-parser 轻 10 倍 | \ No newline at end of file From 8263cf7f69822bcf5acd298ff1e225bef854d874 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 14:08:00 +0800 Subject: [PATCH 7/9] =?UTF-8?q?docs:=20unify=20at=20the=20Tool=20layer=20?= =?UTF-8?q?=E2=80=94=20neutral=20ToolResult,=20MCP=20shape=20stays=20insid?= =?UTF-8?q?e=20McpBackend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/design-openapi-adapter.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/design-openapi-adapter.md b/docs/design-openapi-adapter.md index 3edbf22..384f663 100644 --- a/docs/design-openapi-adapter.md +++ b/docs/design-openapi-adapter.md @@ -24,7 +24,7 @@ MCP 后端从协议里拿工具列表;OpenAPI 后端把 spec 编译成工具列 | 锚点 | 含义 | |---|---| | ToolDef | 同一种工具定义 (JSON Schema inputSchema) → 同一个 flag 编译器 (`flags.ts` 不动) | -| Result envelope | callTool 返回同一种 MCP 形状结果 → 同一个 `extractData` / 输出路径 | +| ToolResult | callTool 返回同一种**中立内部结果** `{data, text?, isError?}` — MCP 形状在 McpBackend 边界内一次转换,内部总线不耦合任何协议 | | Cache | 同一个 `.tools.json` TTL/refresh 语义 | | Errors | 同一张错误码表 → 同一套 exit code 协议 | @@ -58,22 +58,22 @@ export function openBackend(cfg: AgentCliConfig, name: string): Backend; // 按 cfg.servers[name].type 分发: "mcp 类" (stdio/http) → McpBackend, "openapi" → OpenApiBackend ``` -- `McpBackend`: 薄封装现有 `client.ts` 的 `listTools/callTool`,逻辑一行不改。 +- `McpBackend`: 封装现有 `client.ts` 的 `listTools/callTool`,并在边界内把 MCP 形状结果转换为中立的 `ToolResult` (现 dispatch.ts 里的 `extractData`/`extractText`/`parseMaybeEncoded` 迁入此处 — 那本就是 MCP 特有的解析逻辑)。 - `OpenApiBackend`: 新增。忽略 `daemon` 选项 (无状态 HTTP,无持久连接需求),`via` 恒为 `"direct"`。 - **改造点**: `dispatch.ts` 与 `index.ts` 的 `server tools` 改调 `openBackend(...)`;`client.ts` 保持 MCP 专用。 - `ToolDef` = 现有 `McpTool` 更名 (纯重命名,字段不变,新增可选 `tags?: string[]` 供 help 分组)。 -OpenAPI 的 callTool 返回值伪装成 MCP 形状 — 这是"统一"的实惠: +内部结果类型与两个后端各自的转换 (MCP 形状**不越过** backend 边界): ```ts -{ - content: [{ type: "text", text: rawBody }], - structuredContent: parsedJson ?? undefined, - isError: status >= 400 -} +// src/backend/types.ts — 内部中立契约 +export interface ToolResult { data: unknown; text?: string; isError?: boolean; } +// McpBackend 转换: structuredContent → data;纯文本 content → parseMaybeEncoded(text) → data, text=原文 +// OpenApiBackend 转换: JSON body → data;原文 → text;status≥400 已在后端抛映射后的 AgentCliError +``` ``` -dispatch 的 `extractData` / `extractText` / `--output text` 全部直接工作。 +dispatch 消费 `{data, text, isError}`:`--output json` 打印 data;`--output text` = data 为对象则 pretty-print,否则用 text;isError → `EXECUTION_ERROR`。错误处理统一为**后端抛类型化 AgentCliError**(连接/超时/认证/openapi 的 HTTP 状态码映射),dispatch 只透传 — exit code 协议不变。 ## 3. 配置层 @@ -206,12 +206,12 @@ callTool(tool, args) → ``` src/backend/types.ts 新 Backend 接口 + ToolDef (自 types.ts 迁移更名) src/backend/index.ts 新 openBackend(cfg, name) 工厂 -src/backend/mcp.ts 新 薄封装 client.ts (零逻辑) +src/backend/mcp.ts 新 封装 client.ts + MCP→ToolResult 转换 (extractData 自 dispatch.ts 迁入) src/openapi/specstore.ts 新 快照下载/复制/回源刷新 (fetch + fs, 0600) src/openapi/ref.ts 新 $ref 内联解析 (循环/深度保护) src/openapi/compile.ts 新 spec → ToolDef[] + operation 元数据 src/openapi/exec.ts 新 args → HTTP 请求 → envelope + 错误映射 -src/dispatch.ts 改 listTools/callTool → openBackend(...) (≈5 行) +src/dispatch.ts 改 改调 openBackend(...);MCP 解析函数迁出 (净减代码) src/index.ts 改 server add --openapi/--base-url;server tools 走 backend src/types.ts 改 +OpenApiServerSpec;McpTool → ToolDef (tags?) src/config.ts 改 removeServer 清快照 @@ -240,9 +240,10 @@ src/flags.ts / jsonout / errors / fuzzy / daemon/* 不动 | # | 决策 | 备选与理由 | |---|---|---| | 1 | 统一点放在 Backend 接口而非"编译期生成 CLI 代码" | 动态运行时与现有 MCP 路径同构;不引入代码生成、构建步骤 | -| 2 | callTool 返回 MCP 形状 envelope | dispatch/输出层零改动;代价是 openapi 侧一次包装,可控 | +| 2 | 内部契约 = ToolDef (入) + ToolResult (出),中立类型;后端抛类型化错误 | 备选"openapi 结果伪装成 MCP envelope"被否 — 内部格式被单一协议绑架;备选"openapi→MCP server 桥"被否 — 多一跳进程、协议协商开销、错误映射失控 | | 3 | add 时快照落盘 | 离线可用、可审计、spec 变更不破坏已注册 CLI;live 模式 (每次调用回源拉 spec) 被否 — 慢且不稳 | | 4 | body 属性打平为顶层 flags | agent 体验优先 (不用每次 --input);冲突用 `body_` 前缀消解而非报错 | | 5 | operationId 缺失兜底为机械 slug | 语义化改名 (如 list_issues) 需要启发式,不可预测;机械规则 agent 可推理 | | 6 | daemon 不服务 openapi | 无状态 HTTP 无持久连接收益;少一条守护进程职责,复杂度换不来性能 | -| 7 | 零新增依赖 | fetch 内建;$ref 自写解析器 (<100 行) 比引入 api-ref-parser 轻 10 倍 | \ No newline at end of file +| 7 | 零新增依赖 | fetch 内建;$ref 自写解析器 (<100 行) 比引入 api-ref-parser 轻 10 倍 | +| 8 | 统一发生在 **Tool 层**,不是 MCP 层 | OpenAPI spec → ToolDef 直达,不经 MCP 协议;MCP 只是"另一个生产 ToolDef 的后端"。反向桥接 (ToolDef→MCP) 是 P3 可选衍生品 | From 11ce80f2611732ee8708e48d1a3b6d438958d336 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 14:29:29 +0800 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20OpenAPI=203.x=20backend=20=E2=80=94?= =?UTF-8?q?=20one=20CLI,=20any=20tool=20backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify MCP and OpenAPI behind a neutral Backend contract: - ToolDef in / ToolResult out; MCP content shapes converted inside McpBackend (extractData/parseMaybeEncoded migrated from dispatch.ts) - OpenAPI spec compiler: operations -> tools, params + JSON body properties flatten into flags, local $ref inlining with cycle decay, tag-grouped help - spec snapshots at add time (offline-friendly), --refresh re-pulls origin - HTTP status -> typed errors (401/403 AUTH_REQUIRED, 404 NOT_FOUND, 4xx/5xx EXECUTION_ERROR with httpStatus + API message), ${ENV} header expansion - zero new dependencies; 41 new tests (compile units + live HTTP e2e), all 48 existing MCP tests unchanged --- README.md | 57 ++++++- fixtures/openapi.json | 103 ++++++++++++ package.json | 4 +- skills/agentcli/SKILL.md | 34 ++-- src/backend/index.ts | 21 +++ src/backend/mcp.ts | 77 +++++++++ src/backend/types.ts | 42 +++++ src/client.ts | 23 ++- src/config.ts | 9 +- src/daemon/server.ts | 8 +- src/dispatch.ts | 131 ++++++--------- src/flags.ts | 4 +- src/index.ts | 64 +++++--- src/openapi/backend.ts | 42 +++++ src/openapi/compile.ts | 208 ++++++++++++++++++++++++ src/openapi/exec.ts | 125 ++++++++++++++ src/openapi/ref.ts | 56 +++++++ src/openapi/specstore.ts | 112 +++++++++++++ src/types.ts | 47 ++++-- test/openapi-compile.test.ts | 154 ++++++++++++++++++ test/openapi-e2e.test.ts | 304 +++++++++++++++++++++++++++++++++++ test/skill.test.ts | 6 + 22 files changed, 1488 insertions(+), 143 deletions(-) create mode 100644 fixtures/openapi.json create mode 100644 src/backend/index.ts create mode 100644 src/backend/mcp.ts create mode 100644 src/backend/types.ts create mode 100644 src/openapi/backend.ts create mode 100644 src/openapi/compile.ts create mode 100644 src/openapi/exec.ts create mode 100644 src/openapi/ref.ts create mode 100644 src/openapi/specstore.ts create mode 100644 test/openapi-compile.test.ts create mode 100644 test/openapi-e2e.test.ts diff --git a/README.md b/README.md index 57364e0..36b9904 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AgentCLI -**One CLI. Any MCP server.** Call MCP servers from the command line — dynamically, with no per-tool wrapper code. +**One CLI. Any tool backend.** Call MCP servers and OpenAPI (REST) APIs from the command line — dynamically, with no per-tool wrapper code. > ```bash > $ agentcli github search_issues --repo apache/hertzbeat --query "memory leak" @@ -9,11 +9,18 @@ > "meta": { "durationMs": 412, "via": "daemon" } } > ``` -Every MCP tool becomes a CLI command the first time you type it. Flags are compiled from the tool's JSON Schema on the fly. +Every MCP tool — and every OpenAPI operation — becomes a CLI command the first time you type it. Flags are compiled from the tool's JSON Schema on the fly. ## Why -Agents that register every MCP tool upfront pay for it in context: hundreds of schemas loaded at startup, most never used. A CLI flips the model to **progressive disclosure**: discover with `--help`, drill down only as far as needed. stdout/stderr separation, a binary exit code (0/1), and self-describing JSON errors keep the whole thing machine-consumable. +Agents that register every tool upfront pay for it in context: hundreds of schemas loaded at startup, most never used. A CLI flips the model to **progressive disclosure**: discover with `--help`, drill down only as far as needed. stdout/stderr separation, a binary exit code (0/1), and self-describing JSON errors keep the whole thing machine-consumable. + +The same loop works for both backend kinds: + +| Backend | Register | Tools come from | +|---|---|---| +| MCP (stdio / HTTP) | `agentcli server add -- ` or `--url` | `tools/list` over the protocol | +| OpenAPI 3.x | `agentcli server add --openapi ` | the spec — every operation becomes a tool | ## Install @@ -23,7 +30,7 @@ pnpm add -g @happyvibing/agentcli # or: npm install -g @happyvibing/agentcli Requires Node >= 20.10. Or run from source: `git clone && pnpm install && pnpm build && pnpm link --global`. -## Quick start +## Quick start (MCP) ```bash # 1. Register a server @@ -68,10 +75,50 @@ Global: --schema Print the raw tool input schema ``` +## OpenAPI APIs + +Register an OpenAPI 3.x JSON spec and every operation becomes a CLI command — same discovery, same flags, same output envelope: + +```bash +# Register (the spec is snapshotted locally; --refresh re-pulls it) +agentcli server add petstore --openapi https://petstore3.swagger.io/api/v3/openapi.json \ + --header "Authorization: Bearer ${PETSTORE_TOKEN}" # ${ENV} expands at call time + +# Discover — operations grouped by their spec tags +agentcli petstore -h + +# Execute — path/query params and JSON body properties are all flags +agentcli petstore getPetById --petId 1 +agentcli petstore addPet --name rex --kind dog +``` + +How an OpenAPI spec maps onto the CLI: + +- **operationId** becomes the tool name (missing ones get a mechanical slug like `get_pets_petId`) +- path / query / header parameters and **request-body properties flatten into flags** — `POST /pets {name, tag}` is `--name x --tag y` +- HTTP failures map to the same error codes: 401/403 → `AUTH_REQUIRED`, 404 → `NOT_FOUND`, other 4xx/5xx → `EXECUTION_ERROR` with `httpStatus` in details +- OpenAPI calls are stateless HTTP — `meta.via` is always `direct` (no daemon involved) + +Swagger 2.0 and YAML specs are rejected with an upgrade/conversion hint. Local spec files work too (`--openapi ./api.json`) — useful for internal APIs. + +## Architecture + +The core only knows "tools + JSON Schema". Backends produce tool definitions; everything below (flag compiler, help, output, caching, errors) is shared: + +``` +agentcli --flags + │ + dispatch (backend-agnostic) + │ ToolDef { name, description, inputSchema } + ┌──────┴──────┐ + McpBackend OpenApiBackend + daemon/direct snapshot → compile → fetch +``` + ## Agent Skill `skills/agentcli/SKILL.md` teaches an agent this runtime — the discover → inspect → execute → observe loop and self-describing error recovery. Install it with the skills CLI (also listed on [skills.sh](https://skills.sh)): ```bash npx skills add happyvibing/agentcli@agentcli -g -``` +``` \ No newline at end of file diff --git a/fixtures/openapi.json b/fixtures/openapi.json new file mode 100644 index 0000000..751ecbe --- /dev/null +++ b/fixtures/openapi.json @@ -0,0 +1,103 @@ +{ + "openapi": "3.0.3", + "info": { "title": "MiniAPI", "version": "1.0.0", "description": "Test fixture spec" }, + "servers": [{ "url": "https://api.mini.test/v1" }], + "tags": [{ "name": "pets" }, { "name": "store" }], + "paths": { + "/pets/{petId}": { + "get": { + "tags": ["pets"], + "summary": "Get a pet by id", + "operationId": "getPetById", + "parameters": [ + { "name": "petId", "in": "path", "required": true, "schema": { "type": "integer" } }, + { "name": "verbose", "in": "query", "schema": { "type": "boolean" } }, + { "$ref": "#/components/parameters/Limit" } + ], + "responses": { "200": { "description": "ok" } } + } + }, + "/pets": { + "get": { + "tags": ["pets"], + "summary": "List pets", + "operationId": "listPets", + "parameters": [ + { "name": "tags", "in": "query", "schema": { "type": "array", "items": { "type": "string" } } }, + { "$ref": "#/components/parameters/Limit" } + ], + "responses": { "200": { "description": "ok" } } + }, + "post": { + "tags": ["pets"], + "summary": "Create a pet", + "operationId": "addPet", + "requestBody": { + "required": true, + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } + }, + "responses": { "201": { "description": "created" } } + } + }, + "/store/order": { + "get": { + "tags": ["store"], + "summary": "Get an order", + "operationId": "getOrder", + "parameters": [{ "name": "X-Request-Id", "in": "header", "schema": { "type": "string" } }], + "responses": { "200": { "description": "ok" } } + } + }, + "/no-op-id": { + "get": { + "summary": "Operation without operationId", + "parameters": [{ "name": "q", "in": "query", "schema": { "type": "string" } }], + "responses": { "200": { "description": "ok" } } + } + }, + "/circular": { + "post": { + "summary": "Circular ref body", + "operationId": "postCircular", + "requestBody": { + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Node" } } } + }, + "responses": { "200": { "description": "ok" } } + } + }, + "/batch": { + "post": { + "summary": "Array body", + "operationId": "postBatch", + "requestBody": { + "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } + }, + "responses": { "200": { "description": "ok" } } + } + } + }, + "components": { + "parameters": { + "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 10 }, "description": "Page size" } + }, + "schemas": { + "Pet": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string", "description": "Pet name" }, + "tag": { "type": "string" }, + "kind": { "$ref": "#/components/schemas/Kind" } + } + }, + "Kind": { "type": "string", "enum": ["cat", "dog"] }, + "Node": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "children": { "type": "array", "items": { "$ref": "#/components/schemas/Node" } } + } + } + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index 6179cd6..57fd8c3 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "@happyvibing/agentcli", - "version": "0.1.1", + "version": "0.2.0", "repository": { "type": "git", "url": "git+ssh://git@github.com/happyvibing/agentcli.git" }, - "description": "AgentCLI \u2014 call MCP servers from the command line. One CLI, any MCP server.", + "description": "AgentCLI \u2014 call MCP servers and OpenAPI APIs from the command line. One CLI, any tool backend.", "type": "module", "bin": { "agentcli": "bin/agentcli.js" diff --git a/skills/agentcli/SKILL.md b/skills/agentcli/SKILL.md index 0199ae7..753fc1e 100644 --- a/skills/agentcli/SKILL.md +++ b/skills/agentcli/SKILL.md @@ -1,6 +1,6 @@ --- name: agentcli -description: Use the agentcli runtime to discover and execute MCP server tools from the command line. Use this skill whenever the user asks to work with external systems — GitHub, Feishu/Lark, Notion, Slack, databases, k8s, internal APIs — through MCP servers, or whenever a task involves finding or calling tools via agentcli. Triggers include mentions of agentcli, MCP servers/tools, or tasks like 'search GitHub issues', 'send a Feishu message', 'create a Notion page' when an MCP server may provide the capability. +description: Use the agentcli runtime to discover and execute tools from MCP servers and OpenAPI APIs via the command line. Use this skill whenever the user asks to work with external systems — GitHub, Feishu/Lark, Notion, Slack, databases, k8s, internal REST APIs — through MCP servers or OpenAPI specs, or whenever a task involves finding or calling tools via agentcli. Triggers include mentions of agentcli, MCP servers/tools, OpenAPI specs, or tasks like 'search GitHub issues', 'send a Feishu message', 'call the internal API' when a configured server may provide the capability. license: MIT metadata: compatibility: [claude-code, codex, cursor, opencode] @@ -8,9 +8,9 @@ metadata: # AgentCLI -`agentcli` turns configured MCP servers into CLI commands. You do not need to know -what tools exist ahead of time — discover them at runtime. Never dump tool docs into -memory; look them up on demand. +`agentcli` turns configured MCP servers and OpenAPI APIs into CLI commands. You +do not need to know what tools exist ahead of time — discover them at runtime. +Never dump tool docs into memory; look them up on demand. ## Core loop: discover → inspect → execute → observe @@ -35,6 +35,19 @@ Machine-readable schema instead of help text: agentcli --schema ``` +## Registering servers + +```bash +agentcli server add -- # stdio MCP +agentcli server add --url # HTTP MCP +agentcli server add --openapi # OpenAPI 3.x API + [--base-url ] [--header "Authorization: Bearer ${ENV_VAR}"] +``` + +OpenAPI: every operation becomes a tool — path/query params and JSON body +properties are all flags. `${ENV_VAR}` in headers expands at call time; an +unset variable is AUTH_REQUIRED. Ask before adding servers. + ## Rules 1. **Never guess flags.** If you have not seen this tool's `--help` in this session, run it first. @@ -71,6 +84,7 @@ Every failure prints one self-describing JSON line to stderr — no lookup table ``` - `code` names the class: INVALID_ARGUMENT | NOT_FOUND | AUTH_REQUIRED | TIMEOUT | CONNECT_FAILED | EXECUTION_ERROR | INTERNAL - `message` is the diagnosis; `hint` (when present) is the recommended next action — follow it. +- OpenAPI servers map HTTP statuses onto the same codes: 401/403 → AUTH_REQUIRED, 404 → NOT_FOUND, other 4xx/5xx → EXECUTION_ERROR (details carry httpStatus and the API's own error message). ## Composability @@ -85,10 +99,10 @@ Prefer filtering with tool flags or `jq` over loading everything into context. ## Housekeeping -- Tool listings are cached (10 min). Use `--refresh` if a server's tools changed. -- `meta.via` in the result envelope says `daemon` (persistent connection) or `direct`; purely informational. -- A background daemon (`agentcli daemon start`) holds server connections so repeated +- Tool listings are cached (10 min). Use `--refresh` if a server's tools changed + (for OpenAPI servers it also re-pulls the spec from its origin). +- `meta.via` in the result envelope says `daemon` (persistent MCP connection) or `direct`; + OpenAPI calls are always `direct`. Purely informational. +- A background daemon (`agentcli daemon start`) holds MCP server connections so repeated calls return in milliseconds; calls route through it automatically. Force the -per-call path with `--no-daemon` or `AGENTCLI_NO_DAEMON=1` if it misbehaves. -- Register servers yourself if missing: `agentcli server add -- ` - (stdio) or `agentcli server add --url ` (HTTP). Ask before adding servers. \ No newline at end of file +per-call path with `--no-daemon` or `AGENTCLI_NO_DAEMON=1` if it misbehaves. \ No newline at end of file diff --git a/src/backend/index.ts b/src/backend/index.ts new file mode 100644 index 0000000..a48e814 --- /dev/null +++ b/src/backend/index.ts @@ -0,0 +1,21 @@ +// Backend factory: dispatches on the configured server spec type. Everything +// above this line (dispatch.ts, help, output) is backend-agnostic. +import { errors } from "../errors.js"; +import type { AgentCliConfig } from "../types.js"; +import type { Backend } from "./types.js"; +import { mcpBackend } from "./mcp.js"; +import { openApiBackend } from "../openapi/backend.js"; + +export function openBackend(cfg: AgentCliConfig, serverName: string): Backend { + const spec = cfg.servers[serverName]; + if (!spec) { + throw errors.notFound( + 'server "' + serverName + '" is not configured', + "Add one: agentcli server add " + serverName + " -- | --url | --openapi " + ); + } + if (spec.type === "openapi") return openApiBackend(serverName, spec); + return mcpBackend(cfg, serverName); +} + +export type { Backend, ToolResult, ListToolsResult, CallToolResult } from "./types.js"; \ No newline at end of file diff --git a/src/backend/mcp.ts b/src/backend/mcp.ts new file mode 100644 index 0000000..45d7376 --- /dev/null +++ b/src/backend/mcp.ts @@ -0,0 +1,77 @@ +// MCP backend: adapts the MCP client path (daemon/direct, client.ts) to the +// neutral Backend contract. MCP-specific result parsing lives here — it never +// leaks past this boundary. +import { listTools as mcpListTools, callTool as mcpCallTool } from "../client.js"; +import type { AgentCliConfig, ToolDef } from "../types.js"; +import type { Backend, CallToolResult, ListToolsOptions, ListToolsResult, ToolResult } from "./types.js"; + +interface McpContentItem { + type: string; + text?: string; + [key: string]: unknown; +} + +interface McpCallToolResult { + content?: McpContentItem[]; + isError?: boolean; + structuredContent?: unknown; + [key: string]: unknown; +} + +function extractText(result: McpCallToolResult): string { + return (result.content || []) + .filter((c) => c && c.type === "text") + .map((c) => c.text || "") + .join("\n"); +} + +// Some servers return JSON.stringify(JSON.stringify(payload)) — text content that +// is still a JSON string after one parse. Unwrap while it stays a string (bounded). +function parseMaybeEncoded(text: string, maxDepth = 3): unknown { + let v: unknown = text; + for (let i = 0; typeof v === "string" && i <= maxDepth; i++) { + try { + v = JSON.parse(v); + } catch { + break; + } + } + return v; +} + +// MCP result -> neutral ToolResult: +// structuredContent if the server sent one; +// else if all items are text: parsed JSON (double-encoded strings unwrapped), +// joined text when it is not JSON; +// else pass the content items through. +function toToolResult(mcp: McpCallToolResult): ToolResult { + let data: unknown; + if (mcp.structuredContent !== undefined) { + data = mcp.structuredContent; + } else { + const items = mcp.content || []; + if (items.length === 0) { + data = null; + } else if (items.every((c) => c && c.type === "text")) { + data = parseMaybeEncoded(items.map((c) => c.text || "").join("\n")); + } else { + data = { content: items }; + } + } + const text = extractText(mcp); + return { data, text: text === "" ? undefined : text, isError: !!mcp.isError }; +} + +export function mcpBackend(cfg: AgentCliConfig, serverName: string): Backend { + return { + kind: "mcp", + async listTools(opts: ListToolsOptions = {}): Promise { + const { tools, cached, via } = await mcpListTools(cfg, serverName, opts); + return { tools: tools as ToolDef[], cached, via }; + }, + async callTool(tool: string, args: Record, opts: { timeoutMs?: number; daemon?: boolean } = {}): Promise { + const { result, via } = await mcpCallTool(cfg, serverName, tool, args, opts); + return { result: toToolResult(result as McpCallToolResult), via }; + }, + }; +} \ No newline at end of file diff --git a/src/backend/types.ts b/src/backend/types.ts new file mode 100644 index 0000000..99b7494 --- /dev/null +++ b/src/backend/types.ts @@ -0,0 +1,42 @@ +// The Backend contract — the single unification point of the runtime. +// Core (dispatch/flags/help/output) only ever sees ToolDef in and ToolResult +// out; protocol shapes (MCP content arrays, HTTP responses) are converted +// inside each backend and never cross this boundary. +import type { ToolDef } from "../types.js"; + +// Neutral result: every backend converts its native result into this shape. +// data — machine-readable payload (JSON envelope `data`, jq-ready) +// text — raw human-readable text, when the source produced one +// isError — the tool ran but reported a logical error (MCP isError) +export interface ToolResult { + data: unknown; + text?: string; + isError?: boolean; +} + +export interface ListToolsResult { + tools: ToolDef[]; + cached: boolean; + via: "daemon" | "direct"; +} + +export interface CallToolResult { + result: ToolResult; + via: "daemon" | "direct"; +} + +export interface ListToolsOptions { + refresh?: boolean; + timeoutMs?: number; +} + +export interface CallToolOptions { + timeoutMs?: number; + daemon?: boolean; // MCP only; the OpenAPI backend is always direct +} + +export interface Backend { + readonly kind: "mcp" | "openapi"; + listTools(opts?: ListToolsOptions): Promise; + callTool(tool: string, args: Record, opts?: CallToolOptions): Promise; +} \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index c6eb487..774b0cb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -6,9 +6,21 @@ import net from "node:net"; import { readToolsCache, writeToolsCache } from "./config.js"; import { errors, reviveError, AgentCliError } from "./errors.js"; import { socketPath } from "./daemon/paths.js"; -import type { AgentCliConfig, ServerSpec, McpTool, ListToolsResultEnvelope, CallToolResultEnvelope, DaemonResponse } from "./types.js"; +import type { AgentCliConfig, ServerSpec, ToolDef, DaemonResponse } from "./types.js"; import pkg from "../package.json" with { type: "json" }; +// MCP-side result envelopes (the neutral Backend contract lives in backend/types.ts). +export interface ListToolsResultEnvelope { + tools: ToolDef[]; + cached: boolean; + via: "daemon" | "direct"; +} + +export interface CallToolResultEnvelope { + result: unknown; + via: "daemon" | "direct"; +} + const CLIENT_INFO = { name: "agentcli", version: pkg.version }; const DEFAULT_TTL_MS = 10 * 60 * 1000; const DEFAULT_TIMEOUT_MS = 60 * 1000; @@ -32,8 +44,6 @@ export function timeoutFromEnv(): number { // us accept servers speaking the newer spec. Core methods (initialize / // tools/list / tools/call) are wire-stable across these versions. // Override with AGENTCLI_PROTOCOL_VERSIONS (comma-separated). Dedup-safe once -// the SDK ships these versions itself. - let sdkPromise: Promise<{ Client: typeof import("@modelcontextprotocol/sdk/client/index.js").Client; StdioClientTransport: typeof import("@modelcontextprotocol/sdk/client/stdio.js").StdioClientTransport; @@ -97,6 +107,9 @@ type Transport = InstanceType { const { StdioClientTransport, StreamableHTTPClientTransport } = await sdk(); + if (spec.type === "openapi") { + throw errors.invalidArgument("an openapi server spec reached the MCP transport (this is a bug — openapi servers bypass MCP)"); + } if (spec.type === "http") { return new StreamableHTTPClientTransport(new URL(spec.url), { requestInit: { headers: parseHeaders(spec.headers) }, @@ -238,7 +251,7 @@ export async function listTools( const spec = requireServer(cfg, serverName); if (daemonEnabled(daemon)) { try { - const r = (await daemonRequest("listTools", { server: serverName, refresh }, { timeoutMs: timeoutMs ?? 30000 })) as { tools: McpTool[]; cached: boolean }; + const r = (await daemonRequest("listTools", { server: serverName, refresh }, { timeoutMs: timeoutMs ?? 30000 })) as { tools: ToolDef[]; cached: boolean }; return { tools: r.tools || [], cached: !!r.cached, via: "daemon" }; } catch (e) { if (!(e instanceof DaemonUnavailable) && !(e as DaemonUnavailable)?.unavailable) throw e; @@ -251,7 +264,7 @@ export async function listTools( try { const tools = await withClient(spec, serverName, async (client: McpClient) => { const res = await client.listTools(undefined, { timeout: timeoutMs }); - return (res.tools as McpTool[]) || []; + return (res.tools as ToolDef[]) || []; }, timeoutMs); writeToolsCache(serverName, tools); return { tools, cached: false, via: "direct" }; diff --git a/src/config.ts b/src/config.ts index 9be8683..1e241f1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import os from "node:os"; import { errors } from "./errors.js"; -import type { AgentCliConfig, ServerSpec, McpTool, ToolsCache } from "./types.js"; +import type { AgentCliConfig, ServerSpec, ToolDef, ToolsCache } from "./types.js"; export const RESERVED_NAMES = new Set(["server", "call", "help", "version", "config", "doctor", "completion", "daemon"]); @@ -66,9 +66,10 @@ export function removeServer(cfg: AgentCliConfig, name: string): void { delete cfg.servers[name]; saveConfig(cfg); - // Clean up the stale tools cache file for the removed server. + // Clean up the stale tools cache and openapi spec snapshot for the removed server. try { fs.rmSync(path.join(cacheDir(), name + ".tools.json"), { force: true }); + fs.rmSync(path.join(path.dirname(configPath()), "specs", name + ".json"), { force: true }); } catch { // ignore } @@ -80,7 +81,7 @@ function cacheFile(server: string): string { return path.join(cacheDir(), server + ".tools.json"); } -export function readToolsCache(server: string, ttlMs: number): { tools: McpTool[]; fresh: boolean } | null { +export function readToolsCache(server: string, ttlMs: number): { tools: ToolDef[]; fresh: boolean } | null { let raw: ToolsCache; try { raw = JSON.parse(fs.readFileSync(cacheFile(server), "utf8")); @@ -91,7 +92,7 @@ export function readToolsCache(server: string, ttlMs: number): { tools: McpTool[ return { tools: raw.tools, fresh: Date.now() - raw.fetchedAt < ttlMs }; } -export function writeToolsCache(server: string, tools: McpTool[]): void { +export function writeToolsCache(server: string, tools: ToolDef[]): void { fs.mkdirSync(cacheDir(), { recursive: true }); fs.writeFileSync(cacheFile(server), JSON.stringify({ fetchedAt: Date.now(), tools }, null, 2)); } diff --git a/src/daemon/server.ts b/src/daemon/server.ts index e905ae0..f421e41 100644 --- a/src/daemon/server.ts +++ b/src/daemon/server.ts @@ -7,7 +7,7 @@ import { createTransport, mapError, sdk, ttlFromEnv } from "../client.js"; import { loadConfig } from "../config.js"; import { errors, serializeError, AgentCliError } from "../errors.js"; import { socketPath, pidPath, ensureDaemonDir } from "./paths.js"; -import type { AgentCliConfig, ServerSpec, McpTool, DaemonStatusData, DaemonResponse } from "../types.js"; +import type { AgentCliConfig, ServerSpec, ToolDef, DaemonStatusData, DaemonResponse } from "../types.js"; import pkg from "../../package.json" with { type: "json" }; const CLIENT_INFO = { name: "agentcli-daemon", version: pkg.version }; @@ -22,7 +22,7 @@ type McpClient = InstanceType = new Map(); - tools: Map = new Map(); + tools: Map = new Map(); stats = { startedAt: Date.now(), requests: 0, toolCalls: 0 }; shuttingDown = false; idleTimer: ReturnType | null = null; @@ -69,7 +69,7 @@ class DaemonState { return client; } - async listTools({ server, refresh }: { server: string; refresh?: boolean }): Promise<{ tools: McpTool[]; cached: boolean }> { + async listTools({ server, refresh }: { server: string; refresh?: boolean }): Promise<{ tools: ToolDef[]; cached: boolean }> { const cfg = this.freshConfig(); const spec = cfg.servers[server]; if (!spec) { @@ -86,7 +86,7 @@ class DaemonState { } catch (e) { throw mapError(e, server); } - const tools = (res.tools as McpTool[]) || []; + const tools = (res.tools as ToolDef[]) || []; this.tools.set(server, { tools, fetchedAt: Date.now() }); return { tools, cached: false }; } diff --git a/src/dispatch.ts b/src/dispatch.ts index 0a9d99d..8c78934 100644 --- a/src/dispatch.ts +++ b/src/dispatch.ts @@ -1,66 +1,18 @@ // Dispatch `agentcli [flags]` — the core execution path. -// Hand-rolled token parsing (commander is bypassed for the dynamic part). -import { listTools, callTool } from "./client.js"; +// Backend-agnostic: all server interaction goes through the Backend contract. +import { openBackend } from "./backend/index.js"; +import type { Backend } from "./backend/types.js"; import { suggest } from "./fuzzy.js"; import { buildFlagPlan, parseToolArgs, readInputJson, mergeArgs, renderToolHelp, validateRequired } from "./flags.js"; import { errors, EXIT } from "./errors.js"; import { printJson } from "./jsonout.js"; -import type { AgentCliConfig, McpTool } from "./types.js"; +import type { AgentCliConfig, ToolDef } from "./types.js"; function firstLine(text: string | undefined): string { return String(text || "").split("\n")[0]; } -interface McpContentItem { - type: string; - text?: string; - [key: string]: unknown; -} - -interface McpCallToolResult { - content?: McpContentItem[]; - isError?: boolean; - structuredContent?: unknown; - [key: string]: unknown; -} - -function extractText(result: McpCallToolResult): string { - return (result.content || []) - .filter((c) => c && c.type === "text") - .map((c) => c.text || "") - .join("\n"); -} - -// Some servers return JSON.stringify(JSON.stringify(payload)) — text content that -// is still a JSON string after one parse. Unwrap while it stays a string (bounded). -function parseMaybeEncoded(text: string, maxDepth = 3): unknown { - let v: unknown = text; - for (let i = 0; typeof v === "string" && i <= maxDepth; i++) { - try { - v = JSON.parse(v); - } catch { - break; - } - } - return v; -} - -// Result -> data: -// structuredContent if the server sent one; -// else if all items are text: parsed JSON (double-encoded strings unwrapped), -// joined text when it is not JSON; -// else pass the content items through. -function extractData(result: McpCallToolResult): unknown { - if (result.structuredContent !== undefined) return result.structuredContent; - const items = result.content || []; - if (items.length === 0) return null; - if (items.every((c) => c && c.type === "text")) { - return parseMaybeEncoded(items.map((c) => c.text || "").join("\n")); - } - return { content: items }; -} - -function toolNotFound(serverName: string, toolName: string, tools: McpTool[]): AgentCliError { +function toolNotFound(serverName: string, toolName: string, tools: ToolDef[]): Error { const similar = suggest(toolName, tools.map((t) => t.name)).slice(0, 5); const hints: string[] = []; if (similar.length) hints.push("Similar tools: " + similar.join(", ")); @@ -68,25 +20,58 @@ function toolNotFound(serverName: string, toolName: string, tools: McpTool[]): A return errors.notFound('tool "' + toolName + '" not found on server "' + serverName + '"', hints.join(" | ")); } -import type { AgentCliError } from "./errors.js"; +function kindLabel(backend: Backend): string { + return backend.kind === "openapi" ? "OpenAPI server" : "MCP server"; +} -async function printServerHelp(cfg: AgentCliConfig, serverName: string, refresh: boolean): Promise { - const { tools } = await listTools(cfg, serverName, { refresh }); - const lines = [serverName + " — MCP server (" + tools.length + " tools)", "", "Usage:", " agentcli " + serverName + " [flags]", "", "Tools:"]; - for (const t of tools) { +function printServerHelp(backend: Backend, serverName: string, tools: ToolDef[]): number { + const lines = [serverName + " — " + kindLabel(backend) + " (" + tools.length + " tools)", "", "Usage:", " agentcli " + serverName + " [flags]", ""]; + const row = (t: ToolDef) => { const name = String(t.name); const pad = name.length >= 28 ? " " : " ".repeat(28 - name.length); - lines.push(" " + name + pad + firstLine(t.description)); + return " " + name + pad + firstLine(t.description); + }; + if (tools.some((t) => t.tags && t.tags.length)) { + // Group by first tag (spec order preserved); untagged tools go last. + const groups = new Map(); + const untagged: ToolDef[] = []; + for (const t of tools) { + const g = t.tags && t.tags[0]; + if (g) { + if (!groups.has(g)) groups.set(g, []); + (groups.get(g) as ToolDef[]).push(t); + } else { + untagged.push(t); + } + } + for (const [tag, list] of groups) { + lines.push(tag + ":"); + for (const t of list) lines.push(row(t)); + lines.push(""); + } + if (untagged.length) { + lines.push("other:"); + for (const t of untagged) lines.push(row(t)); + lines.push(""); + } + lines.push(" agentcli " + serverName + " --help Help for a specific tool"); + console.log(lines.join("\n")); + return EXIT.OK; } + lines.push("Tools:"); + for (const t of tools) lines.push(row(t)); lines.push("", " agentcli " + serverName + " --help Help for a specific tool"); console.log(lines.join("\n")); return EXIT.OK; } export async function runServerCommand(cfg: AgentCliConfig, serverName: string, tail: string[]): Promise { + const backend = openBackend(cfg, serverName); + // 1. server-level help: `agentcli ` or `agentcli --help` if (tail.length === 0 || tail[0] === "--help" || tail[0] === "-h") { - return printServerHelp(cfg, serverName, tail.includes("--refresh")); + const { tools } = await backend.listTools({ refresh: tail.includes("--refresh") }); + return printServerHelp(backend, serverName, tools); } const toolName = tail[0]; @@ -99,7 +84,7 @@ export async function runServerCommand(cfg: AgentCliConfig, serverName: string, const wantsHelp = rest.includes("--help") || rest.includes("-h"); const wantsSchema = rest.includes("--schema"); const noDaemon = rest.includes("--no-daemon"); - const { tools, cached } = await listTools(cfg, serverName, { refresh: rest.includes("--refresh"), daemon: !noDaemon }); + const { tools, cached } = await backend.listTools({ refresh: rest.includes("--refresh") }); const tool = tools.find((t) => t.name === toolName); if (!tool) throw toolNotFound(serverName, toolName, tools); @@ -132,34 +117,24 @@ export async function runServerCommand(cfg: AgentCliConfig, serverName: string, // 4. execute const started = Date.now(); - const { result, via } = await callTool(cfg, serverName, toolName, args, { timeoutMs, daemon: !noDaemon }); - - const mcpResult = result as McpCallToolResult; + const { result, via } = await backend.callTool(toolName, args, { timeoutMs, daemon: !noDaemon }); - if (mcpResult.isError) { - throw errors.execution(extractText(mcpResult) || "tool reported an error without a message"); + if (result.isError) { + throw errors.execution(result.text || "tool reported an error without a message"); } // 5. observe if (output === "text") { - const text = extractText(mcpResult); - if (text === "") { - // Non-text content items: fall back to the machine-readable form. - process.stdout.write(JSON.stringify(extractData(mcpResult)) + "\n"); - } else { - // Double-encoded JSON pretty-prints; genuine prose passes through raw. - const v = parseMaybeEncoded(text); - const out = v !== null && typeof v === "object" ? JSON.stringify(v, null, 2) : text; - process.stdout.write(out + "\n"); - } + const out = result.data !== null && typeof result.data === "object" ? JSON.stringify(result.data, null, 2) : result.text ?? JSON.stringify(result.data); + process.stdout.write(out + "\n"); return EXIT.OK; } printJson({ ok: true, server: serverName, tool: toolName, - data: extractData(mcpResult), + data: result.data, meta: { durationMs: Date.now() - started, schemaCached: cached, via }, }); return EXIT.OK; -} +} \ No newline at end of file diff --git a/src/flags.ts b/src/flags.ts index 93cc6fa..f17f0d6 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -3,7 +3,7 @@ // --input accepts inline JSON, @file, or - (stdin). Flags override --input keys. import fs from "node:fs"; import { errors } from "./errors.js"; -import type { ToolInputSchema, ToolPropertySchema, FlagPlan, FlagSpec, McpTool } from "./types.js"; +import type { ToolInputSchema, ToolPropertySchema, FlagPlan, FlagSpec, ToolDef } from "./types.js"; const CONTROL_FLAGS = new Set(["input", "output", "schema", "refresh", "help", "timeout-ms", "no-daemon"]); @@ -180,7 +180,7 @@ export function validateRequired(plan: FlagPlan, args: Record): ); } -export function renderToolHelp(serverName: string, tool: McpTool): string { +export function renderToolHelp(serverName: string, tool: ToolDef): string { const plan = buildFlagPlan(tool.inputSchema); const lines: string[] = []; lines.push(serverName + " " + tool.name); diff --git a/src/index.ts b/src/index.ts index bff55e0..1d796d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,16 @@ import { Command } from "commander"; +import path from "node:path"; import pkg from "../package.json" with { type: "json" }; -import { loadConfig, addServer, removeServer } from "./config.js"; +import { loadConfig, addServer, removeServer, writeToolsCache } from "./config.js"; import { AgentCliError, errors, EXIT } from "./errors.js"; import { printError, printJson } from "./jsonout.js"; import { runServerCommand } from "./dispatch.js"; -import { listTools } from "./client.js"; +import { openBackend } from "./backend/index.js"; +import { snapshotSpec, snapshotPath } from "./openapi/specstore.js"; +import { compileSpec } from "./openapi/compile.js"; import { startDaemon, stopDaemon, daemonStatus } from "./daemon/lifecycle.js"; import { suggest } from "./fuzzy.js"; -import type { AgentCliConfig, ServerSpec, StdioServerSpec, HttpServerSpec } from "./types.js"; +import type { AgentCliConfig, ServerSpec, StdioServerSpec, HttpServerSpec, OpenApiServerSpec } from "./types.js"; const { version } = pkg; @@ -79,14 +82,16 @@ function serversHelpSection(cfg: AgentCliConfig): string { return [ "", "No servers configured yet:", - " agentcli server add -- [args...] # stdio server", - " agentcli server add --url # Streamable HTTP server", + " agentcli server add -- [args...] # stdio MCP server", + " agentcli server add --url # Streamable HTTP MCP server", + " agentcli server add --openapi # OpenAPI 3.x API", "", ].join("\n"); } const lines = entries.map(([name, spec]: [string, ServerSpec]) => { - const detail = spec.type === "http" ? spec.url : [spec.command, ...(spec.args || [])].join(" "); - return " " + name.padEnd(16) + (spec.type === "http" ? "http - " : "stdio - ") + detail; + const kind = spec.type === "http" ? "http" : spec.type === "openapi" ? "openapi" : "stdio"; + const detail = spec.type === "http" ? spec.url : spec.type === "openapi" ? spec.origin : [spec.command, ...(spec.args || [])].join(" "); + return " " + name.padEnd(16) + kind + " - " + detail; }); return [ "", @@ -102,13 +107,13 @@ function buildBuiltins(cfg: AgentCliConfig): Command { program .name("agentcli") .version(version) - .description("AgentCLI -- call MCP servers from the command line.") + .description("AgentCLI -- call MCP servers and OpenAPI APIs from the command line.") .exitOverride() .configureOutput({ writeErr: () => {} }); program.addHelpText("after", serversHelpSection(cfg)); - const server = program.command("server").description("Manage configured MCP servers."); + const server = program.command("server").description("Manage configured servers (MCP or OpenAPI)."); server.addHelpText( "after", "\nConfigured servers: " + (Object.keys(cfg.servers).join(", ") || "none") + "\n agentcli server list Details as JSON\n" @@ -116,13 +121,35 @@ function buildBuiltins(cfg: AgentCliConfig): Command { server .command("add ") - .description("Register a server: stdio via `-- `, or Streamable HTTP via --url.") + .description("Register a server: stdio via `-- `, HTTP via --url, or an OpenAPI 3.x spec via --openapi.") .option("--url ", "Streamable HTTP endpoint of the MCP server") - .option("--header ", "HTTP header sent on every request (repeatable)") + .option("--openapi ", "OpenAPI 3.x JSON spec (local path or URL) — every operation becomes a tool") + .option("--base-url ", "Override the API base URL (with --openapi)") + .option("--header ", "HTTP header sent on every request (repeatable; ${ENV_VAR} placeholders expand at call time)") .option("--env ", "Extra env vars for a stdio server (repeatable)") .argument("[cmd...]", "stdio server command and args (after --)") - .action(async (name: string, cmd: string[], opts: { url?: string; header?: string[]; env?: string[] }) => { + .action(async (name: string, cmd: string[], opts: { url?: string; openapi?: string; baseUrl?: string; header?: string[]; env?: string[] }) => { const current = loadConfig(); + if (opts.openapi) { + if (opts.url) throw errors.invalidArgument("--openapi and --url are mutually exclusive"); + if (cmd && cmd.length) throw errors.invalidArgument("--openapi and a command are mutually exclusive"); + // fail fast: snapshot + compile at add time so bad specs never register + const hasScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(opts.openapi); + const origin = hasScheme ? opts.openapi : path.resolve(opts.openapi); + const doc = await snapshotSpec(name, origin); + const tools = compileSpec(doc, { baseUrl: opts.baseUrl, originUrl: hasScheme ? origin : undefined }); + const spec: OpenApiServerSpec = { + type: "openapi", + spec: snapshotPath(name), + origin, + ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}), + headers: parseKeyValueList(opts.header, "header", '"Name: value"', ":"), + }; + addServer(current, name, spec); + writeToolsCache(name, tools); + printJson({ ok: true, server: name, operations: tools.length, config: process.env.AGENTCLI_CONFIG }); + return; + } if (opts.url) { if (cmd && cmd.length) throw errors.invalidArgument("--url and a command are mutually exclusive"); const spec: HttpServerSpec = { type: "http", url: opts.url, headers: parseKeyValueList(opts.header, "header", '"Name: value"', ":") }; @@ -131,7 +158,7 @@ function buildBuiltins(cfg: AgentCliConfig): Command { if (!cmd || cmd.length === 0) { throw errors.invalidArgument( "a stdio server needs a command", - "agentcli server add -- [args...] | agentcli server add --url " + "agentcli server add -- [args...] | agentcli server add --url | agentcli server add --openapi " ); } const [command, ...args] = cmd; @@ -147,12 +174,13 @@ function buildBuiltins(cfg: AgentCliConfig): Command { .option("-o, --output ", "json | text (default: json)") .action((opts: { output?: string }) => { const current = loadConfig(); - const rows: Array<{ name: string; type: string; command?: string; url?: string }> = Object.entries(current.servers).map(([name, spec]: [string, ServerSpec]) => { + const rows: Array<{ name: string; type: string; command?: string; url?: string; origin?: string }> = Object.entries(current.servers).map(([name, spec]: [string, ServerSpec]) => { if (spec.type === "http") return { name, type: spec.type, url: spec.url }; + if (spec.type === "openapi") return { name, type: spec.type, origin: spec.origin }; return { name, type: spec.type, command: [spec.command, ...(spec.args || [])].join(" ") }; }); if (opts.output === "text") { - for (const r of rows) console.log(r.name.padEnd(16) + r.type.padEnd(8) + (r.command || r.url || "")); + for (const r of rows) console.log(r.name.padEnd(16) + r.type.padEnd(8) + (r.command || r.url || r.origin || "")); return; } printJson({ ok: true, data: rows }); @@ -169,10 +197,10 @@ function buildBuiltins(cfg: AgentCliConfig): Command { server .command("tools ") .description("List the tools a server exposes.") - .option("--refresh", "Bypass the tools cache") + .option("--refresh", "Bypass the tools cache (OpenAPI: also re-pull the spec from its origin)") .option("-o, --output ", "json | text (default: json)") .action(async (name: string, opts: { refresh?: boolean; output?: string }) => { - const { tools } = await listTools(loadConfig(), name, { refresh: !!opts.refresh }); + const { tools } = await openBackend(loadConfig(), name).listTools({ refresh: !!opts.refresh }); if (opts.output === "text") { for (const t of tools) console.log(String(t.name).padEnd(28) + String(t.description || "").split("\n")[0]); return; @@ -264,4 +292,4 @@ export async function run(argv: string[]): Promise { } catch (e) { exitWithError(e); } -} +} \ No newline at end of file diff --git a/src/openapi/backend.ts b/src/openapi/backend.ts new file mode 100644 index 0000000..6cd2cbe --- /dev/null +++ b/src/openapi/backend.ts @@ -0,0 +1,42 @@ +// OpenAPI backend: serves ToolDefs compiled from the spec snapshot and executes +// them over plain HTTP. Stateless — no daemon involvement; `via` is "direct". +import { readToolsCache, writeToolsCache } from "../config.js"; +import { ttlFromEnv, timeoutFromEnv } from "../client.js"; +import { errors } from "../errors.js"; +import type { OpenApiServerSpec, ToolDef } from "../types.js"; +import type { Backend, ListToolsResult, ToolResult } from "../backend/types.js"; +import { compileSpec } from "./compile.js"; +import { isHttpUrl, loadSnapshotDoc, refreshSnapshot } from "./specstore.js"; +import { executeOperation } from "./exec.js"; + +export function openApiBackend(name: string, spec: OpenApiServerSpec): Backend { + async function listToolsInternal({ refresh = false, timeoutMs }: { refresh?: boolean; timeoutMs?: number } = {}): Promise { + if (!refresh) { + const cached = readToolsCache(name, ttlFromEnv()); + if (cached && cached.fresh) return { tools: cached.tools, cached: true, via: "direct" }; + } else { + // --refresh: re-pull the spec from its origin (URL or local file) + await refreshSnapshot(name, spec.origin, timeoutMs); + } + const doc = await loadSnapshotDoc(name, spec.origin); + const tools = compileSpec(doc, { baseUrl: spec.baseUrl, originUrl: isHttpUrl(spec.origin) ? spec.origin : undefined }); + writeToolsCache(name, tools); + return { tools, cached: false, via: "direct" }; + } + + return { + kind: "openapi", + async listTools(opts = {}): Promise { + return listToolsInternal(opts); + }, + async callTool(tool: string, args: Record, opts: { timeoutMs?: number } = {}): Promise<{ result: ToolResult; via: "direct" }> { + const { tools } = await listToolsInternal(); + const def = tools.find((t: ToolDef) => t.name === tool); + if (!def || !def.openapiMeta) { + throw errors.notFound('tool "' + tool + '" not found on server "' + name + '"', "List tools: agentcli " + name + " --help"); + } + const result = await executeOperation(name, spec, def.openapiMeta, args, opts.timeoutMs ?? timeoutFromEnv()); + return { result, via: "direct" }; + }, + }; +} \ No newline at end of file diff --git a/src/openapi/compile.ts b/src/openapi/compile.ts new file mode 100644 index 0000000..1c2411e --- /dev/null +++ b/src/openapi/compile.ts @@ -0,0 +1,208 @@ +// OpenAPI 3.x spec -> ToolDef[] compiler. Each operation becomes one tool: +// path/query/header params and JSON body properties are flattened into a single +// inputSchema, so the existing flag compiler (flags.ts) works unchanged. +import { errors } from "../errors.js"; +import type { OpenApiOperationMeta, ToolDef, ToolInputSchema, ToolPropertySchema } from "../types.js"; +import { resolveRefs } from "./ref.js"; + +const METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"]; + +interface OpenApiParam { + name?: string; + in?: string; + required?: boolean; + description?: string; + schema?: unknown; + content?: Record; + [key: string]: unknown; +} + +interface OpenApiOperation { + operationId?: string; + summary?: string; + description?: string; + tags?: string[]; + parameters?: unknown[]; + requestBody?: unknown; + [key: string]: unknown; +} + +function sanitizeName(raw: string): string { + return raw.replace(/[^a-zA-Z0-9_.-]/g, "_"); +} + +// Mechanical, predictable fallback: GET /pets/{petId} -> get_pets_petId +function slugName(method: string, path: string): string { + const p = path + .replace(/^\//, "") + .replace(/\//g, "_") + .replace(/[{}]/g, ""); + return sanitizeName(method.toLowerCase() + "_" + p); +} + +function expandServerUrl(server: { url?: string; variables?: Record }): string { + const url = server.url || ""; + return url.replace(/\{([^}]+)\}/g, (_m, v: string) => server.variables?.[v]?.default ?? "{" + v + "}"); +} + +function resolveBaseUrl(doc: Record, override?: string, originUrl?: string): string { + const base = override || ""; + if (base) return base.replace(/\/+$/, ""); + const servers = (doc.servers as Array<{ url?: string; variables?: Record }> | undefined)?.[0]; + if (servers && servers.url) { + let url = expandServerUrl(servers).replace(/\/+$/, ""); + // Relative server URLs (e.g. "/api/v3") resolve against the spec's origin. + if (url.startsWith("/") && originUrl) { + try { + url = new URL(url, originUrl).toString().replace(/\/+$/, ""); + } catch { + // leave as-is; a later new URL() in exec will produce a clear error + } + } + return url; + } + throw errors.invalidArgument( + "spec declares no servers entry and no --base-url was given", + "agentcli server add --openapi --base-url https://api.example.com" + ); +} + +// Top-level allOf: merge properties/required from each resolved branch. +function mergeAllOf(schema: Record): Record { + if (!Array.isArray(schema.allOf)) return schema; + const props: Record = {}; + const required = new Set((schema.required as string[]) || []); + for (const sub of schema.allOf as Record[]) { + Object.assign(props, (sub.properties as Record) || {}); + for (const r of (sub.required as string[]) || []) required.add(r); + } + Object.assign(props, (schema.properties as Record) || {}); + const out: Record = { ...schema, properties: props }; + if (required.size) out.required = [...required]; + else delete out.required; + delete out.allOf; + return out; +} + +export interface CompileOptions { + baseUrl?: string; // --base-url override + originUrl?: string; // spec origin URL, for resolving relative server entries +} + +export function compileSpec(doc: unknown, opts: CompileOptions = {}): ToolDef[] { + if (!doc || typeof doc !== "object") throw errors.invalidArgument("OpenAPI spec is not a JSON object"); + const d = doc as Record; + if (typeof d.swagger === "string") { + throw errors.invalidArgument("spec is Swagger 2.0, only OpenAPI 3.x is supported", "Upgrade the spec to 3.x (e.g. with openapi-diff / swagger2openapi)"); + } + if (typeof d.openapi !== "string" || !d.openapi.startsWith("3.")) { + throw errors.invalidArgument('spec has no "openapi: 3.x" version field', "Expected OpenAPI 3.x JSON"); + } + const baseUrl = resolveBaseUrl(d, opts.baseUrl, opts.originUrl); + + const paths = (d.paths as Record>) || {}; + const tools: ToolDef[] = []; + const nameCount = new Map(); + + for (const [path, pathItem] of Object.entries(paths)) { + if (!path.startsWith("/") || !pathItem || typeof pathItem !== "object") continue; + const sharedParams = Array.isArray(pathItem.parameters) ? (pathItem.parameters as unknown[]) : []; + + for (const method of METHODS) { + const op = pathItem[method] as OpenApiOperation | undefined; + if (!op || typeof op !== "object") continue; + + // --- name --- + let name = op.operationId ? sanitizeName(op.operationId) : slugName(method, path); + const seen = nameCount.get(name) || 0; + nameCount.set(name, seen + 1); + if (seen > 0) name = name + "_" + (seen + 1); + + // --- parameters: path-item level + operation level (op wins by name+in) --- + const opParams = Array.isArray(op.parameters) ? op.parameters : []; + const merged: OpenApiParam[] = []; + const keyed = (p: OpenApiParam) => (p.in || "") + ":" + (p.name || ""); + const opResolved = opParams.map((p) => resolveRefs(p, d) as OpenApiParam); + for (const p of sharedParams.map((p) => resolveRefs(p, d) as OpenApiParam)) { + if (!opResolved.some((q) => keyed(q) === keyed(p))) merged.push(p); + } + merged.push(...opResolved); + + const properties: Record = {}; + const required = new Set(); + const meta: OpenApiOperationMeta = { method: method.toUpperCase(), path, baseUrl, pathParams: {}, queryParams: {}, headerParams: {}, bodyProps: {} }; + const taken = new Set(); // flag names already used + + for (const param of merged) { + const loc = param.in; + if (loc !== "path" && loc !== "query" && loc !== "header") continue; // cookie skipped + if (!param.name) continue; + let schema = (param.schema as ToolPropertySchema) || undefined; + if (!schema && param.content) { + const json = Object.entries(param.content).find(([mime]) => mime.startsWith("application/json")); + if (json) schema = json[1].schema as ToolPropertySchema; + } + schema = (resolveRefs(schema || { type: "string" }, d) as ToolPropertySchema) || { type: "string" }; + if (param.description && !schema.description) schema = { ...schema, description: param.description }; + + let flag = param.name; + if (taken.has(flag)) flag = loc + "_" + param.name; // same name in two locations + taken.add(flag); + properties[flag] = schema; + if (loc === "path") { + required.add(flag); + meta.pathParams[flag] = param.name; + } else if (loc === "query") { + if (param.required) required.add(flag); + meta.queryParams[flag] = param.name; + } else { + if (param.required) required.add(flag); + meta.headerParams[flag] = param.name; + } + } + + // --- requestBody (application/json only) --- + let bodyNote = ""; + const rb = resolveRefs(op.requestBody, d) as { content?: Record } | undefined; + const jsonContent = rb?.content && Object.entries(rb.content).find(([mime]) => mime.startsWith("application/json")); + if (rb && !jsonContent) { + const mimes = Object.keys(rb.content || {}).join(", ") || "(none)"; + bodyNote = "\n(body content type not supported: " + mimes + ")"; + } + if (jsonContent && jsonContent[1].schema) { + let schema = resolveRefs(jsonContent[1].schema, d) as Record; + schema = mergeAllOf(schema); + const props = (schema.properties as Record) || {}; + const bodyRequired = new Set((schema.required as string[]) || []); + if (schema.type === "object" && Object.keys(props).length) { + // flatten body properties to top-level flags + for (const [propName, pschema] of Object.entries(props)) { + let flag = propName; + if (taken.has(flag)) flag = "body_" + propName; // collision with a param + taken.add(flag); + properties[flag] = pschema; + if (bodyRequired.has(propName)) required.add(flag); + meta.bodyProps[flag] = propName; + } + } else { + // non-object body (array/string/...): single param holding the whole body + let flag = "body"; + if (taken.has(flag)) flag = "body_"; + taken.add(flag); + properties[flag] = schema as ToolPropertySchema; + meta.rawBody = flag; + } + } + + // --- description --- + const descParts = [op.summary, op.description].filter((s) => typeof s === "string" && s) as string[]; + const description = (descParts.join("\n\n") || method.toUpperCase() + " " + path) + "\n(" + method.toUpperCase() + " " + path + ")" + bodyNote; + + const inputSchema: ToolInputSchema = { type: "object", properties }; + if (required.size) inputSchema.required = [...required]; + + tools.push({ name, description, inputSchema, tags: op.tags, openapiMeta: meta }); + } + } + return tools; +} \ No newline at end of file diff --git a/src/openapi/exec.ts b/src/openapi/exec.ts new file mode 100644 index 0000000..6c31ef9 --- /dev/null +++ b/src/openapi/exec.ts @@ -0,0 +1,125 @@ +// OpenAPI executor: compiled operation metadata + CLI args -> HTTP request -> +// neutral ToolResult. HTTP failures throw typed AgentCliErrors (the dispatch +// layer never sees an HTTP status). +import { errors } from "../errors.js"; +import { timeoutFromEnv } from "../client.js"; +import type { OpenApiOperationMeta, OpenApiServerSpec } from "../types.js"; +import type { ToolResult } from "../backend/types.js"; +import pkg from "../../package.json" with { type: "json" }; + +const ENV_VAR = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g; + +// Expand ${VAR} placeholders in configured header values. A referenced env var +// that is unset is a missing credential: AUTH_REQUIRED, never a silent empty header. +function expandEnvValue(value: string, header: string): string { + return value.replace(ENV_VAR, (m, v: string) => { + const val = process.env[v]; + if (val === undefined) { + throw errors.auth('missing env var ' + v + ' for header "' + header + '"'); + } + return val; + }); +} + +function truncate(s: string, max = 2048): string { + return s.length > max ? s.slice(0, max) + "…" : s; +} + +// Pull a human-readable message out of an error body if one exists. +function extractErrorMessage(bodyText: string): string | undefined { + try { + const v = JSON.parse(bodyText) as { message?: unknown; error?: { message?: unknown } | string }; + if (typeof v.message === "string" && v.message) return v.message; + if (typeof v.error === "string" && v.error) return v.error; + if (v.error && typeof v.error === "object" && typeof v.error.message === "string") return v.error.message; + } catch { + // not JSON + } + return undefined; +} + +function mapHttpError(serverName: string, status: number, bodyText: string, retryAfter: string | null): Error { + const msg = serverName + ": HTTP " + status + (extractErrorMessage(bodyText) ? " — " + extractErrorMessage(bodyText) : ""); + if (status === 401 || status === 403) return errors.auth(msg); + if (status === 404) return errors.notFound(msg, "Check the path parameters and the API base URL"); + if (status === 429) return errors.execution(msg, { httpStatus: status, retryAfter: retryAfter || undefined }); + return errors.execution(msg, { httpStatus: status, body: truncate(bodyText) }); +} + +export async function executeOperation( + serverName: string, + spec: OpenApiServerSpec, + meta: OpenApiOperationMeta, + args: Record, + timeoutMs?: number +): Promise { + // 1. headers: Accept, header params, config headers (env-expanded), UA + const headers: Record = { Accept: "application/json", "User-Agent": "agentcli/" + pkg.version }; + for (const [flag, orig] of Object.entries(meta.headerParams)) { + if (args[flag] !== undefined) headers[orig] = String(args[flag]); + } + for (const [k, v] of Object.entries(spec.headers || {})) { + headers[k] = expandEnvValue(String(v), k); + } + + // 2. path + query + let path = meta.path; + for (const [flag, orig] of Object.entries(meta.pathParams)) { + const v = args[flag]; + if (v === undefined) throw errors.invalidArgument("missing path parameter --" + flag); + path = path.split("{" + orig + "}").join(encodeURIComponent(String(v))); + } + const query = new URLSearchParams(); + for (const [flag, orig] of Object.entries(meta.queryParams)) { + const v = args[flag]; + if (v === undefined) continue; + if (Array.isArray(v)) for (const item of v) query.append(orig, String(item)); + else query.append(orig, String(v)); + } + const urlStr = meta.baseUrl + path + (query.size ? "?" + query.toString() : ""); + + // 3. body + let body: string | undefined; + if (meta.rawBody && args[meta.rawBody] !== undefined) { + body = JSON.stringify(args[meta.rawBody]); + } else if (Object.keys(meta.bodyProps).length) { + const bodyObj: Record = {}; + for (const [flag, orig] of Object.entries(meta.bodyProps)) { + if (args[flag] !== undefined) bodyObj[orig] = args[flag]; + } + if (Object.keys(bodyObj).length) body = JSON.stringify(bodyObj); + } + if (body !== undefined) headers["Content-Type"] = "application/json"; + + // 4. fetch + let res: Response; + try { + res = await fetch(urlStr, { + method: meta.method, + headers, + body, + signal: AbortSignal.timeout(timeoutMs ?? timeoutFromEnv()), + }); + } catch (e) { + const err = e as Error; + if (err.name === "TimeoutError" || err.name === "AbortError") throw errors.timeout(serverName + ": request timed out"); + throw errors.connect(serverName + ": " + (err.message || String(e))); + } + + // 5. HTTP status -> typed errors + const text = await res.text(); + if (res.status >= 400) { + throw mapHttpError(serverName, res.status, text, res.headers.get("retry-after")); + } + + // 6. neutral ToolResult + let data: unknown = null; + if (text !== "") { + try { + data = JSON.parse(text); + } catch { + data = text; // non-JSON body: keep raw (json envelope carries it as a string) + } + } + return { data, text: text === "" ? undefined : text }; +} \ No newline at end of file diff --git a/src/openapi/ref.ts b/src/openapi/ref.ts new file mode 100644 index 0000000..b09675c --- /dev/null +++ b/src/openapi/ref.ts @@ -0,0 +1,56 @@ +// Local $ref resolver: inlines #/components/... references into standalone +// schemas. External refs are rejected with a clear error; circular refs decay +// into an opaque object marker instead of recursing forever. +import { errors } from "../errors.js"; + +const MAX_DEPTH = 16; +const CIRCULAR: Record = { type: "object", description: "(circular $ref)" }; +// Keys that never affect execution and are often huge — skip resolving inside. +const SKIP_KEYS = new Set(["example", "examples", "externalDocs"]); + +function lookupPointer(root: unknown, ref: string): unknown { + const parts = ref + .slice(2) + .split("/") + .map((s) => decodeURIComponent(s).replace(/~1/g, "/").replace(/~0/g, "~")); + let cur: unknown = root; + for (const p of parts) { + if (cur === null || typeof cur !== "object") return undefined; + cur = (cur as Record)[p]; + } + return cur; +} + +export function resolveRefs(node: unknown, root: unknown, seen: ReadonlySet = new Set(), depth = 0): unknown { + if (depth > MAX_DEPTH) return { type: "object", description: "(too deeply nested)" }; + if (Array.isArray(node)) { + return node.map((v) => resolveRefs(v, root, seen, depth + 1)); + } + if (node === null || typeof node !== "object") return node; + const obj = node as Record; + const ref = typeof obj.$ref === "string" ? obj.$ref : undefined; + if (ref) { + if (!ref.startsWith("#/")) { + throw errors.invalidArgument( + 'external $ref is not supported: "' + ref + '"', + "Inline the referenced schema into the spec, or bundle it (tools like openapi-cli / redocly can bundle)" + ); + } + if (seen.has(ref)) return { ...CIRCULAR }; + const target = lookupPointer(root, ref); + if (target === undefined) { + throw errors.invalidArgument('unresolvable $ref: "' + ref + '"', "Fix the spec — the referenced path does not exist"); + } + const { $ref: _drop, ...siblings } = obj; + const resolved = resolveRefs(target, root, new Set(seen).add(ref), depth + 1); + if (resolved !== null && typeof resolved === "object" && Object.keys(siblings).length) { + return { ...(resolved as object), ...siblings }; + } + return resolved; + } + const out: Record = {}; + for (const [k, v] of Object.entries(obj)) { + out[k] = SKIP_KEYS.has(k) ? v : resolveRefs(v, root, seen, depth + 1); + } + return out; +} \ No newline at end of file diff --git a/src/openapi/specstore.ts b/src/openapi/specstore.ts new file mode 100644 index 0000000..d1f8b75 --- /dev/null +++ b/src/openapi/specstore.ts @@ -0,0 +1,112 @@ +// Spec snapshots: every registered OpenAPI server gets a local copy under +// /specs/.json at add time — offline-friendly and immune to +// upstream spec churn. --refresh re-pulls from the recorded origin. +import fs from "node:fs"; +import path from "node:path"; +import { configPath } from "../config.js"; +import { errors } from "../errors.js"; + +const FETCH_TIMEOUT_MS = 30000; + +export function specsDir(): string { + return process.env.AGENTCLI_SPECS_DIR || path.join(path.dirname(configPath()), "specs"); +} + +export function snapshotPath(name: string): string { + return path.join(specsDir(), name + ".json"); +} + +function looksLikeYaml(text: string): boolean { + return /^\s*(openapi|swagger)\s*:\s*["']?3/.test(text); +} + +// Parse + validate a raw spec document (OpenAPI 3.x JSON only). +export function parseSpecJson(raw: string, source: string): Record { + let doc: unknown; + try { + doc = JSON.parse(raw); + } catch (e) { + const err = e as Error; + if (looksLikeYaml(raw)) { + throw errors.invalidArgument("spec at " + source + " looks like YAML — only JSON is supported", "Convert it: redocly bundle spec.yaml --output spec.json, then re-add"); + } + throw errors.invalidArgument("spec at " + source + " is not valid JSON", err.message); + } + if (!doc || typeof doc !== "object" || Array.isArray(doc)) { + throw errors.invalidArgument("spec at " + source + " is not a JSON object"); + } + return doc as Record; +} + +export function isHttpUrl(s: string): boolean { + return /^https?:\/\//i.test(s); +} + +async function fetchOrigin(origin: string, timeoutMs = FETCH_TIMEOUT_MS): Promise { + let res: Response; + try { + res = await fetch(origin, { signal: AbortSignal.timeout(timeoutMs), headers: { Accept: "application/json" } }); + } catch (e) { + const err = e as Error; + if (err.name === "TimeoutError" || err.name === "AbortError") throw errors.timeout("fetching spec timed out: " + origin); + throw errors.connect("cannot fetch spec from " + origin + ": " + err.message); + } + if (!res.ok) { + throw errors.connect("cannot fetch spec from " + origin + ": HTTP " + res.status); + } + return res.text(); +} + +// Snapshot from a URL or local file path. Validates JSON + 3.x shape. +export async function snapshotSpec(name: string, origin: string, timeoutMs?: number): Promise> { + let raw: string; + if (isHttpUrl(origin)) { + raw = await fetchOrigin(origin, timeoutMs); + } else { + try { + raw = fs.readFileSync(origin, "utf8"); + } catch { + throw errors.invalidArgument("cannot read spec file: " + origin, "Check the path (or use an http(s) URL)"); + } + } + const doc = parseSpecJson(raw, origin); + // fail fast on version problems (compileSpec re-checks, this covers add time) + if (typeof doc.swagger === "string") { + throw errors.invalidArgument("spec is Swagger 2.0, only OpenAPI 3.x is supported", "Upgrade the spec to 3.x (e.g. with swagger2openapi)"); + } + if (typeof doc.openapi !== "string" || !doc.openapi.startsWith("3.")) { + throw errors.invalidArgument('spec has no "openapi: 3.x" version field', "Expected OpenAPI 3.x JSON"); + } + fs.mkdirSync(specsDir(), { recursive: true }); + const p = snapshotPath(name); + fs.writeFileSync(p, JSON.stringify(doc, null, 2) + "\n", { mode: 0o600 }); + try { fs.chmodSync(p, 0o600); } catch { /* best effort */ } + return doc; +} + +// Load the local snapshot. Corrupt snapshot + URL origin -> auto re-pull. +export async function loadSnapshotDoc(name: string, origin: string): Promise> { + const p = snapshotPath(name); + const read = (): string => { + try { + return fs.readFileSync(p, "utf8"); + } catch { + throw errors.invalidArgument("spec snapshot is missing for server \"" + name + '"', "Re-add the server: agentcli server remove " + name + " && agentcli server add " + name + " --openapi "); + } + }; + let raw: string; + try { + raw = read(); + return parseSpecJson(raw, p); + } catch (e) { + if (!isHttpUrl(origin)) throw e; + // corrupt/stale snapshot with a URL origin: re-pull once + await snapshotSpec(name, origin); + return parseSpecJson(read(), p); + } +} + +// --refresh: re-pull from origin (URL or local file) and re-snapshot. +export async function refreshSnapshot(name: string, origin: string, timeoutMs?: number): Promise> { + return snapshotSpec(name, origin, timeoutMs); +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index ed38a40..0047e27 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,6 @@ // Shared types used across the codebase. +// ToolDef is the unified internal tool contract: every backend (MCP, OpenAPI, +// future ones) produces these; dispatch/flags consume them backend-agnostically. export interface StdioServerSpec { type: "stdio"; @@ -13,7 +15,15 @@ export interface HttpServerSpec { headers?: Record | string[]; } -export type ServerSpec = StdioServerSpec | HttpServerSpec; +export interface OpenApiServerSpec { + type: "openapi"; + spec: string; // absolute path to the local snapshot + origin: string; // original source: URL or absolute file path (--refresh re-pulls) + baseUrl?: string; // overrides spec.servers[0].url + headers?: Record; // may contain ${ENV_VAR} placeholders +} + +export type ServerSpec = StdioServerSpec | HttpServerSpec | OpenApiServerSpec; export interface AgentCliConfig { version: 1; @@ -40,13 +50,31 @@ export interface ToolPropertySchema { [key: string]: unknown; } -export interface McpTool { +// The unified tool definition (formerly McpTool). MCP tool listings map 1:1; +// the OpenAPI compiler emits these from spec operations. +export interface ToolDef { name: string; description?: string; inputSchema?: ToolInputSchema; + tags?: string[]; + // OpenAPI backend only: execution metadata (method/path/param locations). + openapiMeta?: OpenApiOperationMeta; [key: string]: unknown; } +// Execution metadata embedded in ToolDef by the OpenAPI compiler and consumed +// by the OpenAPI executor. All maps are flagName -> original spec name. +export interface OpenApiOperationMeta { + method: string; // uppercase HTTP method + path: string; // path template, e.g. /pets/{petId} + baseUrl: string; // resolved base URL (override or spec.servers[0]) + pathParams: Record; + queryParams: Record; + headerParams: Record; + bodyProps: Record; // flagName -> body property name + rawBody?: string; // flagName holding the whole request body (non-object body schemas) +} + export interface FlagSpec { name: string; type: string; @@ -69,7 +97,7 @@ export interface FlagPlan { } export interface ToolsCache { - tools: McpTool[]; + tools: ToolDef[]; fetchedAt: number; } @@ -95,17 +123,6 @@ export interface DaemonResponse { }; } -export interface CallToolResultEnvelope { - result: unknown; - via: "daemon" | "direct"; -} - -export interface ListToolsResultEnvelope { - tools: McpTool[]; - cached: boolean; - via: "daemon" | "direct"; -} - export interface DaemonStatusData { pid: number; startedAt: number; @@ -114,4 +131,4 @@ export interface DaemonStatusData { toolCalls: number; connectedServers: string[]; socket: string; -} +} \ No newline at end of file diff --git a/test/openapi-compile.test.ts b/test/openapi-compile.test.ts new file mode 100644 index 0000000..cc8dba9 --- /dev/null +++ b/test/openapi-compile.test.ts @@ -0,0 +1,154 @@ +// Unit tests for the OpenAPI spec -> ToolDef compiler. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { compileSpec } from "../src/openapi/compile.js"; +import { resolveRefs } from "../src/openapi/ref.js"; +import { AgentCliError } from "../src/errors.js"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const doc = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "fixtures", "openapi.json"), "utf8")); + +function byName(tools: ReturnType, name: string) { + const t = tools.find((x) => x.name === name); + assert.ok(t, "expected tool " + name + " to compile"); + return t; +} + +test("compiles operations with operationId as tool names", () => { + const tools = compileSpec(doc); + const names = tools.map((t) => t.name); + assert.ok(names.includes("getPetById")); + assert.ok(names.includes("listPets")); + assert.ok(names.includes("addPet")); + assert.ok(names.includes("getOrder")); +}); + +test("missing operationId falls back to a mechanical slug", () => { + const tools = compileSpec(doc); + assert.ok(tools.some((t) => t.name === "get_no-op-id")); +}); + +test("duplicate names get deterministic suffixes", () => { + const dup = JSON.parse(JSON.stringify(doc)); + dup.paths["/pets/{petId}"].get.operationId = "listPets"; + const tools = compileSpec(dup); + const names = tools.map((t) => t.name); + assert.equal(names.filter((n) => n.startsWith("listPets")).length, 2); + assert.ok(names.includes("listPets_2")); +}); + +test("path params are forced required; query params optional unless declared", () => { + const tool = byName(compileSpec(doc), "getPetById"); + assert.deepEqual(tool.inputSchema?.required, ["petId"]); + assert.equal(tool.inputSchema?.properties?.petId.type, "integer"); + assert.equal(tool.inputSchema?.properties?.verbose.type, "boolean"); +}); + +test("parameter $refs are inlined with defaults", () => { + const tool = byName(compileSpec(doc), "getPetById"); + const limit = tool.inputSchema?.properties?.limit; + assert.equal(limit?.type, "integer"); + assert.equal(limit?.default, 10); + assert.equal(limit?.description, "Page size"); + assert.equal(tool.openapiMeta?.queryParams.limit, "limit"); +}); + +test("body properties flatten to top-level flags", () => { + const tool = byName(compileSpec(doc), "addPet"); + const props = tool.inputSchema?.properties || {}; + assert.equal(props.name.type, "string"); + assert.deepEqual(props.kind.enum, ["cat", "dog"]); // resolved $ref inlined + assert.deepEqual(tool.inputSchema?.required, ["name"]); + assert.equal(tool.openapiMeta?.bodyProps.name, "name"); +}); + +test("array body becomes a whole-body param", () => { + const tool = byName(compileSpec(doc), "postBatch"); + assert.equal(tool.openapiMeta?.rawBody, "body"); + assert.equal(tool.inputSchema?.properties?.body.type, "array"); +}); + +test("circular $refs decay instead of recursing", () => { + const tools = compileSpec(doc); // must not hang + const tool = byName(tools, "postCircular"); + const children = tool.inputSchema?.properties?.children as { items?: { description?: string } }; + assert.match(String(children?.items?.description || ""), /circular/); +}); + +test("tags are collected for help grouping", () => { + const tools = compileSpec(doc); + assert.deepEqual(byName(tools, "getPetById").tags, ["pets"]); + assert.deepEqual(byName(tools, "getOrder").tags, ["store"]); + assert.equal(byName(tools, "get_no-op-id").tags, undefined); +}); + +test("header params map to header flags", () => { + const tool = byName(compileSpec(doc), "getOrder"); + assert.equal(tool.openapiMeta?.headerParams["X-Request-Id"], "X-Request-Id"); +}); + +test("meta records method, path template, baseUrl", () => { + const tool = byName(compileSpec(doc), "getPetById"); + assert.equal(tool.openapiMeta?.method, "GET"); + assert.equal(tool.openapiMeta?.path, "/pets/{petId}"); + assert.equal(tool.openapiMeta?.baseUrl, "https://api.mini.test/v1"); +}); + +test("baseUrl override wins over spec servers", () => { + const tool = byName(compileSpec(doc, { baseUrl: "https://override.test" }), "getPetById"); + assert.equal(tool.openapiMeta?.baseUrl, "https://override.test"); +}); + +test("no servers and no override -> clear error", () => { + const noServers = { ...doc, servers: [] }; + assert.throws(() => compileSpec(noServers), (e: unknown) => { + assert.ok(e instanceof AgentCliError); + assert.match(e.message, /--base-url/); + return true; + }); +}); + +test("swagger 2.0 is rejected with an upgrade hint", () => { + assert.throws(() => compileSpec({ swagger: "2.0", paths: {} }), (e: unknown) => { + assert.ok(e instanceof AgentCliError); + assert.match(e.message, /Swagger 2.0/); + return true; + }); +}); + +test("non-3.x openapi field is rejected", () => { + assert.throws(() => compileSpec({ openapi: "4.0.0", paths: {} }), AgentCliError); +}); + +test("resolveRefs: external refs are rejected", () => { + assert.throws(() => resolveRefs({ $ref: "https://evil.example.com/schema.json" }, doc), (e: unknown) => { + assert.ok(e instanceof AgentCliError); + assert.match(e.message, /external/); + return true; + }); +}); + +test("resolveRefs: unresolvable pointer is rejected", () => { + assert.throws(() => resolveRefs({ $ref: "#/components/schemas/Missing" }, doc), (e: unknown) => { + assert.ok(e instanceof AgentCliError); + assert.match(e.message, /unresolvable/); + return true; + }); +}); + +test("relative server URL resolves against the spec origin URL", () => { + const relative = JSON.parse(JSON.stringify(doc)); + relative.servers = [{ url: "/api/v3" }]; + const tool = byName(compileSpec(relative, { originUrl: "https://petstore.example.com/specs/openapi.json" }), "getPetById"); + assert.equal(tool.openapiMeta?.baseUrl, "https://petstore.example.com/api/v3"); +}); + +test("path-level parameters merge with operation parameters", () => { + const shared = JSON.parse(JSON.stringify(doc)); + shared.paths["/pets/{petId}"].parameters = [{ name: "X-Shared", in: "header", schema: { type: "string" } }]; + const tool = byName(compileSpec(shared), "getPetById"); + assert.equal(tool.inputSchema?.properties?.["X-Shared"].type, "string"); +}); \ No newline at end of file diff --git a/test/openapi-e2e.test.ts b/test/openapi-e2e.test.ts new file mode 100644 index 0000000..e6bde73 --- /dev/null +++ b/test/openapi-e2e.test.ts @@ -0,0 +1,304 @@ +// End-to-end: register an OpenAPI spec, then call operations against a real +// local HTTP server (base-url override). Covers URL building, body flattening, +// auth header injection with ${ENV} expansion, HTTP error mapping, snapshots. +import { test, before, after } from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import { spawn } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.join(__dirname, "..", ".."); +const BIN = path.join(ROOT, "bin", "agentcli.js"); +const FIXTURE_SPEC = path.join(ROOT, "fixtures", "openapi.json"); + +let tmp: string; +let configFile: string; +let originSpec: string; +let httpServer: http.Server; +let port = 0; + +// Requests seen by the test server (for assertions). +let lastRequest: { method: string; url: string; headers: http.IncomingHttpHeaders; body: string }; + +// Async spawn: the in-process HTTP test server must keep serving while the +// CLI child runs (spawnSync would block the event loop and deadlock both). +interface CliResult { + status: number | null; + stdout: string; + stderr: string; +} + +function cli(args: string[], env: Record = {}): Promise { + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, [BIN, ...args], { + cwd: ROOT, + env: { ...process.env, AGENTCLI_CONFIG: configFile, ...env }, + }); + let stdout = ""; + let stderr = ""; + child.stdout.on("data", (d: Buffer) => (stdout += d.toString())); + child.stderr.on("data", (d: Buffer) => (stderr += d.toString())); + child.on("error", reject); + child.on("close", (code) => resolve({ status: code ?? 0, stdout, stderr })); + }); +} + +before(async () => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), "agentcli-openapi-")); + configFile = path.join(tmp, "config.json"); + // copy the fixture so we can mutate "origin" without touching the repo file + originSpec = path.join(tmp, "origin-spec.json"); + fs.copyFileSync(FIXTURE_SPEC, originSpec); + + // the CLI child expands ${TEST_TOKEN} from its own environment + process.env.TEST_TOKEN = "secret-token"; + + httpServer = http.createServer((req, res) => { + let body = ""; + req.on("data", (c: Buffer) => (body += c.toString())); + req.on("end", () => { + lastRequest = { method: req.method || "", url: req.url || "", headers: req.headers, body }; + const url = new URL(req.url || "/", "http://x"); + // petId drives the response shape: error mapping without extra routes + const petId = url.pathname.match(/\/pets\/(\d+)$/)?.[1]; + if (petId === "404") { + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ message: "pet not found" })); + return; + } + if (petId === "401") { + res.writeHead(401, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ message: "bad token" })); + return; + } + if (petId === "500") { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: { message: "kaboom" } })); + return; + } + if (url.pathname === "/v1/pets" && req.method === "POST" && body.includes('"boom"')) { + res.writeHead(429, { "Content-Type": "application/json", "Retry-After": "7" }); + res.end(JSON.stringify({ message: "slow down" })); + return; + } + if (petId === "999") { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("

not json

"); + return; + } + const query: Record = {}; + for (const k of new Set(url.searchParams.keys())) { + const all = url.searchParams.getAll(k); + query[k] = all.length > 1 ? all : all[0]; + } + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ ok: true, method: req.method, path: url.pathname, query })); + }); + }); + await new Promise((resolve) => httpServer.listen(0, "127.0.0.1", resolve)); + port = (httpServer.address() as { port: number }).port; + + // register with a header template + base-url override pointing at the test server + const add = await cli([ + "server", "add", "mini", + "--openapi", originSpec, + "--base-url", "http://127.0.0.1:" + port + "/v1", + "--header", "Authorization: Bearer ${TEST_TOKEN}", + ], { TEST_TOKEN: "secret-token" }); + assert.equal(add.status, 0, add.stderr); + const out = JSON.parse(add.stdout); + assert.equal(out.ok, true); + assert.equal(typeof out.operations, "number"); + assert.ok(out.operations >= 7); +}); + +after(() => { + httpServer.close(); + fs.rmSync(tmp, { recursive: true, force: true }); +}); + +test("add snapshots the spec and writes the compiled cache", async () => { + const snapshot = path.join(path.dirname(configFile), "specs", "mini.json"); + assert.ok(fs.existsSync(snapshot), "snapshot must exist next to the config"); + const cache = path.join(path.dirname(configFile), "cache", "mini.tools.json"); + assert.ok(fs.existsSync(cache)); + const cfg = JSON.parse(fs.readFileSync(configFile, "utf8")); + assert.equal(cfg.servers.mini.type, "openapi"); + assert.equal(cfg.servers.mini.baseUrl, "http://127.0.0.1:" + port + "/v1"); +}); + +test("server -h lists operations grouped by tag", async () => { + const r = await cli(["mini", "-h"]); + assert.equal(r.status, 0, r.stderr); + assert.match(r.stdout as string, /pets:/); + assert.match(r.stdout as string, /store:/); + assert.match(r.stdout as string, /getPetById/); + assert.match(r.stdout as string, /OpenAPI server/); +}); + +test("GET with path param builds the URL and injects the auth header", async () => { + const r = await cli(["mini", "getPetById", "--petId", "7"], { TEST_TOKEN: "t123" }); + assert.equal(r.status, 0, r.stderr); + const out = JSON.parse(r.stdout as string); + assert.equal(out.ok, true); + assert.equal(out.data.path, "/v1/pets/7"); + assert.equal(out.data.method, "GET"); + assert.equal(out.meta.via, "direct"); + assert.equal(lastRequest.headers.authorization, "Bearer t123"); +}); + +test("query params (incl. arrays via repeated flags) serialize", async () => { + const r = await cli(["mini", "listPets", "--tags", "a", "--tags", "b", "--limit", "3"]); + assert.equal(r.status, 0, r.stderr); + assert.deepEqual(JSON.parse(r.stdout as string).data.query, { tags: ["a", "b"], limit: "3" }); + assert.match(lastRequest.url, /tags=a&tags=b/); +}); + +test("flattened body props POST as JSON", async () => { + const r = await cli(["mini", "addPet", "--name", "rex", "--kind", "dog"]); + assert.equal(r.status, 0, r.stderr); + assert.deepEqual(JSON.parse(lastRequest.body), { name: "rex", kind: "dog" }); + assert.match(lastRequest.headers["content-type"] as string, /application\/json/); +}); + +test("required body prop is enforced client-side", async () => { + const r = await cli(["mini", "addPet"]); + assert.equal(r.status, 1); + assert.match(JSON.parse(r.stderr as string).error.message, /missing required parameter: name/); +}); + +test("header param maps to a request header", async () => { + const r = await cli(["mini", "getOrder"]); + assert.equal(r.status, 0, r.stderr); + const r2 = await cli(["mini", "getOrder", "--X-Request-Id", "abc-1"]); + assert.equal(r2.status, 0, r2.stderr); + assert.equal(lastRequest.headers["x-request-id"], "abc-1"); +}); + +test("array body passes through via repeated flags", async () => { + const r = await cli(["mini", "postBatch", "--body", "a", "--body", "b"]); + assert.equal(r.status, 0, r.stderr); + assert.deepEqual(JSON.parse(lastRequest.body), ["a", "b"]); +}); + +test("HTTP 404 -> NOT_FOUND with the API's own message", async () => { + const r = await cli(["mini", "getPetById", "--petId", "404"]); + assert.equal(r.status, 1); + const err = JSON.parse(r.stderr as string); + assert.equal(err.error.code, "NOT_FOUND"); + assert.match(err.error.message, /pet not found/); +}); + +test("HTTP 401 -> AUTH_REQUIRED", async () => { + const r = await cli(["mini", "getPetById", "--petId", "401"]); + assert.equal(r.status, 1); + assert.equal(JSON.parse(r.stderr as string).error.code, "AUTH_REQUIRED"); +}); + +test("HTTP 500 -> EXECUTION_ERROR with status in details", async () => { + const r = await cli(["mini", "getPetById", "--petId", "500"]); + assert.equal(r.status, 1); + const err = JSON.parse(r.stderr as string); + assert.equal(err.error.code, "EXECUTION_ERROR"); + assert.match(err.error.message, /kaboom/); + assert.equal(err.error.details.httpStatus, 500); +}); + +test("HTTP 429 -> EXECUTION_ERROR with retryAfter", async () => { + const r = await cli(["mini", "addPet", "--name", "boom"]); + assert.equal(r.status, 1); + const err = JSON.parse(r.stderr as string); + assert.equal(err.error.code, "EXECUTION_ERROR"); + assert.equal(err.error.details.retryAfter, "7"); +}); + +test("unset ${ENV} in a header -> AUTH_REQUIRED before any request", async () => { + // empty string is a defined var; force undefined by deleting it from the child env + const env: Record = { ...process.env, AGENTCLI_CONFIG: configFile }; + delete env.TEST_TOKEN; + const r2 = await new Promise((resolve, reject) => { + const child = spawn(process.execPath, [BIN, "mini", "listPets"], { cwd: ROOT, env: env as NodeJS.ProcessEnv }); + let stdout = ""; + let stderr = ""; + child.stdout.on("data", (d: Buffer) => (stdout += d.toString())); + child.stderr.on("data", (d: Buffer) => (stderr += d.toString())); + child.on("error", reject); + child.on("close", (code) => resolve({ status: code ?? 0, stdout, stderr })); + }); + assert.equal(r2.status, 1); + assert.equal(JSON.parse(r2.stderr).error.code, "AUTH_REQUIRED"); + assert.match(r2.stderr, /TEST_TOKEN/); +}); + +test("non-JSON 200 body: text output passes through raw; json wraps as string", async () => { + const text = await cli(["mini", "getPetById", "--petId", "999", "--output", "text"]); + assert.equal(text.status, 0, text.stderr); + assert.equal((text.stdout as string).trim(), "

not json

"); + + const json = await cli(["mini", "getPetById", "--petId", "999"]); + assert.equal(json.status, 0, json.stderr); + assert.equal(JSON.parse(json.stdout as string).data, "

not json

"); +}); + +test("--schema prints the flattened input schema", async () => { + const r = await cli(["mini", "addPet", "--schema"]); + assert.equal(r.status, 0, r.stderr); + const schema = JSON.parse(r.stdout as string); + assert.equal(schema.properties.name.type, "string"); + assert.deepEqual(schema.properties.kind.enum, ["cat", "dog"]); +}); + +test("snapshot independence: calls work after the origin file disappears", async () => { + fs.rmSync(originSpec); + const r = await cli(["mini", "listPets"]); + assert.equal(r.status, 0, r.stderr); +}); + +test("--refresh re-pulls a file origin (updated spec wins)", async () => { + const updated = JSON.parse(fs.readFileSync(FIXTURE_SPEC, "utf8")); + updated.paths["/refreshed"] = { get: { operationId: "refreshedOp", responses: { "200": { description: "ok" } } } }; + fs.writeFileSync(originSpec, JSON.stringify(updated)); + const stale = await cli(["mini", "-h"]); + assert.equal(stale.status, 0, stale.stderr); + assert.ok(!(stale.stdout as string).includes("refreshedOp")); + const r = await cli(["server", "tools", "mini", "--refresh"]); + assert.equal(r.status, 0, r.stderr); + const names = JSON.parse(r.stdout as string).data.map((t: { name: string }) => t.name); + assert.ok(names.includes("refreshedOp")); +}); + +test("server remove cleans up snapshot and cache", async () => { + const r = await cli(["server", "remove", "mini"]); + assert.equal(r.status, 0, r.stderr); + assert.ok(!fs.existsSync(path.join(path.dirname(configFile), "specs", "mini.json"))); + assert.ok(!fs.existsSync(path.join(path.dirname(configFile), "cache", "mini.tools.json"))); +}); + +test("add rejects swagger 2.0 specs with a clear error", async () => { + const swaggerPath = path.join(tmp, "swagger.json"); + fs.writeFileSync(swaggerPath, JSON.stringify({ swagger: "2.0", info: { title: "x", version: "1" }, paths: {} })); + const r = await cli(["server", "add", "old", "--openapi", swaggerPath]); + assert.equal(r.status, 1); + assert.match(r.stderr as string, /Swagger 2.0/); +}); + +test("add rejects YAML with a conversion hint", async () => { + const yamlPath = path.join(tmp, "spec.yaml"); + fs.writeFileSync(yamlPath, "openapi: 3.0.3\ninfo:\n title: x\n"); + const r = await cli(["server", "add", "y", "--openapi", yamlPath]); + assert.equal(r.status, 1); + assert.match(r.stderr as string, /YAML/); +}); + +test("add rejects --openapi together with --url and with a command", async () => { + const a = await cli(["server", "add", "x1", "--openapi", originSpec, "--url", "http://mcp.example.com"]); + assert.equal(a.status, 1); + assert.match(a.stderr as string, /mutually exclusive/); + const b = await cli(["server", "add", "x2", "--openapi", originSpec, "--", "node", "-v"]); + assert.equal(b.status, 1); + assert.match(b.stderr as string, /mutually exclusive/); +}); \ No newline at end of file diff --git a/test/skill.test.ts b/test/skill.test.ts index 4f36d71..bbcf7d3 100644 --- a/test/skill.test.ts +++ b/test/skill.test.ts @@ -27,6 +27,12 @@ test("skill teaches the core loop commands", () => { assert.match(raw, / -h|--help/); }); +test("skill teaches OpenAPI registration alongside MCP", () => { + const raw = fs.readFileSync(SKILL, "utf8"); + assert.match(raw, /--openapi/); + assert.match(raw, /\$\{ENV_VAR\}/); +}); + test("skill documents the escape hatch and flags", () => { const raw = fs.readFileSync(SKILL, "utf8"); assert.match(raw, /--input/); From 583f3667b3c268f0176a71c45a570ee4860edff4 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 6 Sep 2026 15:12:55 +0800 Subject: [PATCH 9/9] docs: add examples/ with a 30-second OpenAPI walkthrough (open-meteo mini spec) --- README.md | 2 +- examples/README.md | 51 +++++++++++++++++++++++++++++ examples/open-meteo-mini.json | 61 +++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 examples/README.md create mode 100644 examples/open-meteo-mini.json diff --git a/README.md b/README.md index 36b9904..9dd4dc0 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ How an OpenAPI spec maps onto the CLI: - HTTP failures map to the same error codes: 401/403 → `AUTH_REQUIRED`, 404 → `NOT_FOUND`, other 4xx/5xx → `EXECUTION_ERROR` with `httpStatus` in details - OpenAPI calls are stateless HTTP — `meta.via` is always `direct` (no daemon involved) -Swagger 2.0 and YAML specs are rejected with an upgrade/conversion hint. Local spec files work too (`--openapi ./api.json`) — useful for internal APIs. +Swagger 2.0 and YAML specs are rejected with an upgrade/conversion hint. Local spec files work too (`--openapi ./api.json`) — useful for internal APIs. See [`examples/`](examples/) for a 30-second walkthrough against a real, auth-free API. ## Architecture diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..681efe4 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,51 @@ +# Examples + +Ready-to-try OpenAPI specs for `agentcli server add --openapi`. + +## open-meteo-mini.json + +A trimmed spec for the [Open-Meteo](https://open-meteo.com) weather API — +free, no auth, so it works out of the box: + +```bash +# 1. register (use an absolute path or run from the repo root) +agentcli server add weather --openapi examples/open-meteo-mini.json + +# 2. discover — operations grouped by tag +agentcli weather -h + +# 3. inspect — flags generated from the spec's JSON Schema +agentcli weather getForecast -h + +# 4. execute against the real API +agentcli weather getForecast --latitude 39.9 --longitude 116.4 --current temperature_2m +agentcli weather getForecast --latitude 31.2 --longitude 121.5 --current temperature_2m,wind_speed_10m +``` + +Pipe-friendly: + +```bash +agentcli weather getForecast --latitude 39.9 --longitude 116.4 \ + --current temperature_2m --output text | jq -r '.current.temperature_2m' +``` + +## Your own API + +The same flow works for any OpenAPI 3.x JSON spec (Swagger 2.0 and YAML are +rejected with a conversion hint): + +```bash +agentcli server add myapi --openapi ./api.json \ + --base-url https://staging.example.com \ + --header "Authorization: Bearer ${MY_TOKEN}" # ${ENV} expands at call time +``` + +Path/query/header parameters and JSON body properties all become flags: + +```bash +# POST /pets { "name": "rex", "kind": "dog" } +agentcli myapi addPet --name rex --kind dog + +# complex nested bodies go through --input (flags still override) +agentcli myapi createOrder --input '{"order":{"items":[{"sku":"a","qty":2}]}}' +``` \ No newline at end of file diff --git a/examples/open-meteo-mini.json b/examples/open-meteo-mini.json new file mode 100644 index 0000000..477350c --- /dev/null +++ b/examples/open-meteo-mini.json @@ -0,0 +1,61 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Open-Meteo Weather", + "version": "1.0" + }, + "servers": [ + { + "url": "https://api.open-meteo.com" + } + ], + "tags": [ + { + "name": "forecast" + } + ], + "paths": { + "/v1/forecast": { + "get": { + "tags": [ + "forecast" + ], + "summary": "Get current & hourly weather for a location", + "operationId": "getForecast", + "parameters": [ + { + "name": "latitude", + "in": "query", + "required": true, + "schema": { + "type": "number" + }, + "description": "Latitude, e.g. 39.9" + }, + { + "name": "longitude", + "in": "query", + "required": true, + "schema": { + "type": "number" + }, + "description": "Longitude, e.g. 116.4" + }, + { + "name": "current", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma list: temperature_2m,wind_speed_10m" + } + ], + "responses": { + "200": { + "description": "ok" + } + } + } + } + } +}