webmcp-codegen 0.3.0 → 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/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/wire.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * The webmcp-codegen CLI.\n *\n * The path we optimize for is the zero-everything first run:\n *\n * npx webmcp-codegen generate\n *\n * No install, no config file, no flags. The CLI finds your API spec, finds\n * the package that is your web app, writes working tools into it, wires the\n * registration into your app's entry file, and tells you how to see it all\n * working. Choices we had to ask for are remembered in .webmcp-codegen.json\n * so we never ask twice.\n *\n * When you outgrow the defaults:\n *\n * --spec/--out quick overrides without a config file\n * init writes codegen.config.mjs for full control (needs the\n * package installed, since the config imports from it)\n *\n * Plus the flags you'd expect on a codegen tool: --dry-run to preview,\n * --watch to re-run on change, --skip-audit to bypass the safety report,\n * --force to write through audit errors, --config to point at a config\n * file somewhere else.\n */\n\nimport { existsSync, watch } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport { basename, join, relative } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport { parseArgs } from \"node:util\";\nimport { CONFIG_FILE_NAMES, loadConfig } from \"./config.js\";\nimport { loadDataFile, saveDataFile } from \"./data-file.js\";\nimport { findSpecs } from \"./detect.js\";\nimport { js } from \"./generators/js.js\";\nimport { type GenerateResult, runGenerate } from \"./pipeline.js\";\nimport { type GenerateFlags, resolveSetup, type Setup } from \"./setup.js\";\nimport { openapi } from \"./sources/openapi.js\";\nimport type { CodegenConfig, ReviewedTool } from \"./types.js\";\nimport { applyWiring, planWiring, type WirePlan } from \"./wire.js\";\n\nconst HELP = `webmcp-codegen: generate WebMCP tools from the API contracts you already have\n\nFastest start (no install, no config):\n npx webmcp-codegen generate --dry-run Detect your spec, preview the tools\n npx webmcp-codegen generate Write the tool files, wire them up\n\nCommands:\n init Write a codegen.config.mjs for full control\n generate Generate (or update) your WebMCP tools\n generate --watch Re-generate when files change\n dev Open the tools dashboard (list, edit, try tools)\n\nFlags for generate:\n --spec PATH Which OpenAPI spec to use (auto-detected when omitted)\n --out DIR Where the tool files go (default: your web app's src/webmcp)\n --dry-run Preview what would be written, write nothing\n --skip-audit Skip the safety report\n --force Write files even when the audit reports errors\n --config PATH Use a config file at PATH\n\nFlags for dev:\n --port N Dashboard port (default: 4700)\n`;\n\nconst CONFIG_FILE = \"codegen.config.mjs\";\n\n/** How many tools to list before folding the rest into a count. */\nconst MAX_LISTED_TOOLS = 15;\n\nasync function main(): Promise<number> {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n \"dry-run\": { type: \"boolean\", default: false },\n \"skip-audit\": { type: \"boolean\", default: false },\n force: { type: \"boolean\", default: false },\n watch: { type: \"boolean\", default: false },\n config: { type: \"string\" },\n spec: { type: \"string\" },\n out: { type: \"string\" },\n port: { type: \"string\" },\n help: { type: \"boolean\", default: false },\n },\n });\n\n const command = positionals[0];\n if (values.help || !command) {\n console.log(HELP);\n return 0;\n }\n\n switch (command) {\n case \"init\":\n return init();\n case \"dev\":\n return dev(Number.parseInt(values.port ?? \"4700\", 10));\n case \"generate\":\n return generate({\n dryRun: values[\"dry-run\"],\n skipAudit: values[\"skip-audit\"],\n force: values.force,\n watch: values.watch,\n configPath: values.config,\n spec: values.spec,\n out: values.out,\n });\n default:\n console.error(`Unknown command \"${command}\".\\n`);\n console.log(HELP);\n return 1;\n }\n}\n\n/** Detect the project's API spec and write a starter config. */\nasync function init(): Promise<number> {\n const cwd = process.cwd();\n const configPath = join(cwd, CONFIG_FILE);\n\n if (existsSync(configPath)) {\n console.error(`${CONFIG_FILE} already exists. Nothing to do.`);\n return 1;\n }\n\n const specs = await findSpecs(cwd);\n const specPath = specs.length > 0 ? `./${specs[0]}` : \"./openapi.yaml\";\n\n await writeFile(\n configPath,\n `import { defineConfig } from \"webmcp-codegen\";\nimport { openapi } from \"webmcp-codegen/sources\";\nimport { js } from \"webmcp-codegen/generators\";\n\nexport default defineConfig({\n sources: [openapi({ spec: \"${specPath}\" })],\n generate: [js({ outDir: \"./src/webmcp\" })],\n safety: {\n // Extra field names to treat as PII, on top of the built-in list:\n // piiFields: [\"internalId\"],\n // Tools to skip entirely (matched against name and route):\n // exclude: [\"internal\"],\n },\n});\n`,\n );\n\n // The config imports from the package, so keeping it means installing it.\n console.log(\"Installed the package? A config file needs it:\");\n console.log(\" npm install -D webmcp-codegen\\n\");\n if (specs.length > 0) {\n console.log(`Found ${specs[0]}. Wrote ${CONFIG_FILE}.`);\n console.log(\"\\nNext: npx webmcp-codegen generate --dry-run\");\n } else {\n console.log(`No OpenAPI spec found, so ${CONFIG_FILE} points at ./openapi.yaml.`);\n console.log(\"Edit the `spec` path to point at your spec, then run:\");\n console.log(\"\\n npx webmcp-codegen generate --dry-run\");\n }\n return 0;\n}\n\nasync function generate(flags: GenerateFlags): Promise<number> {\n const cwd = process.cwd();\n\n if (flags.watch) {\n // Watch mode never exits; it re-runs generate on every relevant change.\n // Wiring is idempotent, so it is part of every pass and quietly no-ops\n // after the first.\n await watchLoop(cwd, flags);\n return 0;\n }\n\n const result = await runOnce(cwd, flags);\n return result.blocked ? 1 : 0;\n}\n\n/** One generate pass: resolve setup, run the pipeline, wire, report. */\nasync function runOnce(cwd: string, flags: GenerateFlags): Promise<GenerateResult> {\n const setup = await resolveSetup(cwd, flags);\n const data = await loadDataFile(cwd);\n const result = await runGenerate(setup.config, {\n cwd,\n dryRun: flags.dryRun,\n skipAudit: flags.skipAudit,\n force: flags.force,\n overrides: data.overrides,\n });\n\n // Registration wiring: additive, idempotent, and only for real runs.\n let wiring: WirePlan | null = null;\n if (!result.blocked && setup.app) {\n const outDir = findOutDir(setup.config);\n if (outDir) {\n wiring = await planWiring(cwd, setup.app, outDir);\n if (wiring && wiring.edits.length > 0 && !flags.dryRun && result.wrote) {\n await applyWiring(wiring);\n }\n }\n }\n\n // Remember the choices detection made, so the next run never re-asks.\n if (!setup.fromConfigFile && !flags.dryRun && result.wrote) {\n await saveDataFile(cwd, setup.remember);\n }\n\n printReport(result, flags, setup, wiring, cwd);\n return result;\n}\n\n/** Pull the outDir back out of the resolved config (there is one generator). */\nfunction findOutDir(config: CodegenConfig): string | undefined {\n return config.generate[0]?.outDir;\n}\n\n/**\n * The report is the product's voice: plain language, no jargon, one line per\n * file, findings grouped by severity, and a summary that says what happens\n * next — including the one command's worth of \"try it\" at the end.\n */\nfunction printReport(\n result: GenerateResult,\n flags: GenerateFlags,\n setup: Setup,\n wiring: WirePlan | null,\n cwd: string,\n): void {\n const { tools, skipped, findings, files, notes, blocked } = result;\n\n console.log(`\\nwebmcp-codegen (${setup.label}): ${tools.length} tool(s)`);\n\n for (const note of notes) {\n console.log(`\\n note: ${note}`);\n }\n\n if (skipped.length > 0) {\n console.log(`\\n ${skipped.length} endpoint(s) skipped:`);\n for (const entry of skipped) {\n console.log(` ${entry.ref}: ${entry.reason}`);\n }\n }\n\n console.log(\"\");\n const listed = tools.slice(0, MAX_LISTED_TOOLS);\n for (const tool of listed) {\n const state = tool.enabledByDefault ? \"\" : \" starts disabled\";\n console.log(` ${tool.name} [${tool.sideEffect}]${state} ← ${tool.source.ref}`);\n }\n if (tools.length > listed.length) {\n console.log(` …and ${tools.length - listed.length} more`);\n }\n\n if (findings.length > 0) {\n console.log(\"\");\n for (const finding of findings) {\n const icon = finding.level === \"error\" ? \"✖\" : \"⚠\";\n const where = finding.tool ? ` (${finding.tool})` : \"\";\n console.log(` ${icon} ${finding.message}${where}`);\n }\n }\n\n if (files.length > 0) {\n console.log(\"\");\n for (const file of files) {\n if (file.action === \"unchanged\" && !file.conflict) continue;\n const shown = file.conflict\n ? `conflict → wrote ${relative(cwd, file.conflict)}`\n : file.action;\n console.log(` ${shown}: ${relative(cwd, file.path)}`);\n }\n }\n\n if (wiring) {\n if (wiring.alreadyWired) {\n console.log(\"\\n registration: already wired into your app\");\n } else if (wiring.edits.length > 0) {\n console.log(flags.dryRun ? \"\\n registration (would do):\" : \"\\n registration:\");\n for (const edit of wiring.edits) {\n console.log(` ${edit.summary}`);\n }\n if (!flags.dryRun) {\n console.log(\" undo: delete the added lines (nothing else was touched)\");\n }\n }\n } else if (!blocked && setup.app) {\n console.log(\"\\n registration: could not find your app's entry file, so add this by hand:\");\n console.log(' import { registerAllTools } from \"<path-to>/src/webmcp\";');\n console.log(\" void registerAllTools();\");\n }\n\n if (blocked) {\n console.log(\n \"\\nGeneration blocked by audit errors. Fix them, or re-run with --force to write anyway.\",\n );\n return;\n }\n if (flags.dryRun) {\n console.log(\"\\nDry run: nothing written. Re-run without --dry-run to write these files.\");\n return;\n }\n\n printNextSteps(result);\n}\n\n/**\n * The parting message. The read tools already work; the mutations are one\n * uncomment away; and we end with the single most convincing thing a new\n * user can do: watch an agent call their app.\n */\nfunction printNextSteps(result: GenerateResult): void {\n const enabled = result.tools.filter((tool) => tool.enabledByDefault);\n const disabled = result.tools.filter((tool) => !tool.enabledByDefault);\n\n const parts: string[] = [];\n if (enabled.length > 0) parts.push(`${enabled.length} read tool(s) work out of the box`);\n if (disabled.length > 0) {\n parts.push(`${disabled.length} tool(s) start disabled (open the file and uncomment to enable)`);\n }\n console.log(`\\nDone. ${parts.join(\"; \")}.`);\n\n const suggestion = pickSuggestedTool(result.tools);\n console.log(\"\\nTry it:\");\n console.log(\" 1. Start your app and open it in Chrome.\");\n console.log(\" 2. Turn on chrome://flags/#enable-webmcp-testing and reload the page.\");\n if (suggestion) {\n console.log(` 3. Ask the agent: \"${suggestion}\"`);\n } else {\n console.log(\" 3. Ask the agent to use one of your tools.\");\n }\n}\n\n/**\n * The example request in the \"try it\" line. Pick an enabled read tool —\n * preferably one whose name sounds like listing or looking something up —\n * and phrase it the way a user would say it, from the spec's own description.\n */\nfunction pickSuggestedTool(tools: ReviewedTool[]): string | undefined {\n const reads = tools.filter((tool) => tool.enabledByDefault && tool.endpointRole === \"endpoint\");\n if (reads.length === 0) return undefined;\n const tool =\n reads.find((candidate) => /^(list|get|search|find|fetch|recent)-/.test(candidate.name)) ??\n reads[0];\n if (!tool) return undefined;\n\n const description = tool.description.trim().replace(/\\.$/, \"\");\n // A template description (\"GET /v1/trips\") would read as jargon; fall back\n // to the tool name in words (\"list trips\").\n const looksTemplated = /^(GET|POST|PUT|PATCH|DELETE)\\s/.test(description);\n const phrase = looksTemplated\n ? tool.name.replace(/-/g, \" \")\n : description.charAt(0).toLowerCase() + description.slice(1);\n return phrase;\n}\n\n/**\n * The tools dashboard: a local control panel for what was generated.\n * Runs until Ctrl+C; nothing is written to the app, ever.\n */\nasync function dev(port: number): Promise<number> {\n const { startDevServer } = await import(\"./dev/server.js\");\n const server = await startDevServer({ cwd: process.cwd(), port });\n console.log(`\\nwebmcp-codegen dashboard: http://localhost:${port}`);\n console.log(\"List, describe, enable, and try your tools. Ctrl+C to stop.\\n\");\n\n await new Promise<void>((resolveExit) => {\n process.on(\"SIGINT\", () => {\n server.close();\n resolveExit();\n });\n });\n return 0;\n}\n\n/**\n * Re-run generate when anything relevant changes. Node's recursive watcher\n * covers Linux/macOS/Windows on Node 20+, which is our engine floor anyway.\n */\nasync function watchLoop(cwd: string, flags: GenerateFlags): Promise<void> {\n await runOnce(cwd, { ...flags, dryRun: false });\n console.log(\"\\nWatching for changes… (Ctrl+C to stop)\");\n\n let timer: NodeJS.Timeout | undefined;\n watch(cwd, { recursive: true }, (_event, filename) => {\n if (!filename) return;\n // Only source-ish changes are worth regenerating for. Never react to our\n // own outputs (generated files, the data file) or watch mode loops.\n if (/node_modules|\\.git|\\/dist|\\/src\\/webmcp|\\.webmcp-codegen\\.json/.test(filename)) return;\n if (!/\\.(ya?ml|json|ts|tsx|mts|mjs)$/.test(filename)) return;\n clearTimeout(timer);\n timer = setTimeout(() => {\n runOnce(cwd, { ...flags, dryRun: false }).catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : error);\n });\n }, 300);\n });\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n });\n","/**\n * Registration wiring: making the app actually register its tools.\n *\n * Generated files do nothing until something calls registerAllTools() once at\n * startup. Rather than telling the developer to go do that, we do it for\n * them — under strict rules, because this is the one place we edit *their*\n * files instead of ours:\n *\n * 1. Edits are additive only. We insert lines; we never change or remove\n * existing ones.\n * 2. Idempotent. If the wiring is already there, we do nothing.\n * 3. Honest. Every edit is reported with exact paths and how to undo it.\n * 4. When we cannot find the entry point with confidence, we do not guess:\n * we print the two lines and where they go, and leave it to the human.\n */\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport type { WebApp } from \"./detect-app.js\";\n\nexport interface WireEdit {\n /** Absolute path of the file to create or modify. */\n path: string;\n action: \"create\" | \"modify\";\n /** The full new contents (for modify: the existing contents plus our lines). */\n contents: string;\n /** One human sentence per edit, for the report. */\n summary: string;\n}\n\nexport interface WirePlan {\n edits: WireEdit[];\n /** Set when we already found the wiring in place. */\n alreadyWired?: boolean;\n}\n\n/**\n * Compute the wiring edits for the detected app, or null when we cannot\n * locate the entry point with confidence (the CLI then prints manual\n * instructions instead).\n */\nexport async function planWiring(\n cwd: string,\n app: WebApp,\n outDir: string,\n): Promise<WirePlan | null> {\n switch (app.framework) {\n case \"next\":\n return planNextWiring(cwd, app, outDir);\n case \"vite-react\":\n return planViteWiring(cwd, app, outDir);\n default:\n // Nuxt/SvelteKit/unknown: we know where the tools go but not where the\n // app boots. Print instructions rather than guess-edit an entry file.\n return null;\n }\n}\n\n/** Apply a plan. Kept trivial on purpose: the plan already did the thinking. */\nexport async function applyWiring(plan: WirePlan): Promise<void> {\n for (const edit of plan.edits) {\n await writeFile(edit.path, edit.contents, \"utf8\");\n }\n}\n\n/* ── Next.js (app router) ──────────────────────────────────────────────── */\n\n/**\n * Next needs the registration to run on the client, so we generate a tiny\n * \"use client\" component next to the tools and mount it in the root layout:\n *\n * import { WebMCPRegister } from \"../webmcp/register\"; ← added\n * ...\n * <body>\n * <WebMCPRegister /> ← added\n * {children}\n */\nasync function planNextWiring(cwd: string, app: WebApp, outDir: string): Promise<WirePlan | null> {\n const layoutCandidates = [\n join(cwd, app.dir, \"src/app/layout.tsx\"),\n join(cwd, app.dir, \"src/app/layout.jsx\"),\n join(cwd, app.dir, \"app/layout.tsx\"),\n join(cwd, app.dir, \"app/layout.jsx\"),\n ];\n const layoutPath = await firstExisting(layoutCandidates);\n if (!layoutPath) return null;\n\n const registerPath = join(cwd, outDir, \"register.tsx\");\n const layout = await readFile(layoutPath, \"utf8\");\n if (layout.includes(\"WebMCPRegister\")) return { edits: [], alreadyWired: true };\n\n // Import path from the layout's directory to the register component.\n const importPath = withoutExtension(relative(dirname(layoutPath), registerPath));\n\n const edits: WireEdit[] = [\n {\n path: registerPath,\n action: \"create\",\n contents: nextRegisterComponent(),\n summary: `created ${relative(cwd, registerPath)} (a client component that registers your tools on page load)`,\n },\n ];\n\n const withImport = insertAfterLastImport(\n layout,\n `import { WebMCPRegister } from \"${importPath}\";`,\n );\n if (!withImport) return null;\n // Mount right after <body ...>, each element on its own line.\n const withComponent = withImport.replace(\n /<body([^>]*)>\\s*/,\n \"<body$1>\\n <WebMCPRegister />\\n \",\n );\n if (withComponent === withImport) return null; // No <body> tag found; do not guess.\n\n edits.push({\n path: layoutPath,\n action: \"modify\",\n contents: withComponent,\n summary: `added 2 lines to ${relative(cwd, layoutPath)} (an import and <WebMCPRegister /> inside <body>)`,\n });\n return { edits };\n}\n\nfunction nextRegisterComponent(): string {\n return `\"use client\";\n\nimport { useEffect } from \"react\";\nimport { registerAllTools } from \"./index\";\n\n/**\n * Registers the generated WebMCP tools once, on page load.\n * Generated by webmcp-codegen. Safe to move; keep it mounted near the root.\n */\nexport function WebMCPRegister() {\n useEffect(() => {\n void registerAllTools();\n }, []);\n return null;\n}\n`;\n}\n\n/* ── Vite + React (SPAs) ───────────────────────────────────────────────── */\n\n/**\n * A Vite app boots in main.tsx, so wiring is two added lines there:\n *\n * import { registerAllTools } from \"./webmcp\"; ← added\n * void registerAllTools(); ← added\n */\nasync function planViteWiring(cwd: string, app: WebApp, outDir: string): Promise<WirePlan | null> {\n const entryCandidates = [\n join(cwd, app.dir, \"src/main.tsx\"),\n join(cwd, app.dir, \"src/main.jsx\"),\n join(cwd, app.dir, \"src/index.tsx\"),\n join(cwd, app.dir, \"src/index.jsx\"),\n ];\n const entryPath = await firstExisting(entryCandidates);\n if (!entryPath) return null;\n\n const entry = await readFile(entryPath, \"utf8\");\n if (entry.includes(\"registerAllTools\")) return { edits: [], alreadyWired: true };\n\n const importPath = withoutExtension(relative(dirname(entryPath), join(cwd, outDir, \"index\")));\n const withWiring = insertAfterLastImport(\n entry,\n `import { registerAllTools } from \"${importPath}\";\\n\\nvoid registerAllTools();`,\n );\n if (!withWiring) return null;\n\n return {\n edits: [\n {\n path: entryPath,\n action: \"modify\",\n contents: withWiring,\n summary: `added 2 lines to ${relative(cwd, entryPath)} (an import and a registerAllTools() call)`,\n },\n ],\n };\n}\n\n/* ── Shared helpers ────────────────────────────────────────────────────── */\n\n/** Insert a line after the file's last top-level import statement. */\nfunction insertAfterLastImport(source: string, line: string): string | null {\n const lines = source.split(\"\\n\");\n let lastImport = -1;\n for (let index = 0; index < lines.length; index += 1) {\n if (/^import\\s/.test(lines[index] as string)) lastImport = index;\n }\n if (lastImport === -1) return null;\n lines.splice(lastImport + 1, 0, line);\n return lines.join(\"\\n\");\n}\n\n/**\n * Turn a filesystem path into a JS import specifier: no extension, and an\n * explicit \"./\" when the target is in the same directory or deeper —\n * `relative()` alone yields \"webmcp/index\", which JS would read as a\n * package name, not a file.\n */\nfunction withoutExtension(path: string): string {\n const bare = path.replace(/\\.(tsx?|jsx?)$/, \"\").replace(/\\/index$/, \"\");\n return bare.startsWith(\".\") ? bare : `./${bare}`;\n}\n\nasync function firstExisting(paths: string[]): Promise<string | undefined> {\n for (const path of paths) {\n try {\n await readFile(path, \"utf8\");\n return path;\n } catch {\n // Try the next candidate.\n }\n }\n return undefined;\n}\n"],"mappings":"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","names":["writeFile","join","relative","join","writeFile","relative","tool"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/cli-output.ts","../src/data-file.ts","../src/detect.ts","../src/dev/server.ts","../src/setup.ts","../src/detect-app.ts","../src/dev/ui.ts","../src/wire.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * webmcp-codegen's command line.\n *\n * Design goals, in order:\n * 1. The default output is the summary you need, not a log dump.\n * 2. Every line earns its place; if it doesn't help you decide, it's gone.\n * 3. The next step is always visible, never assumed.\n * 4. Beautiful enough that developers screenshot it.\n *\n * Commands:\n * webmcp-codegen the interactive dashboard (same as `dev`)\n * webmcp-codegen generate write tool files from your spec\n * webmcp-codegen init write a codegen.config.mjs for full control\n * webmcp-codegen --help detailed help with examples\n *\n * Zero dependencies: argument parsing is Node's util.parseArgs, output is\n * ANSI escapes we control character by character.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { dim, renderSummary, renderVerbose } from \"./cli-output.js\";\nimport { CONFIG_FILE_NAMES } from \"./config.js\";\nimport { saveDataFile } from \"./data-file.js\";\nimport { findSpecs } from \"./detect.js\";\nimport { startDevServer } from \"./dev/server.js\";\nimport { runGenerate } from \"./pipeline.js\";\nimport { resolveSetup } from \"./setup.js\";\nimport { applyWiring, planWiring, type WirePlan } from \"./wire.js\";\n\nconst HELP = `\nwebmcp-codegen — generate WebMCP tools from your OpenAPI spec\n\nUsage\n npx webmcp-codegen [command] [flags]\n\nCommands\n generate Generate tool files from your spec (default when no command given)\n dev Open the tools dashboard (list, describe, toggle, test)\n init Write a codegen.config.mjs for full control\n\nFlags\n --spec PATH Which OpenAPI spec to use (auto-detected when omitted)\n --out DIR Where tool files go (default: your web app's src/webmcp)\n --dry-run Preview what would be written, write nothing\n --verbose Show every tool, not just the summary\n --force Write files even when the audit reports errors\n --skip-audit Skip the safety report\n --config PATH Use a config file at PATH\n --port N Dashboard port (default: 4700)\n --help Show this help\n\nExamples\n npx webmcp-codegen generate # detect spec, generate tools\n npx webmcp-codegen generate --dry-run # preview without writing\n npx webmcp-codegen dev # open the dashboard\n npx webmcp-codegen generate --verbose # see all 72 tools listed\n\nDocs\n https://webmcp-codegen.vercel.app/docs\n`;\n\nexport interface CliFlags {\n dryRun: boolean;\n skipAudit: boolean;\n force: boolean;\n verbose: boolean;\n watch: boolean;\n config?: string;\n spec?: string;\n out?: string;\n port?: number;\n help: boolean;\n}\n\nasync function main(): Promise<number> {\n const { values, positionals } = parseArgs({\n args: process.argv.slice(2),\n allowPositionals: true,\n options: {\n \"dry-run\": { type: \"boolean\", default: false },\n \"skip-audit\": { type: \"boolean\", default: false },\n force: { type: \"boolean\", default: false },\n verbose: { type: \"boolean\", default: false },\n watch: { type: \"boolean\", default: false },\n config: { type: \"string\" },\n spec: { type: \"string\" },\n out: { type: \"string\" },\n port: { type: \"string\" },\n help: { type: \"boolean\", default: false },\n },\n });\n\n const flags: CliFlags = {\n dryRun: values[\"dry-run\"] ?? false,\n skipAudit: values[\"skip-audit\"] ?? false,\n force: values.force ?? false,\n verbose: values.verbose ?? false,\n watch: values.watch ?? false,\n config: values.config,\n spec: values.spec,\n out: values.out,\n port: values.port ? Number.parseInt(values.port, 10) : undefined,\n help: values.help,\n };\n\n if (flags.help) {\n console.log(HELP);\n return 0;\n }\n\n const command = positionals[0] ?? \"generate\";\n\n switch (command) {\n case \"init\":\n return init();\n case \"dev\":\n return dev(flags.port ?? 4700);\n case \"generate\":\n return generate(flags);\n default:\n console.error(`Unknown command: ${command}\\n`);\n console.log(HELP);\n return 1;\n }\n}\n\nasync function init(): Promise<number> {\n const cwd = process.cwd();\n const configFile = CONFIG_FILE_NAMES[0] ?? \"codegen.config.mjs\";\n const configPath = join(cwd, configFile);\n\n if (existsSync(configPath)) {\n console.error(`\\n✖ ${configFile} already exists. Nothing to do.\\n`);\n return 1;\n }\n\n const specs = await findSpecs(cwd);\n const specPath = specs.length > 0 ? `./${specs[0]}` : \"./openapi.yaml\";\n\n await writeFile(\n configPath,\n `import { defineConfig } from \"webmcp-codegen\";\nimport { openapi } from \"webmcp-codegen/sources\";\nimport { js } from \"webmcp-codegen/generators\";\n\nexport default defineConfig({\n sources: [openapi({ spec: \"${specPath}\" })],\n generate: [js({ outDir: \"./src/webmcp\" })],\n safety: {\n // Extra field names to treat as PII, on top of the built-in list:\n // piiFields: [\"internalId\"],\n // Tools to skip entirely (matched against name and route):\n // exclude: [\"internal\"],\n },\n});\n`,\n );\n\n console.log(`\\n✔ Wrote ${configFile}\\n`);\n console.log(\"Edit it to add sources, change the output directory, or set safety options.\");\n console.log(\"Docs: https://webmcp-codegen.vercel.app/docs/configuration\\n\");\n return 0;\n}\n\nasync function dev(port: number): Promise<number> {\n const cwd = process.cwd();\n const server = await startDevServer({ cwd, port, open: true });\n console.log(`\\n✔ Dashboard: http://localhost:${port}\\n`);\n console.log(\"List, describe, enable, and test your tools. Ctrl+C to stop.\\n\");\n\n await new Promise<void>((resolveExit) => {\n process.on(\"SIGINT\", () => {\n server.close();\n resolveExit();\n });\n });\n return 0;\n}\n\nasync function generate(flags: CliFlags): Promise<number> {\n const cwd = process.cwd();\n const setup = await resolveSetup(cwd, {\n dryRun: flags.dryRun,\n skipAudit: flags.skipAudit,\n force: flags.force,\n watch: flags.watch,\n spec: flags.spec,\n out: flags.out,\n configPath: flags.config,\n });\n\n const result = await runGenerate(setup.config, {\n cwd,\n dryRun: flags.dryRun,\n force: flags.force,\n skipAudit: flags.skipAudit,\n });\n\n // Registration wiring: additive, idempotent, and only for real runs.\n let wiring: WirePlan | null = null;\n if (!result.blocked && setup.app) {\n const outDir = setup.config.generate[0]?.outDir;\n if (outDir) {\n wiring = await planWiring(cwd, setup.app, outDir);\n if (wiring && wiring.edits.length > 0 && !flags.dryRun && result.wrote) {\n await applyWiring(wiring);\n }\n }\n }\n\n // Remember the choices detection made, so the next run never re-asks.\n if (!setup.fromConfigFile && !flags.dryRun && result.wrote) {\n await saveDataFile(cwd, setup.remember);\n }\n\n if (flags.verbose) {\n renderVerbose(result, setup, cwd);\n } else {\n renderSummary(result, setup, cwd, wiring);\n }\n\n if (flags.watch && !flags.dryRun) {\n // TODO: implement watch mode\n console.log(dim(\"\\n --watch is not implemented yet. Run generate again after changes.\\n\"));\n }\n\n return result.blocked ? 1 : 0;\n}\n\nmain().then(\n (code) => process.exit(code),\n (error) => {\n console.error(\"\\n✖ Unexpected error:\", error instanceof Error ? error.message : error);\n console.error(\"\\nPlease report this: https://github.com/SouravInsights/groundstate/issues\\n\");\n process.exit(1);\n },\n);\n","/**\n * CLI output rendering. The design goal: a summary you can scan in three\n * seconds, with the next step always visible. Verbose mode is for when you\n * want the full list.\n *\n * Uses ANSI escapes for color and layout. No dependencies — we control every\n * character so the output looks the same in every terminal.\n */\n\nimport type { GenerateResult } from \"./pipeline.js\";\nimport type { Setup } from \"./setup.js\";\nimport type { WirePlan } from \"./wire.js\";\n\n// ANSI escapes\nconst ESC = \"\\x1b[\";\nconst RESET = `${ESC}0m`;\nconst BOLD = `${ESC}1m`;\nconst DIM = `${ESC}2m`;\nconst ITALIC = `${ESC}3m`;\n\nconst FG = {\n red: `${ESC}31m`,\n green: `${ESC}32m`,\n yellow: `${ESC}33m`,\n blue: `${ESC}34m`,\n magenta: `${ESC}35m`,\n cyan: `${ESC}36m`,\n white: `${ESC}37m`,\n gray: `${ESC}90m`,\n};\n\nfunction c(color: keyof typeof FG, text: string): string {\n return `${FG[color]}${text}${RESET}`;\n}\n\nfunction bold(text: string): string {\n return `${BOLD}${text}${RESET}`;\n}\n\nexport function dim(text: string): string {\n return `${DIM}${text}${RESET}`;\n}\n\nfunction italic(text: string): string {\n return `${ITALIC}${text}${RESET}`;\n}\n\n/** A colored header block. */\nfunction header(title: string, subtitle: string): string {\n const line = \"─\".repeat(Math.max(title.length, subtitle.length) + 4);\n return `${c(\"cyan\", line)}\\n ${bold(title)}\\n ${subtitle}\\n${c(\"cyan\", line)}`;\n}\n\n/** A risk badge with color. */\nfunction badge(risk: string): string {\n switch (risk) {\n case \"read\":\n return c(\"green\", \"[read]\");\n case \"write\":\n return c(\"yellow\", \"[write]\");\n case \"destructive\":\n return c(\"red\", \"[destructive]\");\n default:\n return `[${risk}]`;\n }\n}\n\n/** Group findings by kind for a scannable summary. */\nfunction groupFindings(findings: GenerateResult[\"findings\"]): Record<string, number> {\n const groups: Record<string, number> = {};\n for (const f of findings) {\n const kind =\n f.message.includes(\"sign-in\") || f.message.includes(\"auth\")\n ? \"auth\"\n : f.message.includes(\"Admin\")\n ? \"admin\"\n : f.message.includes(\"PII\") || f.message.includes(\"email\")\n ? \"PII\"\n : f.message.includes(\"POST treated as a read\")\n ? \"POST as read\"\n : \"other\";\n groups[kind] = (groups[kind] ?? 0) + 1;\n }\n return groups;\n}\n\n/** The default summary output — three seconds to scan. */\nexport function renderSummary(\n result: GenerateResult,\n setup: Setup,\n _cwd: string,\n wiring?: WirePlan | null,\n): void {\n const { tools, findings } = result;\n const reads = tools.filter((t) => t.sideEffect === \"read\").length;\n const writes = tools.filter((t) => t.sideEffect === \"write\").length;\n const destructives = tools.filter((t) => t.sideEffect === \"destructive\").length;\n const skipped = result.skipped.length;\n const warnings = findings.filter((f) => f.level === \"warning\").length;\n const errors = findings.filter((f) => f.level === \"error\").length;\n\n // Header\n console.log(\"\");\n console.log(header(\"webmcp-codegen\", `${tools.length} tools from ${setup.label}`));\n console.log(\"\");\n\n // Summary line\n const summaryParts = [\n c(\"green\", `${reads} read`),\n c(\"yellow\", `${writes} write`),\n destructives > 0 ? c(\"red\", `${destructives} destructive`) : null,\n skipped > 0 ? dim(`${skipped} skipped`) : null,\n ].filter(Boolean);\n console.log(` ${summaryParts.join(\" \")}\\n`);\n\n // Warnings summary\n if (warnings > 0 || errors > 0) {\n const groups = groupFindings(findings);\n const parts = Object.entries(groups).map(([kind, count]) => {\n const color = kind === \"auth\" || kind === \"admin\" ? \"yellow\" : \"gray\";\n return `${c(color as keyof typeof FG, kind)} ${count}`;\n });\n console.log(` ${c(\"yellow\", \"⚠\")} ${warnings + errors} finding(s): ${parts.join(\", \")}`);\n console.log(dim(` Run with --verbose to see details\\n`));\n }\n\n // Files\n const outDir = setup.config.generate[0]?.outDir ?? \"src/webmcp\";\n console.log(` ${c(\"cyan\", \"→\")} ${bold(outDir)}\\n`);\n\n // Registration\n if (wiring && !wiring.alreadyWired) {\n console.log(` ${c(\"green\", \"✔\")} Registration wired into your app\\n`);\n }\n\n // Next step\n console.log(` ${bold(\"Next:\")} ${c(\"cyan\", \"npx webmcp-codegen dev\")} to review and test\\n`);\n}\n\n/** Verbose output — every tool, grouped by risk. */\nexport function renderVerbose(result: GenerateResult, setup: Setup, _cwd: string): void {\n const { tools, findings, skipped } = result;\n\n console.log(\"\");\n console.log(bold(`webmcp-codegen: ${tools.length} tools from ${setup.label}`));\n console.log(\"\");\n\n // Skipped first (why things were excluded)\n if (skipped.length > 0) {\n console.log(c(\"gray\", \"Skipped:\"));\n for (const s of skipped) {\n console.log(` ${dim(s.ref)}`);\n console.log(` ${italic(s.reason)}`);\n }\n console.log(\"\");\n }\n\n // Tools grouped by risk\n const byRisk = {\n read: tools.filter((t) => t.sideEffect === \"read\"),\n write: tools.filter((t) => t.sideEffect === \"write\"),\n destructive: tools.filter((t) => t.sideEffect === \"destructive\"),\n };\n\n for (const [risk, group] of Object.entries(byRisk)) {\n if (group.length === 0) continue;\n console.log(badge(risk));\n for (const tool of group) {\n const disabled = tool.enabledByDefault ? \"\" : dim(\" (starts disabled)\");\n console.log(` ${tool.name}${disabled}`);\n if (tool.description) console.log(` ${dim(tool.description)}`);\n }\n console.log(\"\");\n }\n\n // Findings\n if (findings.length > 0) {\n console.log(bold(\"Findings:\"));\n for (const f of findings) {\n const icon = f.level === \"error\" ? c(\"red\", \"✖\") : c(\"yellow\", \"⚠\");\n const where = f.tool ? dim(` (${f.tool})`) : \"\";\n console.log(` ${icon} ${f.message}${where}`);\n }\n console.log(\"\");\n }\n\n console.log(` ${c(\"cyan\", \"→\")} Files in ${setup.config.generate[0]?.outDir ?? \"src/webmcp\"}\\n`);\n}\n","/**\n * The remembered-choices file: `.webmcp-codegen.json` at the project root.\n *\n * It is plain data — never code — so it works in the pure-npx flow (no\n * install needed) and can be read and written safely by the CLI and the dev\n * dashboard alike. It holds two kinds of things:\n *\n * - choices we asked for once and should never ask again\n * (\"which of these packages is your web app?\")\n * - overrides per-tool edits made in the dashboard (description,\n * enabled). They are applied after the safety review, so\n * they survive regeneration.\n *\n * The config file (codegen.config.mjs) stays the source of truth for\n * *structure* (sources, generators, safety). This file is for *choices and\n * tweaks*. Editing it by hand is fine; it is meant to be committed.\n */\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { ToolOverrides } from \"./types.js\";\n\nexport const DATA_FILE_NAME = \".webmcp-codegen.json\";\n\nexport interface DataFile {\n /** The spec we used (or were told to use), relative to the project root. */\n spec?: string;\n /** The web app package directory, relative to the project root. */\n app?: string;\n /** Per-tool tweaks, keyed by tool name. */\n overrides?: ToolOverrides;\n}\n\nexport async function loadDataFile(cwd: string): Promise<DataFile> {\n try {\n const parsed = JSON.parse(await readFile(join(cwd, DATA_FILE_NAME), \"utf8\")) as DataFile;\n return parsed && typeof parsed === \"object\" ? parsed : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Merge and write. Only the keys given are touched; everything already in\n * the file (especially overrides) survives. Writes nothing when the merged\n * result equals what's already there, so watch mode never loops on us.\n */\nexport async function saveDataFile(cwd: string, patch: Partial<DataFile>): Promise<void> {\n const current = await loadDataFile(cwd);\n const next: DataFile = { ...current, ...patch };\n if (JSON.stringify(next) === JSON.stringify(current)) return;\n await writeFile(join(cwd, DATA_FILE_NAME), `${JSON.stringify(next, null, 2)}\\n`, \"utf8\");\n}\n","/**\n * Spec auto-detection: the reason `npx webmcp-codegen generate` works with\n * zero arguments, zero config, and zero install.\n *\n * The rule is deliberately boring: walk the project (skipping the obvious\n * noise), recognize the usual spec filenames, and return what we find\n * shallowest-first. When exactly one spec exists we just use it; the CLI\n * layer decides what to do about zero or several.\n */\n\nimport type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\n\n/** Filenames we recognize as API specs. */\nexport const SPEC_FILE_PATTERN = /^(openapi|swagger|api)\\.(ya?ml|json)$/i;\n\n/** Directories never worth descending into. */\nconst IGNORED_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \".turbo\",\n \".next\",\n \"dist\",\n \"build\",\n \"coverage\",\n]);\n\n/**\n * How deep we look. Enough for monorepo layouts like\n * apps/server/openapi/openapi.json (depth 3) without wandering forever.\n */\nconst MAX_DEPTH = 5;\n\n/**\n * Find API spec files under `cwd`, returned as paths relative to `cwd`,\n * shallowest first. A root-level spec is a likelier intent than one\n * buried six folders deep.\n */\nexport async function findSpecs(cwd: string): Promise<string[]> {\n const found: { path: string; depth: number }[] = [];\n\n async function walk(dir: string, depth: number): Promise<void> {\n if (depth > MAX_DEPTH) return;\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return; // Unreadable directory. Skip it, never die on detection.\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!IGNORED_DIRS.has(entry.name)) await walk(join(dir, entry.name), depth + 1);\n } else if (SPEC_FILE_PATTERN.test(entry.name)) {\n found.push({ path: join(dir, entry.name), depth });\n }\n }\n }\n\n await walk(cwd, 0);\n return found.sort((a, b) => a.depth - b.depth).map((entry) => relative(cwd, entry.path));\n}\n","/**\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 requiresAuth?: boolean;\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 requiresAuth: tool.requiresAuth,\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 * Setup resolution: where the tools come from and where they go.\n *\n * Shared by the CLI (`generate`) and the dev dashboard (`dev`), so both see\n * the same project the same way:\n *\n * 1. a config file (codegen.config.mjs or --config) for full control\n * 2. --spec/--out flags as quick overrides, no config needed\n * 3. remembered choices from .webmcp-codegen.json\n * 4. auto-detection: the spec by filename, the web app by its package.json\n *\n * Branches 2-4 build the config right here, which is what makes\n * `npx webmcp-codegen generate` work without installing the package: the\n * user's project never has to resolve a webmcp-codegen import.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport { CONFIG_FILE_NAMES, loadConfig } from \"./config.js\";\nimport { loadDataFile } from \"./data-file.js\";\nimport { findSpecs } from \"./detect.js\";\nimport { findWebApps, type WebApp } from \"./detect-app.js\";\nimport { js } from \"./generators/js.js\";\nimport { openapi } from \"./sources/openapi.js\";\nimport type { CodegenConfig } from \"./types.js\";\n\nexport interface GenerateFlags {\n dryRun: boolean;\n skipAudit: boolean;\n force: boolean;\n watch: boolean;\n configPath?: string;\n spec?: string;\n out?: string;\n}\n\nexport interface Setup {\n config: CodegenConfig;\n label: string;\n /** The web app we detected (when detection ran). Drives placement + wiring. */\n app?: WebApp;\n /** True when the config came from a config file rather than detection. */\n fromConfigFile: boolean;\n /** Choices to remember in .webmcp-codegen.json after a successful run. */\n remember: { spec?: string; app?: string };\n}\n\n/**\n * Where the tools come from and where they go, in priority order:\n *\n * 1. a config file (codegen.config.mjs or --config) for full control\n * 2. --spec/--out flags as quick overrides, no config needed\n * 3. remembered choices from .webmcp-codegen.json\n * 4. auto-detection: the spec by filename, the web app by its package.json\n *\n * Branches 2-4 build the config right here inside the CLI, which is what\n * makes `npx webmcp-codegen generate` work without installing the package:\n * the user's project never has to resolve a webmcp-codegen import.\n */\nexport async function resolveSetup(cwd: string, flags: GenerateFlags): Promise<Setup> {\n const hasConfigFile = flags.configPath\n ? existsSync(join(cwd, flags.configPath))\n : CONFIG_FILE_NAMES.some((name) => existsSync(join(cwd, name)));\n\n if (hasConfigFile) {\n const { config, path } = await loadConfig(cwd, flags.configPath);\n if (flags.spec || flags.out) {\n console.warn(`Note: --spec/--out are ignored; ${basename(path)} is in charge here.`);\n }\n // Wiring still works with a config file if we can find the app.\n const data = await loadDataFile(cwd);\n const apps = await findWebApps(cwd);\n const app = apps.find((candidate) => candidate.dir === data.app) ?? apps[0];\n return { config, label: basename(path), app, fromConfigFile: true, remember: {} };\n }\n if (flags.configPath) {\n throw new Error(`No config file at \"${flags.configPath}\".`);\n }\n\n const data = await loadDataFile(cwd);\n\n // The spec: flag wins, then the remembered choice, then detection.\n const spec = flags.spec ?? data.spec ?? (await detectSpec(cwd));\n\n // The web app: detection decides placement. Only asked once; the answer\n // is remembered in .webmcp-codegen.json.\n let app: WebApp | undefined;\n if (!flags.out) {\n const apps = await findWebApps(cwd);\n const remembered = apps.find((candidate) => candidate.dir === data.app);\n if (remembered) {\n app = remembered;\n } else if (apps.length === 1) {\n app = apps[0];\n console.log(`Found your web app: ${app?.dir} (${app?.framework})`);\n } else if (apps.length > 1) {\n app = await askWhichApp(apps);\n }\n }\n\n const outDir = flags.out ?? (app && app.dir !== \".\" ? `${app.dir}/src/webmcp` : \"./src/webmcp\");\n return {\n config: { sources: [openapi({ spec })], generate: [js({ outDir })] },\n label: flags.spec ? `--spec ${spec}` : `detected ${spec}`,\n app,\n fromConfigFile: false,\n remember: { spec, app: app?.dir },\n };\n}\n\n/**\n * The one question this CLI asks. Several packages look like the web app;\n * a human picks, and .webmcp-codegen.json remembers it. Non-interactive\n * shells (CI) get the best guess with a note, never a hang.\n */\nasync function askWhichApp(apps: WebApp[]): Promise<WebApp> {\n if (!process.stdin.isTTY) {\n const first = apps[0] as WebApp;\n console.log(`Several packages look like web apps; using ${first.dir}. Override with --out.`);\n return first;\n }\n console.log(\"Several packages look like the web app. Which one should the tools live in?\");\n apps.forEach((app, index) => {\n console.log(` ${index + 1}. ${app.dir} (${app.framework})${index === 0 ? \" [default]\" : \"\"}`);\n });\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer = await rl.question(\"Choice [1]: \");\n const picked = Number.parseInt(answer.trim() || \"1\", 10);\n return apps[picked - 1] ?? (apps[0] as WebApp);\n } finally {\n rl.close();\n }\n}\n\n/**\n * Find the project's API spec. One candidate: use it and say so. Several:\n * list them and make the human pick. None: say exactly what to do next.\n */\nasync function detectSpec(cwd: string): Promise<string> {\n const specs = await findSpecs(cwd);\n\n if (specs.length === 0) {\n throw new Error(\n \"No OpenAPI spec found in this project.\\n\" +\n \"Point at one: npx webmcp-codegen generate --spec path/to/openapi.json\",\n );\n }\n if (specs.length > 1) {\n const list = specs.map((spec) => ` - ${spec}`).join(\"\\n\");\n throw new Error(\n `Found ${specs.length} API specs:\\n${list}\\n\\n` +\n `Pick one: npx webmcp-codegen generate --spec ${specs[0]}`,\n );\n }\n\n console.log(`Detected ${specs[0]} (override with --spec)`);\n return specs[0] as string;\n}\n","/**\n * Web-app detection: where the generated tools should live.\n *\n * The tools are browser code, so they belong in whichever package *is* the\n * web app — not next to the spec, and not wherever the command happened to\n * run. In a monorepo like:\n *\n * apps/\n * ├── server/ (has the openapi.json)\n * └── web/ (has next in its package.json) ← tools go here\n *\n * detection means reading package.json files and looking for a browser\n * framework. One candidate: we use it and say so. Several: the CLI asks\n * once and remembers the answer in .webmcp-codegen.json.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface WebApp {\n /** Package directory relative to the project root, e.g. \"apps/web\". */\n dir: string;\n framework: \"next\" | \"vite-react\" | \"nuxt\" | \"sveltekit\" | \"unknown\";\n}\n\n/** The frameworks we recognize, best-supported first. */\nconst FRAMEWORKS: { dep: string; framework: WebApp[\"framework\"] }[] = [\n { dep: \"next\", framework: \"next\" },\n { dep: \"nuxt\", framework: \"nuxt\" },\n { dep: \"@sveltejs/kit\", framework: \"sveltekit\" },\n];\n\n/**\n * Find web apps in the project. Returns candidates with the likeliest first\n * (a known framework beats \"has react\", an app named \"web\" beats \"admin\").\n */\nexport async function findWebApps(cwd: string): Promise<WebApp[]> {\n const packageDirs = await findPackageDirs(cwd);\n const apps: WebApp[] = [];\n\n for (const dir of packageDirs) {\n const pkg = await readPackageJson(join(cwd, dir));\n if (!pkg) continue;\n const deps = {\n ...(pkg.dependencies as Record<string, string> | undefined),\n ...(pkg.devDependencies as Record<string, string> | undefined),\n };\n const known = FRAMEWORKS.find(({ dep }) => deps[dep]);\n // A bare react+vite pair is a Vite SPA; react alone is too weak a signal.\n const framework =\n known?.framework ?? (deps.react && deps.vite ? (\"vite-react\" as const) : undefined);\n if (framework) apps.push({ dir, framework });\n }\n\n // Prefer known frameworks, then the package literally named like the app.\n return apps.sort((a, b) => score(b) - score(a));\n\n function score(app: WebApp): number {\n return (\n (app.framework === \"unknown\" ? 0 : 10) +\n (/(^|\\/)(web|app|frontend|client)$/.test(app.dir) ? 2 : 0)\n );\n }\n}\n\n/** Every directory holding a package.json, root first. */\nasync function findPackageDirs(cwd: string): Promise<string[]> {\n const dirs: string[] = [];\n const root = await readPackageJson(join(cwd, \"\"));\n if (root) {\n dirs.push(\".\");\n for (const pattern of await workspaceGlobs(cwd, root)) {\n dirs.push(...(await expandShallowGlob(cwd, pattern)));\n }\n }\n return [...new Set(dirs)];\n}\n\n/** Workspace globs from package.json workspaces or pnpm-workspace.yaml. */\nasync function workspaceGlobs(cwd: string, rootPkg: Record<string, unknown>): Promise<string[]> {\n const workspaces = rootPkg.workspaces;\n if (Array.isArray(workspaces)) return workspaces as string[];\n if (\n workspaces &&\n typeof workspaces === \"object\" &&\n Array.isArray((workspaces as { packages?: unknown }).packages)\n ) {\n return (workspaces as { packages: string[] }).packages;\n }\n // pnpm monorepos: parse the \"packages:\" list out of pnpm-workspace.yaml.\n // Kept deliberately shallow: we only support single-star globs anyway.\n return readPnpmWorkspaceGlobs(cwd);\n}\n\nasync function readPnpmWorkspaceGlobs(cwd: string): Promise<string[]> {\n try {\n const text = await readFile(join(cwd, \"pnpm-workspace.yaml\"), \"utf8\");\n const packagesBlock = /^packages:\\s*\\n((?:\\s+-\\s+.+\\n?)+)/m.exec(text);\n if (!packagesBlock) return [];\n return [...(packagesBlock[1] as string).matchAll(/^\\s+-\\s+['\"]?([^'\"\\n]+?)['\"]?\\s*$/gm)].map(\n (match) => match[1] as string,\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Expand a workspace glob, but only one star deep (\"apps/*\"). Deep globs\n * (\"packages/**\") are truncated at the first star; a monorepo app is never\n * buried deeper than that in practice.\n */\nasync function expandShallowGlob(cwd: string, pattern: string): Promise<string[]> {\n const starAt = pattern.indexOf(\"*\");\n const base = starAt === -1 ? pattern : pattern.slice(0, starAt).replace(/\\/$/, \"\");\n if (starAt === -1) return [base];\n try {\n const entries = await readdir(join(cwd, base), { withFileTypes: true });\n return entries.filter((entry) => entry.isDirectory()).map((entry) => `${base}/${entry.name}`);\n } catch {\n return [];\n }\n}\n\nasync function readPackageJson(dir: string): Promise<Record<string, unknown> | undefined> {\n try {\n return JSON.parse(await readFile(join(dir, \"package.json\"), \"utf8\")) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n","/**\n * The dashboard's UI: a single HTML page with embedded CSS and JS.\n * Design goals: professional, scannable, no visual noise.\n *\n * Layout:\n * - Left sidebar: search + tool list, grouped by risk\n * - Right panel: tool detail with edit, toggle, and test sections\n *\n * No framework, no build step. Plain HTML/CSS/JS shipped as a string.\n */\n\ninterface UiTool {\n name: string;\n description: string;\n sideEffect: string;\n enabled: boolean;\n endpointRole: string;\n piiInOutput: string[];\n findings: { level: string; message: string }[];\n inputSchema?: Record<string, unknown>;\n serverUrl?: string;\n requiresAuth?: boolean;\n verb?: string;\n path?: string;\n}\n\ninterface UiState {\n label: string;\n outDir?: string;\n tools: UiTool[];\n skipped: { ref: string; reason: string }[];\n notes: string[];\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 --surface: #10131a;\n --surface-raised: #161a23;\n --line: #1e2330;\n --line-subtle: #161a23;\n --ink: #e9ecf2;\n --dim: #9aa3b2;\n --faint: #5d6575;\n --ghost: #3b4150;\n --accent: #58a6ff;\n --accent-dim: rgba(88, 166, 255, 0.15);\n --signal: #e3b341;\n --signal-dim: rgba(227, 179, 65, 0.15);\n --fault: #f47067;\n --fault-dim: rgba(244, 112, 103, 0.15);\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 overflow: hidden;\n }\n ::selection { background: var(--accent); color: var(--baseline); }\n\n /* Layout */\n .app { display: flex; height: 100vh; }\n .sidebar {\n width: 320px;\n min-width: 320px;\n border-right: 1px solid var(--line);\n display: flex;\n flex-direction: column;\n background: var(--surface);\n }\n .main {\n flex: 1;\n overflow-y: auto;\n background: var(--baseline);\n }\n\n /* Sidebar header */\n .sidebar-header {\n padding: 20px 20px 16px;\n border-bottom: 1px solid var(--line-subtle);\n }\n .brand {\n display: flex;\n align-items: center;\n gap: 10px;\n font-weight: 600;\n font-size: 15px;\n margin-bottom: 4px;\n }\n .brand-mark {\n width: 24px;\n height: 24px;\n background: linear-gradient(135deg, var(--accent), #7c3aed);\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 700;\n color: white;\n }\n .brand-sub {\n color: var(--faint);\n font-size: 12px;\n }\n\n /* Search */\n .search-wrap {\n padding: 12px 16px;\n border-bottom: 1px solid var(--line-subtle);\n }\n .search {\n width: 100%;\n background: var(--surface-raised);\n border: 1px solid var(--line);\n border-radius: 6px;\n padding: 8px 12px 8px 32px;\n color: var(--ink);\n font-size: 13px;\n font-family: inherit;\n position: relative;\n }\n .search:focus {\n outline: none;\n border-color: var(--accent);\n }\n .search-icon {\n position: absolute;\n left: 28px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--faint);\n pointer-events: none;\n }\n .search-wrap { position: relative; }\n\n /* Tool list */\n .tool-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n }\n .tool-group {\n padding: 8px 16px 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--faint);\n }\n .tool {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n background: none;\n color: var(--ink);\n font-size: 13px;\n font-family: var(--mono);\n text-align: left;\n cursor: pointer;\n transition: background 0.1s;\n }\n .tool:hover { background: var(--surface-raised); }\n .tool[aria-selected=\"true\"] {\n background: var(--accent-dim);\n border-right: 2px solid var(--accent);\n }\n .tool-indicator {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n .tool-indicator.read { background: var(--accent); }\n .tool-indicator.write { background: var(--signal); }\n .tool-indicator.destructive { background: var(--fault); }\n .tool-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .tool-badge {\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 4px;\n background: var(--surface-raised);\n color: var(--dim);\n text-transform: uppercase;\n letter-spacing: 0.02em;\n }\n .tool-badge.disabled { color: var(--signal); }\n\n /* Main content */\n .detail {\n max-width: 640px;\n margin: 0 auto;\n padding: 32px 40px;\n }\n .placeholder {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--faint);\n text-align: center;\n padding: 40px;\n }\n .placeholder-icon {\n width: 48px;\n height: 48px;\n border-radius: 12px;\n background: var(--surface-raised);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 16px;\n color: var(--ghost);\n }\n .placeholder kbd {\n background: var(--surface-raised);\n padding: 2px 6px;\n border-radius: 4px;\n font-family: var(--mono);\n font-size: 12px;\n }\n\n /* Detail header */\n .detail-header {\n margin-bottom: 24px;\n padding-bottom: 20px;\n border-bottom: 1px solid var(--line-subtle);\n }\n .detail-crumb {\n font-size: 12px;\n color: var(--faint);\n margin-bottom: 8px;\n font-family: var(--mono);\n }\n .detail-title {\n font-size: 24px;\n font-weight: 600;\n margin: 0 0 8px;\n font-family: var(--mono);\n }\n .detail-route {\n font-family: var(--mono);\n font-size: 13px;\n color: var(--dim);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .verb {\n font-weight: 600;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 11px;\n }\n .verb.read { color: var(--accent); background: var(--accent-dim); }\n .verb.write { color: var(--signal); background: var(--signal-dim); }\n .verb.destructive { color: var(--fault); background: var(--fault-dim); }\n\n /* Badges */\n .badges {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n flex-wrap: wrap;\n }\n .badge {\n font-size: 11px;\n padding: 3px 8px;\n border-radius: 4px;\n font-weight: 500;\n }\n .badge.read { color: var(--accent); background: var(--accent-dim); }\n .badge.write { color: var(--signal); background: var(--signal-dim); }\n .badge.destructive { color: var(--fault); background: var(--fault-dim); }\n .badge.disabled { color: var(--signal); background: var(--signal-dim); }\n .badge.auth { color: var(--fault); background: var(--fault-dim); }\n\n /* Sections */\n .section {\n margin-bottom: 28px;\n }\n .section-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--faint);\n margin-bottom: 10px;\n }\n\n /* Description edit */\n .description-edit {\n width: 100%;\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 6px;\n padding: 12px;\n color: var(--ink);\n font-size: 14px;\n font-family: inherit;\n line-height: 1.5;\n resize: vertical;\n min-height: 80px;\n }\n .description-edit:focus {\n outline: none;\n border-color: var(--accent);\n }\n .edit-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-top: 10px;\n }\n .btn {\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s;\n border: 1px solid var(--line);\n background: var(--surface);\n color: var(--ink);\n }\n .btn:hover { background: var(--surface-raised); border-color: var(--ghost); }\n .btn-primary {\n background: var(--accent);\n border-color: var(--accent);\n color: var(--baseline);\n }\n .btn-primary:hover { background: #4a95ee; border-color: #4a95ee; }\n .saved-indicator {\n font-size: 12px;\n color: var(--accent);\n opacity: 0;\n transition: opacity 0.2s;\n }\n .saved-indicator.show { opacity: 1; }\n .edit-hint {\n font-size: 12px;\n color: var(--faint);\n margin-top: 8px;\n line-height: 1.5;\n }\n\n /* Toggle */\n .toggle-row {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px;\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 8px;\n }\n .switch {\n width: 40px;\n height: 22px;\n border-radius: 11px;\n background: var(--surface-raised);\n border: 1px solid var(--line);\n position: relative;\n cursor: pointer;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n .switch::after {\n content: \"\";\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dim);\n top: 2px;\n left: 2px;\n transition: all 0.2s;\n }\n .switch[aria-checked=\"true\"] {\n background: var(--accent);\n border-color: var(--accent);\n }\n .switch[aria-checked=\"true\"]::after {\n left: 20px;\n background: white;\n }\n .toggle-copy { font-size: 13px; line-height: 1.5; }\n .toggle-copy strong { display: block; margin-bottom: 2px; }\n\n /* Try it */\n .try-section {\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 8px;\n overflow: hidden;\n }\n .try-header {\n padding: 14px 16px;\n border-bottom: 1px solid var(--line-subtle);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .try-header h3 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n }\n .try-note {\n font-size: 11px;\n color: var(--faint);\n }\n .try-body { padding: 16px; }\n .auth-note {\n background: var(--signal-dim);\n border: 1px solid var(--signal);\n color: var(--signal);\n padding: 10px 12px;\n border-radius: 6px;\n font-size: 12px;\n margin-bottom: 14px;\n line-height: 1.5;\n }\n .base-url-input {\n width: 100%;\n background: var(--baseline);\n border: 1px solid var(--line);\n border-radius: 6px;\n padding: 8px 12px;\n color: var(--ink);\n font-size: 13px;\n font-family: var(--mono);\n margin-bottom: 14px;\n }\n .base-url-input:focus {\n outline: none;\n border-color: var(--accent);\n }\n .param-list { margin-bottom: 14px; }\n .param {\n margin-bottom: 12px;\n }\n .param-label {\n display: block;\n font-size: 12px;\n font-weight: 500;\n margin-bottom: 4px;\n color: var(--dim);\n }\n .param-label .req { color: var(--fault); }\n .param-hint {\n font-size: 11px;\n color: var(--faint);\n margin-top: 2px;\n }\n .param-input {\n width: 100%;\n background: var(--baseline);\n border: 1px solid var(--line);\n border-radius: 6px;\n padding: 8px 12px;\n color: var(--ink);\n font-size: 13px;\n font-family: var(--mono);\n }\n .param-input:focus {\n outline: none;\n border-color: var(--accent);\n }\n .run-btn {\n width: 100%;\n padding: 10px;\n background: var(--accent);\n border: none;\n border-radius: 6px;\n color: var(--baseline);\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.15s;\n }\n .run-btn:hover { background: #4a95ee; }\n .run-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n .result {\n margin-top: 14px;\n padding: 12px;\n background: var(--baseline);\n border: 1px solid var(--line);\n border-radius: 6px;\n font-family: var(--mono);\n font-size: 12px;\n white-space: pre-wrap;\n word-break: break-all;\n max-height: 300px;\n overflow-y: auto;\n }\n .result.ok { border-color: var(--accent); }\n .result.err { border-color: var(--fault); }\n\n /* Findings */\n .findings {\n margin-bottom: 20px;\n }\n .finding {\n display: flex;\n gap: 8px;\n padding: 10px 12px;\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 6px;\n margin-bottom: 8px;\n font-size: 13px;\n line-height: 1.5;\n }\n .finding.warning { border-left: 3px solid var(--signal); }\n .finding.error { border-left: 3px solid var(--fault); }\n .finding-icon { flex-shrink: 0; }\n\n /* Scrollbar */\n ::-webkit-scrollbar { width: 8px; height: 8px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }\n ::-webkit-scrollbar-thumb:hover { background: var(--ghost); }\n</style>\n</head>\n<body>\n<div class=\"app\">\n <aside class=\"sidebar\">\n <div class=\"sidebar-header\">\n <div class=\"brand\">\n <div class=\"brand-mark\">W</div>\n <span>webmcp-codegen</span>\n </div>\n <div class=\"brand-sub\" id=\"tool-count\"></div>\n </div>\n <div class=\"search-wrap\">\n <svg class=\"search-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <path d=\"m21 21-4.35-4.35\"></path>\n </svg>\n <input type=\"text\" class=\"search\" id=\"search\" placeholder=\"Search tools...\" spellcheck=\"false\" />\n </div>\n <div class=\"tool-list\" id=\"tool-list\"></div>\n </aside>\n <main class=\"main\" id=\"main\">\n <div class=\"placeholder\" id=\"placeholder\">\n <div class=\"placeholder-icon\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z\"/>\n </svg>\n </div>\n <p>Select a tool to view details</p>\n <p style=\"font-size: 12px; margin-top: 8px;\">\n <kbd>↑</kbd> <kbd>↓</kbd> to navigate &nbsp;·&nbsp; <kbd>⌘K</kbd> to search\n </p>\n </div>\n <div class=\"detail\" id=\"detail\" hidden></div>\n </main>\n</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 placeholderEl = document.getElementById(\"placeholder\");\n var searchEl = document.getElementById(\"search\");\n var countEl = document.getElementById(\"tool-count\");\n\n function esc(text) {\n var div = document.createElement(\"div\");\n div.textContent = text == null ? \"\" : String(text);\n return div.innerHTML;\n }\n\n function api(path, options) {\n return fetch(path, options).then(function (res) {\n if (!res.ok) throw new Error(\"Request failed: \" + res.status);\n return res.json();\n });\n }\n\n function load() {\n api(\"/api/state\").then(function (data) {\n state = data;\n countEl.textContent = data.tools.length + \" tools from \" + data.label;\n renderList();\n renderDetail();\n });\n }\n\n function visibleTools() {\n if (!state) return [];\n var f = filter.toLowerCase();\n return state.tools.filter(function (tool) {\n return tool.name.toLowerCase().indexOf(f) !== -1 ||\n (tool.description && tool.description.toLowerCase().indexOf(f) !== -1);\n });\n }\n\n function groupTools(tools) {\n var groups = { read: [], write: [], destructive: [] };\n tools.forEach(function (tool) {\n var key = tool.sideEffect || \"read\";\n if (!groups[key]) groups[key] = [];\n groups[key].push(tool);\n });\n return groups;\n }\n\n function renderList() {\n var tools = visibleTools();\n var groups = groupTools(tools);\n var html = \"\";\n\n [\"read\", \"write\", \"destructive\"].forEach(function (risk) {\n var group = groups[risk];\n if (!group || group.length === 0) return;\n html += '<div class=\"tool-group\">' + risk + ' (' + group.length + ')</div>';\n group.forEach(function (tool) {\n var isSelected = tool.name === selected;\n html += '<button class=\"tool\" data-name=\"' + esc(tool.name) + '\" aria-selected=\"' + isSelected + '\">' +\n '<span class=\"tool-indicator ' + risk + '\"></span>' +\n '<span class=\"tool-name\">' + esc(tool.name) + \"</span>\" +\n (!tool.enabled ? '<span class=\"tool-badge disabled\">off</span>' : \"\") +\n \"</button>\";\n });\n });\n\n if (tools.length === 0) {\n html = '<div style=\"padding: 20px; text-align: center; color: var(--faint);\">No tools match your search</div>';\n }\n\n listEl.innerHTML = html;\n\n Array.prototype.forEach.call(listEl.querySelectorAll(\".tool\"), function (btn) {\n btn.addEventListener(\"click\", function () {\n selected = btn.getAttribute(\"data-name\");\n renderList();\n renderDetail();\n });\n });\n }\n\n function currentTool() {\n if (!state || !selected) return null;\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.hidden = true;\n placeholderEl.hidden = false;\n return;\n }\n\n placeholderEl.hidden = true;\n detailEl.hidden = false;\n\n var badges = [\n '<span class=\"badge ' + tool.sideEffect + '\">' + tool.sideEffect + \"</span>\",\n !tool.enabled ? '<span class=\"badge disabled\">starts disabled</span>' : \"\",\n tool.endpointRole !== \"endpoint\" ? '<span class=\"badge auth\">' + tool.endpointRole + \"</span>\" : \"\",\n tool.piiInOutput.length > 0 ? '<span class=\"badge write\">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 ' + finding.level + '\"><span class=\"finding-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 hint = field.description ? '<div class=\"param-hint\">' + esc(field.description) + \"</div>\" : \"\";\n return '<div class=\"param\"><label class=\"param-label\">' + esc(key) + req + '</label>' +\n '<input class=\"param-input\" data-field=\"' + esc(key) + '\" data-type=\"' + esc(field.type || \"string\") + '\" type=\"' + type + '\" spellcheck=\"false\" />' +\n hint + \"</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-header\">' +\n '<div class=\"detail-crumb\">' + esc(state.label) + (state.outDir ? \" → \" + esc(state.outDir) : \"\") + \"</div>\" +\n '<h1 class=\"detail-title\">' + esc(tool.name) + \"</h1>\" +\n '<div class=\"detail-route\">' +\n '<span class=\"verb ' + tool.sideEffect + '\">' + esc(tool.verb || \"GET\") + \"</span>\" +\n \"<span>\" + esc(tool.path || \"\") + \"</span>\" +\n \"</div>\" +\n '<div class=\"badges\">' + badges + \"</div>\" +\n \"</div>\" +\n\n (findings ? '<div class=\"section\"><div class=\"section-label\">Audit findings</div>' + findings + \"</div>\" : \"\") +\n\n '<div class=\"section\">' +\n '<div class=\"section-label\">Description</div>' +\n '<textarea class=\"description-edit\" id=\"desc\" spellcheck=\"false\">' + esc(tool.description) + \"</textarea>\" +\n '<div class=\"edit-actions\">' +\n '<button class=\"btn btn-primary\" id=\"save-desc\">Save</button>' +\n '<span class=\"saved-indicator\" id=\"saved\">Saved</span>' +\n \"</div>\" +\n '<div class=\"edit-hint\">Agents pick tools by this text. Saved to .webmcp-codegen.json, so it survives regeneration. ⌘S to save.</div>' +\n \"</div>\" +\n\n '<div class=\"section\">' +\n '<div class=\"section-label\">Status</div>' +\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 \"</div>\" +\n\n '<div class=\"section\">' +\n '<div class=\"section-label\">Test</div>' +\n '<div class=\"try-section\">' +\n '<div class=\"try-header\"><h3>Run this tool</h3><span class=\"try-note\">server-side, no browser session</span></div>' +\n '<div class=\"try-body\">' +\n (tool.requiresAuth\n ? '<div class=\"auth-note\">⚠ This endpoint requires a browser session. The dashboard runs server-side, so you will get a 401. Test it in Chrome DevTools where you are signed in.</div>'\n : \"\") +\n '<input class=\"base-url-input\" id=\"base-url\" type=\"text\" placeholder=\"Base URL (e.g. http://localhost:3000)\" value=\"' + esc(baseUrl) + '\" spellcheck=\"false\" />' +\n (fields || '<div style=\"color: var(--faint); font-size: 13px; margin-bottom: 14px;\">This tool takes no inputs.</div>') +\n '<button class=\"run-btn\" id=\"run\">Run tool</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 var saved = document.getElementById(\"saved\");\n saved.classList.add(\"show\");\n setTimeout(function () { saved.classList.remove(\"show\"); }, 2000);\n }).catch(function (error) { alert(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 }).catch(function (error) { alert(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 runEl.textContent = \"Running...\";\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 runEl.textContent = \"Run tool\";\n });\n }\n\n /* Keyboard navigation */\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","/**\n * Registration wiring: making the app actually register its tools.\n *\n * Generated files do nothing until something calls registerAllTools() once at\n * startup. Rather than telling the developer to go do that, we do it for\n * them — under strict rules, because this is the one place we edit *their*\n * files instead of ours:\n *\n * 1. Edits are additive only. We insert lines; we never change or remove\n * existing ones.\n * 2. Idempotent. If the wiring is already there, we do nothing.\n * 3. Honest. Every edit is reported with exact paths and how to undo it.\n * 4. When we cannot find the entry point with confidence, we do not guess:\n * we print the two lines and where they go, and leave it to the human.\n */\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport type { WebApp } from \"./detect-app.js\";\n\nexport interface WireEdit {\n /** Absolute path of the file to create or modify. */\n path: string;\n action: \"create\" | \"modify\";\n /** The full new contents (for modify: the existing contents plus our lines). */\n contents: string;\n /** One human sentence per edit, for the report. */\n summary: string;\n}\n\nexport interface WirePlan {\n edits: WireEdit[];\n /** Set when we already found the wiring in place. */\n alreadyWired?: boolean;\n}\n\n/**\n * Compute the wiring edits for the detected app, or null when we cannot\n * locate the entry point with confidence (the CLI then prints manual\n * instructions instead).\n */\nexport async function planWiring(\n cwd: string,\n app: WebApp,\n outDir: string,\n): Promise<WirePlan | null> {\n switch (app.framework) {\n case \"next\":\n return planNextWiring(cwd, app, outDir);\n case \"vite-react\":\n return planViteWiring(cwd, app, outDir);\n default:\n // Nuxt/SvelteKit/unknown: we know where the tools go but not where the\n // app boots. Print instructions rather than guess-edit an entry file.\n return null;\n }\n}\n\n/** Apply a plan. Kept trivial on purpose: the plan already did the thinking. */\nexport async function applyWiring(plan: WirePlan): Promise<void> {\n for (const edit of plan.edits) {\n await writeFile(edit.path, edit.contents, \"utf8\");\n }\n}\n\n/* ── Next.js (app router) ──────────────────────────────────────────────── */\n\n/**\n * Next needs the registration to run on the client, so we generate a tiny\n * \"use client\" component next to the tools and mount it in the root layout:\n *\n * import { WebMCPRegister } from \"../webmcp/register\"; ← added\n * ...\n * <body>\n * <WebMCPRegister /> ← added\n * {children}\n */\nasync function planNextWiring(cwd: string, app: WebApp, outDir: string): Promise<WirePlan | null> {\n const layoutCandidates = [\n join(cwd, app.dir, \"src/app/layout.tsx\"),\n join(cwd, app.dir, \"src/app/layout.jsx\"),\n join(cwd, app.dir, \"app/layout.tsx\"),\n join(cwd, app.dir, \"app/layout.jsx\"),\n ];\n const layoutPath = await firstExisting(layoutCandidates);\n if (!layoutPath) return null;\n\n const registerPath = join(cwd, outDir, \"register.tsx\");\n const layout = await readFile(layoutPath, \"utf8\");\n if (layout.includes(\"WebMCPRegister\")) return { edits: [], alreadyWired: true };\n\n // Import path from the layout's directory to the register component.\n const importPath = withoutExtension(relative(dirname(layoutPath), registerPath));\n\n const edits: WireEdit[] = [\n {\n path: registerPath,\n action: \"create\",\n contents: nextRegisterComponent(),\n summary: `created ${relative(cwd, registerPath)} (a client component that registers your tools on page load)`,\n },\n ];\n\n const withImport = insertAfterLastImport(\n layout,\n `import { WebMCPRegister } from \"${importPath}\";`,\n );\n if (!withImport) return null;\n // Mount right after <body ...>, each element on its own line.\n const withComponent = withImport.replace(\n /<body([^>]*)>\\s*/,\n \"<body$1>\\n <WebMCPRegister />\\n \",\n );\n if (withComponent === withImport) return null; // No <body> tag found; do not guess.\n\n edits.push({\n path: layoutPath,\n action: \"modify\",\n contents: withComponent,\n summary: `added 2 lines to ${relative(cwd, layoutPath)} (an import and <WebMCPRegister /> inside <body>)`,\n });\n return { edits };\n}\n\nfunction nextRegisterComponent(): string {\n return `\"use client\";\n\nimport { useEffect } from \"react\";\nimport { registerAllTools } from \"./index\";\n\n/**\n * Registers the generated WebMCP tools once, on page load.\n * Generated by webmcp-codegen. Safe to move; keep it mounted near the root.\n */\nexport function WebMCPRegister() {\n useEffect(() => {\n void registerAllTools();\n }, []);\n return null;\n}\n`;\n}\n\n/* ── Vite + React (SPAs) ───────────────────────────────────────────────── */\n\n/**\n * A Vite app boots in main.tsx, so wiring is two added lines there:\n *\n * import { registerAllTools } from \"./webmcp\"; ← added\n * void registerAllTools(); ← added\n */\nasync function planViteWiring(cwd: string, app: WebApp, outDir: string): Promise<WirePlan | null> {\n const entryCandidates = [\n join(cwd, app.dir, \"src/main.tsx\"),\n join(cwd, app.dir, \"src/main.jsx\"),\n join(cwd, app.dir, \"src/index.tsx\"),\n join(cwd, app.dir, \"src/index.jsx\"),\n ];\n const entryPath = await firstExisting(entryCandidates);\n if (!entryPath) return null;\n\n const entry = await readFile(entryPath, \"utf8\");\n if (entry.includes(\"registerAllTools\")) return { edits: [], alreadyWired: true };\n\n const importPath = withoutExtension(relative(dirname(entryPath), join(cwd, outDir, \"index\")));\n const withWiring = insertAfterLastImport(\n entry,\n `import { registerAllTools } from \"${importPath}\";\\n\\nvoid registerAllTools();`,\n );\n if (!withWiring) return null;\n\n return {\n edits: [\n {\n path: entryPath,\n action: \"modify\",\n contents: withWiring,\n summary: `added 2 lines to ${relative(cwd, entryPath)} (an import and a registerAllTools() call)`,\n },\n ],\n };\n}\n\n/* ── Shared helpers ────────────────────────────────────────────────────── */\n\n/** Insert a line after the file's last top-level import statement. */\nfunction insertAfterLastImport(source: string, line: string): string | null {\n const lines = source.split(\"\\n\");\n let lastImport = -1;\n for (let index = 0; index < lines.length; index += 1) {\n if (/^import\\s/.test(lines[index] as string)) lastImport = index;\n }\n if (lastImport === -1) return null;\n lines.splice(lastImport + 1, 0, line);\n return lines.join(\"\\n\");\n}\n\n/**\n * Turn a filesystem path into a JS import specifier: no extension, and an\n * explicit \"./\" when the target is in the same directory or deeper —\n * `relative()` alone yields \"webmcp/index\", which JS would read as a\n * package name, not a file.\n */\nfunction withoutExtension(path: string): string {\n const bare = path.replace(/\\.(tsx?|jsx?)$/, \"\").replace(/\\/index$/, \"\");\n return bare.startsWith(\".\") ? bare : `./${bare}`;\n}\n\nasync function firstExisting(paths: string[]): Promise<string | undefined> {\n for (const path of paths) {\n try {\n await readFile(path, \"utf8\");\n return path;\n } catch {\n // Try the next candidate.\n }\n }\n return undefined;\n}\n"],"mappings":"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;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;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;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;AAw0BT;;;AH7yBA,eAAsB,eAAe,SAA4C;AAC/E,QAAM,QAAQ,MAAM,aAAa,QAAQ,KAAK;AAAA,IAC5C,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,OAAO;AAAA,IACP,OAAO;AAAA,EACT,CAAC;AAID,iBAAe,eAAwC;AACrD,UAAM,OAAO,MAAM,aAAa,QAAQ,GAAG;AAC3C,UAAM,SAAS,MAAM,YAAY,MAAM,QAAQ;AAAA,MAC7C,KAAK,QAAQ;AAAA,MACb,QAAQ;AAAA,MACR,WAAW,KAAK;AAAA,IAClB,CAAC;AACD,WAAO;AAAA,MACL,OAAO,MAAM;AAAA,MACb,QAAQ,MAAM,OAAO,SAAS,CAAC,GAAG;AAAA,MAClC,OAAO,OAAO,MAAM,IAAI,CAAC,SAAS,SAAS,MAAM,OAAO,QAAQ,CAAC;AAAA,MACjE,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,aAAa,OAAO,SAAS,aAAa;AACvD,QAAI;AACF,YAAM,MAAM,SAAS,QAAQ;AAAA,IAC/B,SAAS,OAAO;AACd,eAAS,UAAU,KAAK,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,IAC3F;AAAA,EACF,CAAC;AAED,iBAAe,MACb,SACA,UACe;AACf,UAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,kBAAkB;AAE1D,QAAI,QAAQ,WAAW,SAAS,IAAI,aAAa,KAAK;AACpD,eAAS,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACtE,eAAS,IAAI,cAAc,CAAC;AAC5B;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,SAAS,IAAI,aAAa,cAAc;AAC7D,eAAS,UAAU,KAAK,MAAM,aAAa,CAAC;AAC5C;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,UAAU,IAAI,aAAa,iBAAiB;AACjE,YAAM,OAAQ,MAAM,SAAS,OAAO;AAKpC,UAAI,CAAC,KAAK,MAAM;AACd,iBAAS,UAAU,KAAK,EAAE,OAAO,qBAAqB,CAAC;AACvD;AAAA,MACF;AACA,YAAM,OAAO,MAAM,aAAa,QAAQ,GAAG;AAC3C,YAAM,YAAY,EAAE,GAAI,KAAK,aAAa,CAAC,EAAG;AAC9C,YAAM,WAAW,UAAU,KAAK,IAAI,KAAK,CAAC;AAE1C,gBAAU,KAAK,IAAI,IAAI;AAAA,QACrB,GAAG;AAAA,QACH,GAAI,KAAK,gBAAgB,SAAY,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,QAC1E,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAChE;AACA,YAAM,aAAa,QAAQ,KAAK,EAAE,UAAU,CAAC;AAC7C,eAAS,UAAU,KAAK,EAAE,IAAI,MAAM,OAAO,uBAAuB,CAAC;AACnE;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,UAAU,IAAI,aAAa,YAAY;AAC5D,YAAM,OAAQ,MAAM,SAAS,OAAO;AACpC,YAAM,QAAQ,MAAM,aAAa;AACjC,YAAM,OAAO,MAAM,MAAM,KAAK,CAAC,cAAc,UAAU,SAAS,KAAK,IAAI;AACzE,UAAI,CAAC,MAAM;AACT,iBAAS,UAAU,KAAK,EAAE,OAAO,kBAAkB,KAAK,IAAI,KAAK,CAAC;AAClE;AAAA,MACF;AACA,YAAM,SAAS,MAAM,YAAY,MAAM,KAAK,SAAS,CAAC,GAAG,KAAK,OAAO;AACrE,eAAS,UAAU,OAAO,KAAK,MAAM,KAAK,MAAM;AAChD;AAAA,IACF;AAEA,aAAS,UAAU,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EAChD;AAEA,QAAM,IAAI;AAAA,IAAc,CAAC,kBACvB,OAAO,OAAO,QAAQ,MAAM,aAAa,aAAa;AAAA,EACxD;AAEA,MAAI,QAAQ,SAAS,MAAO,aAAY,oBAAoB,QAAQ,IAAI,EAAE;AAC1E,SAAO;AACT;AAEA,SAAS,SACP,MACA,UACiC;AACjC,QAAM,CAAC,MAAM,GAAG,IAAI,IAAI,KAAK,OAAO,IAAI,MAAM,GAAG;AACjD,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX;AAAA,IACA,MAAM,KAAK,KAAK,GAAG;AAAA,IACnB,aAAa,KAAK;AAAA,IAClB,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,SAAS,KAAK;AAAA,IACd,cAAc,KAAK;AAAA,IACnB,aAAa,KAAK;AAAA,IAClB,aAAa,KAAK;AAAA,IAClB,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IAC/D,GAAI,KAAK,iBAAiB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA,IACrE,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IACtD,cAAc,KAAK;AAAA,IACnB,UAAU,SACP,OAAO,CAAC,YAAY,QAAQ,SAAS,KAAK,IAAI,EAC9C,IAAI,CAAC,aAAa,EAAE,OAAO,QAAQ,OAAO,SAAS,QAAQ,QAAQ,EAAE;AAAA,EAC1E;AACF;AASA,eAAe,YACb,MACA,OACA,iBAC2E;AAC3E,QAAM,OAAO,mBAAmB,KAAK;AACrC,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OACE;AAAA,IAEJ;AAAA,EACF;AACA,MAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,MAAM;AACpC,WAAO,EAAE,IAAI,OAAO,OAAO,kCAAkC;AAAA,EAC/D;AAEA,MAAI,OAAO,KAAK;AAChB,aAAW,SAAS,KAAK,gBAAgB,QAAQ,CAAC,GAAG;AACnD,WAAO,KAAK,QAAQ,IAAI,KAAK,KAAK,mBAAmB,OAAO,MAAM,KAAK,KAAK,EAAE,CAAC,CAAC;AAAA,EAClF;AACA,QAAM,MAAM,IAAI,IAAI,MAAM,IAAI;AAC9B,aAAW,SAAS,KAAK,gBAAgB,SAAS,CAAC,GAAG;AACpD,UAAM,QAAQ,MAAM,KAAK;AACzB,QAAI,UAAU,UAAa,UAAU,KAAM,KAAI,aAAa,IAAI,OAAO,OAAO,KAAK,CAAC;AAAA,EACtF;AACA,QAAM,aAAa,KAAK,gBAAgB,QAAQ,CAAC;AACjD,QAAM,OACJ,WAAW,WAAW,KAAK,WAAW,CAAC,MAAM,SACzC,MAAM,OACN,WAAW,SAAS,IAClB,OAAO,YAAY,WAAW,IAAI,CAAC,UAAU,CAAC,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,IACnE;AAER,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ,KAAK;AAAA,MACb,SAAS,SAAS,SAAY,EAAE,gBAAgB,mBAAmB,IAAI;AAAA,MACvE,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,IACpD,CAAC;AACD,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,SAAkB;AACtB,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AAAA,IAER;AACA,WAAO,EAAE,IAAI,SAAS,IAAI,QAAQ,SAAS,QAAQ,MAAM,OAAO;AAAA,EAClE,SAAS,OAAO;AACd,WAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,EACpF;AACF;AAEA,SAAS,SACP,UACA,QACA,MACM;AACN,WAAS,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AACjE,WAAS,IAAI,KAAK,UAAU,IAAI,CAAC;AACnC;AAEA,SAAS,SAAS,SAAgE;AAChF,SAAO,IAAI,QAAQ,CAAC,aAAa,WAAW;AAC1C,QAAI,OAAO;AACX,YAAQ,GAAG,QAAQ,CAAC,UAAkB;AACpC,cAAQ,MAAM,SAAS,MAAM;AAAA,IAC/B,CAAC;AACD,YAAQ,GAAG,OAAO,MAAM;AACtB,UAAI;AACF,oBAAY,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC;AAAA,MAC1C,QAAQ;AACN,eAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,MACvC;AAAA,IACF,CAAC;AACD,YAAQ,GAAG,SAAS,MAAM;AAAA,EAC5B,CAAC;AACH;AAEA,SAAS,YAAY,KAAmB;AACtC,QAAM,UACJ,QAAQ,aAAa,WAAW,SAAS,QAAQ,aAAa,UAAU,UAAU;AACpF,QAAM,SAAS,CAAC,GAAG,GAAG,EAAE,OAAO,UAAU,OAAO,QAAQ,aAAa,QAAQ,CAAC,EAAE,MAAM;AACxF;;;AItQA,SAAS,YAAAC,WAAU,aAAAC,kBAAiB;AACpC,SAAS,SAAS,QAAAC,OAAM,YAAAC,iBAAgB;AAwBxC,eAAsB,WACpB,KACA,KACA,QAC0B;AAC1B,UAAQ,IAAI,WAAW;AAAA,IACrB,KAAK;AACH,aAAO,eAAe,KAAK,KAAK,MAAM;AAAA,IACxC,KAAK;AACH,aAAO,eAAe,KAAK,KAAK,MAAM;AAAA,IACxC;AAGE,aAAO;AAAA,EACX;AACF;AAGA,eAAsB,YAAY,MAA+B;AAC/D,aAAW,QAAQ,KAAK,OAAO;AAC7B,UAAMF,WAAU,KAAK,MAAM,KAAK,UAAU,MAAM;AAAA,EAClD;AACF;AAcA,eAAe,eAAe,KAAa,KAAa,QAA0C;AAChG,QAAM,mBAAmB;AAAA,IACvBC,MAAK,KAAK,IAAI,KAAK,oBAAoB;AAAA,IACvCA,MAAK,KAAK,IAAI,KAAK,oBAAoB;AAAA,IACvCA,MAAK,KAAK,IAAI,KAAK,gBAAgB;AAAA,IACnCA,MAAK,KAAK,IAAI,KAAK,gBAAgB;AAAA,EACrC;AACA,QAAM,aAAa,MAAM,cAAc,gBAAgB;AACvD,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,eAAeA,MAAK,KAAK,QAAQ,cAAc;AACrD,QAAM,SAAS,MAAMF,UAAS,YAAY,MAAM;AAChD,MAAI,OAAO,SAAS,gBAAgB,EAAG,QAAO,EAAE,OAAO,CAAC,GAAG,cAAc,KAAK;AAG9E,QAAM,aAAa,iBAAiBG,UAAS,QAAQ,UAAU,GAAG,YAAY,CAAC;AAE/E,QAAM,QAAoB;AAAA,IACxB;AAAA,MACE,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU,sBAAsB;AAAA,MAChC,SAAS,WAAWA,UAAS,KAAK,YAAY,CAAC;AAAA,IACjD;AAAA,EACF;AAEA,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,mCAAmC,UAAU;AAAA,EAC/C;AACA,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,gBAAgB,WAAW;AAAA,IAC/B;AAAA,IACA;AAAA,EACF;AACA,MAAI,kBAAkB,WAAY,QAAO;AAEzC,QAAM,KAAK;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAAS,oBAAoBA,UAAS,KAAK,UAAU,CAAC;AAAA,EACxD,CAAC;AACD,SAAO,EAAE,MAAM;AACjB;AAEA,SAAS,wBAAgC;AACvC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBT;AAUA,eAAe,eAAe,KAAa,KAAa,QAA0C;AAChG,QAAM,kBAAkB;AAAA,IACtBD,MAAK,KAAK,IAAI,KAAK,cAAc;AAAA,IACjCA,MAAK,KAAK,IAAI,KAAK,cAAc;AAAA,IACjCA,MAAK,KAAK,IAAI,KAAK,eAAe;AAAA,IAClCA,MAAK,KAAK,IAAI,KAAK,eAAe;AAAA,EACpC;AACA,QAAM,YAAY,MAAM,cAAc,eAAe;AACrD,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,QAAQ,MAAMF,UAAS,WAAW,MAAM;AAC9C,MAAI,MAAM,SAAS,kBAAkB,EAAG,QAAO,EAAE,OAAO,CAAC,GAAG,cAAc,KAAK;AAE/E,QAAM,aAAa,iBAAiBG,UAAS,QAAQ,SAAS,GAAGD,MAAK,KAAK,QAAQ,OAAO,CAAC,CAAC;AAC5F,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,qCAAqC,UAAU;AAAA;AAAA;AAAA,EACjD;AACA,MAAI,CAAC,WAAY,QAAO;AAExB,SAAO;AAAA,IACL,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,oBAAoBC,UAAS,KAAK,SAAS,CAAC;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AACF;AAKA,SAAS,sBAAsB,QAAgB,MAA6B;AAC1E,QAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,MAAI,aAAa;AACjB,WAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACpD,QAAI,YAAY,KAAK,MAAM,KAAK,CAAW,EAAG,cAAa;AAAA,EAC7D;AACA,MAAI,eAAe,GAAI,QAAO;AAC9B,QAAM,OAAO,aAAa,GAAG,GAAG,IAAI;AACpC,SAAO,MAAM,KAAK,IAAI;AACxB;AAQA,SAAS,iBAAiB,MAAsB;AAC9C,QAAM,OAAO,KAAK,QAAQ,kBAAkB,EAAE,EAAE,QAAQ,YAAY,EAAE;AACtE,SAAO,KAAK,WAAW,GAAG,IAAI,OAAO,KAAK,IAAI;AAChD;AAEA,eAAe,cAAc,OAA8C;AACzE,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAMH,UAAS,MAAM,MAAM;AAC3B,aAAO;AAAA,IACT,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;;;ARzLA,IAAM,OAAO;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;AA6Cb,eAAe,OAAwB;AACrC,QAAM,EAAE,QAAQ,YAAY,IAAI,UAAU;AAAA,IACxC,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,IAC1B,kBAAkB;AAAA,IAClB,SAAS;AAAA,MACP,WAAW,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MAC7C,cAAc,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MAChD,OAAO,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MACzC,SAAS,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MAC3C,OAAO,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MACzC,QAAQ,EAAE,MAAM,SAAS;AAAA,MACzB,MAAM,EAAE,MAAM,SAAS;AAAA,MACvB,KAAK,EAAE,MAAM,SAAS;AAAA,MACtB,MAAM,EAAE,MAAM,SAAS;AAAA,MACvB,MAAM,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,IAC1C;AAAA,EACF,CAAC;AAED,QAAM,QAAkB;AAAA,IACtB,QAAQ,OAAO,SAAS,KAAK;AAAA,IAC7B,WAAW,OAAO,YAAY,KAAK;AAAA,IACnC,OAAO,OAAO,SAAS;AAAA,IACvB,SAAS,OAAO,WAAW;AAAA,IAC3B,OAAO,OAAO,SAAS;AAAA,IACvB,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,IACb,KAAK,OAAO;AAAA,IACZ,MAAM,OAAO,OAAO,OAAO,SAAS,OAAO,MAAM,EAAE,IAAI;AAAA,IACvD,MAAM,OAAO;AAAA,EACf;AAEA,MAAI,MAAM,MAAM;AACd,YAAQ,IAAI,IAAI;AAChB,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,YAAY,CAAC,KAAK;AAElC,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,aAAO,KAAK;AAAA,IACd,KAAK;AACH,aAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/B,KAAK;AACH,aAAO,SAAS,KAAK;AAAA,IACvB;AACE,cAAQ,MAAM,oBAAoB,OAAO;AAAA,CAAI;AAC7C,cAAQ,IAAI,IAAI;AAChB,aAAO;AAAA,EACX;AACF;AAEA,eAAe,OAAwB;AACrC,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,aAAa,kBAAkB,CAAC,KAAK;AAC3C,QAAM,aAAaI,MAAK,KAAK,UAAU;AAEvC,MAAIC,YAAW,UAAU,GAAG;AAC1B,YAAQ,MAAM;AAAA,SAAO,UAAU;AAAA,CAAmC;AAClE,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,MAAM,UAAU,GAAG;AACjC,QAAM,WAAW,MAAM,SAAS,IAAI,KAAK,MAAM,CAAC,CAAC,KAAK;AAEtD,QAAMC;AAAA,IACJ;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAK2B,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUrC;AAEA,UAAQ,IAAI;AAAA,eAAa,UAAU;AAAA,CAAI;AACvC,UAAQ,IAAI,6EAA6E;AACzF,UAAQ,IAAI,8DAA8D;AAC1E,SAAO;AACT;AAEA,eAAe,IAAI,MAA+B;AAChD,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,eAAe,EAAE,KAAK,MAAM,MAAM,KAAK,CAAC;AAC7D,UAAQ,IAAI;AAAA,qCAAmC,IAAI;AAAA,CAAI;AACvD,UAAQ,IAAI,gEAAgE;AAE5E,QAAM,IAAI,QAAc,CAAC,gBAAgB;AACvC,YAAQ,GAAG,UAAU,MAAM;AACzB,aAAO,MAAM;AACb,kBAAY;AAAA,IACd,CAAC;AAAA,EACH,CAAC;AACD,SAAO;AACT;AAEA,eAAe,SAAS,OAAkC;AACxD,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,QAAQ,MAAM,aAAa,KAAK;AAAA,IACpC,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM;AAAA,IACjB,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,MAAM,MAAM;AAAA,IACZ,KAAK,MAAM;AAAA,IACX,YAAY,MAAM;AAAA,EACpB,CAAC;AAED,QAAM,SAAS,MAAM,YAAY,MAAM,QAAQ;AAAA,IAC7C;AAAA,IACA,QAAQ,MAAM;AAAA,IACd,OAAO,MAAM;AAAA,IACb,WAAW,MAAM;AAAA,EACnB,CAAC;AAGD,MAAI,SAA0B;AAC9B,MAAI,CAAC,OAAO,WAAW,MAAM,KAAK;AAChC,UAAM,SAAS,MAAM,OAAO,SAAS,CAAC,GAAG;AACzC,QAAI,QAAQ;AACV,eAAS,MAAM,WAAW,KAAK,MAAM,KAAK,MAAM;AAChD,UAAI,UAAU,OAAO,MAAM,SAAS,KAAK,CAAC,MAAM,UAAU,OAAO,OAAO;AACtE,cAAM,YAAY,MAAM;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAGA,MAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,UAAU,OAAO,OAAO;AAC1D,UAAM,aAAa,KAAK,MAAM,QAAQ;AAAA,EACxC;AAEA,MAAI,MAAM,SAAS;AACjB,kBAAc,QAAQ,OAAO,GAAG;AAAA,EAClC,OAAO;AACL,kBAAc,QAAQ,OAAO,KAAK,MAAM;AAAA,EAC1C;AAEA,MAAI,MAAM,SAAS,CAAC,MAAM,QAAQ;AAEhC,YAAQ,IAAI,IAAI,yEAAyE,CAAC;AAAA,EAC5F;AAEA,SAAO,OAAO,UAAU,IAAI;AAC9B;AAEA,KAAK,EAAE;AAAA,EACL,CAAC,SAAS,QAAQ,KAAK,IAAI;AAAA,EAC3B,CAAC,UAAU;AACT,YAAQ,MAAM,8BAAyB,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACrF,YAAQ,MAAM,8EAA8E;AAC5F,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":["existsSync","writeFile","join","join","join","readdir","readFile","join","join","data","app","readFile","writeFile","join","relative","join","existsSync","writeFile"]}
@@ -1,4 +1,4 @@
1
- import { T as ToolGenerator } from '../types-DWUum51l.js';
1
+ import { T as ToolGenerator } from '../types-DfK3AA5H.js';
2
2
 
