@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.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.
Files changed (380) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +122 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +108 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +195 -255
  6. package/ROADMAP.md +143 -15
  7. package/SECURITY.md +31 -9
  8. package/dist/BUILD-MANIFEST.json +72 -47
  9. package/dist/adapters.js +4 -23
  10. package/dist/agent-lists.js +1 -1
  11. package/dist/agents-guide.js +113 -0
  12. package/dist/authoring-files.js +60 -0
  13. package/dist/authoring.js +11 -1
  14. package/dist/aws.js +4 -3
  15. package/dist/build-cloudflare.js +11 -24
  16. package/dist/build-static.js +134 -0
  17. package/dist/bulk.js +37 -0
  18. package/dist/capabilities.js +262 -0
  19. package/dist/capability-query.js +71 -0
  20. package/dist/catalog.js +105 -0
  21. package/dist/cli.js +165 -34
  22. package/dist/client-address.js +1 -1
  23. package/dist/compliance-rules/baseline.js +9 -17
  24. package/dist/compliance-rules/privacy.js +7 -18
  25. package/dist/compliance-rules/shared.js +0 -2
  26. package/dist/compliance-rules/strict.js +5 -5
  27. package/dist/compliance.js +6 -8
  28. package/dist/conditions.js +88 -0
  29. package/dist/config.js +69 -6
  30. package/dist/context.js +155 -0
  31. package/dist/ecosystem-cli.js +88 -0
  32. package/dist/egress.js +98 -0
  33. package/dist/examples.js +92 -0
  34. package/dist/explain-cli.js +64 -0
  35. package/dist/explain.js +131 -0
  36. package/dist/extensions.js +231 -0
  37. package/dist/function-sources.js +49 -5
  38. package/dist/function-worker.js +3 -1
  39. package/dist/functions.js +84 -13
  40. package/dist/guest-api.js +29 -3
  41. package/dist/index.js +40 -6
  42. package/dist/init-with.js +165 -0
  43. package/dist/interchange-cli.js +42 -0
  44. package/dist/interchange.js +189 -0
  45. package/dist/manifest.js +109 -0
  46. package/dist/match.js +2 -2
  47. package/dist/mcp-authoring.js +147 -0
  48. package/dist/mcp.js +97 -0
  49. package/dist/observability.js +7 -21
  50. package/dist/operator-host.js +29 -0
  51. package/dist/plugins.js +12 -0
  52. package/dist/policies/agents.js +2 -2
  53. package/dist/policies/cache.js +8 -3
  54. package/dist/policies/compression.js +2 -1
  55. package/dist/policies/security.js +0 -0
  56. package/dist/policies.js +1 -1
  57. package/dist/policy.js +56 -15
  58. package/dist/prerender.js +100 -41
  59. package/dist/project-tests.js +3 -3
  60. package/dist/provider-verification.js +92 -0
  61. package/dist/proxy.js +44 -0
  62. package/dist/readiness.js +34 -11
  63. package/dist/recipes.js +41 -0
  64. package/dist/route-diff.js +106 -0
  65. package/dist/router.js +45 -7
  66. package/dist/runtime.js +164 -64
  67. package/dist/sandbox.js +48 -0
  68. package/dist/scaffold.js +0 -0
  69. package/dist/schema-query.js +62 -0
  70. package/dist/scripts/operational-drills.js +12 -54
  71. package/dist/server.js +3 -29
  72. package/dist/signals.js +24 -0
  73. package/dist/site.js +0 -0
  74. package/dist/tooling.js +96 -0
  75. package/dist/trusted-functions.js +210 -0
  76. package/dist/types/adapters.d.ts +7 -4
  77. package/dist/types/agent-lists.d.ts +0 -1
  78. package/dist/types/agents-guide.d.ts +17 -0
  79. package/dist/types/authoring-files.d.ts +10 -0
  80. package/dist/types/aws.d.ts +3 -1
  81. package/dist/types/build-cloudflare.d.ts +1 -0
  82. package/dist/types/build-static.d.ts +43 -0
  83. package/dist/types/bulk.d.ts +27 -0
  84. package/dist/types/capabilities.d.ts +64 -0
  85. package/dist/types/capability-query.d.ts +24 -0
  86. package/dist/types/catalog.d.ts +65 -0
  87. package/dist/types/client-address.d.ts +0 -1
  88. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  89. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  90. package/dist/types/compliance-rules/shared.d.ts +0 -2
  91. package/dist/types/compliance-rules/strict.d.ts +0 -5
  92. package/dist/types/compliance.d.ts +0 -3
  93. package/dist/types/conditions.d.ts +19 -0
  94. package/dist/types/config.d.ts +21 -2
  95. package/dist/types/context.d.ts +66 -0
  96. package/dist/types/ecosystem-cli.d.ts +17 -0
  97. package/dist/types/egress.d.ts +46 -0
  98. package/dist/types/examples.d.ts +50 -0
  99. package/dist/types/explain-cli.d.ts +11 -0
  100. package/dist/types/explain.d.ts +95 -0
  101. package/dist/types/extensions.d.ts +177 -0
  102. package/dist/types/function-sources.d.ts +9 -0
  103. package/dist/types/functions.d.ts +48 -5
  104. package/dist/types/guest-api.d.ts +1 -0
  105. package/dist/types/index.d.ts +36 -6
  106. package/dist/types/init-with.d.ts +30 -0
  107. package/dist/types/interchange-cli.d.ts +16 -0
  108. package/dist/types/interchange.d.ts +42 -0
  109. package/dist/types/manifest.d.ts +79 -0
  110. package/dist/types/match.d.ts +1 -0
  111. package/dist/types/mcp-authoring.d.ts +92 -0
  112. package/dist/types/mcp.d.ts +12 -0
  113. package/dist/types/observability.d.ts +3 -14
  114. package/dist/types/operator-host.d.ts +8 -0
  115. package/dist/types/plugins.d.ts +2 -0
  116. package/dist/types/policies/agents.d.ts +0 -2
  117. package/dist/types/policies/compression.d.ts +2 -0
  118. package/dist/types/policies/security.d.ts +0 -1
  119. package/dist/types/policy.d.ts +15 -4
  120. package/dist/types/project-tests.d.ts +3 -2
  121. package/dist/types/provider-verification.d.ts +53 -0
  122. package/dist/types/proxy.d.ts +21 -0
  123. package/dist/types/readiness.d.ts +10 -3
  124. package/dist/types/recipes.d.ts +30 -0
  125. package/dist/types/route-diff.d.ts +27 -0
  126. package/dist/types/router.d.ts +2 -1
  127. package/dist/types/runtime.d.ts +11 -27
  128. package/dist/types/sandbox.d.ts +12 -0
  129. package/dist/types/scaffold.d.ts +0 -2
  130. package/dist/types/schema-query.d.ts +12 -0
  131. package/dist/types/server.d.ts +1 -4
  132. package/dist/types/signals.d.ts +25 -0
  133. package/dist/types/site.d.ts +0 -1
  134. package/dist/types/tooling.d.ts +115 -0
  135. package/dist/types/trusted-functions.d.ts +29 -0
  136. package/dist/types/types.d.ts +71 -7
  137. package/dist/types/typescript-authoring.d.ts +12 -0
  138. package/dist/types/vercel.d.ts +3 -1
  139. package/dist/types/verify-deployment.d.ts +47 -0
  140. package/dist/types.js +37 -5
  141. package/dist/typescript-authoring.js +142 -0
  142. package/dist/vercel.js +4 -3
  143. package/dist/verify-deployment.js +270 -0
  144. package/docs/AI-AUTHORING.md +232 -15
  145. package/docs/AWS.md +4 -4
  146. package/docs/BEST-PRACTICES.md +3 -2
  147. package/docs/BULK.md +79 -0
  148. package/docs/CAPABILITIES.md +192 -0
  149. package/docs/CAPACITY.md +129 -32
  150. package/docs/CI.md +142 -0
  151. package/docs/CLOUDFLARE.md +1 -2
  152. package/docs/COMPLIANCE.md +6 -9
  153. package/docs/CONDITIONS.md +74 -0
  154. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  155. package/docs/EGRESS.md +125 -0
  156. package/docs/EXTENSIONS.md +398 -0
  157. package/docs/FRAMEWORK.md +198 -0
  158. package/docs/FUNCTION-SECURITY.md +129 -32
  159. package/docs/INSTALL.md +45 -12
  160. package/docs/INTERCHANGE.md +134 -0
  161. package/docs/LOAD-TESTING.md +4 -4
  162. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  163. package/docs/MIDDLEWARE.md +29 -16
  164. package/docs/MONITORING.md +2 -19
  165. package/docs/NEXT-PHASE-PLAN.md +98 -0
  166. package/docs/NEXT-STEPS.md +634 -0
  167. package/docs/OBSERVABILITY.md +11 -18
  168. package/docs/OPEN-DECISIONS.md +212 -0
  169. package/docs/OPERATIONAL-PROOF.md +30 -31
  170. package/docs/OPERATIONS.md +29 -35
  171. package/docs/PLUGINS.md +37 -0
  172. package/docs/POLICIES.md +23 -309
  173. package/docs/PRERENDER.md +41 -1
  174. package/docs/PROJECT-DIRECTION.md +75 -8
  175. package/docs/PROVIDER-VERIFICATION.md +84 -0
  176. package/docs/READINESS.md +21 -1
  177. package/docs/README.md +87 -34
  178. package/docs/RECIPES.md +99 -0
  179. package/docs/RELEASE-READINESS.md +57 -35
  180. package/docs/RELEASE-SECURITY.md +116 -7
  181. package/docs/RESILIENCE.md +16 -15
  182. package/docs/ROUTING.md +8 -10
  183. package/docs/SANDBOX-REVIEW.md +19 -6
  184. package/docs/SCAFFOLDING.md +0 -2
  185. package/docs/SECURITY-AUDIT.md +41 -1
  186. package/docs/SPECIFICATION.md +150 -29
  187. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
  188. package/docs/SPIKE-BUSINESS-SUITE.md +1021 -0
  189. package/docs/SPIKE-CORE-LAYERING.md +337 -0
  190. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
  191. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  192. package/docs/SPIKE-EXTENSIONS.md +6 -0
  193. package/docs/SPIKE-LAMBDA-COMPILE.md +201 -0
  194. package/docs/SPIKE-MONOREPO.md +322 -0
  195. package/docs/STANDARDS.md +150 -142
  196. package/docs/STARTERS.md +21 -1
  197. package/docs/STATIC.md +94 -0
  198. package/docs/TOOLING.md +295 -0
  199. package/docs/TUNNELS.md +0 -3
  200. package/docs/TYPESCRIPT-AUTHORING.md +82 -0
  201. package/docs/TYPESCRIPT.md +25 -4
  202. package/docs/USABILITY-REVIEW.md +129 -0
  203. package/docs/VERCEL.md +4 -5
  204. package/docs/VERSION-ALIGNMENT.md +205 -0
  205. package/docs/YAML-GUIDE.md +15 -479
  206. package/docs/YAML-REFERENCE.md +143 -22
  207. package/docs/policies/agents.md +1 -1
  208. package/docs/policies/cache.md +13 -0
  209. package/docs/policies/contract.md +52 -0
  210. package/docs/policies/hardened.md +56 -0
  211. package/docs/policies/interoperability.md +169 -0
  212. package/docs/policies/operations.md +45 -0
  213. package/docs/yaml/assets.md +36 -0
  214. package/docs/yaml/conditions.md +20 -0
  215. package/docs/yaml/functions.md +160 -0
  216. package/docs/yaml/middleware.md +29 -0
  217. package/docs/yaml/organization.md +74 -0
  218. package/docs/yaml/policies.md +37 -0
  219. package/docs/yaml/redirects.md +64 -0
  220. package/docs/yaml/responses.md +57 -0
  221. package/docs/yaml/site.md +24 -0
  222. package/examples/assets/example.yaml +17 -0
  223. package/examples/aws/example.yaml +20 -0
  224. package/examples/cloudflare/example.yaml +19 -0
  225. package/examples/compliance/example.yaml +11 -0
  226. package/examples/conditions/README.md +12 -0
  227. package/examples/conditions/example.yaml +19 -0
  228. package/examples/conditions/tests/requests.json +13 -0
  229. package/examples/conditions/urlcode.yaml +24 -0
  230. package/examples/cookbook/README.md +8 -4
  231. package/examples/cookbook/example.yaml +17 -0
  232. package/examples/cookbook/functions/catalog.mjs +3 -0
  233. package/examples/cookbook/functions/fail.mjs +4 -0
  234. package/examples/cookbook/functions/items.mjs +3 -0
  235. package/examples/cookbook/functions/profile.mjs +3 -0
  236. package/examples/cookbook/functions/resource.mjs +3 -0
  237. package/examples/cookbook/functions/status.mjs +3 -0
  238. package/examples/cookbook/middleware/auth.mjs +48 -0
  239. package/examples/cookbook/middleware/body.mjs +15 -0
  240. package/examples/cookbook/middleware/bucket.mjs +29 -0
  241. package/examples/cookbook/middleware/cors.mjs +21 -0
  242. package/examples/cookbook/middleware/debug.mjs +13 -0
  243. package/examples/cookbook/middleware/envelope.mjs +11 -0
  244. package/examples/cookbook/middleware/errors.mjs +11 -0
  245. package/examples/cookbook/middleware/etag.mjs +18 -0
  246. package/examples/cookbook/middleware/locale.mjs +20 -0
  247. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  248. package/examples/cookbook/middleware/methods.mjs +15 -0
  249. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  250. package/examples/cookbook/middleware/referer.mjs +12 -0
  251. package/examples/cookbook/middleware/request-id.mjs +16 -0
  252. package/examples/cookbook/route-index.json +676 -0
  253. package/examples/cookbook/routes/middleware.yaml +126 -0
  254. package/examples/cookbook/tests/requests.json +526 -0
  255. package/examples/cookbook/urlcode.yaml +1 -0
  256. package/examples/egress/README.md +22 -0
  257. package/examples/egress/example.yaml +19 -0
  258. package/examples/egress/urlcode.yaml +19 -0
  259. package/examples/extensions/README.md +7 -0
  260. package/examples/extensions/example.yaml +21 -0
  261. package/examples/extensions/urlcode.yaml +25 -0
  262. package/examples/monitoring/example.yaml +8 -0
  263. package/examples/prerender/README.md +2 -2
  264. package/examples/prerender/example.yaml +16 -0
  265. package/examples/provider-conformance/README.md +12 -0
  266. package/examples/provider-conformance/example.yaml +14 -0
  267. package/examples/provider-conformance/urlcode.yaml +34 -0
  268. package/examples/tunnel/example.yaml +8 -0
  269. package/examples/vercel/example.yaml +19 -0
  270. package/llms-full.txt +3084 -0
  271. package/llms.txt +61 -21
  272. package/package.json +36 -7
  273. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  274. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +122 -0
  275. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +108 -0
  276. package/recipes/authenticated-json-api/README.md +51 -0
  277. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  278. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  279. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  280. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  281. package/recipes/contact-form/README.md +25 -0
  282. package/recipes/contact-form/functions/contact.mjs +17 -0
  283. package/recipes/contact-form/recipe.yaml +33 -0
  284. package/recipes/contact-form/tests/requests.json +47 -0
  285. package/recipes/contact-form/urlcode.yaml +18 -0
  286. package/recipes/cors-api/README.md +16 -0
  287. package/recipes/cors-api/functions/items.mjs +3 -0
  288. package/recipes/cors-api/middleware/cors.mjs +21 -0
  289. package/recipes/cors-api/recipe.yaml +26 -0
  290. package/recipes/cors-api/tests/requests.json +65 -0
  291. package/recipes/cors-api/urlcode.yaml +12 -0
  292. package/recipes/health-page/README.md +13 -0
  293. package/recipes/health-page/recipe.yaml +23 -0
  294. package/recipes/health-page/tests/requests.json +36 -0
  295. package/recipes/health-page/urlcode.yaml +19 -0
  296. package/recipes/json-api/README.md +6 -0
  297. package/recipes/json-api/functions/echo.mjs +3 -0
  298. package/recipes/json-api/recipe.yaml +25 -0
  299. package/recipes/json-api/tests/requests.json +34 -0
  300. package/recipes/json-api/urlcode.yaml +12 -0
  301. package/recipes/middleware/README.md +34 -0
  302. package/recipes/middleware/functions/catalog.mjs +3 -0
  303. package/recipes/middleware/functions/fail.mjs +4 -0
  304. package/recipes/middleware/functions/items.mjs +3 -0
  305. package/recipes/middleware/functions/profile.mjs +3 -0
  306. package/recipes/middleware/functions/resource.mjs +3 -0
  307. package/recipes/middleware/functions/status.mjs +3 -0
  308. package/recipes/middleware/middleware/auth.mjs +48 -0
  309. package/recipes/middleware/middleware/body.mjs +15 -0
  310. package/recipes/middleware/middleware/bucket.mjs +29 -0
  311. package/recipes/middleware/middleware/cors.mjs +21 -0
  312. package/recipes/middleware/middleware/debug.mjs +13 -0
  313. package/recipes/middleware/middleware/envelope.mjs +11 -0
  314. package/recipes/middleware/middleware/errors.mjs +11 -0
  315. package/recipes/middleware/middleware/etag.mjs +18 -0
  316. package/recipes/middleware/middleware/locale.mjs +20 -0
  317. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  318. package/recipes/middleware/middleware/methods.mjs +15 -0
  319. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  320. package/recipes/middleware/middleware/referer.mjs +12 -0
  321. package/recipes/middleware/middleware/request-id.mjs +16 -0
  322. package/recipes/middleware/public/guide.txt +1 -0
  323. package/recipes/middleware/recipe.yaml +50 -0
  324. package/recipes/middleware/tests/requests.json +528 -0
  325. package/recipes/middleware/urlcode.yaml +127 -0
  326. package/recipes/protected-download/README.md +22 -0
  327. package/recipes/protected-download/files/report.txt +1 -0
  328. package/recipes/protected-download/recipe.yaml +31 -0
  329. package/recipes/protected-download/tests/requests.json +32 -0
  330. package/recipes/protected-download/urlcode.yaml +15 -0
  331. package/recipes/redirect/README.md +7 -0
  332. package/recipes/redirect/recipe.yaml +25 -0
  333. package/recipes/redirect/tests/requests.json +19 -0
  334. package/recipes/redirect/urlcode.yaml +9 -0
  335. package/recipes/static-plus-api/README.md +15 -0
  336. package/recipes/static-plus-api/functions/info.mjs +3 -0
  337. package/recipes/static-plus-api/public/assets/index.html +3 -0
  338. package/recipes/static-plus-api/public/assets/site.css +1 -0
  339. package/recipes/static-plus-api/public/index.html +8 -0
  340. package/recipes/static-plus-api/recipe.yaml +29 -0
  341. package/recipes/static-plus-api/tests/requests.json +56 -0
  342. package/recipes/static-plus-api/urlcode.yaml +23 -0
  343. package/recipes/typescript/README.md +8 -0
  344. package/recipes/typescript/functions/hello.ts +5 -0
  345. package/recipes/typescript/recipe.yaml +23 -0
  346. package/recipes/typescript/tests/requests.json +18 -0
  347. package/recipes/typescript/urlcode.yaml +5 -0
  348. package/recipes/webhook-receiver/README.md +20 -0
  349. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  350. package/recipes/webhook-receiver/recipe.yaml +27 -0
  351. package/recipes/webhook-receiver/tests/requests.json +59 -0
  352. package/recipes/webhook-receiver/urlcode.yaml +23 -0
  353. package/schemas/recipe.schema.json +139 -0
  354. package/schemas/urlcode.schema.json +659 -110
  355. package/skills/urlcode/SKILL.md +119 -0
  356. package/starters/default/.github/workflows/urlcode.yml +23 -0
  357. package/starters/default/.mcp.json +12 -0
  358. package/starters/default/AGENTS.md +79 -0
  359. package/starters/default/urlcode.yaml +0 -1
  360. package/dist/link-api.js +0 -136
  361. package/dist/link-cli.js +0 -141
  362. package/dist/link-events.js +0 -76
  363. package/dist/link-records.js +0 -31
  364. package/dist/link-store-worker.js +0 -150
  365. package/dist/link-store.js +0 -250
  366. package/dist/management-policy.js +0 -41
  367. package/dist/sqlite-version.js +0 -6
  368. package/dist/types/link-api.d.ts +0 -30
  369. package/dist/types/link-cli.d.ts +0 -36
  370. package/dist/types/link-events.d.ts +0 -27
  371. package/dist/types/link-records.d.ts +0 -11
  372. package/dist/types/link-store-worker.d.ts +0 -1
  373. package/dist/types/link-store.d.ts +0 -130
  374. package/dist/types/management-policy.d.ts +0 -9
  375. package/dist/types/sqlite-version.d.ts +0 -1
  376. package/docs/DYNAMIC-LINKS.md +0 -561
  377. package/docs/MANAGEMENT-SECURITY.md +0 -82
  378. package/examples/live-links/README.md +0 -11
  379. package/examples/live-links/tests/requests.json +0 -6
  380. package/examples/live-links/urlcode.yaml +0 -16
