import { readFileSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; const root = resolve(import.meta.dirname, ".."); const source = readFileSync(resolve(root, "skills/paperclip/references/api-reference.md"), "utf8"); const key = (method, path) => `${method} ${path.replace(/:[A-Za-z][A-Za-z0-9_]*|\{[^}]+\}/g, "{}")}`; const entries = {}; let section = "Paperclip API Reference"; for (const line of source.split("\n")) { if (/^#{2,5} /.test(line)) section = line.replace(/^#+ /, ""); const table = /^\|\s*(GET|POST|PATCH|PUT|DELETE)\s*\|\s*`([^`]+)`\s*\|\s*(.*?)\s*\|/.exec(line); if (table) entries[key(table[1], table[2])] = { section, description: table[3] }; } for (const match of source.matchAll(/^(GET|POST|PATCH|PUT|DELETE) (\/api\/[^\s]+)\n(\{[^\n]*\}|\{\n[\s\S]*?\n\})/gm)) { try { const body = JSON.parse(match[3]); const id = key(match[1], match[2]); // Runtime consumers look up endpoint templates from OpenAPI. Narrative // URLs with literal resource IDs must not create unreachable entries. if (!entries[id]) continue; // Keep examples for each interaction kind / issue disposition, so new // question or waiting examples do not displace existing confirmation flows. const variant = body.kind ?? body.status ?? ""; const examples = entries[id].examples ??= []; if (examples.filter(({ body: example }) => (example.kind ?? example.status ?? "") === variant).length < 2) { examples.push({ body }); } } catch { /* Narrative/pseudocode blocks are not executable examples. */ } } const destination = resolve(root, "server/src/services/native-runtime/runner-api-reference.ts"); const output = `// Generated by scripts/generate-runner-api-reference.mjs from the legacy skill reference.\nexport const runnerApiReference: Record = ${JSON.stringify(entries, null, 2)};\n`; if (process.argv.includes("--check")) { if (readFileSync(destination, "utf8") !== output) throw new Error("Runner API skill enrichment is stale"); } else writeFileSync(destination, output);