3
3
  /**
4
4
  * The `js` generator, named after what lands in your repo: plain JavaScript/
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  js
3
- } from "../chunk-TGOJ3HUE.js";
3
+ } from "../chunk-EAKYM4YS.js";
4
4
  import "../chunk-KSQMJERY.js";
5
5
  export {
6
6
  js
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CodegenConfig, a as ToolOverrides, R as ReviewedTool, b as SkippedEndpoint, A as AuditFinding, G as GeneratedFile } from './types-DWUum51l.js';
2
- export { c as CandidateTool, J as JsonSchema, d as RiskTier, e as SafetyOptions, f as SideEffect, S as Source, g as SourceKind, T as ToolGenerator, h as ToolHints } from './types-DWUum51l.js';
1
+ import { C as CodegenConfig, a as ToolOverrides, R as ReviewedTool, S as SkippedEndpoint, A as AuditFinding, G as GeneratedFile } from './types-DfK3AA5H.js';
2
+ export { b as CandidateTool, J as JsonSchema, c as RiskTier, d as SafetyOptions, e as SideEffect, f as Source, g as SourceKind, T as ToolGenerator, h as ToolHints } from './types-DfK3AA5H.js';
3
3
 
4
4
  /**
5
5
  * Config: `defineConfig` for authoring, `loadConfig` for the CLI.
@@ -1,4 +1,4 @@
1
- import { S as Source } from '../types-DWUum51l.js';
1
+ import { f as Source } from '../types-DfK3AA5H.js';
2
2
 
3
3
  /**
4
4
  * The OpenAPI source.
@@ -186,4 +186,4 @@ interface CodegenConfig {
186
186
  safety?: SafetyOptions;
187
187
  }
188
188
 
189
- export type { AuditFinding as A, CodegenConfig as C, GeneratedFile as G, JsonSchema as J, ReviewedTool as R, Source as S, ToolGenerator as T, ToolOverrides as a, SkippedEndpoint as b, CandidateTool as c, RiskTier as d, SafetyOptions as e, SideEffect as f, SourceKind as g, ToolHints as h };
189
+ export type { AuditFinding as A, CodegenConfig as C, GeneratedFile as G, JsonSchema as J, ReviewedTool as R, SkippedEndpoint as S, ToolGenerator as T, ToolOverrides as a, CandidateTool as b, RiskTier as c, SafetyOptions as d, SideEffect as e, Source as f, SourceKind as g, ToolHints as h };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webmcp-codegen",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Generate safe, typed, human-reviewed WebMCP tools from the API contracts you already have (OpenAPI, tRPC, Zod).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,235 +0,0 @@
1
- import {
2
- CONFIG_FILE_NAMES,
3
- loadConfig
4
- } from "./chunk-MJQ5B6HB.js";
5
- import {
6
- js
7
- } from "./chunk-TGOJ3HUE.js";
8
- import {
9
- openapi
10
- } from "./chunk-3LTHWIAP.js";
11
-
12
- // src/data-file.ts
13
- import { readFile, writeFile } from "fs/promises";
14
- import { join } from "path";
15
- var DATA_FILE_NAME = ".webmcp-codegen.json";
16
- async function loadDataFile(cwd) {
17
- try {
18
- const parsed = JSON.parse(await readFile(join(cwd, DATA_FILE_NAME), "utf8"));
19
- return parsed && typeof parsed === "object" ? parsed : {};
20
- } catch {
21
- return {};
22
- }
23
- }
24
- async function saveDataFile(cwd, patch) {
25
- const current = await loadDataFile(cwd);
26
- const next = { ...current, ...patch };
27
- if (JSON.stringify(next) === JSON.stringify(current)) return;
28
- await writeFile(join(cwd, DATA_FILE_NAME), `${JSON.stringify(next, null, 2)}
29
- `, "utf8");
30
- }
31
-
32
- // src/detect.ts
33
- import { readdir } from "fs/promises";
34
- import { join as join2, relative } from "path";
35
- var SPEC_FILE_PATTERN = /^(openapi|swagger|api)\.(ya?ml|json)$/i;
36
- var IGNORED_DIRS = /* @__PURE__ */ new Set([
37
- "node_modules",
38
- ".git",
39
- ".turbo",
40
- ".next",
41
- "dist",
42
- "build",
43
- "coverage"
44
- ]);
45
- var MAX_DEPTH = 5;
46
- async function findSpecs(cwd) {
47
- const found = [];
48
- async function walk(dir, depth) {
49
- if (depth > MAX_DEPTH) return;
50
- let entries;
51
- try {
52
- entries = await readdir(dir, { withFileTypes: true });
53
- } catch {
54
- return;
55
- }
56
- for (const entry of entries) {
57
- if (entry.isDirectory()) {
58
- if (!IGNORED_DIRS.has(entry.name)) await walk(join2(dir, entry.name), depth + 1);
59
- } else if (SPEC_FILE_PATTERN.test(entry.name)) {
60
- found.push({ path: join2(dir, entry.name), depth });
61
- }
62
- }
63
- }
64
- await walk(cwd, 0);
65
- return found.sort((a, b) => a.depth - b.depth).map((entry) => relative(cwd, entry.path));
66
- }
67
-
68
- // src/setup.ts
69
- import { existsSync } from "fs";
70
- import { basename, join as join4 } from "path";
71
- import { createInterface } from "readline/promises";
72
-
73
- // src/detect-app.ts
74
- import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
75
- import { join as join3 } from "path";
76
- var FRAMEWORKS = [
77
- { dep: "next", framework: "next" },
78
- { dep: "nuxt", framework: "nuxt" },
79
- { dep: "@sveltejs/kit", framework: "sveltekit" }
80
- ];
81
- async function findWebApps(cwd) {
82
- const packageDirs = await findPackageDirs(cwd);
83
- const apps = [];
84
- for (const dir of packageDirs) {
85
- const pkg = await readPackageJson(join3(cwd, dir));
86
- if (!pkg) continue;
87
- const deps = {
88
- ...pkg.dependencies,
89
- ...pkg.devDependencies
90
- };
91
- const known = FRAMEWORKS.find(({ dep }) => deps[dep]);
92
- const framework = known?.framework ?? (deps.react && deps.vite ? "vite-react" : void 0);
93
- if (framework) apps.push({ dir, framework });
94
- }
95
- return apps.sort((a, b) => score(b) - score(a));
96
- function score(app) {
97
- return (app.framework === "unknown" ? 0 : 10) + (/(^|\/)(web|app|frontend|client)$/.test(app.dir) ? 2 : 0);
98
- }
99
- }
100
- async function findPackageDirs(cwd) {
101
- const dirs = [];
102
- const root = await readPackageJson(join3(cwd, ""));
103
- if (root) {
104
- dirs.push(".");
105
- for (const pattern of await workspaceGlobs(cwd, root)) {
106
- dirs.push(...await expandShallowGlob(cwd, pattern));
107
- }
108
- }
109
- return [...new Set(dirs)];
110
- }
111
- async function workspaceGlobs(cwd, rootPkg) {
112
- const workspaces = rootPkg.workspaces;
113
- if (Array.isArray(workspaces)) return workspaces;
114
- if (workspaces && typeof workspaces === "object" && Array.isArray(workspaces.packages)) {
115
- return workspaces.packages;
116
- }
117
- return readPnpmWorkspaceGlobs(cwd);
118
- }
119
- async function readPnpmWorkspaceGlobs(cwd) {
120
- try {
121
- const text = await readFile2(join3(cwd, "pnpm-workspace.yaml"), "utf8");
122
- const packagesBlock = /^packages:\s*\n((?:\s+-\s+.+\n?)+)/m.exec(text);
123
- if (!packagesBlock) return [];
124
- return [...packagesBlock[1].matchAll(/^\s+-\s+['"]?([^'"\n]+?)['"]?\s*$/gm)].map(
125
- (match) => match[1]
126
- );
127
- } catch {
128
- return [];
129
- }
130
- }
131
- async function expandShallowGlob(cwd, pattern) {
132
- const starAt = pattern.indexOf("*");
133
- const base = starAt === -1 ? pattern : pattern.slice(0, starAt).replace(/\/$/, "");
134
- if (starAt === -1) return [base];
135
- try {
136
- const entries = await readdir2(join3(cwd, base), { withFileTypes: true });
137
- return entries.filter((entry) => entry.isDirectory()).map((entry) => `${base}/${entry.name}`);
138
- } catch {
139
- return [];
140
- }
141
- }
142
- async function readPackageJson(dir) {
143
- try {
144
- return JSON.parse(await readFile2(join3(dir, "package.json"), "utf8"));
145
- } catch {
146
- return void 0;
147
- }
148
- }
149
-
150
- // src/setup.ts
151
- async function resolveSetup(cwd, flags) {
152
- const hasConfigFile = flags.configPath ? existsSync(join4(cwd, flags.configPath)) : CONFIG_FILE_NAMES.some((name) => existsSync(join4(cwd, name)));
153
- if (hasConfigFile) {
154
- const { config, path } = await loadConfig(cwd, flags.configPath);
155
- if (flags.spec || flags.out) {
156
- console.warn(`Note: --spec/--out are ignored; ${basename(path)} is in charge here.`);
157
- }
158
- const data2 = await loadDataFile(cwd);
159
- const apps = await findWebApps(cwd);
160
- const app2 = apps.find((candidate) => candidate.dir === data2.app) ?? apps[0];
161
- return { config, label: basename(path), app: app2, fromConfigFile: true, remember: {} };
162
- }
163
- if (flags.configPath) {
164
- throw new Error(`No config file at "${flags.configPath}".`);
165
- }
166
- const data = await loadDataFile(cwd);
167
- const spec = flags.spec ?? data.spec ?? await detectSpec(cwd);
168
- let app;
169
- if (!flags.out) {
170
- const apps = await findWebApps(cwd);
171
- const remembered = apps.find((candidate) => candidate.dir === data.app);
172
- if (remembered) {
173
- app = remembered;
174
- } else if (apps.length === 1) {
175
- app = apps[0];
176
- console.log(`Found your web app: ${app?.dir} (${app?.framework})`);
177
- } else if (apps.length > 1) {
178
- app = await askWhichApp(apps);
179
- }
180
- }
181
- const outDir = flags.out ?? (app && app.dir !== "." ? `${app.dir}/src/webmcp` : "./src/webmcp");
182
- return {
183
- config: { sources: [openapi({ spec })], generate: [js({ outDir })] },
184
- label: flags.spec ? `--spec ${spec}` : `detected ${spec}`,
185
- app,
186
- fromConfigFile: false,
187
- remember: { spec, app: app?.dir }
188
- };
189
- }
190
- async function askWhichApp(apps) {
191
- if (!process.stdin.isTTY) {
192
- const first = apps[0];
193
- console.log(`Several packages look like web apps; using ${first.dir}. Override with --out.`);
194
- return first;
195
- }
196
- console.log("Several packages look like the web app. Which one should the tools live in?");
197
- apps.forEach((app, index) => {
198
- console.log(` ${index + 1}. ${app.dir} (${app.framework})${index === 0 ? " [default]" : ""}`);
199
- });
200
- const rl = createInterface({ input: process.stdin, output: process.stdout });
201
- try {
202
- const answer = await rl.question("Choice [1]: ");
203
- const picked = Number.parseInt(answer.trim() || "1", 10);
204
- return apps[picked - 1] ?? apps[0];
205
- } finally {
206
- rl.close();
207
- }
208
- }
209
- async function detectSpec(cwd) {
210
- const specs = await findSpecs(cwd);
211
- if (specs.length === 0) {
212
- throw new Error(
213
- "No OpenAPI spec found in this project.\nPoint at one: npx webmcp-codegen generate --spec path/to/openapi.json"
214
- );
215
- }
216
- if (specs.length > 1) {
217
- const list = specs.map((spec) => ` - ${spec}`).join("\n");
218
- throw new Error(
219
- `Found ${specs.length} API specs:
220
- ${list}
221
-
222
- Pick one: npx webmcp-codegen generate --spec ${specs[0]}`
223
- );
224
- }
225
- console.log(`Detected ${specs[0]} (override with --spec)`);
226
- return specs[0];
227
- }
228
-
229
- export {
230
- loadDataFile,
231
- saveDataFile,
232
- findSpecs,
233
- resolveSetup
234
- };
235
- //# sourceMappingURL=chunk-JVBVTHZ7.js.map