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,161 @@
1
+ import HTMLtoDOCX from "@turbodocx/html-to-docx";
2
+ import ExcelJS from "exceljs";
3
+ import { marked } from "marked";
4
+ import TurndownService from "turndown";
5
+ import { offlineContext } from "./chromium.js";
6
+ const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
7
+ export const MIME = {
8
+ png: "image/png",
9
+ mp4: "video/mp4",
10
+ mp3: "audio/mpeg",
11
+ pdf: "application/pdf",
12
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
13
+ html: "text/html",
14
+ md: "text/markdown",
15
+ txt: "text/plain",
16
+ csv: "text/csv",
17
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
18
+ json: "application/json",
19
+ zip: "application/zip",
20
+ };
21
+ /** A calm print stylesheet for documents written as Markdown. */
22
+ export function markdownToHtml(markdown, title = "Document") {
23
+ const body = marked.parse(markdown, { async: false });
24
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(title)}</title>
25
+ <style>
26
+ @page { size: A4; margin: 22mm 20mm; }
27
+ body { font-family: "Inter", "Helvetica Neue", Arial, sans-serif; color: #1d1a17; line-height: 1.55; font-size: 11pt; max-width: 760px; margin: 40px auto; padding: 0 24px; }
28
+ h1 { font-size: 22pt; line-height: 1.2; margin: 0 0 12pt; }
29
+ h2 { font-size: 14pt; margin: 22pt 0 6pt; }
30
+ h3 { font-size: 12pt; margin: 16pt 0 4pt; }
31
+ table { border-collapse: collapse; width: 100%; margin: 10pt 0; font-size: 10pt; }
32
+ th, td { border-bottom: 1px solid #e6e1da; padding: 6pt 8pt; text-align: left; vertical-align: top; }
33
+ th { font-weight: 600; background: #f7f4ef; }
34
+ a { color: #b4532a; }
35
+ blockquote { border-left: 3px solid #e6e1da; margin: 0; padding-left: 12pt; color: #5d564e; }
36
+ code { background: #f4f1ec; padding: 1px 4px; border-radius: 3px; }
37
+ @media print { body { margin: 0; padding: 0; max-width: none; } }
38
+ </style></head><body>${body}</body></html>`;
39
+ }
40
+ export function escapeHtml(s) {
41
+ return s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
42
+ }
43
+ export function htmlToMarkdown(html) {
44
+ const body = html.match(/<body[^>]*>([\s\S]*)<\/body>/i)?.[1] ?? html;
45
+ return turndown.turndown(body.replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<script[\s\S]*?<\/script>/gi, ""));
46
+ }
47
+ export async function htmlToPdf(html) {
48
+ const context = await offlineContext();
49
+ try {
50
+ const page = await context.newPage();
51
+ await page.setContent(html, { waitUntil: "networkidle", timeout: 30_000 });
52
+ return await page.pdf({ format: "A4", printBackground: true, preferCSSPageSize: true });
53
+ }
54
+ finally {
55
+ await context.close();
56
+ }
57
+ }
58
+ export async function htmlToPng(html, width = 1200, opts = {}) {
59
+ const context = await offlineContext({
60
+ viewport: { width, height: opts.height ?? 900 },
61
+ deviceScaleFactor: opts.scale ?? 2,
62
+ });
63
+ try {
64
+ const page = await context.newPage();
65
+ await page.setContent(html, { waitUntil: "networkidle", timeout: 30_000 });
66
+ return await page.screenshot({ fullPage: opts.fullPage ?? true, type: "png" });
67
+ }
68
+ finally {
69
+ await context.close();
70
+ }
71
+ }
72
+ export async function htmlToDocx(html) {
73
+ const cleaned = html.replace(/<script[\s\S]*?<\/script>/gi, "");
74
+ const out = await HTMLtoDOCX(cleaned, null, {
75
+ table: { row: { cantSplit: true } },
76
+ font: "Arial",
77
+ });
78
+ if (out instanceof ArrayBuffer) {
79
+ return new Uint8Array(out);
80
+ }
81
+ if (typeof Blob !== "undefined" && out instanceof Blob) {
82
+ return new Uint8Array(await out.arrayBuffer());
83
+ }
84
+ return new Uint8Array(out);
85
+ }
86
+ /** The first array of objects inside a JSON value — the table a step produced. */
87
+ export function firstTable(value) {
88
+ if (Array.isArray(value) && value.every((r) => r && typeof r === "object" && !Array.isArray(r))) {
89
+ return value;
90
+ }
91
+ if (value && typeof value === "object") {
92
+ for (const v of Object.values(value)) {
93
+ const t = firstTable(v);
94
+ if (t) {
95
+ return t;
96
+ }
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ function tableColumns(rows) {
102
+ return [...new Set(rows.flatMap((r) => Object.keys(r)))];
103
+ }
104
+ function cell(v) {
105
+ return v === null || v === undefined ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
106
+ }
107
+ export function tableToCsv(rows) {
108
+ const cols = tableColumns(rows);
109
+ const esc = (s) => (/[",\n;]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s);
110
+ return [
111
+ cols.map(esc).join(","),
112
+ ...rows.map((r) => cols.map((c) => esc(cell(r[c]))).join(",")),
113
+ ].join("\n");
114
+ }
115
+ /** Every table of a step's JSON result, by its key; a bare array is the one table "Data". */
116
+ export function allTables(value) {
117
+ const isTable = (v) => Array.isArray(v) &&
118
+ v.length > 0 &&
119
+ v.every((r) => r && typeof r === "object" && !Array.isArray(r));
120
+ if (isTable(value)) {
121
+ return [{ name: "Data", rows: value }];
122
+ }
123
+ if (!value || typeof value !== "object") {
124
+ return [];
125
+ }
126
+ return Object.entries(value).flatMap(([name, v]) => isTable(v)
127
+ ? [{ name, rows: v }]
128
+ : Array.isArray(v) && v.length && v.every((x) => typeof x !== "object")
129
+ ? [{ name, rows: v.map((x) => ({ [name]: x })) }]
130
+ : []);
131
+ }
132
+ /** A workbook with one sheet per table. `headers` renames columns (column id → label). */
133
+ export async function tablesToXlsx(tables) {
134
+ const wb = new ExcelJS.Workbook();
135
+ for (const [index, table] of tables.entries()) {
136
+ const ws = wb.addWorksheet(table.name.replace(/[\\/*?:[\]]/g, " ").slice(0, 31) || `Sheet${index + 1}`);
137
+ const cols = table.headers ? Object.keys(table.headers) : tableColumns(table.rows);
138
+ ws.columns = cols.map((c) => {
139
+ const header = table.headers?.[c] ?? c;
140
+ return { header, key: c, width: Math.min(40, Math.max(12, header.length + 4)) };
141
+ });
142
+ for (const r of table.rows) {
143
+ ws.addRow(Object.fromEntries(cols.map((c) => [c, typeof r[c] === "number" ? r[c] : cell(r[c])])));
144
+ }
145
+ ws.getRow(1).font = { bold: true };
146
+ }
147
+ return new Uint8Array(await wb.xlsx.writeBuffer());
148
+ }
149
+ export function tableToXlsx(rows) {
150
+ return tablesToXlsx([{ name: "Data", rows }]);
151
+ }
152
+ export function jsonToMarkdown(value) {
153
+ const rows = firstTable(value);
154
+ if (rows?.length) {
155
+ const cols = tableColumns(rows);
156
+ const head = `| ${cols.join(" | ")} |\n| ${cols.map(() => "---").join(" | ")} |`;
157
+ const body = rows.map((r) => `| ${cols.map((c) => cell(r[c]).replace(/\|/g, "\\|")).join(" | ")} |`);
158
+ return [head, ...body].join("\n");
159
+ }
160
+ return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
161
+ }
@@ -0,0 +1,277 @@
1
+ import { formatsFor, widgetSize } from "@engenty-wizards/shared/definition";
2
+ import { listAsMarkdown } from "@engenty-wizards/shared/store";
3
+ import { and, eq } from "drizzle-orm";
4
+ import { zipSync } from "fflate";
5
+ import { db, schema } from "../db/client.js";
6
+ import { extFor, inlineAssetRefs, loadAsset, loadAssetText, saveAsset } from "../files/storage.js";
7
+ import { widgetMp4, widgetPdf, widgetPng } from "../widgets/render.js";
8
+ import { allTables, firstTable, htmlToDocx, htmlToMarkdown, htmlToPdf, htmlToPng, jsonToMarkdown, MIME, markdownToHtml, tablesToXlsx, tableToCsv, } from "./convert.js";
9
+ import { guardHtml } from "./guard.js";
10
+ export function slug(s) {
11
+ return (s
12
+ .toLowerCase()
13
+ .normalize("NFKD")
14
+ .replace(/[̀-ͯ]/g, "")
15
+ .replace(/ß/g, "ss")
16
+ .replace(/[^a-z0-9]+/g, "-")
17
+ .replace(/^-|-$/g, "")
18
+ .slice(0, 60) || "result");
19
+ }
20
+ /**
21
+ * Renders of an asset (PDF, PNG, MP4 …) are kept next to it: a shared link opened a hundred
22
+ * times renders once. A regenerated output is a new asset, so its renders start fresh.
23
+ */
24
+ async function cachedRender(source, format, produce) {
25
+ const name = `${source.id}.${format}`;
26
+ const hit = await db.query.asset.findFirst({
27
+ where: and(eq(schema.asset.kind, "render"), eq(schema.asset.name, name)),
28
+ });
29
+ if (hit) {
30
+ const found = await loadAsset(hit.id);
31
+ if (found) {
32
+ return new Uint8Array(found.data);
33
+ }
34
+ }
35
+ const data = await produce();
36
+ if (data) {
37
+ const row = await db.query.asset.findFirst({ where: eq(schema.asset.id, source.id) });
38
+ await saveAsset({
39
+ runId: row?.runId ?? null,
40
+ stepId: row?.stepId ?? null,
41
+ kind: "render",
42
+ mime: MIME[format],
43
+ name,
44
+ data,
45
+ });
46
+ }
47
+ return data;
48
+ }
49
+ /** The HTML a step produced, ready to show or save: images inlined, network and frames locked. */
50
+ export async function stepHtml(output) {
51
+ const asset = output.assets?.find((a) => a.mime === "text/html");
52
+ if (!asset) {
53
+ return null;
54
+ }
55
+ return guardHtml(await inlineAssetRefs(await loadAssetText(asset.id)));
56
+ }
57
+ /** One step's output in one format. Conversions happen on demand. */
58
+ export async function renderDownload(step, output, format, baseName) {
59
+ if (!formatsFor(step).includes(format)) {
60
+ return null;
61
+ }
62
+ const name = slug(baseName);
63
+ const file = (ext) => `${name}.${ext}`;
64
+ if (step.type === "widget" && step.video) {
65
+ // A film was rendered when its step ran: the video and its poster are the files.
66
+ const asset = output.assets?.find((a) => a.kind === (format === "mp4" ? "video" : "poster"));
67
+ const found = asset ? await loadAsset(asset.id) : null;
68
+ return found
69
+ ? { data: new Uint8Array(found.data), mime: found.row.mime, filename: file(format) }
70
+ : null;
71
+ }
72
+ if (step.type === "widget") {
73
+ const source = output.assets?.find((a) => a.mime === "text/html");
74
+ if (format === "json") {
75
+ return {
76
+ data: JSON.stringify(output.json ?? {}, null, 2),
77
+ mime: MIME.json,
78
+ filename: file("json"),
79
+ };
80
+ }
81
+ if (!source) {
82
+ return null;
83
+ }
84
+ const html = await loadAssetText(source.id);
85
+ const size = widgetSize(step);
86
+ const data = format === "html"
87
+ ? html
88
+ : await cachedRender(source, format, async () => {
89
+ switch (format) {
90
+ case "png":
91
+ return widgetPng(html, size);
92
+ case "pdf":
93
+ return widgetPdf(html, size);
94
+ case "mp4":
95
+ return output.widget?.duration ? widgetMp4(html, size) : null;
96
+ default:
97
+ return null;
98
+ }
99
+ });
100
+ return data ? { data, mime: MIME[format], filename: file(format) } : null;
101
+ }
102
+ if (step.type === "generate") {
103
+ const asset = output.assets?.[0];
104
+ if (!asset) {
105
+ return null;
106
+ }
107
+ if (format === "zip") {
108
+ const zip = await zipAssets(output.assets ?? []);
109
+ return zip ? { data: zip, mime: MIME.zip, filename: file("zip") } : null;
110
+ }
111
+ if (step.asset === "image" || step.asset === "video" || step.asset === "voice") {
112
+ const found = await loadAsset(asset.id);
113
+ return found
114
+ ? {
115
+ data: new Uint8Array(found.data),
116
+ mime: found.row.mime,
117
+ filename: file(extFor(found.row.mime)),
118
+ }
119
+ : null;
120
+ }
121
+ const html = await inlineAssetRefs(await loadAssetText(asset.id));
122
+ const render = (produce) => cachedRender(asset, format, produce);
123
+ switch (format) {
124
+ case "html":
125
+ return { data: guardHtml(html), mime: MIME.html, filename: file("html") };
126
+ case "pdf":
127
+ return { data: await render(() => htmlToPdf(html)), mime: MIME.pdf, filename: file("pdf") };
128
+ case "png":
129
+ return { data: await render(() => htmlToPng(html)), mime: MIME.png, filename: file("png") };
130
+ case "docx":
131
+ return {
132
+ data: await render(() => htmlToDocx(html)),
133
+ mime: MIME.docx,
134
+ filename: file("docx"),
135
+ };
136
+ case "md":
137
+ return { data: htmlToMarkdown(html), mime: MIME.md, filename: file("md") };
138
+ default:
139
+ return null;
140
+ }
141
+ }
142
+ if (step.type === "agent") {
143
+ if (format === "zip") {
144
+ const zip = await zipAssets(output.assets?.filter((a) => a.kind === "file") ?? []);
145
+ return zip ? { data: zip, mime: MIME.zip, filename: file("zip") } : null;
146
+ }
147
+ const text = output.text ?? "";
148
+ if (step.output.format === "json") {
149
+ const rows = firstTable(output.json);
150
+ const tables = allTables(output.json);
151
+ switch (format) {
152
+ case "json":
153
+ return {
154
+ data: JSON.stringify(output.json, null, 2),
155
+ mime: MIME.json,
156
+ filename: file("json"),
157
+ };
158
+ case "csv":
159
+ return rows ? { data: tableToCsv(rows), mime: MIME.csv, filename: file("csv") } : null;
160
+ case "xlsx":
161
+ return tables.length
162
+ ? { data: await tablesToXlsx(tables), mime: MIME.xlsx, filename: file("xlsx") }
163
+ : null;
164
+ case "md":
165
+ return { data: jsonAsMarkdown(output.json), mime: MIME.md, filename: file("md") };
166
+ default:
167
+ return null;
168
+ }
169
+ }
170
+ switch (format) {
171
+ case "md":
172
+ return { data: text, mime: MIME.md, filename: file("md") };
173
+ case "txt":
174
+ return { data: text, mime: MIME.txt, filename: file("txt") };
175
+ case "html":
176
+ return { data: markdownToHtml(text, baseName), mime: MIME.html, filename: file("html") };
177
+ case "pdf":
178
+ return {
179
+ data: await htmlToPdf(markdownToHtml(text, baseName)),
180
+ mime: MIME.pdf,
181
+ filename: file("pdf"),
182
+ };
183
+ case "docx":
184
+ return {
185
+ data: await htmlToDocx(markdownToHtml(text, baseName)),
186
+ mime: MIME.docx,
187
+ filename: file("docx"),
188
+ };
189
+ default:
190
+ return null;
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ /** A structured result as readable Markdown: text fields as paragraphs, tables as tables. */
196
+ /** The files a step collected, as one archive. Two files of the same name both stay. */
197
+ async function zipAssets(assets) {
198
+ const entries = {};
199
+ for (const asset of assets) {
200
+ const found = await loadAsset(asset.id);
201
+ if (!found) {
202
+ continue;
203
+ }
204
+ let name = asset.name;
205
+ for (let n = 2; name in entries; n++) {
206
+ name = asset.name.replace(/(\.[^.]+)?$/, (ext) => `-${n}${ext}`);
207
+ }
208
+ entries[name] = new Uint8Array(found.data);
209
+ }
210
+ return Object.keys(entries).length ? zipSync(entries, { level: 6 }) : null;
211
+ }
212
+ /**
213
+ * A stored list in one format. Column labels head the spreadsheet; ids stay the JSON keys.
214
+ * `files` reads a kept file by its path — a zip holds the files the rows name, and the list.
215
+ */
216
+ export async function listDownload(def, rows, format, baseName, files) {
217
+ const name = slug(baseName);
218
+ if (format === "zip") {
219
+ const column = def.check?.file;
220
+ if (!column || !files) {
221
+ return null;
222
+ }
223
+ const entries = {};
224
+ for (const row of rows) {
225
+ const path = String(row.cells[column] ?? "").trim();
226
+ const data = path && !(path in entries) ? await files(path) : null;
227
+ if (data) {
228
+ entries[path] = data;
229
+ }
230
+ }
231
+ const sheet = await listDownload(def, rows, "xlsx", baseName);
232
+ if (sheet) {
233
+ entries[sheet.filename] = sheet.data;
234
+ }
235
+ return { data: zipSync(entries, { level: 6 }), mime: MIME.zip, filename: `${name}.zip` };
236
+ }
237
+ const cells = rows.map((r) => r.cells);
238
+ const headers = Object.fromEntries(def.columns.map((c) => [c.id, c.name]));
239
+ const labelled = cells.map((row) => Object.fromEntries(def.columns.map((c) => [c.name, row[c.id] ?? null])));
240
+ switch (format) {
241
+ case "xlsx":
242
+ return {
243
+ data: await tablesToXlsx([{ name: def.title, rows: cells, headers }]),
244
+ mime: MIME.xlsx,
245
+ filename: `${name}.xlsx`,
246
+ };
247
+ case "csv":
248
+ return { data: tableToCsv(labelled), mime: MIME.csv, filename: `${name}.csv` };
249
+ case "json":
250
+ return { data: JSON.stringify(cells, null, 2), mime: MIME.json, filename: `${name}.json` };
251
+ case "md":
252
+ return {
253
+ data: `# ${def.title}\n\n${listAsMarkdown(def, rows, 5000)}`,
254
+ mime: MIME.md,
255
+ filename: `${name}.md`,
256
+ };
257
+ default:
258
+ return null;
259
+ }
260
+ }
261
+ export function jsonAsMarkdown(value) {
262
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
263
+ return jsonToMarkdown(value);
264
+ }
265
+ return Object.entries(value)
266
+ .map(([key, v]) => {
267
+ const heading = `### ${key}`;
268
+ if (typeof v === "string" || typeof v === "number") {
269
+ return `${heading}\n${v}`;
270
+ }
271
+ if (Array.isArray(v) && v.every((x) => typeof x !== "object")) {
272
+ return `${heading}\n${v.map((x) => `- ${x}`).join("\n")}`;
273
+ }
274
+ return `${heading}\n${jsonToMarkdown(v)}`;
275
+ })
276
+ .join("\n\n");
277
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Every generated HTML document (documents, dashboards, widgets) gets this before anyone sees
3
+ * it: scripts may run, but the page can load nothing and send nothing — everything it shows is
4
+ * inlined. Links open in a new tab instead of inside the preview frame.
5
+ */
6
+ export const HTML_CSP = "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; style-src 'unsafe-inline'; img-src data: blob:; media-src data: blob:; font-src data:; connect-src data: blob:; worker-src blob:";
7
+ /** The response header for a generated document opened on its own: an opaque origin, scripts allowed. */
8
+ export const HTML_RESPONSE_CSP = `sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox; ${HTML_CSP}`;
9
+ /**
10
+ * `media`: one more origin images, video and audio may come from — a film's page gets this
11
+ * run's clips from the renderer that way; they are too large to inline.
12
+ */
13
+ export function guardHtml(html, media) {
14
+ const csp = media
15
+ ? HTML_CSP.replace("img-src", `img-src ${media}`).replace("media-src", `media-src ${media}`)
16
+ : HTML_CSP;
17
+ const head = `<meta http-equiv="Content-Security-Policy" content="${csp}"><base target="_blank">`;
18
+ if (/<head[^>]*>/i.test(html)) {
19
+ return html.replace(/<head[^>]*>/i, (m) => `${m}${head}`);
20
+ }
21
+ if (/<html[^>]*>/i.test(html)) {
22
+ return html.replace(/<html[^>]*>/i, (m) => `${m}<head>${head}</head>`);
23
+ }
24
+ return `<!doctype html><html><head>${head}</head><body>${html}</body></html>`;
25
+ }
@@ -0,0 +1,74 @@
1
+ import { Hono } from "hono";
2
+ import { z } from "zod";
3
+ import { withTenant } from "../db/client.js";
4
+ import { authenticate, principalOf } from "../mcp/auth.js";
5
+ import { ServiceError } from "../services/errors.js";
6
+ import { deleteFile, draftFiles, writeFile } from "../services/files.js";
7
+ import { createWizard, ownedWizard, publishWizard, writeDraft } from "../services/wizards.js";
8
+ import { tenantStatus } from "../tenants/control.js";
9
+ const importSchema = z.object({
10
+ /** An earlier import's wizard: it is updated instead of a new one being made. */
11
+ wizardId: z.string().optional(),
12
+ projectId: z.string().optional(),
13
+ definition: z.unknown(),
14
+ files: z
15
+ .array(z.object({ path: z.string(), mime: z.string().optional(), data: z.string() }))
16
+ .max(200)
17
+ .default([]),
18
+ publish: z.boolean().default(false),
19
+ });
20
+ /**
21
+ * The runtime's API for other programs, signed in like an MCP client (access token or API key).
22
+ * `POST /wizards/import` takes a whole wizard — definition and workspace — validated like any
23
+ * other write: this is how a desktop app publishes to the cloud.
24
+ */
25
+ export const apiRoutes = new Hono().post("/wizards/import", async (c) => {
26
+ const authInfo = await authenticate(c.req.raw);
27
+ if (authInfo instanceof Response) {
28
+ return authInfo;
29
+ }
30
+ const who = principalOf(authInfo);
31
+ const body = importSchema.parse(await c.req.json());
32
+ if (!who.scopes.includes("wizards:write") ||
33
+ (body.publish && !who.scopes.includes("wizards:publish"))) {
34
+ return c.json({ error: "This token may not do that.", code: "insufficient_scope" }, 403);
35
+ }
36
+ if ((await tenantStatus(who.tenantId)) === "suspended") {
37
+ return c.json({ error: "tenant_suspended" }, 403);
38
+ }
39
+ return withTenant(who.tenantId, async () => {
40
+ const writer = { source: "mcp", client: who.client };
41
+ let wizardId = body.wizardId;
42
+ if (wizardId) {
43
+ const existing = await ownedWizard(who.userId, wizardId).catch(() => null);
44
+ wizardId = existing?.id;
45
+ }
46
+ if (!wizardId) {
47
+ // The workspace must be there before the definition is checked against it.
48
+ const created = await createWizard(who.userId, { projectId: body.projectId }, writer);
49
+ wizardId = created.id;
50
+ }
51
+ const incoming = new Set(body.files.map((f) => f.path));
52
+ for (const old of await draftFiles(wizardId)) {
53
+ if (!incoming.has(old.path)) {
54
+ await deleteFile(who.userId, wizardId, old.path);
55
+ }
56
+ }
57
+ for (const file of body.files) {
58
+ await writeFile(who.userId, wizardId, file.path, Buffer.from(file.data, "base64"), file.mime);
59
+ }
60
+ const current = await ownedWizard(who.userId, wizardId);
61
+ const written = await writeDraft(who.userId, wizardId, { definition: body.definition, baseRevision: current.revision }, writer);
62
+ if (!body.publish) {
63
+ return c.json({ wizardId, revision: written.revision, issues: written.issues });
64
+ }
65
+ if (written.issues.length) {
66
+ throw new ServiceError("has_issues", "Der Wizard hat noch Fehler.", {
67
+ issues: written.issues,
68
+ wizardId,
69
+ });
70
+ }
71
+ const published = await publishWizard(who.userId, wizardId);
72
+ return c.json({ wizardId, revision: written.revision, issues: [], ...published });
73
+ });
74
+ });
@@ -0,0 +1,52 @@
1
+ import { basePath } from "../env.js";
2
+ /** Uploads stay small; a clip from the phone's camera is the one thing that needs more room. */
3
+ export function uploadLimit(mime) {
4
+ return mime.startsWith("video/") ? 40_000_000 : 15_000_000;
5
+ }
6
+ export function wizardManifest(token, title, description) {
7
+ const start = `${basePath}/w/${token}`;
8
+ return {
9
+ id: start,
10
+ name: title,
11
+ short_name: title.length > 14 ? `${title.slice(0, 13).trimEnd()}…` : title,
12
+ description,
13
+ start_url: start,
14
+ scope: `${basePath}/`,
15
+ display: "standalone",
16
+ background_color: "#faf8f5",
17
+ theme_color: "#faf8f5",
18
+ icons: [
19
+ {
20
+ src: `${basePath}/studio/icons/icon-192.png`,
21
+ sizes: "192x192",
22
+ type: "image/png",
23
+ purpose: "any",
24
+ },
25
+ {
26
+ src: `${basePath}/studio/icons/icon-512.png`,
27
+ sizes: "512x512",
28
+ type: "image/png",
29
+ purpose: "any",
30
+ },
31
+ {
32
+ src: `${basePath}/studio/icons/icon-maskable-512.png`,
33
+ sizes: "512x512",
34
+ type: "image/png",
35
+ purpose: "maskable",
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ /**
41
+ * One byte range of a file, as phones ask for audio and video: Safari plays a clip only from a
42
+ * server that answers ranges. `null` = no (usable) range asked, send it whole.
43
+ */
44
+ export function byteRange(header, size) {
45
+ const m = header?.match(/^bytes=(\d*)-(\d*)$/);
46
+ if (!m || (!m[1] && !m[2]) || size === 0) {
47
+ return null;
48
+ }
49
+ const start = m[1] ? Number(m[1]) : Math.max(0, size - Number(m[2]));
50
+ const end = m[1] && m[2] ? Math.min(Number(m[2]), size - 1) : size - 1;
51
+ return start > end || start >= size ? "unsatisfiable" : { start, end };
52
+ }
@@ -0,0 +1,49 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { Hono } from "hono";
5
+ import { z } from "zod";
6
+ import { closeTenantDb, tenantDb } from "../db/client.js";
7
+ import { env } from "../env.js";
8
+ import { removeTenantObjects } from "../files/objects.js";
9
+ import { forgetTenant, managed } from "../manage.js";
10
+ import { forgetTenantRows, setTenantStatus } from "../tenants/control.js";
11
+ function fromManage(header) {
12
+ const given = Buffer.from(header?.replace(/^Bearer\s+/i, "") ?? "");
13
+ const expected = Buffer.from(env.manage.serviceKey);
14
+ return (managed &&
15
+ expected.length > 0 &&
16
+ given.length === expected.length &&
17
+ timingSafeEqual(given, expected));
18
+ }
19
+ /**
20
+ * What the Manage-App tells this runtime about a tenant: suspend, resume, delete. Deleting
21
+ * removes the tenant's files and everything the control database knows; the tenant's database
22
+ * itself is deleted by whoever created it (the Manage-App, or here when it is a file).
23
+ */
24
+ export const internalRoutes = new Hono().post("/tenants/:id", async (c) => {
25
+ if (!fromManage(c.req.header("authorization"))) {
26
+ return c.json({ error: "unauthorized" }, 401);
27
+ }
28
+ const id = c.req.param("id");
29
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(id)) {
30
+ return c.json({ error: "bad tenant id" }, 400);
31
+ }
32
+ const { action } = z
33
+ .object({ action: z.enum(["suspend", "resume", "delete"]) })
34
+ .parse(await c.req.json());
35
+ forgetTenant(id);
36
+ if (action === "delete") {
37
+ closeTenantDb(id);
38
+ await removeTenantObjects(id);
39
+ await forgetTenantRows(id);
40
+ for (const suffix of ["", "-wal", "-shm"]) {
41
+ await rm(join(env.dataDir, "tenants", `${id}.db${suffix}`), { force: true });
42
+ }
43
+ return c.json({ ok: true });
44
+ }
45
+ // The tenant row exists once its database was opened here.
46
+ await tenantDb(id);
47
+ await setTenantStatus(id, action === "suspend" ? "suspended" : "active");
48
+ return c.json({ ok: true });
49
+ });
@@ -0,0 +1,36 @@
1
+ import { CAPABILITY_IDS, INDUSTRY_IDS, ITEM_FORMAT_IDS, USE_CASE_IDS, } from "@engenty-wizards/shared/marketplace";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { asLang, marketplaceDetail, searchMarketplace, starEntry, } from "../services/marketplace.js";
5
+ const searchSchema = z.object({
6
+ q: z.string().trim().max(200).default(""),
7
+ lang: z.string().optional(),
8
+ useCase: z.enum(USE_CASE_IDS).optional(),
9
+ industry: z.enum(INDUSTRY_IDS).optional(),
10
+ format: z.enum(ITEM_FORMAT_IDS).optional(),
11
+ capability: z.enum(CAPABILITY_IDS).optional(),
12
+ starred: z
13
+ .enum(["1", "0"])
14
+ .optional()
15
+ .transform((v) => v === "1"),
16
+ limit: z.coerce.number().int().min(1).max(100).default(60),
17
+ offset: z.coerce.number().int().min(0).default(0),
18
+ });
19
+ /** The marketplace for someone signed in: the search on "new", an entry, and the stars. */
20
+ export const marketplaceStudio = new Hono()
21
+ .get("/", async (c) => {
22
+ const { lang, ...search } = searchSchema.parse(c.req.query());
23
+ return c.json(await searchMarketplace({ ...search, lang: asLang(lang) }));
24
+ })
25
+ .get("/:id", async (c) => {
26
+ const detail = await marketplaceDetail(c.req.param("id"), asLang(c.req.query("lang")));
27
+ return detail ? c.json(detail) : c.json({ error: "not found" }, 404);
28
+ })
29
+ .put("/:id/star", async (c) => {
30
+ await starEntry(c.get("user").tenantId, c.req.param("id"), true);
31
+ return c.json({ ok: true });
32
+ })
33
+ .delete("/:id/star", async (c) => {
34
+ await starEntry(c.get("user").tenantId, c.req.param("id"), false);
35
+ return c.json({ ok: true });
36
+ });