wizards 0.1.9

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 (308) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +170 -0
  3. package/apps/runtime/dist/agents/architect.js +453 -0
  4. package/apps/runtime/dist/agents/project-assistant.js +354 -0
  5. package/apps/runtime/dist/agents/subscription.js +206 -0
  6. package/apps/runtime/dist/agents/thought.js +21 -0
  7. package/apps/runtime/dist/agents/website.js +197 -0
  8. package/apps/runtime/dist/app.js +153 -0
  9. package/apps/runtime/dist/auth/account.js +121 -0
  10. package/apps/runtime/dist/auth/index.js +329 -0
  11. package/apps/runtime/dist/auth/keys.js +55 -0
  12. package/apps/runtime/dist/auth/local-ticket.js +23 -0
  13. package/apps/runtime/dist/authoring/example.js +73 -0
  14. package/apps/runtime/dist/authoring/guide.js +259 -0
  15. package/apps/runtime/dist/authoring/ops.js +117 -0
  16. package/apps/runtime/dist/cli/autostart.js +134 -0
  17. package/apps/runtime/dist/cli/clients.js +44 -0
  18. package/apps/runtime/dist/cli/environment.js +81 -0
  19. package/apps/runtime/dist/cli/exec.js +45 -0
  20. package/apps/runtime/dist/cli/home.js +74 -0
  21. package/apps/runtime/dist/cli/machine.js +145 -0
  22. package/apps/runtime/dist/cli/main.js +125 -0
  23. package/apps/runtime/dist/cli/setup.js +273 -0
  24. package/apps/runtime/dist/cli/start.js +190 -0
  25. package/apps/runtime/dist/cli/status.js +120 -0
  26. package/apps/runtime/dist/cli/ui.js +12 -0
  27. package/apps/runtime/dist/cli/update.js +58 -0
  28. package/apps/runtime/dist/connectors/builtin.js +70 -0
  29. package/apps/runtime/dist/connectors/external.js +400 -0
  30. package/apps/runtime/dist/connectors/gmail.js +107 -0
  31. package/apps/runtime/dist/connectors/imap.js +217 -0
  32. package/apps/runtime/dist/connectors/index.js +266 -0
  33. package/apps/runtime/dist/connectors/mail.js +72 -0
  34. package/apps/runtime/dist/connectors/outlook.js +90 -0
  35. package/apps/runtime/dist/credits/credits.js +103 -0
  36. package/apps/runtime/dist/credits/estimate.js +132 -0
  37. package/apps/runtime/dist/db/adopt.js +149 -0
  38. package/apps/runtime/dist/db/client.js +146 -0
  39. package/apps/runtime/dist/db/control-schema.js +70 -0
  40. package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
  41. package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
  42. package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
  43. package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
  44. package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
  45. package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
  46. package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
  47. package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
  48. package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
  49. package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
  50. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
  51. package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
  52. package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
  53. package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
  54. package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
  55. package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
  56. package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
  57. package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
  58. package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
  59. package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
  60. package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
  61. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
  62. package/apps/runtime/dist/db/schema.js +278 -0
  63. package/apps/runtime/dist/documents/parse.js +230 -0
  64. package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
  65. package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
  66. package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
  67. package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
  68. package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
  69. package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
  70. package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
  71. package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
  72. package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
  73. package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
  74. package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
  75. package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
  76. package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
  77. package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
  78. package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
  79. package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
  80. package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
  81. package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
  82. package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
  83. package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
  84. package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
  85. package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
  86. package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
  87. package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
  88. package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
  89. package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
  90. package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
  91. package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
  92. package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
  93. package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
  94. package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
  95. package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
  96. package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
  97. package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
  98. package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
  99. package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
  100. package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
  101. package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
  102. package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
  103. package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
  104. package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
  105. package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
  106. package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
  107. package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
  108. package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
  109. package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
  110. package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
  111. package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
  112. package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
  113. package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
  114. package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
  115. package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
  116. package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
  117. package/apps/runtime/dist/engine/asks.js +60 -0
  118. package/apps/runtime/dist/engine/events.js +46 -0
  119. package/apps/runtime/dist/engine/input.js +170 -0
  120. package/apps/runtime/dist/engine/prepare.js +51 -0
  121. package/apps/runtime/dist/engine/requirements.js +124 -0
  122. package/apps/runtime/dist/engine/resources.js +89 -0
  123. package/apps/runtime/dist/engine/runner.js +560 -0
  124. package/apps/runtime/dist/engine/steps.js +598 -0
  125. package/apps/runtime/dist/engine/template.js +183 -0
  126. package/apps/runtime/dist/engine/types.js +3 -0
  127. package/apps/runtime/dist/env.js +224 -0
  128. package/apps/runtime/dist/files/blobs.js +22 -0
  129. package/apps/runtime/dist/files/objects.js +86 -0
  130. package/apps/runtime/dist/files/storage.js +95 -0
  131. package/apps/runtime/dist/geocode.js +58 -0
  132. package/apps/runtime/dist/harness/claude.js +138 -0
  133. package/apps/runtime/dist/harness/codex.js +228 -0
  134. package/apps/runtime/dist/harness/cursor.js +93 -0
  135. package/apps/runtime/dist/harness/env.js +124 -0
  136. package/apps/runtime/dist/harness/gemini.js +126 -0
  137. package/apps/runtime/dist/harness/index.js +99 -0
  138. package/apps/runtime/dist/harness/model.js +267 -0
  139. package/apps/runtime/dist/harness/prompt.js +121 -0
  140. package/apps/runtime/dist/harness/terminal.js +193 -0
  141. package/apps/runtime/dist/harness/types.js +7 -0
  142. package/apps/runtime/dist/index.js +76 -0
  143. package/apps/runtime/dist/limits.js +57 -0
  144. package/apps/runtime/dist/link-preview.js +59 -0
  145. package/apps/runtime/dist/manage.js +151 -0
  146. package/apps/runtime/dist/mcp/auth.js +102 -0
  147. package/apps/runtime/dist/mcp/bridge.js +59 -0
  148. package/apps/runtime/dist/mcp/handler.js +42 -0
  149. package/apps/runtime/dist/mcp/scopes.js +5 -0
  150. package/apps/runtime/dist/mcp/tools.js +401 -0
  151. package/apps/runtime/dist/media/ffmpeg.js +121 -0
  152. package/apps/runtime/dist/media/generate.js +120 -0
  153. package/apps/runtime/dist/media/marking.js +121 -0
  154. package/apps/runtime/dist/media/transcribe.js +30 -0
  155. package/apps/runtime/dist/model-errors.js +3 -0
  156. package/apps/runtime/dist/models.js +489 -0
  157. package/apps/runtime/dist/plugin.js +61 -0
  158. package/apps/runtime/dist/render/chromium.js +85 -0
  159. package/apps/runtime/dist/render/convert.js +161 -0
  160. package/apps/runtime/dist/render/downloads.js +277 -0
  161. package/apps/runtime/dist/render/guard.js +25 -0
  162. package/apps/runtime/dist/routes/api.js +74 -0
  163. package/apps/runtime/dist/routes/delivery.js +52 -0
  164. package/apps/runtime/dist/routes/internal.js +49 -0
  165. package/apps/runtime/dist/routes/marketplace.js +36 -0
  166. package/apps/runtime/dist/routes/runs.js +805 -0
  167. package/apps/runtime/dist/routes/studio.js +702 -0
  168. package/apps/runtime/dist/routes/wizard-stream.js +30 -0
  169. package/apps/runtime/dist/running.js +39 -0
  170. package/apps/runtime/dist/sandbox/agentos.js +191 -0
  171. package/apps/runtime/dist/sandbox/docker.js +77 -0
  172. package/apps/runtime/dist/sandbox/index.js +30 -0
  173. package/apps/runtime/dist/sandbox/types.js +1 -0
  174. package/apps/runtime/dist/secrets/crypto.js +28 -0
  175. package/apps/runtime/dist/secrets/signing.js +34 -0
  176. package/apps/runtime/dist/secrets/vault.js +59 -0
  177. package/apps/runtime/dist/services/architect.js +92 -0
  178. package/apps/runtime/dist/services/brand.js +20 -0
  179. package/apps/runtime/dist/services/cloud.js +52 -0
  180. package/apps/runtime/dist/services/draft-events.js +27 -0
  181. package/apps/runtime/dist/services/errors.js +24 -0
  182. package/apps/runtime/dist/services/files.js +129 -0
  183. package/apps/runtime/dist/services/links.js +16 -0
  184. package/apps/runtime/dist/services/marketplace.js +292 -0
  185. package/apps/runtime/dist/services/package.js +202 -0
  186. package/apps/runtime/dist/services/profile.js +23 -0
  187. package/apps/runtime/dist/services/project-files.js +266 -0
  188. package/apps/runtime/dist/services/project-index.js +109 -0
  189. package/apps/runtime/dist/services/projects.js +181 -0
  190. package/apps/runtime/dist/services/runs.js +224 -0
  191. package/apps/runtime/dist/services/shares.js +129 -0
  192. package/apps/runtime/dist/services/widgets.js +34 -0
  193. package/apps/runtime/dist/services/wizards.js +342 -0
  194. package/apps/runtime/dist/settings.js +16 -0
  195. package/apps/runtime/dist/store/index.js +279 -0
  196. package/apps/runtime/dist/tenants/control.js +106 -0
  197. package/apps/runtime/dist/tenants/tenant.js +26 -0
  198. package/apps/runtime/dist/tools/browser.js +380 -0
  199. package/apps/runtime/dist/tools/connector.js +204 -0
  200. package/apps/runtime/dist/tools/index.js +234 -0
  201. package/apps/runtime/dist/tools/mail.js +104 -0
  202. package/apps/runtime/dist/tools/net-guard.js +59 -0
  203. package/apps/runtime/dist/tools/project.js +53 -0
  204. package/apps/runtime/dist/tools/shared.js +42 -0
  205. package/apps/runtime/dist/tools/store.js +238 -0
  206. package/apps/runtime/dist/update.js +118 -0
  207. package/apps/runtime/dist/widgets/bundle.js +99 -0
  208. package/apps/runtime/dist/widgets/render.js +312 -0
  209. package/apps/runtime/dist/widgets/runtime.js +84 -0
  210. package/apps/runtime/dist/widgets/script.js +30 -0
  211. package/apps/runtime/dist/widgets/step.js +192 -0
  212. package/apps/web/dist/ai-labels/ai-black.svg +20 -0
  213. package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
  214. package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
  215. package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
  216. package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
  217. package/apps/web/dist/ai-labels/ai-white.svg +21 -0
  218. package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
  219. package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
  220. package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
  221. package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
  222. package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
  223. package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
  224. package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
  225. package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
  226. package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
  227. package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
  228. package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
  229. package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
  230. package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
  231. package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
  232. package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
  233. package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
  234. package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
  235. package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
  236. package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
  237. package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
  238. package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
  239. package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
  240. package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
  241. package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
  242. package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
  243. package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
  244. package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
  245. package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
  246. package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
  247. package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
  248. package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
  249. package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
  250. package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
  251. package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
  252. package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
  253. package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
  254. package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
  255. package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
  256. package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
  257. package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
  258. package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
  259. package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
  260. package/apps/web/dist/embed.js +102 -0
  261. package/apps/web/dist/favicon.svg +1 -0
  262. package/apps/web/dist/icons/apple-touch-icon.png +0 -0
  263. package/apps/web/dist/icons/icon-192.png +0 -0
  264. package/apps/web/dist/icons/icon-512.png +0 -0
  265. package/apps/web/dist/icons/icon-maskable-512.png +0 -0
  266. package/apps/web/dist/index.html +46 -0
  267. package/apps/web/dist/install.sh +532 -0
  268. package/apps/web/dist/manifest.webmanifest +20 -0
  269. package/apps/web/dist/sw.js +15 -0
  270. package/bin/wizards.mjs +13 -0
  271. package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
  272. package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
  273. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
  274. package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
  275. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
  276. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
  277. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
  278. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
  279. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
  280. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
  281. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
  282. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
  283. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
  284. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
  285. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
  286. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
  287. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
  288. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
  289. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
  290. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
  291. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
  292. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
  293. package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
  294. package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
  295. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
  296. package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
  297. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
  298. package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
  299. package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
  300. package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
  301. package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
  302. package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
  303. package/node_modules/@engenty-wizards/shared/package.json +10 -0
  304. package/package.json +62 -0
  305. package/plugin/.claude-plugin/plugin.json +9 -0
  306. package/plugin/.mcp.json +8 -0
  307. package/plugin/commands/wizard.md +8 -0
  308. package/plugin/skills/build-wizard/SKILL.md +27 -0
