webmcp-codegen 0.0.1 → 0.2.0

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 ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CONFIG_FILE_NAMES,
4
+ loadConfig,
5
+ runGenerate
6
+ } from "./chunk-R3DEBBQ3.js";
7
+ import {
8
+ js
9
+ } from "./chunk-GDJDVR4E.js";
10
+ import {
11
+ openapi
12
+ } from "./chunk-WYGVTIGI.js";
13
+ import "./chunk-BIKKPCRT.js";
14
+ import "./chunk-5L4KN6F4.js";
15
+
16
+ // src/cli.ts
17
+ import { existsSync, watch } from "fs";
18
+ import { writeFile } from "fs/promises";
19
+ import { basename, join as join2, relative as relative2 } from "path";
20
+ import { parseArgs } from "util";
21
+
22
+ // src/detect.ts
23
+ import { readdir } from "fs/promises";
24
+ import { join, relative } from "path";
25
+ var SPEC_FILE_PATTERN = /^(openapi|swagger|api)\.(ya?ml|json)$/i;
26
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
27
+ "node_modules",
28
+ ".git",
29
+ ".turbo",
30
+ ".next",
31
+ "dist",
32
+ "build",
33
+ "coverage"
34
+ ]);
35
+ var MAX_DEPTH = 5;
36
+ async function findSpecs(cwd) {
37
+ const found = [];
38
+ async function walk(dir, depth) {
39
+ if (depth > MAX_DEPTH) return;
40
+ let entries;
41
+ try {
42
+ entries = await readdir(dir, { withFileTypes: true });
43
+ } catch {
44
+ return;
45
+ }
46
+ for (const entry of entries) {
47
+ if (entry.isDirectory()) {
48
+ if (!IGNORED_DIRS.has(entry.name)) await walk(join(dir, entry.name), depth + 1);
49
+ } else if (SPEC_FILE_PATTERN.test(entry.name)) {
50
+ found.push({ path: join(dir, entry.name), depth });
51
+ }
52
+ }
53
+ }
54
+ await walk(cwd, 0);
55
+ return found.sort((a, b) => a.depth - b.depth).map((entry) => relative(cwd, entry.path));
56
+ }
57
+
58
+ // src/cli.ts
59
+ var HELP = `webmcp-codegen \u2014 generate WebMCP tools from the API contracts you already have
60
+
61
+ Fastest start (no install, no config):
62
+ npx webmcp-codegen generate --dry-run Detect your spec, preview the tools
63
+ npx webmcp-codegen generate Write the tool files
64
+
65
+ Commands:
66
+ init Write a codegen.config.mjs for full control
67
+ generate Generate (or update) your WebMCP tools
68
+ generate --watch Re-generate when files change
69
+
70
+ Flags for generate:
71
+ --spec PATH Which OpenAPI spec to use (auto-detected when omitted)
72
+ --out DIR Where the tool files go (default: ./src/webmcp)
73
+ --dry-run Preview what would be written, write nothing
74
+ --skip-audit Skip the safety report
75
+ --force Write files even when the audit reports errors
76
+ --config PATH Use a config file at PATH
77
+ `;
78
+ var CONFIG_FILE = "codegen.config.mjs";
79
+ async function main() {
80
+ const { positionals, values } = parseArgs({
81
+ allowPositionals: true,
82
+ options: {
83
+ "dry-run": { type: "boolean", default: false },
84
+ "skip-audit": { type: "boolean", default: false },
85
+ force: { type: "boolean", default: false },
86
+ watch: { type: "boolean", default: false },
87
+ config: { type: "string" },
88
+ spec: { type: "string" },
89
+ out: { type: "string" },
90
+ help: { type: "boolean", default: false }
91
+ }
92
+ });
93
+ const command = positionals[0];
94
+ if (values.help || !command) {
95
+ console.log(HELP);
96
+ return 0;
97
+ }
98
+ switch (command) {
99
+ case "init":
100
+ return init();
101
+ case "generate":
102
+ return generate({
103
+ dryRun: values["dry-run"],
104
+ skipAudit: values["skip-audit"],
105
+ force: values.force,
106
+ watch: values.watch,
107
+ configPath: values.config,
108
+ spec: values.spec,
109
+ out: values.out
110
+ });
111
+ default:
112
+ console.error(`Unknown command "${command}".
113
+ `);
114
+ console.log(HELP);
115
+ return 1;
116
+ }
117
+ }
118
+ async function init() {
119
+ const cwd = process.cwd();
120
+ const configPath = join2(cwd, CONFIG_FILE);
121
+ if (existsSync(configPath)) {
122
+ console.error(`${CONFIG_FILE} already exists \u2014 nothing to do.`);
123
+ return 1;
124
+ }
125
+ const specs = await findSpecs(cwd);
126
+ const specPath = specs.length > 0 ? `./${specs[0]}` : "./openapi.yaml";
127
+ await writeFile(
128
+ configPath,
129
+ `import { defineConfig } from "webmcp-codegen";
130
+ import { openapi } from "webmcp-codegen/sources";
131
+ import { js } from "webmcp-codegen/generators";
132
+
133
+ export default defineConfig({
134
+ sources: [openapi({ spec: "${specPath}" })],
135
+ generate: [js({ outDir: "./src/webmcp" })],
136
+ safety: {
137
+ // Extra field names to treat as PII, on top of the built-in list:
138
+ // piiFields: ["internalId"],
139
+ // Tools to skip entirely (matched against name and route):
140
+ // exclude: ["internal"],
141
+ },
142
+ });
143
+ `
144
+ );
145
+ console.log("Installed the package? A config file needs it:");
146
+ console.log(" npm install -D webmcp-codegen\n");
147
+ if (specs.length > 0) {
148
+ console.log(`Found ${specs[0]} \u2014 wrote ${CONFIG_FILE}.`);
149
+ console.log("\nNext: npx webmcp-codegen generate --dry-run");
150
+ } else {
151
+ console.log(`No OpenAPI spec found, so ${CONFIG_FILE} points at ./openapi.yaml.`);
152
+ console.log("Edit the `spec` path to point at your spec, then run:");
153
+ console.log("\n npx webmcp-codegen generate --dry-run");
154
+ }
155
+ return 0;
156
+ }
157
+ async function generate(flags) {
158
+ const cwd = process.cwd();
159
+ if (flags.watch) {
160
+ await watchLoop(cwd, flags);
161
+ return 0;
162
+ }
163
+ const result = await runOnce(cwd, flags);
164
+ return result.blocked ? 1 : 0;
165
+ }
166
+ async function resolveConfig(cwd, flags) {
167
+ const hasConfigFile = flags.configPath ? existsSync(join2(cwd, flags.configPath)) : CONFIG_FILE_NAMES.some((name) => existsSync(join2(cwd, name)));
168
+ if (hasConfigFile) {
169
+ const { config, path } = await loadConfig(cwd, flags.configPath);
170
+ if (flags.spec || flags.out) {
171
+ console.warn(`Note: --spec/--out are ignored \u2014 ${basename(path)} is in charge here.`);
172
+ }
173
+ return { config, label: basename(path) };
174
+ }
175
+ if (flags.configPath) {
176
+ throw new Error(`No config file at "${flags.configPath}".`);
177
+ }
178
+ const spec = flags.spec ?? await detectSpec(cwd);
179
+ const outDir = flags.out ?? "./src/webmcp";
180
+ return {
181
+ config: { sources: [openapi({ spec })], generate: [js({ outDir })] },
182
+ label: flags.spec ? `--spec ${spec}` : `detected ${spec}`
183
+ };
184
+ }
185
+ async function detectSpec(cwd) {
186
+ const specs = await findSpecs(cwd);
187
+ if (specs.length === 0) {
188
+ throw new Error(
189
+ "No OpenAPI spec found in this project.\nPoint at one: npx webmcp-codegen generate --spec path/to/openapi.json"
190
+ );
191
+ }
192
+ if (specs.length > 1) {
193
+ const list = specs.map((spec) => ` - ${spec}`).join("\n");
194
+ throw new Error(
195
+ `Found ${specs.length} API specs:
196
+ ${list}
197
+
198
+ Pick one: npx webmcp-codegen generate --spec ${specs[0]}`
199
+ );
200
+ }
201
+ console.log(`Detected ${specs[0]} (override with --spec)
202
+ `);
203
+ return specs[0];
204
+ }
205
+ async function runOnce(cwd, flags) {
206
+ const { config, label } = await resolveConfig(cwd, flags);
207
+ const result = await runGenerate(config, {
208
+ cwd,
209
+ dryRun: flags.dryRun,
210
+ skipAudit: flags.skipAudit,
211
+ force: flags.force
212
+ });
213
+ printReport(result, flags, label, cwd);
214
+ return result;
215
+ }
216
+ function printReport(result, flags, configName, cwd) {
217
+ const { tools, findings, files, blocked } = result;
218
+ console.log(`
219
+ webmcp-codegen (${configName}) \u2014 ${tools.length} tool(s)
220
+ `);
221
+ for (const tool of tools) {
222
+ console.log(` ${tool.name} [${tool.riskTier}] \u2190 ${tool.source.ref}`);
223
+ }
224
+ if (findings.length > 0) {
225
+ console.log("");
226
+ for (const finding of findings) {
227
+ const icon = finding.level === "error" ? "\u2716" : "\u26A0";
228
+ const where = finding.tool ? ` (${finding.tool})` : "";
229
+ console.log(` ${icon} ${finding.message}${where}`);
230
+ }
231
+ }
232
+ if (files.length > 0) {
233
+ console.log("");
234
+ for (const file of files) {
235
+ if (file.action === "unchanged" && !file.conflict) continue;
236
+ const shown = file.conflict ? `conflict \u2192 wrote ${relative2(cwd, file.conflict)}` : file.action;
237
+ console.log(` ${shown}: ${relative2(cwd, file.path)}`);
238
+ }
239
+ }
240
+ if (blocked) {
241
+ console.log(
242
+ "\nGeneration blocked by audit errors. Fix them, or re-run with --force to write anyway."
243
+ );
244
+ } else if (flags.dryRun) {
245
+ console.log("\nDry run \u2014 nothing written. Re-run without --dry-run to write these files.");
246
+ } else {
247
+ console.log("\nDone. Fill in each execute() below the marker, then registerAllTools().");
248
+ }
249
+ }
250
+ async function watchLoop(cwd, flags) {
251
+ await runOnce(cwd, { ...flags, dryRun: false });
252
+ console.log("\nWatching for changes\u2026 (Ctrl+C to stop)");
253
+ let timer;
254
+ watch(cwd, { recursive: true }, (_event, filename) => {
255
+ if (!filename) return;
256
+ if (/node_modules|\.git|\/dist|\/src\/webmcp/.test(filename)) return;
257
+ if (!/\.(ya?ml|json|ts|tsx|mts|mjs)$/.test(filename)) return;
258
+ clearTimeout(timer);
259
+ timer = setTimeout(() => {
260
+ runOnce(cwd, { ...flags, dryRun: false }).catch((error) => {
261
+ console.error(error instanceof Error ? error.message : error);
262
+ });
263
+ }, 300);
264
+ });
265
+ }
266
+ main().then((code) => process.exit(code)).catch((error) => {
267
+ console.error(error instanceof Error ? error.message : error);
268
+ process.exit(1);
269
+ });
270
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/detect.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 detects your API spec and\n * generates into ./src/webmcp. 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 { parseArgs } from \"node:util\";\nimport { CONFIG_FILE_NAMES, loadConfig } from \"./config.js\";\nimport { findSpecs } from \"./detect.js\";\nimport { js } from \"./generators/js.js\";\nimport { type GenerateResult, runGenerate } from \"./pipeline.js\";\nimport { openapi } from \"./sources/openapi.js\";\nimport type { CodegenConfig } from \"./types.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\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\nFlags for generate:\n --spec PATH Which OpenAPI spec to use (auto-detected when omitted)\n --out DIR Where the tool files go (default: ./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`;\n\nconst CONFIG_FILE = \"codegen.config.mjs\";\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 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 \"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\ninterface GenerateFlags {\n dryRun: boolean;\n skipAudit: boolean;\n force: boolean;\n watch: boolean;\n configPath?: string;\n spec?: string;\n out?: string;\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 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/**\n * Where the tools come from, in priority order:\n *\n * 1. a config file (codegen.config.mjs or --config) — full control\n * 2. --spec/--out flags — quick overrides, no config needed\n * 3. auto-detection — the zero-argument npx run\n *\n * Branches 2 and 3 build the config right here inside the CLI, which is\n * what makes `npx webmcp-codegen generate` work without installing the\n * package: the user's project never has to resolve a webmcp-codegen import.\n */\nasync function resolveConfig(\n cwd: string,\n flags: GenerateFlags,\n): Promise<{ config: CodegenConfig; label: string }> {\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 return { config, label: basename(path) };\n }\n if (flags.configPath) {\n throw new Error(`No config file at \"${flags.configPath}\".`);\n }\n\n const spec = flags.spec ?? (await detectSpec(cwd));\n const outDir = flags.out ?? \"./src/webmcp\";\n return {\n config: { sources: [openapi({ spec })], generate: [js({ outDir })] },\n label: flags.spec ? `--spec ${spec}` : `detected ${spec}`,\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`);\n return specs[0] as string;\n}\n\n/** One generate pass: resolve the config, run the pipeline, print the report. */\nasync function runOnce(cwd: string, flags: GenerateFlags): Promise<GenerateResult> {\n const { config, label } = await resolveConfig(cwd, flags);\n const result = await runGenerate(config, {\n cwd,\n dryRun: flags.dryRun,\n skipAudit: flags.skipAudit,\n force: flags.force,\n });\n printReport(result, flags, label, cwd);\n return result;\n}\n\n/**\n * The report is the product's voice: plain language, one line per file,\n * findings grouped by severity, and a summary that says what to do next.\n */\nfunction printReport(\n result: GenerateResult,\n flags: GenerateFlags,\n configName: string,\n cwd: string,\n): void {\n const { tools, findings, files, blocked } = result;\n\n console.log(`\\nwebmcp-codegen (${configName}) — ${tools.length} tool(s)\\n`);\n\n for (const tool of tools) {\n console.log(` ${tool.name} [${tool.riskTier}] ← ${tool.source.ref}`);\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 (blocked) {\n console.log(\n \"\\nGeneration blocked by audit errors. Fix them, or re-run with --force to write anyway.\",\n );\n } else if (flags.dryRun) {\n console.log(\"\\nDry run — nothing written. Re-run without --dry-run to write these files.\");\n } else {\n console.log(\"\\nDone. Fill in each execute() below the marker, then registerAllTools().\");\n }\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.\n if (/node_modules|\\.git|\\/dist|\\/src\\/webmcp/.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 * 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"],"mappings":"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","names":["join","relative","join","relative"]}
@@ -0,0 +1,35 @@
1
+ import { T as ToolGenerator } from '../types-Bf5MxWeH.js';
2
+
3
+ /**
4
+ * The `js` generator — named after what lands in your repo: plain JavaScript/
5
+ * TypeScript files that call the spec's imperative API
6
+ * (`document.modelContext.registerTool`).
7
+ *
8
+ * Output layout for `js({ outDir: "./src/webmcp" })`:
9
+ *
10
+ * src/webmcp/
11
+ * ├── runtime.webmcp.ts ← fully generated, never edit
12
+ * ├── index.ts ← fully generated, registers everything
13
+ * ├── get-order-status.webmcp.ts ← generated contract + YOUR execute()
14
+ * └── ...
15
+ *
16
+ * Each per-tool file has two regions, divided by marker comments:
17
+ *
18
+ * generated region schema, input type, tool definition, register()
19
+ * ── end generated ── everything below survives regeneration
20
+ * your region execute(), scaffolded once, then owned by you
21
+ *
22
+ * This file contains only the *file mechanics*: which files exist, and how to
23
+ * update them without destroying hand-written code. The text of the generated
24
+ * code itself lives in js-templates.ts — keeping "what the output looks like"
25
+ * separate from "how files get written" is what keeps both readable.
26
+ */
27
+
28
+ interface JsGeneratorOptions {
29
+ /** Where the tool files go, relative to the project root. */
30
+ outDir: string;
31
+ }
32
+ /** Create the `js` generator for the config's `generate` array. */
33
+ declare function js(options: JsGeneratorOptions): ToolGenerator;
34
+
35
+ export { js };
@@ -0,0 +1,8 @@
1
+ import {
2
+ js
3
+ } from "../chunk-GDJDVR4E.js";
4
+ import "../chunk-5L4KN6F4.js";
5
+ export {
6
+ js
7
+ };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,49 @@
1
+ import { C as CodegenConfig, R as ReviewedTool, A as AuditFinding, G as GeneratedFile } from './types-Bf5MxWeH.js';
2
+ export { a as CandidateTool, J as JsonSchema, b as RiskTier, c as SafetyOptions, d as SideEffect, S as Source, e as SourceKind, T as ToolGenerator, f as ToolHints } from './types-Bf5MxWeH.js';
3
+
4
+ /**
5
+ * Config: `defineConfig` for authoring, `loadConfig` for the CLI.
6
+ *
7
+ * Config files are plain JavaScript (`codegen.config.mjs`) so the CLI can
8
+ * load them with a plain dynamic import — no TypeScript loader, no build
9
+ * step, no extra dependencies. If you want types while authoring, that is
10
+ * what `defineConfig` is for:
11
+ *
12
+ * import { defineConfig } from "webmcp-codegen";
13
+ * export default defineConfig({ ... });
14
+ */
15
+
16
+ /** Identity function whose only job is type-checking the config object. */
17
+ declare function defineConfig(config: CodegenConfig): CodegenConfig;
18
+
19
+ /**
20
+ * The pipeline: sources → normalize → safety review → audit → write.
21
+ *
22
+ * This module is the only place the stages meet. It owns no opinions of its
23
+ * own — naming, safety, and file formats all live in their own modules — it
24
+ * just runs them in order and produces one honest report of what happened
25
+ * (or what *would* happen, when called with `write: false`).
26
+ */
27
+
28
+ interface GenerateOptions {
29
+ /** Project root. Everything (config, spec paths, outDir) resolves from here. */
30
+ cwd: string;
31
+ /** Preview mode: compute everything, write nothing. */
32
+ dryRun?: boolean;
33
+ /** Skip the audit pass entirely (classification still runs — output needs it). */
34
+ skipAudit?: boolean;
35
+ /** Write even when the audit found errors. The report still shows them. */
36
+ force?: boolean;
37
+ }
38
+ interface GenerateResult {
39
+ tools: ReviewedTool[];
40
+ findings: AuditFinding[];
41
+ files: GeneratedFile[];
42
+ /** True when audit errors stopped any file from being written. */
43
+ blocked: boolean;
44
+ /** True when this run actually wrote files (false for dry runs and blocks). */
45
+ wrote: boolean;
46
+ }
47
+ declare function runGenerate(config: CodegenConfig, options: GenerateOptions): Promise<GenerateResult>;
48
+
49
+ export { AuditFinding, CodegenConfig, type GenerateOptions, type GenerateResult, GeneratedFile, ReviewedTool, defineConfig, runGenerate };
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import {
2
+ defineConfig,
3
+ runGenerate
4
+ } from "./chunk-R3DEBBQ3.js";
5
+ import "./chunk-BIKKPCRT.js";
6
+ import "./chunk-5L4KN6F4.js";
7
+ export {
8
+ defineConfig,
9
+ runGenerate
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,28 @@
1
+ import { S as Source } from '../types-Bf5MxWeH.js';
2
+
3
+ /**
4
+ * The OpenAPI source.
5
+ *
6
+ * Reads an OpenAPI 3.x document (YAML or JSON) and turns every operation
7
+ * into a CandidateTool. This is the highest-reach source: most backend
8
+ * frameworks can already emit an OpenAPI spec, so teams get value without
9
+ * changing any application code.
10
+ *
11
+ * What we read from each operation:
12
+ * - name ← operationId, slugified (falls back to method + path)
13
+ * - description ← summary, else the first line of description, else a template
14
+ * - inputSchema ← path + query parameters merged with the JSON request body
15
+ * - outputSchema ← the first 2xx response's JSON schema, when present
16
+ *
17
+ * Header and cookie parameters are skipped on purpose: agents should not be
18
+ * setting those by hand, and auth headers are the app's job, not the tool's.
19
+ */
20
+
21
+ interface OpenApiSourceOptions {
22
+ /** Path to the OpenAPI document, relative to the project root. */
23
+ spec: string;
24
+ }
25
+ /** Create an OpenAPI source for the config's `sources` array. */
26
+ declare function openapi(options: OpenApiSourceOptions): Source;
27
+
28
+ export { openapi };
@@ -0,0 +1,9 @@
1
+ import {
2
+ openapi
3
+ } from "../chunk-WYGVTIGI.js";
4
+ import "../chunk-BIKKPCRT.js";
5
+ import "../chunk-5L4KN6F4.js";
6
+ export {
7
+ openapi
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The shared shapes that flow through the codegen pipeline.
3
+ *
4
+ * Every stage of the pipeline speaks in these types:
5
+ *
6
+ * Source → CandidateTool → (safety review) → ReviewedTool → Generator → GeneratedFile
7
+ *
8
+ * A source (OpenAPI today, tRPC/Zod later) only has to produce CandidateTools.
9
+ * A generator only has to turn ReviewedTools into files. Everything in between
10
+ * lives here so the stages stay independent.
11
+ */
12
+ /**
13
+ * A relaxed JSON Schema type. Real-world schemas (especially from OpenAPI)
14
+ * carry keywords we don't model individually, so unknown keywords are allowed
15
+ * through untouched instead of being rejected.
16
+ */
17
+ interface JsonSchema {
18
+ type?: string;
19
+ properties?: Record<string, JsonSchema>;
20
+ required?: string[];
21
+ items?: JsonSchema;
22
+ enum?: unknown[];
23
+ description?: string;
24
+ format?: string;
25
+ nullable?: boolean;
26
+ anyOf?: JsonSchema[];
27
+ oneOf?: JsonSchema[];
28
+ allOf?: JsonSchema[];
29
+ additionalProperties?: boolean | JsonSchema;
30
+ [keyword: string]: unknown;
31
+ }
32
+ /** Which source a candidate came from. Grows as new sources are added. */
33
+ type SourceKind = "openapi" | "trpc" | "zod" | "prisma" | "graphql" | "manual";
34
+ /**
35
+ * What the tool does to the world. The safety layer derives this from the
36
+ * HTTP method plus naming heuristics; it drives both the MCP hints and the
37
+ * risk tier.
38
+ */
39
+ type SideEffect = "read" | "write" | "destructive" | "unknown";
40
+ /** How dangerous the tool is to expose to an agent. */
41
+ type RiskTier = "safe-read" | "write-confirm" | "destructive-confirm";
42
+ /**
43
+ * A tool being considered for generation. Sources produce these; nothing is
44
+ * written to disk until the safety layer has reviewed every candidate.
45
+ */
46
+ interface CandidateTool {
47
+ /** Stable id used for diffing across regenerations. */
48
+ id: string;
49
+ /** The final tool name an agent will see (validated, de-duplicated). */
50
+ name: string;
51
+ /** Where this candidate came from, e.g. `{ kind: "openapi", ref: "GET /orders/{id}" }`. */
52
+ source: {
53
+ kind: SourceKind;
54
+ ref: string;
55
+ };
56
+ /** Always derived from the source contract, never hand-typed. */
57
+ inputSchema: JsonSchema;
58
+ /** Present when the source has response typing. */
59
+ outputSchema?: JsonSchema;
60
+ /** Name of the generated TypeScript input type, e.g. "GetOrderStatusInput". */
61
+ inputTypeName: string;
62
+ httpMethod?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
63
+ sideEffect: SideEffect;
64
+ requiresAuth: boolean;
65
+ /** Where the description text came from — always reviewable before commit. */
66
+ description: string;
67
+ descriptionSource: "openapi-summary" | "generated-template";
68
+ }
69
+ /** The MCP hints the spec defines for a tool, computed by the safety layer. */
70
+ interface ToolHints {
71
+ readOnlyHint: boolean;
72
+ destructiveHint: boolean;
73
+ idempotentHint: boolean;
74
+ }
75
+ /** A candidate after safety review: classified, hinted, and linted. */
76
+ interface ReviewedTool extends CandidateTool {
77
+ riskTier: RiskTier;
78
+ hints: ToolHints;
79
+ /**
80
+ * Output field paths the PII heuristics flagged (e.g. "user.email").
81
+ * These are fields that would leave the page and reach the agent.
82
+ */
83
+ piiInOutput: string[];
84
+ }
85
+ /** One audit finding. Errors block generation (unless --force); warnings don't. */
86
+ interface AuditFinding {
87
+ level: "error" | "warning";
88
+ /** Tool name this finding is about, or undefined for project-level findings. */
89
+ tool?: string;
90
+ message: string;
91
+ }
92
+ /** A file the generator wants to write. */
93
+ interface GeneratedFile {
94
+ /** Absolute path on disk. */
95
+ path: string;
96
+ /** Full new contents. */
97
+ contents: string;
98
+ /** What writing this file would do — used for the report and --dry-run. */
99
+ action: "create" | "update" | "unchanged";
100
+ /**
101
+ * Present when an existing file was edited by hand in the generated region,
102
+ * so we refused to touch it. The new contents go to a `.new` sibling instead.
103
+ */
104
+ conflict?: string;
105
+ }
106
+ /**
107
+ * A source reads an existing contract and produces candidate tools.
108
+ * Create one with a helper like `openapi({ spec: "./openapi.yaml" })`.
109
+ */
110
+ interface Source {
111
+ readonly kind: SourceKind;
112
+ collect(): Promise<CandidateTool[]>;
113
+ }
114
+ /**
115
+ * A generator turns reviewed tools into files.
116
+ * Named after what lands in your repo: `js`, `html`, `react`, `manifest`.
117
+ */
118
+ interface ToolGenerator {
119
+ readonly kind: string;
120
+ generate(tools: ReviewedTool[], cwd: string): Promise<GeneratedFile[]>;
121
+ }
122
+ /** Safety knobs. Everything here extends defaults; nothing is required. */
123
+ interface SafetyOptions {
124
+ /** Extra field names to treat as PII, on top of the built-in list. */
125
+ piiFields?: string[];
126
+ /** Tool names or source refs to skip entirely (substrings, case-insensitive). */
127
+ exclude?: string[];
128
+ }
129
+ /** The config file shape. Create it with `defineConfig` for type checking. */
130
+ interface CodegenConfig {
131
+ sources: Source[];
132
+ generate: ToolGenerator[];
133
+ safety?: SafetyOptions;
134
+ }
135
+
136
+ export type { AuditFinding as A, CodegenConfig as C, GeneratedFile as G, JsonSchema as J, ReviewedTool as R, Source as S, ToolGenerator as T, CandidateTool as a, RiskTier as b, SafetyOptions as c, SideEffect as d, SourceKind as e, ToolHints as f };
package/package.json CHANGED
@@ -1,17 +1,37 @@
1
1
  {
2
2
  "name": "webmcp-codegen",
3
- "version": "0.0.1",
4
- "description": "Generate safe, typed, human-reviewed WebMCP tools from the API contracts you already have (OpenAPI, tRPC, Zod). Under active development.",
3
+ "version": "0.2.0",
4
+ "description": "Generate safe, typed, human-reviewed WebMCP tools from the API contracts you already have (OpenAPI, tRPC, Zod).",
5
+ "license": "MIT",
5
6
  "type": "module",
6
- "main": "./src/index.js",
7
+ "sideEffects": false,
8
+ "bin": {
9
+ "webmcp-codegen": "./dist/cli.js"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
7
13
  "exports": {
8
- ".": "./src/index.js"
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./sources": {
19
+ "types": "./dist/sources/index.d.ts",
20
+ "import": "./dist/sources/index.js"
21
+ },
22
+ "./generators": {
23
+ "types": "./dist/generators/index.d.ts",
24
+ "import": "./dist/generators/index.js"
25
+ }
9
26
  },
10
27
  "files": [
11
- "src"
28
+ "dist"
12
29
  ],
13
- "engines": {
14
- "node": ">=20"
30
+ "scripts": {
31
+ "build": "tsup src/index.ts src/cli.ts src/sources/index.ts src/generators/index.ts --format esm --dts --sourcemap --clean",
32
+ "dev": "tsup src/index.ts src/cli.ts src/sources/index.ts src/generators/index.ts --format esm --dts --sourcemap --watch",
33
+ "test": "vitest run",
34
+ "typecheck": "tsc --noEmit"
15
35
  },
16
36
  "keywords": [
17
37
  "webmcp",
@@ -22,10 +42,18 @@
22
42
  "zod",
23
43
  "model-context-protocol"
24
44
  ],
25
- "license": "MIT",
45
+ "engines": {
46
+ "node": ">=20"
47
+ },
26
48
  "repository": {
27
49
  "type": "git",
28
50
  "url": "git+https://github.com/SouravInsights/groundstate.git",
29
51
  "directory": "packages/codegen"
52
+ },
53
+ "dependencies": {
54
+ "yaml": "^2.8.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^22.20.1"
30
58
  }
31
59
  }