package/docs/STARTERS.md CHANGED
@@ -30,7 +30,9 @@ Neither path forks the runtime or needs a hosting account or database.
30
30
  ## Files and growth
31
31
 
32
32
  `urlcode.yaml` includes a function route file and a redirect file in a nested
33
- folder. A JavaScript function, HTTP assertions and optional Makefile are included.
33
+ folder. A JavaScript function, HTTP assertions and optional Makefile are included,
34
+ plus `.github/workflows/urlcode.yml`, which runs the [project checks action](CI.md)
35
+ on every push and pull request once the project is on GitHub.
34
36
  See [organization](ORGANIZATION.md) for choosing your own layout. Defaults allow
35
37
  GET/HEAD and use redirect 302; add configuration only when changing behavior.
36
38
 
@@ -40,6 +42,24 @@ application files. Add pages, downloads, more functions and business-specific
40
42
  features to this same project. Update tests and the expected route count as it grows.
41
43
  See [readiness](READINESS.md) and [security](FUNCTION-SECURITY.md).
42
44
 
45
+ To start an extended site instead, install the extension packages from npm
46
+ (`npm install @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin`, published as
47
+ `0.1.0-alpha.x` prereleases) in the directory you run from and pass their names: `urlcode init ../my-site --with
48
+ auth,admin` writes the same starter under `my-site/app/`, merges each package's
49
+ routes and declarations into it, and generates one `host.mjs` and README beside
50
+ it. The contract each package fulfils is in [extensions](EXTENSIONS.md#scaffolding-with-init---with).
51
+
52
+ Both paths carry an `AGENTS.md` for repository-aware assistants. `urlcode init`
53
+ generates it from the installed runtime's capability catalog (the same source as
54
+ `urlcode capabilities`), so it names only the handlers, policies and site keys
55
+ that version implements, plus the exact `validate`, `test` and `audit` commands
56
+ with the starter's route count. The committed copy in `starters/default` is
57
+ regenerated from the same function and a test keeps the two identical. The file
58
+ points at the agent skill the package ships at `skills/urlcode/SKILL.md`.
59
+ Both paths also write `.mcp.json`, which registers the read-only `urlcode mcp`
60
+ server for Claude Code and Codex (`--project app` for an extended site); it is
61
+ never overwritten and carries no `--allow-authoring` ([tooling](TOOLING.md#registering-the-server)).
62
+
43
63
  `examples/assets` contains a larger runnable file/page/download demonstration:
44
64
  `make dev PROJECT=examples/assets`. It is an example, not a separate init choice.
45
65
 
package/docs/STATIC.md ADDED
@@ -0,0 +1,94 @@
1
+ # Deploying to static hosting (S3 + CloudFront)
2
+
3
+ The `static` target is the bottom rung of the [capability
4
+ ladder](SPIKE-CORE-LAYERING.md#the-full-ladder-one-contract-one-vocabulary-per-level):
5
+ the same `urlcode.yaml` a self-hosted server or a serverless adapter runs, with
6
+ no server process at all. `urlcode build --target static` compiles a project
7
+ ahead of time into plain files and two small JSON manifests meant for an S3
8
+ bucket served through CloudFront — nothing here executes a request.
9
+
10
+ ```sh
11
+ urlcode build --target static --project . --out dist --origin https://links.example
12
+ ```
13
+
14
+ ## Declarative routes only, and less than that
15
+
16
+ This target serves **redirects**, **declared responses** (`respond:`) and
17
+ static files (`page`, `static`, `download`) — no path parameters, no query
18
+ passthrough or mapping, GET/HEAD only, and no `enabled: false` or `expires`
19
+ (there is no server to answer a disabled or expired route with 404/410, so the
20
+ build refuses one instead of silently serving it forever). Everything else is
21
+ refused **at build time**, with the route pattern and the reason named:
22
+
23
+ | Handler / feature | Why it is refused |
24
+ | --- | --- |
25
+ | `function`, `middleware` | no server, so no dynamic or sandboxed execution |
26
+ | `extension`, `policies.extensions` | no server, so no operator extension registry |
27
+ | `proxy`, `signals` | no server, so no bounded or fire-and-forget egress |
28
+ | `conditional`, `match` (`conditions`) | no server, so no request-time condition matching |
29
+ | `parameters`, `request.body` | no server, so no request-time validation |
30
+ | `response.headers` | no server, so no per-request headers; set them as S3 object metadata or a CloudFront response headers policy instead |
31
+ | `env`, `secrets` (`bindings`) | no server, so no per-request binding resolution |
32
+ | every `policies.*` | no server, so no runtime policy enforcement |
33
+ | a redirect with a `{parameter}` in its path | S3's per-object redirect is keyed to one exact object, not a pattern |
34
+ | a redirect with `query.pass`/`query.map` | S3's per-object redirect cannot compute a target per request |
35
+ | a redirect with a `status` other than 301 | S3's per-object website redirect always answers 301 |
36
+ | a route declaring methods other than GET/HEAD | static hosting only ever answers GET/HEAD |
37
+ | a route with `enabled: false` or `expires` | no server to answer a disabled/expired route; remove the route instead |
38
+
39
+ Run `urlcode capabilities --target static` for the full catalog.
40
+
41
+ ## What the build emits
42
+
43
+ `--out` (default `dist/static`) receives:
44
+
45
+ - `objects/<key>` — the actual files to upload, one per served route. A `page`
46
+ or `download` route becomes one file; a `static` mount becomes one file per
47
+ entry in its directory. Object keys match the S3 convention: the route
48
+ pattern with its leading `/` stripped, and `/` itself becomes `index.html`
49
+ (matching a bucket's own "Index document" setting). A redirect route also
50
+ gets a zero-byte placeholder object at its key, so it exists to carry
51
+ metadata.
52
+ - `objects.json` — `{ format, objects: [{ key, contentType, cacheControl?,
53
+ contentDisposition? }] }`. Plain file upload loses this metadata (S3 does not
54
+ reliably infer content type or cache headers from an extension-less key), so
55
+ a deploy step reads this file and sets each object's own `Content-Type`,
56
+ `Cache-Control` and `Content-Disposition` from it, for example with
57
+ `aws s3 cp --content-type ... --cache-control ...` per entry, or an
58
+ equivalent `aws s3 sync` post-processing step.
59
+ - `redirects.json` — `{ format, redirects: [{ key, location, status: 301 }] }`.
60
+ Each entry is applied as the S3 object's
61
+ [website redirect location](https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-page-redirect.html)
62
+ metadata (`x-amz-website-redirect-location`), for example
63
+ `aws s3api put-object --website-redirect-location <location> --bucket ... --key <key>`.
64
+ S3 always answers a request for that key with a 301 to `location`
65
+ regardless of the object's own content — this is why a declared `status`
66
+ other than 301 is refused rather than silently downgraded.
67
+ - `manifest.json` — the same project-level semantic manifest every target
68
+ writes (see [tooling](TOOLING.md)).
69
+
70
+ None of these are edited by hand, and `dist/` belongs in `.gitignore`. This
71
+ target ships no runtime import (there is nothing to run), so there is no
72
+ `@jimhoyd/urlcode/static` entry point — the build output is the deployment.
73
+
74
+ ## What this is not: GitHub Pages
75
+
76
+ **This target's claimed support is S3 + CloudFront, not GitHub Pages.**
77
+ GitHub Pages has no per-object redirect metadata and no server-side rewrite
78
+ layer, so a `redirect` route compiled the way this target compiles it (S3
79
+ object metadata) has no equivalent there — the only options are a
80
+ meta-refresh/JavaScript page (visibly a redirect, not an HTTP one: search
81
+ engines, curl and anything that does not run JavaScript sees the source page,
82
+ not a 3xx) or a static 404-page trick, both lower fidelity than what every
83
+ other target in this project does for the same route. Rather than claim a
84
+ portability promise this platform cannot keep, GitHub Pages is out of scope
85
+ for this target. If you need Pages, treat it as a distinct target with its own
86
+ explicit fidelity caveat, not a rename of this one.
87
+
88
+ ## Verification status
89
+
90
+ This target has local build tests only (`test/static.test.ts`): the compiled
91
+ object layout, the redirect manifest, and every refusal above. **It has not
92
+ been deployed to S3 or fronted by CloudFront.** Bucket policy, CloudFront
93
+ caching behavior, TLS/domain setup and the exact `aws s3` invocations above are
94
+ unverified until a real deployment exercises them.
@@ -0,0 +1,295 @@
1
+ # Tooling SDK and local MCP
2
+
3
+ The tooling API consolidates authoring operations without starting a runtime:
4
+
5
+ - `inspectProject(project, {origin?, target?, offset?, limit?})` loads and
6
+ semantically compiles the project and returns route metadata, revision hash and
7
+ target compatibility. Route pages default to 100 entries, maximum 1,000.
8
+ Compatibility contains global `compatible`, `requirementCount` and `issueCount`
9
+ plus a separate `issues` page. Both arrays use the same zero-based `offset`
10
+ and `limit`, independently: an issue page is indexed over all compatibility
11
+ issues, not filtered to the route page. Compatibility includes `hasMore` for
12
+ its issue page. Empty pages never imply compatibility; the verdict and counts
13
+ always cover the entire project. Full requirement arrays are omitted because
14
+ route entries already contain their capability names.
15
+ - `validateProject(project, options)` returns the same compilation verdict and
16
+ compatibility verdict and global counts without route or issue pages.
17
+ `firstIssue` is the first issue across the whole project, or null; supplied
18
+ pagination settings do not change it. The low-level
19
+ `analyzeCompiledCapabilities` API still returns the complete report.
20
+ For unusually long route paths, request smaller pages to fit the MCP output
21
+ byte limit; page entry bounds do not override that transport limit.
22
+ - `explainRoute(project, path, options)` selects the route for a path and
23
+ describes its effective behavior from the compiled IR: methods, handler (with
24
+ its destination, module and export, file, extension or link collection),
25
+ the middleware chain in order, validated inputs (parameters and the request
26
+ body policy), the policies in effect with each compiled policy's inventory,
27
+ extension requirements, the cache outcome (the policy strategy, an explicit
28
+ header, an asset declaration, or the `no-store` the runtime forces on
29
+ extension, proxy and conditional routes), binding names (never values),
30
+ egress origins, response headers, capabilities and per-target support. A miss
31
+ returns `matched: false` with the nearest route patterns. With `extensions`
32
+ (a host file's registrations) each extension requirement also reports whether
33
+ a provider is registered, whether its revision pin matches and whether the
34
+ requirement satisfies the provider's policy schema; nothing is activated.
35
+ `explainProject(project, options)` returns every route the same way.
36
+ - `buildManifest(project, options)` returns the generated semantic manifest
37
+ described under [`urlcode manifest`](#explain-and-manifest).
38
+ - `getCapabilities(target?)` describes local implementation support and separate
39
+ deployment evidence.
40
+ - `getCapability(name)` returns one catalog entry: kind, summary, resolved schema
41
+ fragments, constraints, required operator grants, per-target support, refused
42
+ targets and the bundled recipes and cookbook routes that use it. Unknown names
43
+ throw a `ConfigError` listing the valid names.
44
+ - `getSchemaFragment(path)` returns only the fragment of
45
+ `schemas/urlcode.schema.json` for a dotted path (`route`, `redirect`,
46
+ `policies.cache`, `site.sitemap`) with local `$ref`s inlined; `schemaPathNames()`
47
+ lists the accepted top-level names. Both read bundled package data only.
48
+ - `previewImport(options)` and `previewExport(project, format, acknowledgment?)`
49
+ return conversion reports and candidate text, never writing files. Provider
50
+ semantic differences require the existing explicit acknowledgment and remain
51
+ non-lossless.
52
+ - `listRecipes()` and `showRecipe(name)` expose the fixed bundled recipe catalog.
53
+ - `inspectExtensions({project, hostFile?})` reports each operator-registered
54
+ extension's name, contract version, targets, credential headers, configuration
55
+ and policy JSON Schemas, whether the project declares it, whether its revision
56
+ pin matches and where routes mount or require it, plus the project's declared
57
+ names. With `hostFile` it executes that trusted operator module under the
58
+ `--host-file` rules (absolute path, outside the project) and releases it
59
+ afterwards; without one it lists declarations only. `describeExtensions(project,
60
+ registrations?)` produces the same report from registrations already in hand.
61
+ Neither activates an extension. See [EXTENSIONS.md](EXTENSIONS.md).
62
+ - `buildContext(project, {target?, hostFile?, budget?})` returns the compact
63
+ project context an authoring agent needs before it writes anything (see
64
+ below); `renderContext` produces the YAML rendering and `estimateTokens`
65
+ the characters-per-token estimate the budget uses.
66
+
67
+ ## Project context
68
+
69
+ `urlcode context [--project DIR] [--target T] [--host-file F] [--budget N]
70
+ [--json] [--stats]` emits one deterministic YAML document (JSON with
71
+ `--json`) derived only from the compiled project and the capability catalog,
72
+ never from prose. It uses the same loader and semantic compiler as
73
+ `inspectProject`: no binding values, guest execution, environment reads or
74
+ network. Keys always appear in this order:
75
+
76
+ - `urlcode` (package version) and `schema` (`"1"`).
77
+ - `project`: entry file, route count, handlers used with counts, extensions
78
+ declared, policies in effect at the top level and the number of routes each
79
+ policy applies to, requested env and secret binding names,
80
+ `site` keys, and `files` (include, function and middleware paths). With
81
+ `--host-file`, `host` counts the operator module's extensions and plugins
82
+ without activating them.
83
+ - `routes`: path, methods and handler per route, sorted by path.
84
+ - `constraints`: a fixed list that holds for every project (no guest network,
85
+ no Node APIs, no regex routes, one handler per route, exact or `{param}`
86
+ path segments, subtree mounts only for static and extension routes, no YAML
87
+ interpolation, secrets by operator grant only), each with a value and a note.
88
+ - `targets`: for each capability target (or the one `--target`), which of this
89
+ project's used features are supported, conditional, refused or unknown.
90
+ - `commands`: the exact `validate`, `test`, `audit --expect-routes N` (N is
91
+ the compiled route count), `routes` and `capabilities` invocations.
92
+
93
+ `--budget N` drops sections in a fixed order until the YAML rendering fits
94
+ the estimate: per-route detail, then `targets`, then the constraint notes
95
+ (keys and values stay), then `project.files`, then `commands`. The dropped
96
+ sections are listed under `omitted`. The estimate is `ceil(characters / 4)`;
97
+ there is no tokenizer dependency, so treat both numbers as approximate. A
98
+ budget the smallest rendering cannot meet is an error rather than an
99
+ overrun. `--stats` writes a JSON line to stderr comparing the estimated size
100
+ of the shipped documentation (`docs/*.md` and `llms.txt`) with the emitted
101
+ context, labeled `estimate: characters/4`. The MCP tool `get_context` takes
102
+ `target` and `budget` and returns the same object with `--project .` in the
103
+ commands; it never takes a host file or any other path.
104
+
105
+ Inspection reads declared configuration and function source graphs to validate
106
+ references and compute revision hashes. It compiles route and policy semantics
107
+ using dummy binding values. It never reads environment or dotenv credentials,
108
+ starts guest execution, follows network destinations, or opens operator link
109
+ stores. The result contains no raw compiled route, binding values or source text.
110
+ Inspection is not deployment readiness: missing operator grants, live service
111
+ availability, asset snapshot activation and provider behavior require their own
112
+ checks. Build output remains an explicit separate build API/CLI operation.
113
+
114
+ The package root also exports existing operator-invoked workflow APIs:
115
+ `buildCloudflare(project, options)` compiles and writes a Cloudflare artifact;
116
+ `buildStatic(project, options)` compiles redirects and static files into plain
117
+ objects and redirect metadata for S3 + CloudFront (see [static
118
+ hosting](STATIC.md)); `runProjectTests(project, options)` starts the local
119
+ runtime, executes request fixtures and closes it; `scaffoldProject(project,
120
+ {dryRun})` creates missing placeholders while preserving existing files;
121
+ `initProject(destination)` creates the standard starter; and
122
+ `addRedirect(project, destination, alias?)` updates project YAML under the
123
+ authoring lock. `CloudflareBuildOptions`, `CloudflareBuildReport`,
124
+ `StaticBuildOptions`, `StaticBuildReport`, `ProjectTestOptions`,
125
+ `ProjectTestResult`, `ScaffoldReport` and `ScaffoldUnresolved` describe these
126
+ existing operations.
127
+
128
+ These SDK functions have explicit write or execution effects and are available
129
+ to trusted callers only. Project tests use normal runtime activation, grants and
130
+ sandboxing; granted proxy/signal fixtures can perform real outbound operations.
131
+ Compilation and authoring write caller-selected destinations under each existing
132
+ helper's documented rules. They are **not** MCP tools. MCP remains limited to
133
+ the read-only operations below; adding a package-root export does not grant an
134
+ assistant file-write, guest-execution, deployment or network authority.
135
+
136
+ ## Explain and manifest
137
+
138
+ `urlcode explain [/route] [--project DIR] [--target T] [--host-file F] [--json]`
139
+ prints what `explainRoute` returns: one route in detail, or without a path a
140
+ one-line-per-route table (methods, handler, state, middleware count, policies,
141
+ cache outcome and target support). `--target` narrows the support columns to
142
+ one deployment target; `--host-file` supplies the operator registry so
143
+ extension requirements show their provider. An unknown route exits 1 and names
144
+ the nearest patterns. Everything comes from the compiled configuration: no
145
+ request is evaluated, no function runs and no binding is read.
146
+
147
+ `urlcode manifest [--project DIR] [--json]` emits the semantic manifest:
148
+ `schemaVersion`, the `urlcode` version, the entry file and its includes, the
149
+ `revision` (the same digest `inspectExtensionRevision` returns, so an operator
150
+ pin can be checked against it), the config `configVersion`, every route (path,
151
+ methods, handler, state, middleware, inputs, policy names, extension
152
+ requirements, cache outcome, binding names, egress origins, capabilities and
153
+ per-target support), the union of capabilities used, extension declarations
154
+ (version, configuration keys, mounts and protected routes), recipe provenance
155
+ (from a `recipe.yaml` beside the entry file when one exists), external
156
+ requirements (environment and secret names, proxy and signal origins,
157
+ extensions), the function and middleware
158
+ modules with the routes that use them, and per-target compatibility. Without
159
+ `--json` a short summary prints. The manifest is deterministic: the same
160
+ project produces the same bytes. `urlcode build` writes the same document as
161
+ `manifest.json` beside its output, and `buildManifest` returns it from the SDK.
162
+ It is generated output, never a checked-in source of truth; regenerate it
163
+ rather than editing it.
164
+
165
+ `serveMcp({project, input?, output?, origin?, allowAuthoring?, hostFile?})` serves one
166
+ operator-selected root on stdio. Its tools are `inspect`, `validate`,
167
+ `capabilities`, `get_capability`, `get_schema`, `explain`, `get_manifest`,
168
+ `import_preview`, `export_preview`, `recipes_list`, `recipes_show`,
169
+ `search_recipes`, `search_examples` and `get_context`. When the operator starts
170
+ the server with `--host-file`, it loads that trusted module once for the session
171
+ and additionally advertises `get_extensions`, which returns the
172
+ `inspectExtensions` report; without the option the tool is absent and calls to
173
+ it are rejected. Tools accept no project/file/output path argument; recipe names
174
+ come from the fixed catalog, `get_capability` names from the capability catalog,
175
+ `get_schema` paths from the bundled schema, and the two searches match bundled
176
+ metadata locally (see [recipes](RECIPES.md)).
177
+ There is no shell, arbitrary file read, remote fetch, binding access, write or
178
+ route-execution tool without the explicit [authoring mode](#authoring-mode) flag. Configuration includes and module references retain the
179
+ runtime's existing root containment checks. Returned project and recipe content
180
+ is data, not trusted instructions for the consuming agent.
181
+
182
+ The server implements the MCP **2025-11-25** lifecycle and stdio framing. Clients
183
+ initialize, verify the returned protocol version, then send
184
+ `notifications/initialized` before tool operations. Other requested revisions
185
+ negotiate to this explicit supported version; a client that cannot support it
186
+ must disconnect. Newer lifecycle revisions are not claimed. Requests use UTF-8
187
+ newline-delimited JSON-RPC 2.0, with one request at a time and stream backpressure.
188
+ There is a 1 MiB input-frame and output-message limit; oversized input terminates
189
+ the session after a fixed error, and truncated/invalid frames return protocol
190
+ errors. Import text is additionally capped at 512 KiB. Tool schemas reject
191
+ unknown arguments. Tool operation errors are generic to avoid exposing local
192
+ source paths, credentials or configuration excerpts; inspect locally for details.
193
+
194
+ ## Registering the server
195
+
196
+ `urlcode init` (and `init --with`) writes `.mcp.json` at the project root, the
197
+ shape Claude Code and Codex read:
198
+
199
+ ```json
200
+ { "mcpServers": { "urlcode": { "command": "urlcode", "args": ["mcp", "--project", "."] } } }
201
+ ```
202
+
203
+ For an `init --with` site the file sits beside `host.mjs` and passes
204
+ `--project app`. An existing `.mcp.json` is never overwritten. The file registers
205
+ the read-only server only: `--allow-authoring` (and `--host-file`) are operator
206
+ choices added by hand, never by `init` or by an agent.
207
+
208
+ - **Claude Code** reads `.mcp.json` in the project directory as a project-scoped
209
+ server and asks for approval on first use. Without a global install, replace
210
+ `"command": "urlcode"` with `"node"` and prefix the arguments with
211
+ `node_modules/@jimhoyd/urlcode/dist/cli.js`.
212
+ - **Codex** reads the same `mcpServers` shape; alternatively register it in
213
+ `~/.codex/config.toml`:
214
+
215
+ ```toml
216
+ [mcp_servers.urlcode]
217
+ command = "urlcode"
218
+ args = ["mcp", "--project", "."]
219
+ ```
220
+ - **Any stdio client** spawns `urlcode mcp --project DIR` with the project as the
221
+ working directory, speaks newline-delimited JSON-RPC 2.0 over stdin/stdout,
222
+ and follows the 2025-11-25 lifecycle described above. Nothing listens on a
223
+ port; closing stdin ends the session.
224
+
225
+ The generated `AGENTS.md` and the packaged skill tell agents to prefer
226
+ `get_context`, `get_capability`, `get_schema`, `search_recipes`, `explain` and
227
+ `get_manifest` when the server is registered and to fall back to the matching
228
+ CLI commands otherwise.
229
+
230
+ ## Authoring mode
231
+
232
+ `urlcode mcp --allow-authoring --project DIR` adds six tools to the fourteen read
233
+ tools above. The flag is honored from the operator's command line only: no
234
+ tool argument, environment variable or client capability enables it, and
235
+ without it the server is exactly the read-only server described above.
236
+
237
+ What it can do, all inside the selected project root (resolved with realpath):
238
+
239
+ - `create_route {path, handler, middleware?, file?}` adds one route to
240
+ `urlcode.yaml` or to an include listed in it. `handler` is a route object
241
+ (`{redirect: {...}}`, `{function: {...}}`, `{page: {...}}`, ...) or a short
242
+ form: an `http(s)://` URL becomes a redirect, a `.js`/`.mjs` path becomes a
243
+ function whose `{param}` path segments expand to required bounded string
244
+ parameters and matching `args`. `middleware` entries are sources or objects.
245
+ The merged project is checked before the write (schema, duplicate routes,
246
+ the `auth` short form, and the same reference compilation `urlcode add`
247
+ performs when every referenced source exists). The edit runs under the
248
+ authoring lock and replaces the file atomically. Missing sources are listed
249
+ in `missingSources` for `scaffold_feature`.
250
+ - `add_recipe {name, destination, dryRun?}` runs `recipes add` into a new
251
+ directory under the project. The parent must exist; an existing destination
252
+ is refused, never merged. `dryRun` reports the destination and writes nothing.
253
+ - `scaffold_feature {dryRun?}` runs `urlcode scaffold`: placeholder modules,
254
+ pages and directories for references the YAML makes and the disk lacks.
255
+ Existing files are preserved, never overwritten.
256
+ - `run_validate`, `run_test`, `run_audit` spawn `urlcode validate --local`,
257
+ `urlcode test` and `urlcode audit` against the project with a minimal
258
+ environment (`PATH` only), a two-minute deadline and stdout/stderr each capped
259
+ at 32 KiB. The result carries `exitCode`, `signal`, `stdout`, `stderr` and
260
+ `truncated`. `run_test` activates the local runtime and executes fixtures,
261
+ under the same rules as the CLI.
262
+
263
+ Every tool returns `validation`, the `validateProject` verdict of the project
264
+ after the operation (or `valid: false` with a generic note; use `run_validate`
265
+ for the CLI report).
266
+
267
+ What it cannot do:
268
+
269
+ - Write outside the project root. Paths are project-relative; absolute paths,
270
+ `..`, backslashes, drive letters, and any symlink on the walk are refused
271
+ before the write, and the recipe, scaffold and `urlcode add` paths keep their
272
+ own containment checks.
273
+ - Touch `.env*`, anything under `.git`, `node_modules`, `package.json`,
274
+ credential files (`.pem`, `.key`, `.p12`, `.pfx`), the authoring lock, or
275
+ operator files by name: `*policy*.json`, `*compliance*`, `host.mjs` /
276
+ `host-file.mjs` and link stores (`.sqlite`, `.db` and their WAL/SHM files).
277
+ Operator files belong outside the checkout in the first place.
278
+ - Create or change grants, read bindings or secret values, deploy, build, run
279
+ arbitrary commands, delete or edit existing files (except the one YAML file a
280
+ `create_route` targets), or serve a project other than the one the operator
281
+ selected.
282
+
283
+ Authoring mode is a local, unauthenticated stdio process for an operator who
284
+ already trusts the assistant to edit this checkout. Review the resulting diff
285
+ as you would any contributor's before running `serve` or deploying.
286
+
287
+ Only tools are advertised. Resources, prompts, subscriptions, sampling,
288
+ elicitation, HTTP transport, cancellation and durable tasks are not implemented.
289
+ Closing stdin ends the session after the current bounded operation. Existing
290
+ configuration-loader and semantic-compiler deadlines still apply. This local
291
+ process is not an authenticated remote service or an independent security review.
292
+
293
+ Protocol references: [MCP stdio transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports),
294
+ [MCP lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle),
295
+ and [MCP tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
package/docs/TUNNELS.md CHANGED
@@ -65,9 +65,6 @@ scanners that find it within minutes.
65
65
  - The runtime has **no authentication and no rate limiting**. Put access control
66
66
  in the tunnel: ngrok's OAuth, OIDC or basic auth; Cloudflare Access; a
67
67
  Tailscale ACL.
68
- - **Never tunnel the link-management API.** It is a separate listener bound to
69
- loopback on purpose, holding credentials that can rewrite live links. Tunnel
70
- the application port only.
71
68
  - Prefer `serve` with a fixed snapshot, a project containing no real secrets,
72
69
  and a tunnel you shut down when finished.
73
70
 
@@ -0,0 +1,82 @@
1
+ # Build-time TypeScript guests
2
+
3
+ TypeScript guest authoring is a separate build step. Serving still accepts only
4
+ JavaScript ES modules. The build never imports application modules into Node
5
+ or runs application code, and it is `sandbox`-aware per route
6
+ (docs/SPIKE-DEFAULT-TRUST-MODEL.md): a route that declares `sandbox: true` is
7
+ transpiled under the sandbox's own module rules (relative imports only, no
8
+ dynamic import/bare specifiers, the module/size limits below), exactly as
9
+ before; a trusted (non-`sandbox: true`) route is transpiled without those
10
+ import/size restrictions, since it will run with full Node access — bare/npm
11
+ imports, dynamic `import()`, `import.meta` — once served. Both modes still
12
+ diagnose only syntax, not semantics, and neither imports application code into
13
+ the build process in a way that executes it.
14
+
15
+ ```sh
16
+ urlcode recipes add typescript --out ./hello-source
17
+ urlcode build-typescript --project ./hello-source --out ./hello-built --dry-run
18
+ urlcode build-typescript --project ./hello-source --out ./hello-built
19
+ urlcode validate --local --project ./hello-built
20
+ urlcode serve --project ./hello-built
21
+ ```
22
+
23
+ The SDK equivalent is
24
+ `await buildTypeScriptProject(source, output, {dryRun: false})`. The report lists
25
+ emitted modules and files and explicitly returns `typeChecked: false`.
26
+
27
+ Point `function.source` or `middleware[].source` at a `.ts`, `.js` or `.mjs`
28
+ module. Use explicit relative extensions in imports, such as
29
+ `import {message} from './message.ts'`. The build emits `.ts` as `.js` and
30
+ rewrites the runtime imports and YAML source references. Existing JavaScript
31
+ modules are preserved. Output name collisions, including case-only collisions,
32
+ are refused for portability. Source `.d.ts` entries are not executable modules.
33
+ The runtime's existing handling of relative imports/re-exports applies to the
34
+ emitted graph; unsupported re-export forms remain refused.
35
+
36
+ The trusted pinned TypeScript compiler transpiles ES2022/ES modules with fixed
37
+ settings. It does not read `tsconfig.json`, package scripts, plugins, compiler
38
+ transformers, dependency packages, Node declarations or ambient environment
39
+ files. It diagnoses syntax errors, but does **not** perform semantic type
40
+ checking, in either mode. CommonJS import/export syntax and imports outside
41
+ the project are always refused. Type-only relative imports are erased without
42
+ reading their targets.
43
+
44
+ For a `sandbox: true` route, bare/npm imports (including static type-only
45
+ imports), dynamic runtime imports, `import.meta` and import attributes are
46
+ refused, and the source graph is limited to 128 modules, 1 MiB per source and
47
+ 4 MiB aggregate; the emitted graph must also pass the runtime's own sandboxed
48
+ source parser and byte limits before publication. For a trusted route, none of
49
+ that applies: bare/npm specifiers, dynamic `import()`, `import.meta` and
50
+ import attributes pass through unchanged (resolved by Node at serve time, not
51
+ by this build), and there is no module-count or size ceiling. In both modes,
52
+ no import extension inference occurs — relative imports of project modules
53
+ still need an explicit `.ts`/`.js`/`.mjs` extension to be rewritten and
54
+ followed. This does not execute the modules or replace normal route, policy,
55
+ binding or sandbox validation at activation. Unsupported host/browser APIs
56
+ remain unavailable in QuickJS, even if TypeScript accepts their names. Run
57
+ `urlcode validate --local` and project tests on the output.
58
+
59
+ Includes are flattened into a duplicate-checked entry document. Only referenced
60
+ modules, page/download assets, static trees, site favicon/llms files and the
61
+ `tests/requests.json` fixtures (when present) are snapshotted. Unreferenced files, dotenv, hidden files, package manifests and
62
+ credential extensions are not copied. An explicit reference to a forbidden file
63
+ fails rather than silently excluding it. Asset limits are 16 MiB per file,
64
+ 64 MiB aggregate and 10,000 output files. Empty static directories are refused;
65
+ add an asset before building. Paths have at most 32 segments and 1,024 characters;
66
+ symlinked source files/directories and multiply linked files are refused.
67
+ The input project and output parent must be stable and controlled by the caller
68
+ while building; this is not a hardened service for concurrent hostile writers.
69
+
70
+ Output must be a new directory whose parent already exists. Dependencies are
71
+ written before an atomic rename publishes the complete entry YAML; existing
72
+ outputs are never overwritten. Failed builds remove their newly created output.
73
+ Dry-run still transpiles and validates an isolated temporary snapshot, then
74
+ removes it; it never writes the requested output. Configuration parsing keeps
75
+ the loader's worker limits; transpilation runs in the authoring process and is
76
+ not a sandboxed multi-tenant compilation service.
77
+
78
+ Create operator grants against the **built** configuration/code revision.
79
+ Transpilation and flattened includes change the snapshot digest, so source
80
+ project grants must not be reused. External capabilities remain denied without
81
+ an exact revision-pinned operator policy. The build never resolves bindings or
82
+ copies credentials; it preserves logical binding declarations only.
@@ -16,7 +16,7 @@ beside them, and `dist/BUILD-MANIFEST.json` with a SHA-256 per emitted file.
16
16
  | Import | Runtime | Declarations |
17
17
  |---|---|---|
18
18
  | `urlcode` | `dist/index.js` | `dist/types/index.d.ts` |
19
- | `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
19
+ | `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender`, `@jimhoyd/urlcode/extensions`, `@jimhoyd/urlcode/sandbox` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
20
20
  | `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
21
21
  | `@jimhoyd/urlcode/schema` | `schemas/urlcode.schema.json` | — |
22
22
 
@@ -36,9 +36,7 @@ release cannot ship a declaration that does not resolve.
36
36
  ## The main exported types
37
37
 
38
38
  - `urlcode`: `Runtime`, `RuntimeOptions`, `RuntimeRequest`, `RequestTrace`,
39
- `Server`, `ServerOptions`, `HostPlugin`, `Observer`, `TestPlan`, `LinkStore`,
40
- `LinkRow`, `LinkStoreOptions`, `LinkReader`, `LinkStoreBinding`, `LinkApi`,
41
- `LinkApiOptions`, `LinkEvent`, `LinkObserverOptions`.
39
+ `Server`, `ServerOptions`, `HostPlugin`, `Observer`, `TestPlan`.
42
40
  - `@jimhoyd/urlcode/plugins`: `Plugin`, `PluginRuntime`, `PolicyRequest`,
43
41
  `HandlerResult`, `HeaderPair`, `TargetName`. See [plugins](PLUGINS.md).
44
42
  - `@jimhoyd/urlcode/policies`: `PolicyModule`, `PolicyRegistry`, `PolicyRequest`,
@@ -54,6 +52,29 @@ release cannot ship a declaration that does not resolve.
54
52
  - `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare`: `LambdaEvent`,
55
53
  `LambdaHandler`, `LambdaHandlerOptions`; `VercelHandler`,
56
54
  `VercelHandlerOptions`; `Artifact`, `WorkerRoute`, `Validators`.
55
+ - `@jimhoyd/urlcode/sandbox`: `SandboxPool`, `SandboxEntry`, `SandboxTarget`,
56
+ `SandboxInvocation`, `SandboxPoolOptions`, `functionFile`. The public
57
+ sandboxed-execution primitive: the same QuickJS/worker-thread engine that
58
+ already backs a `sandbox: true` `function`/`middleware` route
59
+ (`FunctionPool`, internally), generalized to an explicit list of
60
+ `{source, export}` entries instead of anything route/YAML-shaped, for an
61
+ extension package that needs to run a project-supplied hook through real
62
+ isolation when the project's own config declares `sandbox: true` on it
63
+ (see [EXTENSIONS.md](EXTENSIONS.md#project-level-lifecycle-hooks) and
64
+ [FUNCTION-SECURITY.md](FUNCTION-SECURITY.md)). There is no trusted-mode
65
+ export here: a hook that does not declare `sandbox: true` is ordinary
66
+ project code the extension `import()`s directly via
67
+ `ExtensionActivation.root`, no primitive required.
68
+
69
+ ```ts
70
+ import { SandboxPool, functionFile } from '@jimhoyd/urlcode/sandbox';
71
+
72
+ const source = await functionFile(root, hookConfig.source); // root: ExtensionActivation.root
73
+ const entries = [{ source, export: hookConfig.export ?? 'default' }];
74
+ const pool = await new SandboxPool(entries, { root, workers: 1 }).start();
75
+ const result = await pool.execute({ entry: entries[0] }, request, context, undefined);
76
+ await pool.close();
77
+ ```
57
78
 
58
79
  ```ts
59
80
  import { startServer, type ServerOptions, type Observer } from '@jimhoyd/urlcode';