webmcp-codegen 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-I4ZL527H.js → chunk-EAKYM4YS.js} +2 -5
- package/dist/chunk-EAKYM4YS.js.map +1 -0
- package/dist/cli.d.ts +32 -0
- package/dist/cli.js +1490 -224
- package/dist/cli.js.map +1 -1
- package/dist/generators/index.d.ts +1 -1
- package/dist/generators/index.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/sources/index.d.ts +1 -1
- package/dist/{types-DWUum51l.d.ts → types-DfK3AA5H.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-CCVNYNJ5.js +0 -235
- package/dist/chunk-CCVNYNJ5.js.map +0 -1
- package/dist/chunk-I4ZL527H.js.map +0 -1
- package/dist/server-DWP6IHCS.js +0 -638
- package/dist/server-DWP6IHCS.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dev/server.ts","../src/dev/ui.ts"],"sourcesContent":["/**\n * The dev dashboard server: `npx webmcp-codegen dev`.\n *\n * A small HTTP server on localhost that serves the tools UI and answers its\n * three kinds of requests: list the tools, save an override (description /\n * enabled) to .webmcp-codegen.json, and run a tool's endpoint for a direct\n * test. It reuses the exact pipeline the CLI runs, so what the dashboard\n * shows is what a generate run would write.\n *\n * It exists only while the command is running, listens on localhost only,\n * and nothing about it ever touches the user's app bundle — by design, so\n * this dev tool can never leak into production.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createServer, type Server } from \"node:http\";\nimport { loadDataFile, saveDataFile } from \"../data-file.js\";\nimport { runGenerate } from \"../pipeline.js\";\nimport { resolveSetup } from \"../setup.js\";\nimport type { JsonSchema, ReviewedTool } from \"../types.js\";\nimport { dashboardHtml } from \"./ui.js\";\n\nexport interface DevServerOptions {\n cwd: string;\n port: number;\n /** Open the browser automatically. False in tests and CI. */\n open?: boolean;\n}\n\ninterface RunRequest {\n name: string;\n input: Record<string, unknown>;\n /** Absolute base URL when the API is not same-origin with the dashboard. */\n baseUrl?: string;\n}\n\n/** The JSON shape the UI renders. */\ninterface DashboardState {\n label: string;\n outDir?: string;\n tools: {\n name: string;\n verb?: string;\n path?: string;\n description: string;\n sideEffect: string;\n riskTier: string;\n enabled: boolean;\n endpointRole: string;\n piiInOutput: string[];\n inputSchema: JsonSchema;\n /** Route info the direct \"run it\" test needs to build a real request. */\n pathTemplate?: string;\n paramLocations?: { path: string[]; query: string[]; body: string[] };\n serverUrl?: string;\n findings: { level: string; message: string }[];\n }[];\n skipped: { ref: string; reason: string }[];\n notes: string[];\n}\n\nexport async function startDevServer(options: DevServerOptions): Promise<Server> {\n const setup = await resolveSetup(options.cwd, {\n dryRun: true,\n skipAudit: false,\n force: false,\n watch: false,\n });\n\n /** Re-run the pipeline fresh on every state request: edits to the spec\n * show up on reload without restarting the dashboard. */\n async function currentState(): Promise<DashboardState> {\n const data = await loadDataFile(options.cwd);\n const result = await runGenerate(setup.config, {\n cwd: options.cwd,\n dryRun: true,\n overrides: data.overrides,\n });\n return {\n label: setup.label,\n outDir: setup.config.generate[0]?.outDir,\n tools: result.tools.map((tool) => toUiTool(tool, result.findings)),\n skipped: result.skipped,\n notes: result.notes,\n };\n }\n\n const server = createServer(async (request, response) => {\n try {\n await route(request, response);\n } catch (error) {\n sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });\n }\n });\n\n async function route(\n request: import(\"node:http\").IncomingMessage,\n response: import(\"node:http\").ServerResponse,\n ): Promise<void> {\n const url = new URL(request.url ?? \"/\", \"http://localhost\");\n\n if (request.method === \"GET\" && url.pathname === \"/\") {\n response.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(dashboardHtml());\n return;\n }\n\n if (request.method === \"GET\" && url.pathname === \"/api/state\") {\n sendJson(response, 200, await currentState());\n return;\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/override\") {\n const body = (await readJson(request)) as {\n name?: string;\n description?: string;\n enabled?: boolean;\n };\n if (!body.name) {\n sendJson(response, 400, { error: \"Missing tool name.\" });\n return;\n }\n const data = await loadDataFile(options.cwd);\n const overrides = { ...(data.overrides ?? {}) };\n const existing = overrides[body.name] ?? {};\n // Explicit fields win over what's stored; undefined means \"untouched\".\n overrides[body.name] = {\n ...existing,\n ...(body.description !== undefined ? { description: body.description } : {}),\n ...(body.enabled !== undefined ? { enabled: body.enabled } : {}),\n };\n await saveDataFile(options.cwd, { overrides });\n sendJson(response, 200, { ok: true, saved: `.webmcp-codegen.json` });\n return;\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/run\") {\n const body = (await readJson(request)) as RunRequest;\n const state = await currentState();\n const tool = state.tools.find((candidate) => candidate.name === body.name);\n if (!tool) {\n sendJson(response, 404, { error: `No tool named \"${body.name}\".` });\n return;\n }\n const result = await runEndpoint(tool, body.input ?? {}, body.baseUrl);\n sendJson(response, result.ok ? 200 : 502, result);\n return;\n }\n\n sendJson(response, 404, { error: \"Not found\" });\n }\n\n await new Promise<void>((resolveListen) =>\n server.listen(options.port, \"127.0.0.1\", resolveListen),\n );\n\n if (options.open !== false) openBrowser(`http://localhost:${options.port}`);\n return server;\n}\n\nfunction toUiTool(\n tool: ReviewedTool,\n findings: { level: string; tool?: string; message: string }[],\n): DashboardState[\"tools\"][number] {\n const [verb, ...rest] = tool.source.ref.split(\" \");\n return {\n name: tool.name,\n verb,\n path: rest.join(\" \"),\n description: tool.description,\n sideEffect: tool.sideEffect,\n riskTier: tool.riskTier,\n enabled: tool.enabledByDefault,\n endpointRole: tool.endpointRole,\n piiInOutput: tool.piiInOutput,\n inputSchema: tool.inputSchema,\n ...(tool.pathTemplate ? { pathTemplate: tool.pathTemplate } : {}),\n ...(tool.paramLocations ? { paramLocations: tool.paramLocations } : {}),\n ...(tool.serverUrl ? { serverUrl: tool.serverUrl } : {}),\n findings: findings\n .filter((finding) => finding.tool === tool.name)\n .map((finding) => ({ level: finding.level, message: finding.message })),\n };\n}\n\n/**\n * The direct \"run it\" test: call the endpoint the way the generated\n * execute() would, but server-side. Two honest limitations the UI states:\n * there is no browser session here (auth cookies do not apply), and the\n * call needs an absolute base URL — the spec's servers entry or one the\n * developer types in.\n */\nasync function runEndpoint(\n tool: DashboardState[\"tools\"][number],\n input: Record<string, unknown>,\n baseUrlOverride?: string,\n): Promise<{ ok: boolean; status?: number; body?: unknown; error?: string }> {\n const base = baseUrlOverride ?? tool.serverUrl;\n if (!base) {\n return {\n ok: false,\n error:\n \"No base URL: the spec lists no absolute server. Type your app's URL \" +\n '(e.g. http://localhost:3000) in the \"base URL\" field and run again.',\n };\n }\n if (!tool.pathTemplate || !tool.verb) {\n return { ok: false, error: \"This tool has no route to call.\" };\n }\n\n let path = tool.pathTemplate;\n for (const param of tool.paramLocations?.path ?? []) {\n path = path.replace(`{${param}}`, encodeURIComponent(String(input[param] ?? \"\")));\n }\n const url = new URL(path, base);\n for (const param of tool.paramLocations?.query ?? []) {\n const value = input[param];\n if (value !== undefined && value !== null) url.searchParams.set(param, String(value));\n }\n const bodyFields = tool.paramLocations?.body ?? [];\n const body =\n bodyFields.length === 1 && bodyFields[0] === \"body\"\n ? input.body\n : bodyFields.length > 0\n ? Object.fromEntries(bodyFields.map((field) => [field, input[field]]))\n : undefined;\n\n try {\n const response = await fetch(url, {\n method: tool.verb,\n headers: body !== undefined ? { \"content-type\": \"application/json\" } : undefined,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n const text = await response.text();\n let parsed: unknown = text;\n try {\n parsed = JSON.parse(text);\n } catch {\n // Plain-text response; keep it as text.\n }\n return { ok: response.ok, status: response.status, body: parsed };\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\n }\n}\n\nfunction sendJson(\n response: import(\"node:http\").ServerResponse,\n status: number,\n body: unknown,\n): void {\n response.writeHead(status, { \"content-type\": \"application/json\" });\n response.end(JSON.stringify(body));\n}\n\nfunction readJson(request: import(\"node:http\").IncomingMessage): Promise<unknown> {\n return new Promise((resolveRead, reject) => {\n let text = \"\";\n request.on(\"data\", (chunk: Buffer) => {\n text += chunk.toString(\"utf8\");\n });\n request.on(\"end\", () => {\n try {\n resolveRead(text ? JSON.parse(text) : {});\n } catch {\n reject(new Error(\"Invalid JSON body\"));\n }\n });\n request.on(\"error\", reject);\n });\n}\n\nfunction openBrowser(url: string): void {\n const command =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n spawn(command, [url], { stdio: \"ignore\", shell: process.platform === \"win32\" }).unref();\n}\n","/**\n * The dashboard UI: one self-contained HTML page (inline CSS and JS), served\n * by the dev server. No framework, no build step, no CDN: the page must work\n * offline and add zero dependencies to the package.\n *\n * Design bar (per the first-run spec): this is a product surface, in the\n * idiom of Storybook / Mintlify / Scalar. Same tokens as the site: dark\n * neutral surfaces, one accent blue, hairline borders, system sans for prose\n * and mono for names and code. Keyboard navigation throughout: up/down move\n * through tools, ⌘K focuses search, ⌘S saves an edit.\n */\n\nexport function dashboardHtml(): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>webmcp-codegen</title>\n<style>\n :root {\n --baseline: #0a0b0f;\n --panel: #0f1117;\n --panel-raised: #151822;\n --line: #20242f;\n --ink: #e9ecf2;\n --dim: #9aa3b2;\n --faint: #5d6575;\n --ghost: #3b4150;\n --accent: #58a6ff;\n --signal: #e3b341;\n --fault: #f47067;\n --sans: ui-sans-serif, system-ui, -apple-system, sans-serif;\n --mono: ui-monospace, SFMono-Regular, Menlo, monospace;\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; height: 100%; }\n body {\n background: var(--baseline);\n color: var(--ink);\n font-family: var(--sans);\n font-size: 14px;\n -webkit-font-smoothing: antialiased;\n }\n ::selection { background: var(--accent); color: var(--baseline); }\n button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }\n input, textarea { font: inherit; color: var(--ink); background: var(--panel); border: 1px solid var(--line); border-radius: 6px; }\n input:focus-visible, textarea:focus-visible, button:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }\n\n #app { display: grid; grid-template-columns: 300px 1fr; height: 100vh; }\n\n /* ── Sidebar ─────────────────────────────────────────────── */\n aside {\n border-right: 1px solid var(--line);\n display: flex;\n flex-direction: column;\n min-height: 0;\n }\n .brand {\n display: flex; align-items: center; gap: 10px;\n padding: 16px 16px 12px;\n font-family: var(--mono); font-size: 13px; letter-spacing: -0.01em;\n }\n .brand::before { content: \"\"; width: 8px; height: 8px; background: var(--accent); }\n .brand .meta { color: var(--faint); font-size: 11px; margin-left: auto; }\n .search { padding: 0 12px 12px; }\n .search input {\n width: 100%; padding: 7px 10px; font-family: var(--mono); font-size: 12px;\n }\n .search input::placeholder { color: var(--ghost); }\n .tools { overflow-y: auto; flex: 1; padding: 4px 8px 16px; }\n .tool {\n display: flex; align-items: center; gap: 8px; width: 100%;\n padding: 7px 8px; border-radius: 6px; text-align: left;\n color: var(--dim);\n }\n .tool:hover { background: var(--panel); color: var(--ink); }\n .tool[aria-selected=\"true\"] { background: var(--panel-raised); color: var(--ink); }\n .tool .name { font-family: var(--mono); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .tool .verb {\n margin-left: auto; font-family: var(--mono); font-size: 10px;\n padding: 2px 6px; border-radius: 4px; border: 1px solid var(--line);\n color: var(--faint); flex-shrink: 0;\n }\n .tool .verb.read { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }\n .tool .verb.write { color: var(--signal); border-color: color-mix(in srgb, var(--signal) 35%, transparent); }\n .tool .verb.destructive { color: var(--fault); border-color: color-mix(in srgb, var(--fault) 35%, transparent); }\n .tool .off {\n width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;\n background: var(--ghost);\n }\n .tool .off[title] { cursor: help; }\n .empty { padding: 24px 16px; color: var(--faint); font-size: 13px; line-height: 1.6; }\n\n /* ── Detail pane ─────────────────────────────────────────── */\n main { overflow-y: auto; min-width: 0; }\n .detail { max-width: 780px; padding: 32px 40px 80px; }\n .crumb { font-family: var(--mono); font-size: 11px; color: var(--ghost); margin-bottom: 10px; }\n h1 { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; margin: 0 0 6px; font-family: var(--mono); }\n .route { font-family: var(--mono); font-size: 12px; color: var(--faint); margin-bottom: 24px; }\n .badges { display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap; }\n .badge {\n font-family: var(--mono); font-size: 11px; padding: 3px 8px;\n border: 1px solid var(--line); border-radius: 999px; color: var(--dim);\n }\n .badge.accent { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }\n .badge.warn { color: var(--signal); border-color: color-mix(in srgb, var(--signal) 35%, transparent); }\n .badge.err { color: var(--fault); border-color: color-mix(in srgb, var(--fault) 35%, transparent); }\n\n .field-label {\n font-family: var(--mono); font-size: 10px; text-transform: uppercase;\n letter-spacing: 0.18em; color: var(--faint); margin: 28px 0 8px;\n }\n textarea#desc {\n width: 100%; min-height: 70px; padding: 10px 12px; font-size: 14px;\n line-height: 1.55; resize: vertical;\n }\n .hint { color: var(--faint); font-size: 12px; margin-top: 6px; line-height: 1.5; }\n .row { display: flex; align-items: center; gap: 12px; }\n .save {\n margin-top: 10px; padding: 7px 14px; border: 1px solid var(--line);\n border-radius: 6px; font-size: 13px; color: var(--ink); background: var(--panel-raised);\n }\n .save:hover { border-color: var(--accent); }\n .save[disabled] { opacity: 0.4; cursor: default; }\n .saved { color: var(--accent); font-size: 12px; font-family: var(--mono); }\n\n /* Toggle */\n .toggle-row { display: flex; align-items: center; gap: 12px; padding: 14px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); margin-top: 28px; }\n .switch { position: relative; width: 34px; height: 20px; border-radius: 999px; background: var(--ghost); transition: background 150ms; flex-shrink: 0; }\n .switch[aria-checked=\"true\"] { background: var(--accent); }\n .switch::after {\n content: \"\"; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;\n border-radius: 50%; background: var(--ink); transition: left 150ms;\n }\n .switch[aria-checked=\"true\"]::after { left: 16px; }\n .toggle-copy { font-size: 13px; color: var(--dim); line-height: 1.5; }\n .toggle-copy strong { color: var(--ink); font-weight: 600; }\n\n /* Findings */\n .finding {\n display: flex; gap: 8px; padding: 10px 12px; border: 1px solid var(--line);\n border-radius: 6px; font-size: 12.5px; line-height: 1.5; color: var(--dim);\n margin-bottom: 8px; background: var(--panel);\n }\n .finding.warn { border-color: color-mix(in srgb, var(--signal) 30%, transparent); }\n .finding.error { border-color: color-mix(in srgb, var(--fault) 30%, transparent); }\n .finding .icon { color: var(--signal); }\n .finding.error .icon { color: var(--fault); }\n\n /* Try it */\n .try { margin-top: 28px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); }\n .try > header { padding: 12px 16px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 10px; }\n .try > header h2 { font-size: 13px; margin: 0; font-weight: 600; }\n .try > header .note { color: var(--faint); font-size: 11.5px; margin-left: auto; }\n .try .body { padding: 16px; }\n .param { display: grid; grid-template-columns: 180px 1fr; gap: 12px; align-items: center; margin-bottom: 10px; }\n .param label { font-family: var(--mono); font-size: 12px; color: var(--dim); }\n .param label .req { color: var(--fault); }\n .param input { padding: 7px 10px; font-family: var(--mono); font-size: 12px; width: 100%; }\n .base-url { margin-bottom: 14px; }\n .base-url input { width: 100%; padding: 7px 10px; font-family: var(--mono); font-size: 12px; }\n .run {\n margin-top: 6px; padding: 8px 16px; border-radius: 6px; font-size: 13px;\n background: var(--accent); color: var(--baseline); font-weight: 600;\n }\n .run[disabled] { opacity: 0.5; cursor: default; }\n pre.result {\n margin: 14px 0 0; padding: 12px 14px; background: var(--baseline);\n border: 1px solid var(--line); border-radius: 6px; font-family: var(--mono);\n font-size: 12px; line-height: 1.55; overflow-x: auto; max-height: 320px;\n color: var(--dim); white-space: pre-wrap; word-break: break-word;\n }\n pre.result.ok { border-color: color-mix(in srgb, var(--accent) 35%, transparent); }\n pre.result.err { border-color: color-mix(in srgb, var(--fault) 35%, transparent); }\n\n .placeholder { max-width: 480px; margin: 20vh auto 0; text-align: center; color: var(--faint); line-height: 1.7; }\n .placeholder kbd {\n font-family: var(--mono); font-size: 11px; border: 1px solid var(--line);\n border-radius: 4px; padding: 1px 5px; color: var(--dim);\n }\n .toast {\n position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);\n background: var(--panel-raised); border: 1px solid var(--line); color: var(--ink);\n padding: 8px 14px; border-radius: 6px; font-size: 12.5px; opacity: 0;\n transition: opacity 150ms; pointer-events: none;\n }\n .toast.show { opacity: 1; }\n</style>\n</head>\n<body>\n<div id=\"app\">\n <aside>\n <div class=\"brand\">webmcp-codegen <span class=\"meta\" id=\"tool-count\"></span></div>\n <div class=\"search\"><input id=\"search\" type=\"text\" placeholder=\"filter tools (⌘K)\" spellcheck=\"false\" /></div>\n <div class=\"tools\" id=\"tool-list\" role=\"listbox\" aria-label=\"Tools\"></div>\n </aside>\n <main id=\"detail\">\n <div class=\"placeholder\">\n <p>Loading your tools…</p>\n </div>\n </main>\n</div>\n<div class=\"toast\" id=\"toast\"></div>\n\n<script>\n(function () {\n var state = null;\n var selected = null;\n var filter = \"\";\n\n var listEl = document.getElementById(\"tool-list\");\n var detailEl = document.getElementById(\"detail\");\n var searchEl = document.getElementById(\"search\");\n var toastEl = document.getElementById(\"toast\");\n\n function esc(text) {\n return String(text).replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n }\n\n function toast(message) {\n toastEl.textContent = message;\n toastEl.classList.add(\"show\");\n setTimeout(function () { toastEl.classList.remove(\"show\"); }, 2200);\n }\n\n function api(path, options) {\n return fetch(path, options).then(function (res) {\n return res.json().then(function (body) {\n if (!res.ok) throw new Error(body.error || (\"Request failed: \" + res.status));\n return body;\n });\n });\n }\n\n function load() {\n return api(\"/api/state\").then(function (next) {\n state = next;\n if (!selected && state.tools.length > 0) selected = state.tools[0].name;\n renderList();\n renderDetail();\n }).catch(function (error) {\n detailEl.innerHTML = '<div class=\"placeholder\"><p>Could not load tools.</p><p>' + esc(error.message) + \"</p></div>\";\n });\n }\n\n function visibleTools() {\n if (!filter) return state.tools;\n var needle = filter.toLowerCase();\n return state.tools.filter(function (tool) {\n return tool.name.indexOf(needle) !== -1 ||\n (tool.path || \"\").toLowerCase().indexOf(needle) !== -1 ||\n tool.description.toLowerCase().indexOf(needle) !== -1;\n });\n }\n\n function renderList() {\n document.getElementById(\"tool-count\").textContent = state.tools.length + \" tools\";\n var tools = visibleTools();\n if (tools.length === 0) {\n listEl.innerHTML = '<div class=\"empty\">No tools match.</div>';\n return;\n }\n listEl.innerHTML = tools.map(function (tool) {\n var disabled = tool.enabled ? \"\" : '<span class=\"off\" title=\"starts disabled\"></span>';\n return '<button class=\"tool\" role=\"option\" aria-selected=\"' + (tool.name === selected) + '\" data-name=\"' + esc(tool.name) + '\">' +\n disabled +\n '<span class=\"name\">' + esc(tool.name) + \"</span>\" +\n '<span class=\"verb ' + esc(tool.sideEffect) + '\">' + esc(tool.verb || \"\") + \"</span>\" +\n \"</button>\";\n }).join(\"\");\n Array.prototype.forEach.call(listEl.children, function (child) {\n child.addEventListener(\"click\", function () {\n selected = child.getAttribute(\"data-name\");\n renderList();\n renderDetail();\n });\n });\n }\n\n function currentTool() {\n return state.tools.find(function (tool) { return tool.name === selected; });\n }\n\n function renderDetail() {\n var tool = currentTool();\n if (!tool) {\n detailEl.innerHTML = '<div class=\"placeholder\"><p>Select a tool on the left.</p><p><kbd>↑</kbd> <kbd>↓</kbd> to move, <kbd>⌘K</kbd> to search.</p></div>';\n return;\n }\n\n var badges = [\n '<span class=\"badge accent\">' + esc(tool.sideEffect) + \"</span>\",\n tool.enabled ? '<span class=\"badge\">enabled</span>' : '<span class=\"badge warn\">starts disabled</span>',\n tool.endpointRole !== \"endpoint\" ? '<span class=\"badge err\">' + esc(tool.endpointRole) + \" endpoint</span>\" : \"\",\n tool.piiInOutput.length > 0 ? '<span class=\"badge warn\">pii: ' + esc(tool.piiInOutput.join(\", \")) + \"</span>\" : \"\",\n ].filter(Boolean).join(\"\");\n\n var findings = tool.findings.map(function (finding) {\n var icon = finding.level === \"error\" ? \"✖\" : \"⚠\";\n return '<div class=\"finding ' + esc(finding.level) + '\"><span class=\"icon\">' + icon + \"</span><span>\" + esc(finding.message) + \"</span></div>\";\n }).join(\"\");\n\n var schema = tool.inputSchema || {};\n var properties = schema.properties || {};\n var required = schema.required || [];\n var fields = Object.keys(properties).map(function (key) {\n var field = properties[key];\n var type = field.type === \"number\" || field.type === \"integer\" ? \"number\" : \"text\";\n var req = required.indexOf(key) !== -1 ? ' <span class=\"req\">*</span>' : \"\";\n var label = esc(key) + req + (field.description ? '<div class=\"hint\">' + esc(field.description) + \"</div>\" : \"\");\n return '<div class=\"param\"><label for=\"f-' + esc(key) + '\">' + label + '</label>' +\n '<input id=\"f-' + esc(key) + '\" data-field=\"' + esc(key) + '\" data-type=\"' + esc(field.type || \"string\") + '\" type=\"' + type + '\" spellcheck=\"false\" /></div>';\n }).join(\"\");\n\n var baseUrl = \"\";\n try { baseUrl = localStorage.getItem(\"webmcp-codegen:baseUrl\") || tool.serverUrl || \"\"; } catch (e) {}\n\n detailEl.innerHTML =\n '<div class=\"detail\">' +\n '<div class=\"crumb\">' + esc(state.label) + (state.outDir ? \" → \" + esc(state.outDir) : \"\") + \"</div>\" +\n \"<h1>\" + esc(tool.name) + \"</h1>\" +\n '<div class=\"route\">' + esc((tool.verb || \"\") + \" \" + (tool.path || \"\")) + \"</div>\" +\n '<div class=\"badges\">' + badges + \"</div>\" +\n\n (findings ? '<div class=\"field-label\">audit findings</div>' + findings : \"\") +\n\n '<div class=\"field-label\">description</div>' +\n '<textarea id=\"desc\" spellcheck=\"false\">' + esc(tool.description) + \"</textarea>\" +\n '<div class=\"row\"><button class=\"save\" id=\"save-desc\">save description</button><span class=\"saved\" id=\"saved-desc\"></span></div>' +\n '<div class=\"hint\">Agents pick tools by this text. Saved to .webmcp-codegen.json, so it survives regeneration. ⌘S to save.</div>' +\n\n '<div class=\"toggle-row\">' +\n '<button class=\"switch\" id=\"toggle-enabled\" role=\"switch\" aria-checked=\"' + tool.enabled + '\" aria-label=\"Enabled\"></button>' +\n '<div class=\"toggle-copy\"><strong>' + (tool.enabled ? \"Enabled\" : \"Disabled\") + \".</strong> \" +\n (tool.enabled\n ? \"This tool works as soon as the app registers it.\"\n : \"The generated code is there, commented out. Flipping this regenerates it enabled on the next run.\") +\n \"</div></div>\" +\n\n '<div class=\"try\"><header><h2>try it</h2><span class=\"note\">direct call, server-side. no browser session here.</span></header>' +\n '<div class=\"body\">' +\n '<div class=\"base-url\"><input id=\"base-url\" type=\"text\" placeholder=\"base URL, e.g. http://localhost:3000\" value=\"' + esc(baseUrl) + '\" spellcheck=\"false\" /></div>' +\n (fields || '<div class=\"hint\">This tool takes no inputs.</div>') +\n '<button class=\"run\" id=\"run\">run</button>' +\n '<pre class=\"result\" id=\"result\" hidden></pre>' +\n \"</div></div>\" +\n \"</div>\";\n\n document.getElementById(\"save-desc\").addEventListener(\"click\", saveDescription);\n document.getElementById(\"toggle-enabled\").addEventListener(\"click\", toggleEnabled);\n document.getElementById(\"run\").addEventListener(\"click\", runTool);\n document.getElementById(\"base-url\").addEventListener(\"change\", function (event) {\n try { localStorage.setItem(\"webmcp-codegen:baseUrl\", event.target.value); } catch (e) {}\n });\n }\n\n function saveDescription() {\n var tool = currentTool();\n var desc = document.getElementById(\"desc\").value.trim();\n if (!tool || !desc) return;\n api(\"/api/override\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ name: tool.name, description: desc }),\n }).then(function () {\n tool.description = desc;\n toast(\"saved to .webmcp-codegen.json\");\n }).catch(function (error) { toast(error.message); });\n }\n\n function toggleEnabled() {\n var tool = currentTool();\n if (!tool) return;\n var next = !tool.enabled;\n api(\"/api/override\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ name: tool.name, enabled: next }),\n }).then(function () {\n tool.enabled = next;\n renderList();\n renderDetail();\n toast(next ? \"enabled on next generate\" : \"disabled on next generate\");\n }).catch(function (error) { toast(error.message); });\n }\n\n function runTool() {\n var tool = currentTool();\n if (!tool) return;\n var input = {};\n Array.prototype.forEach.call(document.querySelectorAll(\"[data-field]\"), function (field) {\n var value = field.value;\n if (value === \"\") return;\n var type = field.getAttribute(\"data-type\");\n if (type === \"number\" || type === \"integer\") value = Number(value);\n if (type === \"boolean\") value = value === \"true\";\n if (type === \"object\" || type === \"array\") {\n try { value = JSON.parse(value); } catch (e) { /* keep as string */ }\n }\n input[field.getAttribute(\"data-field\")] = value;\n });\n var baseUrl = document.getElementById(\"base-url\").value.trim();\n var resultEl = document.getElementById(\"result\");\n var runEl = document.getElementById(\"run\");\n runEl.disabled = true;\n resultEl.hidden = true;\n api(\"/api/run\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ name: tool.name, input: input, baseUrl: baseUrl || undefined }),\n }).then(function (result) {\n resultEl.hidden = false;\n resultEl.className = \"result \" + (result.ok ? \"ok\" : \"err\");\n resultEl.textContent =\n (result.status ? \"HTTP \" + result.status + \"\\\\n\\\\n\" : \"\") +\n (result.error ? result.error : JSON.stringify(result.body, null, 2));\n }).catch(function (error) {\n resultEl.hidden = false;\n resultEl.className = \"result err\";\n resultEl.textContent = error.message;\n }).finally(function () {\n runEl.disabled = false;\n });\n }\n\n /* Keyboard: up/down moves through the visible tools, cmd-K focuses\n search, cmd-S saves the description being edited. */\n document.addEventListener(\"keydown\", function (event) {\n if ((event.metaKey || event.ctrlKey) && event.key === \"k\") {\n event.preventDefault();\n searchEl.focus();\n return;\n }\n if ((event.metaKey || event.ctrlKey) && event.key === \"s\") {\n event.preventDefault();\n saveDescription();\n return;\n }\n if (event.target === searchEl || event.target.tagName === \"TEXTAREA\" || event.target.tagName === \"INPUT\") {\n return;\n }\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n var tools = visibleTools();\n var index = tools.findIndex(function (tool) { return tool.name === selected; });\n var next = event.key === \"ArrowDown\" ? index + 1 : index - 1;\n if (next < 0 || next >= tools.length) return;\n event.preventDefault();\n selected = tools[next].name;\n renderList();\n renderDetail();\n var button = listEl.querySelector('[aria-selected=\"true\"]');\n if (button) button.scrollIntoView({ block: \"nearest\" });\n });\n\n searchEl.addEventListener(\"input\", function (event) {\n filter = event.target.value;\n renderList();\n });\n\n load();\n})();\n</script>\n</body>\n</html>`;\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAS,aAAa;AACtB,SAAS,oBAAiC;;;ACHnC,SAAS,gBAAwB;AACtC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":[]}
|