@@ -0,0 +1,805 @@
1
+ import { FORMATS, LIST_FORMATS } from "@engenty-wizards/shared/definition";
2
+ import { TableColumnValueError } from "@engenty-wizards/shared/engenty/data-tables";
3
+ import { getConnInfo } from "@hono/node-server/conninfo";
4
+ import { and, eq } from "drizzle-orm";
5
+ import { Hono } from "hono";
6
+ import { getCookie, setCookie } from "hono/cookie";
7
+ import { streamSSE } from "hono/streaming";
8
+ import { nanoid } from "nanoid";
9
+ import { z } from "zod";
10
+ import { principalOf } from "../auth/index.js";
11
+ import { ConnectError, connectionViews, connectWithCredentials, disconnect, finishOAuth, oauthStateRun, startOAuth, } from "../connectors/index.js";
12
+ import { db, schema, withTenant } from "../db/client.js";
13
+ import { answerAsk, pendingAsk } from "../engine/asks.js";
14
+ import { signalChanged, subscribe } from "../engine/events.js";
15
+ import { liveResources } from "../engine/resources.js";
16
+ import { cancel, createRun, goBack, NoCreditsError, projectIdOf, RunConflict, RunInputError, retry, reviewStep, runView, submitPage, } from "../engine/runner.js";
17
+ import { basePath, env } from "../env.js";
18
+ import { loadAsset, saveAsset } from "../files/storage.js";
19
+ import { reverseGeocode } from "../geocode.js";
20
+ import { hashIp, verifyTurnstile, wizardUnavailable } from "../limits.js";
21
+ import { ModelUnavailableError } from "../model-errors.js";
22
+ import { listDownload, renderDownload, stepHtml } from "../render/downloads.js";
23
+ import { HTML_RESPONSE_CSP } from "../render/guard.js";
24
+ import { verifySignedUrl } from "../secrets/signing.js";
25
+ import { brandView } from "../services/brand.js";
26
+ import { ServiceError } from "../services/errors.js";
27
+ import { sharedRun, shareImage, shareRun, shareView, unshareRun } from "../services/shares.js";
28
+ import { clearStore, deleteRows, listRows, listSecrets, readStoreFile, StoreError, saveRows, scopeOf, storeFiles, updateRow, } from "../store/index.js";
29
+ import { tenantOfLink, tenantOfRun, tenantStatus, visitorOverLimit } from "../tenants/control.js";
30
+ import { byteRange, uploadLimit, wizardManifest } from "./delivery.js";
31
+ const VISITOR_COOKIE = "wz_vid";
32
+ /** Kept files a review shows inline. */
33
+ const INLINE_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/webp"]);
34
+ function clientIp(c) {
35
+ const fwd = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
36
+ if (fwd) {
37
+ return fwd;
38
+ }
39
+ try {
40
+ return getConnInfo(c).remote.address ?? null;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function visitorId(c, create, embedded = false) {
47
+ let vid = getCookie(c, VISITOR_COOKIE) ?? null;
48
+ if (!vid && create) {
49
+ vid = nanoid(24);
50
+ const secure = env.appUrl.startsWith("https");
51
+ setCookie(c, VISITOR_COOKIE, vid, {
52
+ httpOnly: true,
53
+ path: basePath || "/",
54
+ maxAge: 60 * 60 * 24 * 365,
55
+ // In a frame on another website the wizard is a third party: a browser keeps its cookie
56
+ // only when the cookie says so, and then apart for each website (CHIPS).
57
+ ...(embedded && secure
58
+ ? { sameSite: "None", secure: true, partitioned: true }
59
+ : { sameSite: "Lax", secure }),
60
+ });
61
+ }
62
+ return vid;
63
+ }
64
+ /**
65
+ * The run, if the caller may see it: its visitor, the admin who owns the wizard, or — on the
66
+ * read-only file routes — a signed link handed out to the owner's MCP client.
67
+ */
68
+ async function accessibleRun(c, runId, opts = {}) {
69
+ const run = await db.query.run.findFirst({ where: eq(schema.run.id, runId) });
70
+ if (!run) {
71
+ return null;
72
+ }
73
+ if (opts.signed && verifySignedUrl(c.req.url)) {
74
+ return run;
75
+ }
76
+ const vid = visitorId(c, false);
77
+ if (vid && run.visitorId === vid) {
78
+ return run;
79
+ }
80
+ const user = await principalOf(c);
81
+ if (user && user.tenantId === run.tenantId) {
82
+ return run;
83
+ }
84
+ return null;
85
+ }
86
+ async function viewOf(run) {
87
+ const w = await db.query.wizard.findFirst({ where: eq(schema.wizard.id, run.wizardId) });
88
+ return runView(run, await brandView(w?.projectId ?? ""));
89
+ }
90
+ function commandError(c, err) {
91
+ if (err instanceof RunInputError) {
92
+ return c.json({ error: err.message, fields: err.errors }, 422);
93
+ }
94
+ if (err instanceof RunConflict) {
95
+ return c.json({ error: err.message }, 409);
96
+ }
97
+ throw err;
98
+ }
99
+ /**
100
+ * These routes arrive without a session. The tenant comes from the public token or the run id
101
+ * in the path — looked up in the control database, never taken from the request.
102
+ */
103
+ function tenantFrom(find) {
104
+ return async (c, next) => {
105
+ const tenant = await find(c);
106
+ if (!tenant) {
107
+ return c.json({ error: "not found" }, 404);
108
+ }
109
+ if ((await tenantStatus(tenant)) === "suspended") {
110
+ return c.json({ error: "Dieser Wizard ist gerade nicht verfügbar." }, 403);
111
+ }
112
+ return withTenant(tenant, next);
113
+ };
114
+ }
115
+ const wizardLink = tenantFrom((c) => tenantOfLink(c.req.param("token") ?? "", "wizard"));
116
+ const resultLink = tenantFrom((c) => tenantOfLink(c.req.param("token") ?? "", "result"));
117
+ const logoLink = tenantFrom((c) => tenantOfLink(c.req.param("id") ?? "", "logo"));
118
+ const runTenant = tenantFrom((c) => tenantOfRun(c.req.param("id") ?? ""));
119
+ export const publicRoutes = new Hono()
120
+ .use("/wizards/:token", wizardLink)
121
+ .use("/wizards/:token/*", wizardLink)
122
+ .use("/logos/:id", logoLink)
123
+ .get("/wizards/:token", async (c) => {
124
+ const w = await db.query.wizard.findFirst({
125
+ where: eq(schema.wizard.shareToken, c.req.param("token")),
126
+ });
127
+ if (!w || w.publishedVersion === null) {
128
+ return c.json({ error: "not found" }, 404);
129
+ }
130
+ const version = await db.query.wizardVersion.findFirst({
131
+ where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
132
+ });
133
+ const def = version.definition;
134
+ const reason = await wizardUnavailable(w);
135
+ const body = {
136
+ token: w.shareToken,
137
+ title: def.title,
138
+ description: def.description,
139
+ avatar: def.avatar,
140
+ intro: def.intro ?? null,
141
+ brand: await brandView(w.projectId),
142
+ turnstileSiteKey: env.turnstile.siteKey || null,
143
+ available: !reason,
144
+ unavailableReason: reason,
145
+ };
146
+ return c.json(body);
147
+ })
148
+ .post("/wizards/:token/runs", async (c) => {
149
+ const w = await db.query.wizard.findFirst({
150
+ where: eq(schema.wizard.shareToken, c.req.param("token")),
151
+ });
152
+ if (!w || w.publishedVersion === null) {
153
+ return c.json({ error: "not found" }, 404);
154
+ }
155
+ const reason = await wizardUnavailable(w);
156
+ if (reason) {
157
+ return c.json({ error: reason }, 403);
158
+ }
159
+ const body = z
160
+ .object({ turnstileToken: z.string().optional(), embedded: z.boolean().optional() })
161
+ .parse(await c.req.json().catch(() => ({})));
162
+ const ip = clientIp(c);
163
+ if (!(await verifyTurnstile(body.turnstileToken, ip))) {
164
+ return c.json({ error: "Bitte bestätige kurz, dass du ein Mensch bist." }, 403);
165
+ }
166
+ const vid = visitorId(c, true, body.embedded);
167
+ const ipHash = hashIp(ip);
168
+ if (await visitorOverLimit(vid, ipHash)) {
169
+ return c.json({
170
+ error: "Du hast gerade viele Durchläufe gestartet. Bitte versuche es in einer Stunde wieder.",
171
+ }, 429);
172
+ }
173
+ const version = await db.query.wizardVersion.findFirst({
174
+ where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
175
+ });
176
+ try {
177
+ const runId = await createRun({
178
+ wizardId: w.id,
179
+ definition: version.definition,
180
+ files: version.files,
181
+ version: w.publishedVersion,
182
+ mode: "live",
183
+ visitorId: vid,
184
+ ipHash,
185
+ });
186
+ return c.json({ runId });
187
+ }
188
+ catch (err) {
189
+ if (err instanceof NoCreditsError || err instanceof ModelUnavailableError) {
190
+ return c.json({ error: "Dieser Wizard ist gerade nicht verfügbar." }, 403);
191
+ }
192
+ throw err;
193
+ }
194
+ })
195
+ // A published wizard as an app of its own: added to a phone's home screen it opens on its link.
196
+ .get("/wizards/:token/manifest.webmanifest", async (c) => {
197
+ const w = await db.query.wizard.findFirst({
198
+ where: eq(schema.wizard.shareToken, c.req.param("token")),
199
+ });
200
+ if (!w || w.publishedVersion === null) {
201
+ return c.notFound();
202
+ }
203
+ const version = await db.query.wizardVersion.findFirst({
204
+ where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
205
+ });
206
+ const def = version.definition;
207
+ return c.body(JSON.stringify(wizardManifest(w.shareToken, def.title, def.description)), 200, {
208
+ "content-type": "application/manifest+json; charset=utf-8",
209
+ "cache-control": "public, max-age=300",
210
+ });
211
+ })
212
+ .get("/logos/:id", async (c) => {
213
+ const found = await loadAsset(c.req.param("id"));
214
+ if (found?.row.kind !== "logo") {
215
+ return c.notFound();
216
+ }
217
+ return c.body(new Uint8Array(found.data), 200, {
218
+ "content-type": found.row.mime,
219
+ "cache-control": "public, max-age=86400",
220
+ // An SVG logo is shown, never run.
221
+ "content-security-policy": "sandbox",
222
+ });
223
+ });
224
+ export const runRoutes = new Hono()
225
+ .use("/:id", runTenant)
226
+ .use("/:id/*", runTenant)
227
+ .get("/:id", async (c) => {
228
+ const run = await accessibleRun(c, c.req.param("id"));
229
+ if (!run) {
230
+ return c.json({ error: "not found" }, 404);
231
+ }
232
+ return c.json(await viewOf(run));
233
+ })
234
+ .get("/:id/stream", async (c) => {
235
+ const id = c.req.param("id");
236
+ const first = await accessibleRun(c, id);
237
+ if (!first) {
238
+ return c.json({ error: "not found" }, 404);
239
+ }
240
+ return streamSSE(c, async (stream) => {
241
+ let pending = false;
242
+ let closed = false;
243
+ const push = async () => {
244
+ if (pending || closed) {
245
+ return;
246
+ }
247
+ pending = true;
248
+ await new Promise((r) => setTimeout(r, 120));
249
+ pending = false;
250
+ const run = await db.query.run.findFirst({ where: eq(schema.run.id, id) });
251
+ if (run && !closed) {
252
+ await stream.writeSSE({ event: "view", data: JSON.stringify(await viewOf(run)) });
253
+ }
254
+ };
255
+ const unsubscribe = subscribe(id, () => {
256
+ void push();
257
+ });
258
+ stream.onAbort(() => {
259
+ closed = true;
260
+ unsubscribe();
261
+ });
262
+ await stream.writeSSE({ event: "view", data: JSON.stringify(await viewOf(first)) });
263
+ while (!closed) {
264
+ await stream.sleep(20_000);
265
+ if (!closed) {
266
+ await stream.writeSSE({ event: "ping", data: "" });
267
+ }
268
+ }
269
+ });
270
+ })
271
+ .post("/:id/pages/:stepId", async (c) => {
272
+ const run = await accessibleRun(c, c.req.param("id"));
273
+ if (!run) {
274
+ return c.json({ error: "not found" }, 404);
275
+ }
276
+ const { values } = z
277
+ .object({ values: z.record(z.string(), z.unknown()) })
278
+ .parse(await c.req.json());
279
+ try {
280
+ await submitPage(run.id, c.req.param("stepId"), values);
281
+ return c.json({ ok: true });
282
+ }
283
+ catch (err) {
284
+ return commandError(c, err);
285
+ }
286
+ })
287
+ .post("/:id/reviews/:stepId", async (c) => {
288
+ const run = await accessibleRun(c, c.req.param("id"));
289
+ if (!run) {
290
+ return c.json({ error: "not found" }, 404);
291
+ }
292
+ const action = z
293
+ .discriminatedUnion("type", [
294
+ z.object({ type: z.literal("accept"), edits: z.record(z.string(), z.string()).optional() }),
295
+ z.object({
296
+ type: z.literal("regenerate"),
297
+ target: z.string(),
298
+ note: z.string().max(2000),
299
+ items: z.array(z.number().int().min(0).max(50)).max(20).optional(),
300
+ }),
301
+ ])
302
+ .parse(await c.req.json());
303
+ try {
304
+ await reviewStep(run.id, c.req.param("stepId"), action);
305
+ return c.json({ ok: true });
306
+ }
307
+ catch (err) {
308
+ return commandError(c, err);
309
+ }
310
+ })
311
+ .post("/:id/back", async (c) => {
312
+ const run = await accessibleRun(c, c.req.param("id"));
313
+ if (!run) {
314
+ return c.json({ error: "not found" }, 404);
315
+ }
316
+ try {
317
+ await goBack(run.id);
318
+ return c.json({ ok: true });
319
+ }
320
+ catch (err) {
321
+ return commandError(c, err);
322
+ }
323
+ })
324
+ .post("/:id/retry", async (c) => {
325
+ const run = await accessibleRun(c, c.req.param("id"));
326
+ if (!run) {
327
+ return c.json({ error: "not found" }, 404);
328
+ }
329
+ try {
330
+ await retry(run.id);
331
+ return c.json({ ok: true });
332
+ }
333
+ catch (err) {
334
+ return commandError(c, err);
335
+ }
336
+ })
337
+ .post("/:id/cancel", async (c) => {
338
+ const run = await accessibleRun(c, c.req.param("id"));
339
+ if (!run) {
340
+ return c.json({ error: "not found" }, 404);
341
+ }
342
+ await cancel(run.id);
343
+ return c.json({ ok: true });
344
+ })
345
+ .post("/:id/uploads", async (c) => {
346
+ const run = await accessibleRun(c, c.req.param("id"));
347
+ if (!run) {
348
+ return c.json({ error: "not found" }, 404);
349
+ }
350
+ const form = await c.req.formData();
351
+ const file = form.get("file");
352
+ // A recorder names its type with codecs ("audio/webm;codecs=opus"); the bare type is kept.
353
+ const mime = file instanceof File ? file.type.split(";")[0].trim().toLowerCase() : "";
354
+ const limit = uploadLimit(mime);
355
+ if (!(file instanceof File) || file.size > limit) {
356
+ return c.json({ error: `Bitte eine Datei bis ${Math.round(limit / 1_000_000)} MB wählen.` }, 400);
357
+ }
358
+ const ref = await saveAsset({
359
+ runId: run.id,
360
+ kind: mime.startsWith("image/") ? "upload-image" : "upload",
361
+ mime: mime || "application/octet-stream",
362
+ name: file.name.slice(0, 120),
363
+ data: new Uint8Array(await file.arrayBuffer()),
364
+ });
365
+ return c.json(ref);
366
+ })
367
+ // The address of where the person stands, for a location field — only when a geocoder is set up.
368
+ .post("/:id/geocode", async (c) => {
369
+ const run = await accessibleRun(c, c.req.param("id"));
370
+ if (!run) {
371
+ return c.json({ error: "not found" }, 404);
372
+ }
373
+ const { lat, lng, language } = z
374
+ .object({
375
+ lat: z.number().min(-90).max(90),
376
+ lng: z.number().min(-180).max(180),
377
+ language: z.enum(["de", "en"]).default("de"),
378
+ })
379
+ .parse(await c.req.json());
380
+ return c.json({ label: await reverseGeocode(lat, lng, language) });
381
+ })
382
+ .get("/:id/assets/:assetId", async (c) => {
383
+ const run = await accessibleRun(c, c.req.param("id"), { signed: true });
384
+ if (!run) {
385
+ return c.notFound();
386
+ }
387
+ return serveAsset(c, run, c.req.param("assetId"));
388
+ })
389
+ .get("/:id/steps/:stepId/download", async (c) => {
390
+ const run = await accessibleRun(c, c.req.param("id"), { signed: true });
391
+ if (!run) {
392
+ return c.notFound();
393
+ }
394
+ return serveDownload(c, run, c.req.param("stepId"), c.req.query("format"));
395
+ })
396
+ // --- a running step waits for the person ------------------------------------
397
+ .post("/:id/ask/:askId", async (c) => {
398
+ const run = await accessibleRun(c, c.req.param("id"));
399
+ if (!run) {
400
+ return c.json({ error: "not found" }, 404);
401
+ }
402
+ const answer = z
403
+ .discriminatedUnion("type", [
404
+ z.object({
405
+ type: z.literal("fill"),
406
+ values: z.record(z.string(), z.string().max(500)),
407
+ remember: z.boolean().optional(),
408
+ }),
409
+ z.object({ type: z.literal("done"), remember: z.boolean().optional() }),
410
+ z.object({ type: z.literal("skip") }),
411
+ ])
412
+ .parse(await c.req.json());
413
+ if (!answerAsk(run.id, c.req.param("askId"), answer)) {
414
+ return c.json({ error: "Diese Frage ist nicht mehr offen." }, 409);
415
+ }
416
+ return c.json({ ok: true });
417
+ })
418
+ // The page the wizard's browser shows: the person sees what they are asked to sign in to.
419
+ .get("/:id/browser/screen", async (c) => {
420
+ const run = await accessibleRun(c, c.req.param("id"));
421
+ const page = run?.status === "running" ? liveResources(run.id)?.openPage() : null;
422
+ if (!page) {
423
+ return c.notFound();
424
+ }
425
+ const jpeg = await page.screenshot({ type: "jpeg", quality: 70 }).catch(() => null);
426
+ if (!jpeg) {
427
+ return c.notFound();
428
+ }
429
+ const size = page.viewportSize() ?? { width: 1280, height: 900 };
430
+ return c.body(new Uint8Array(jpeg), 200, {
431
+ "content-type": "image/jpeg",
432
+ "cache-control": "no-store",
433
+ "x-page-width": String(size.width),
434
+ "x-page-height": String(size.height),
435
+ });
436
+ })
437
+ // While the wizard waits for a sign-in, the person can use its page: click, type, scroll.
438
+ .post("/:id/browser/act", async (c) => {
439
+ const run = await accessibleRun(c, c.req.param("id"));
440
+ const page = run && pendingAsk(run.id) ? liveResources(run.id)?.openPage() : null;
441
+ if (!page) {
442
+ return c.json({ error: "Der Wizard wartet gerade nicht auf dich." }, 409);
443
+ }
444
+ const act = z
445
+ .discriminatedUnion("type", [
446
+ z.object({ type: z.literal("click"), x: z.number().min(0), y: z.number().min(0) }),
447
+ z.object({ type: z.literal("type"), text: z.string().max(500) }),
448
+ z.object({ type: z.literal("key"), key: z.enum(["Enter", "Tab", "Backspace", "Escape"]) }),
449
+ z.object({ type: z.literal("scroll"), dy: z.number().min(-2000).max(2000) }),
450
+ ])
451
+ .parse(await c.req.json());
452
+ try {
453
+ if (act.type === "click") {
454
+ // A click may open the sign-in in a new tab; the wizard then goes on there.
455
+ const popup = page
456
+ .context()
457
+ .waitForEvent("page", { timeout: 1500 })
458
+ .catch(() => null);
459
+ await page.mouse.click(act.x, act.y);
460
+ const opened = await popup;
461
+ if (opened) {
462
+ liveResources(run.id)?.adopt(opened);
463
+ }
464
+ }
465
+ else if (act.type === "type") {
466
+ await page.keyboard.type(act.text);
467
+ }
468
+ else if (act.type === "key") {
469
+ await page.keyboard.press(act.key);
470
+ }
471
+ else {
472
+ await page.mouse.wheel(0, act.dy);
473
+ }
474
+ await page.waitForLoadState("domcontentloaded", { timeout: 3000 }).catch(() => undefined);
475
+ }
476
+ catch {
477
+ return c.json({ error: "Das hat auf der Seite nicht geklappt." }, 422);
478
+ }
479
+ return c.json({ ok: true });
480
+ })
481
+ // --- the person's accounts ----------------------------------------------------
482
+ .post("/:id/connections/:connectionId/oauth/:connectorId", async (c) => {
483
+ const run = await accessibleRun(c, c.req.param("id"));
484
+ const connection = run?.definition.connections?.find((x) => x.id === c.req.param("connectionId"));
485
+ if (!run || !connection) {
486
+ return c.json({ error: "not found" }, 404);
487
+ }
488
+ const url = await startOAuth(scopeOf(run), await projectIdOf(run), run.id, connection, c.req.param("connectorId"));
489
+ return c.json({ url });
490
+ })
491
+ .post("/:id/connections/:connectionId/credentials/:connectorId", async (c) => {
492
+ const run = await accessibleRun(c, c.req.param("id"));
493
+ const connection = run?.definition.connections?.find((x) => x.id === c.req.param("connectionId"));
494
+ if (!run || !connection) {
495
+ return c.json({ error: "not found" }, 404);
496
+ }
497
+ const { values } = z
498
+ .object({ values: z.record(z.string(), z.string().max(500)) })
499
+ .parse(await c.req.json());
500
+ try {
501
+ const label = await connectWithCredentials(scopeOf(run), await projectIdOf(run), connection, c.req.param("connectorId"), values);
502
+ signalChanged(run.id);
503
+ return c.json({ ok: true, label });
504
+ }
505
+ catch (err) {
506
+ if (err instanceof ConnectError) {
507
+ return c.json({ error: err.message }, 422);
508
+ }
509
+ throw err;
510
+ }
511
+ })
512
+ .delete("/:id/connections/:connectionId", async (c) => {
513
+ const run = await accessibleRun(c, c.req.param("id"));
514
+ if (!run) {
515
+ return c.json({ error: "not found" }, 404);
516
+ }
517
+ await disconnect(scopeOf(run), c.req.param("connectionId"));
518
+ signalChanged(run.id);
519
+ return c.json({ ok: true });
520
+ })
521
+ // --- the wizard's lists and files for this person -------------------------------
522
+ .post("/:id/lists/:list/rows", async (c) => {
523
+ const run = await accessibleRun(c, c.req.param("id"));
524
+ const def = run?.definition.lists?.find((l) => l.id === c.req.param("list"));
525
+ if (!run || !def) {
526
+ return c.json({ error: "not found" }, 404);
527
+ }
528
+ const { cells } = z
529
+ .object({ cells: z.record(z.string(), z.unknown()) })
530
+ .parse(await c.req.json());
531
+ try {
532
+ await saveRows(scopeOf(run), def, [cells]);
533
+ }
534
+ catch (err) {
535
+ return listError(c, err);
536
+ }
537
+ signalChanged(run.id);
538
+ return c.json({ ok: true });
539
+ })
540
+ .patch("/:id/lists/:list/rows/:rowId", async (c) => {
541
+ const run = await accessibleRun(c, c.req.param("id"));
542
+ const def = run?.definition.lists?.find((l) => l.id === c.req.param("list"));
543
+ if (!run || !def) {
544
+ return c.json({ error: "not found" }, 404);
545
+ }
546
+ const { cells } = z
547
+ .object({ cells: z.record(z.string(), z.unknown()) })
548
+ .parse(await c.req.json());
549
+ try {
550
+ if (!(await updateRow(scopeOf(run), def, c.req.param("rowId"), cells))) {
551
+ return c.json({ error: "not found" }, 404);
552
+ }
553
+ }
554
+ catch (err) {
555
+ return listError(c, err);
556
+ }
557
+ signalChanged(run.id);
558
+ return c.json({ ok: true });
559
+ })
560
+ .delete("/:id/lists/:list/rows/:rowId", async (c) => {
561
+ const run = await accessibleRun(c, c.req.param("id"));
562
+ const def = run?.definition.lists?.find((l) => l.id === c.req.param("list"));
563
+ if (!run || !def) {
564
+ return c.json({ error: "not found" }, 404);
565
+ }
566
+ await deleteRows(scopeOf(run), def, [c.req.param("rowId")]);
567
+ signalChanged(run.id);
568
+ return c.json({ ok: true });
569
+ })
570
+ .get("/:id/lists/:list/download", async (c) => {
571
+ const run = await accessibleRun(c, c.req.param("id"));
572
+ const def = run?.definition.lists?.find((l) => l.id === c.req.param("list"));
573
+ const format = z.enum(FORMATS).safeParse(c.req.query("format"));
574
+ if (!run || !def || !format.success || !LIST_FORMATS.includes(format.data)) {
575
+ return c.notFound();
576
+ }
577
+ const scope = scopeOf(run);
578
+ const download = await listDownload(def, await listRows(scope, def.id), format.data, `${run.definition.title} ${def.title}`, async (path) => {
579
+ const found = await readStoreFile(scope, path).catch(() => null);
580
+ return found ? new Uint8Array(found.data) : null;
581
+ });
582
+ return download ? sendDownload(c, download) : c.notFound();
583
+ })
584
+ .get("/:id/store", async (c) => {
585
+ const run = await accessibleRun(c, c.req.param("id"));
586
+ if (!run) {
587
+ return c.json({ error: "not found" }, 404);
588
+ }
589
+ const scope = scopeOf(run);
590
+ const lists = await Promise.all((run.definition.lists ?? []).map(async (def) => ({
591
+ id: def.id,
592
+ title: def.title,
593
+ rows: (await listRows(scope, def.id)).length,
594
+ })));
595
+ const secrets = await listSecrets(scope);
596
+ return c.json({
597
+ lists,
598
+ files: await storeFiles(scope),
599
+ connections: (await connectionViews(run.definition.connections ?? [], scope, await projectIdOf(run))).filter((x) => x.account),
600
+ keepsSignIns: secrets.some((s) => s.slot === "browser"),
601
+ });
602
+ })
603
+ .get("/:id/store/files/:path{.+}", async (c) => {
604
+ const run = await accessibleRun(c, c.req.param("id"));
605
+ const found = run ? await readStoreFile(scopeOf(run), c.req.param("path")) : null;
606
+ if (!found) {
607
+ return c.notFound();
608
+ }
609
+ const name = (found.file.path.split("/").pop() ?? "file").replace(/"/g, "");
610
+ // Shown beside its row in a review: only what a browser draws without running anything of
611
+ // the file's — PDFs and photos. Everything else stays a download.
612
+ if (c.req.query("inline") && INLINE_TYPES.has(found.file.mime)) {
613
+ return c.body(new Uint8Array(found.data), 200, {
614
+ "content-type": found.file.mime,
615
+ "content-disposition": `inline; filename="${name}"`,
616
+ "x-content-type-options": "nosniff",
617
+ "cache-control": "private, max-age=300",
618
+ });
619
+ }
620
+ return c.body(new Uint8Array(found.data), 200, {
621
+ // Stored files come from mail and the web: always a download, never a page of ours.
622
+ "content-type": "application/octet-stream",
623
+ "content-disposition": `attachment; filename="${name}"`,
624
+ "x-content-type-options": "nosniff",
625
+ });
626
+ })
627
+ .delete("/:id/store", async (c) => {
628
+ const run = await accessibleRun(c, c.req.param("id"));
629
+ if (!run) {
630
+ return c.json({ error: "not found" }, 404);
631
+ }
632
+ await clearStore(scopeOf(run));
633
+ signalChanged(run.id);
634
+ return c.json({ ok: true });
635
+ })
636
+ .post("/:id/share", async (c) => {
637
+ const run = await accessibleRun(c, c.req.param("id"));
638
+ if (!run) {
639
+ return c.json({ error: "not found" }, 404);
640
+ }
641
+ try {
642
+ return c.json(await shareRun(run));
643
+ }
644
+ catch (err) {
645
+ if (err instanceof ServiceError) {
646
+ return c.json(err.toJSON(), err.status);
647
+ }
648
+ throw err;
649
+ }
650
+ })
651
+ .delete("/:id/share", async (c) => {
652
+ const run = await accessibleRun(c, c.req.param("id"));
653
+ if (!run) {
654
+ return c.json({ error: "not found" }, 404);
655
+ }
656
+ await unshareRun(run);
657
+ return c.json({ ok: true });
658
+ });
659
+ function listError(c, err) {
660
+ if (err instanceof TableColumnValueError || err instanceof StoreError) {
661
+ return c.json({ error: err.message }, 422);
662
+ }
663
+ throw err;
664
+ }
665
+ function sendDownload(c, download) {
666
+ const data = new Uint8Array(typeof download.data === "string" ? new TextEncoder().encode(download.data) : download.data);
667
+ return c.body(data, 200, {
668
+ "content-type": download.mime.startsWith("text/")
669
+ ? `${download.mime}; charset=utf-8`
670
+ : download.mime,
671
+ "content-disposition": `attachment; filename="${download.filename}"`,
672
+ });
673
+ }
674
+ /**
675
+ * Where an OAuth provider sends the person back after connecting an account. The page tells the
676
+ * wizard's window and closes itself.
677
+ */
678
+ export const connectCallback = new Hono().get("/callback", async (c) => {
679
+ const code = c.req.query("code");
680
+ const state = c.req.query("state");
681
+ let ok = false;
682
+ let message = "Verbinden wurde abgebrochen.";
683
+ const tenant = state ? await tenantOfRun(oauthStateRun(state) ?? "") : null;
684
+ if (code && state && tenant) {
685
+ try {
686
+ const done = await withTenant(tenant, () => finishOAuth(code, state));
687
+ signalChanged(done.runId);
688
+ ok = true;
689
+ message = "Verbunden. Du kannst dieses Fenster schließen.";
690
+ }
691
+ catch (err) {
692
+ console.error("[connect]", err);
693
+ message = "Verbinden hat nicht geklappt. Bitte noch einmal versuchen.";
694
+ }
695
+ }
696
+ return c.html(`<!doctype html><meta charset="utf-8"><title>engenty wizards</title>
697
+ <body style="font:16px system-ui;display:grid;place-items:center;height:100vh;margin:0;color:#333">
698
+ <p>${message}</p>
699
+ <script>
700
+ try { window.opener && window.opener.postMessage({ type: "wizards:connected", ok: ${ok} }, ${JSON.stringify(env.appUrl)}); } catch (e) {}
701
+ setTimeout(function () { window.close(); }, ${ok ? 300 : 4000});
702
+ </script>`);
703
+ });
704
+ async function serveAsset(c, run, assetId) {
705
+ const found = await loadAsset(assetId);
706
+ if (!found || found.row.runId !== run.id) {
707
+ return c.notFound();
708
+ }
709
+ const headers = {
710
+ "content-type": found.row.mime,
711
+ "cache-control": "private, max-age=3600",
712
+ "x-content-type-options": "nosniff",
713
+ };
714
+ // Files a step collected come from mail and the web: they are downloads, never pages of ours.
715
+ if (found.row.kind === "file") {
716
+ headers["content-disposition"] =
717
+ `attachment; filename="${found.row.name.replace(/["\\\r\n]/g, "")}"`;
718
+ }
719
+ // An SVG or XML document opened on its own could run scripts with this origin.
720
+ if (/svg|xml/.test(found.row.mime)) {
721
+ headers["content-security-policy"] = "sandbox";
722
+ }
723
+ // Generated HTML runs its scripts in an opaque origin and can reach nothing.
724
+ if (found.row.mime === "text/html") {
725
+ const html = await stepHtml({
726
+ assets: [{ id: assetId, kind: found.row.kind, mime: found.row.mime, name: "" }],
727
+ at: "",
728
+ });
729
+ headers["content-type"] = "text/html; charset=utf-8";
730
+ headers["content-security-policy"] = HTML_RESPONSE_CSP;
731
+ return c.body(new TextEncoder().encode(html ?? ""), 200, headers);
732
+ }
733
+ if (/^(audio|video)\//.test(found.row.mime)) {
734
+ headers["accept-ranges"] = "bytes";
735
+ const size = found.data.byteLength;
736
+ const range = byteRange(c.req.header("range"), size);
737
+ if (range === "unsatisfiable") {
738
+ return c.body(null, 416, { "content-range": `bytes */${size}` });
739
+ }
740
+ if (range) {
741
+ headers["content-range"] = `bytes ${range.start}-${range.end}/${size}`;
742
+ return c.body(new Uint8Array(found.data.subarray(range.start, range.end + 1)), 206, headers);
743
+ }
744
+ }
745
+ return c.body(new Uint8Array(found.data), 200, headers);
746
+ }
747
+ async function serveDownload(c, run, stepId, rawFormat) {
748
+ const format = z.enum(FORMATS).safeParse(rawFormat);
749
+ const step = run.definition.steps.find((s) => s.id === stepId);
750
+ const output = step ? run.state.outputs[step.id] : undefined;
751
+ if (!format.success || !step || !output) {
752
+ return c.notFound();
753
+ }
754
+ const label = run.definition.steps
755
+ .flatMap((s) => (s.type === "result" ? s.deliverables : []))
756
+ .find((d) => d.from === step.id)?.label;
757
+ const download = await renderDownload(step, output, format.data, `${run.definition.title} ${label ?? step.title}`);
758
+ if (!download) {
759
+ return c.notFound();
760
+ }
761
+ return sendDownload(c, download);
762
+ }
763
+ /** Steps a shared link shows: the result's deliverables — never uploads or in-between steps. */
764
+ function sharedStepIds(run) {
765
+ return new Set(run.definition.steps.flatMap((s) => s.type === "result" ? s.deliverables.map((d) => d.from) : []));
766
+ }
767
+ /** `/api/shares/:token` — a shared result, readable by anyone holding the link until it expires. */
768
+ export const shareRoutes = new Hono()
769
+ .use("/:token", resultLink)
770
+ .use("/:token/*", resultLink)
771
+ .get("/:token", async (c) => {
772
+ const run = await sharedRun(c.req.param("token"));
773
+ if (!run) {
774
+ return c.json({ error: "Dieser Link ist abgelaufen oder wurde zurückgezogen." }, 404);
775
+ }
776
+ return c.json(await shareView(run));
777
+ })
778
+ .get("/:token/image", async (c) => {
779
+ const run = await sharedRun(c.req.param("token"));
780
+ const image = run ? await shareImage(run) : null;
781
+ if (!image) {
782
+ return c.notFound();
783
+ }
784
+ return c.body(image.data, 200, {
785
+ "content-type": image.mime,
786
+ "cache-control": "public, max-age=3600",
787
+ });
788
+ })
789
+ .get("/:token/assets/:assetId", async (c) => {
790
+ const run = await sharedRun(c.req.param("token"));
791
+ if (!run) {
792
+ return c.notFound();
793
+ }
794
+ const assetId = c.req.param("assetId");
795
+ const allowed = [...sharedStepIds(run)].some((id) => run.state.outputs[id]?.assets?.some((a) => a.id === assetId));
796
+ return allowed ? serveAsset(c, run, assetId) : c.notFound();
797
+ })
798
+ .get("/:token/steps/:stepId/download", async (c) => {
799
+ const run = await sharedRun(c.req.param("token"));
800
+ const stepId = c.req.param("stepId");
801
+ if (!run || !sharedStepIds(run).has(stepId)) {
802
+ return c.notFound();
803
+ }
804
+ return serveDownload(c, run, stepId, c.req.query("format"));
805
+ });