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,762 @@
1
+ import { z } from "zod";
2
+ import { tableColumnsSchema } from "./engenty/data-tables/index.js";
3
+ import { CONNECTION_KINDS } from "./store.js";
4
+ /** The engenty avatars a wizard can wear. Same cast as engenty's ui-core. */
5
+ export const ENGENTY_KINDS = [
6
+ "round",
7
+ "drop",
8
+ "dome",
9
+ "flame",
10
+ "oval",
11
+ "bean",
12
+ "pebble",
13
+ "sprout",
14
+ "tower",
15
+ "wedge",
16
+ ];
17
+ export const FIELD_KINDS = [
18
+ "text",
19
+ "textarea",
20
+ "number",
21
+ "select",
22
+ "multiselect",
23
+ "date",
24
+ "email",
25
+ "url",
26
+ "toggle",
27
+ "color",
28
+ "image",
29
+ "file",
30
+ "items",
31
+ "connection",
32
+ "list",
33
+ "location",
34
+ "audio",
35
+ "signature",
36
+ ];
37
+ export function isLocationValue(v) {
38
+ if (!v || typeof v !== "object" || Array.isArray(v)) {
39
+ return false;
40
+ }
41
+ const { lat, lng, label } = v;
42
+ return ((typeof lat === "number" && typeof lng === "number") ||
43
+ (typeof label === "string" && label.trim().length > 0));
44
+ }
45
+ export function isAudioValue(v) {
46
+ return Boolean(v &&
47
+ typeof v === "object" &&
48
+ typeof v.asset === "string" &&
49
+ v.asset);
50
+ }
51
+ /** A position the way a person reads it: "Hauptplatz 1, Graz (47.07071, 15.43950, ±12 m)". */
52
+ export function locationText(v) {
53
+ const point = typeof v.lat === "number" && typeof v.lng === "number"
54
+ ? `${v.lat.toFixed(5)}, ${v.lng.toFixed(5)}${v.accuracy ? `, ±${Math.round(v.accuracy)} m` : ""}`
55
+ : "";
56
+ const label = v.label?.trim() ?? "";
57
+ return label && point ? `${label} (${point})` : label || point;
58
+ }
59
+ /** A field takes at most this many files. */
60
+ export const MAX_FILES = 30;
61
+ export const TOOL_IDS = ["web_search", "web_fetch", "browser", "sandbox", "image", "http"];
62
+ /**
63
+ * The kinds of model a step can ask for. A step names a class; which model serves it is bound
64
+ * outside the wizard (the model-gateway, or the settings of a runtime that runs alone).
65
+ */
66
+ export const TEXT_CLASSES = ["classifier", "standard", "high", "highest"];
67
+ export const MODEL_CLASSES = [...TEXT_CLASSES, "image", "video", "audio", "speech"];
68
+ /** How hard the model should think, where the bound model can be told. */
69
+ export const EFFORTS = ["low", "medium", "high"];
70
+ export const ASSET_KINDS = ["image", "video", "voice", "document", "dashboard"];
71
+ export const FORMATS = [
72
+ "png",
73
+ "mp4",
74
+ "mp3",
75
+ "pdf",
76
+ "docx",
77
+ "html",
78
+ "md",
79
+ "txt",
80
+ "csv",
81
+ "xlsx",
82
+ "json",
83
+ "zip",
84
+ ];
85
+ export const ASPECT_RATIOS = ["1:1", "16:9", "9:16", "4:5", "3:2", "2:3"];
86
+ /** What a video model renders at: 720p unless the step asks for less. Never more. */
87
+ export const VIDEO_RESOLUTIONS = ["720p", "480p"];
88
+ const id = z
89
+ .string()
90
+ .regex(/^[a-zA-Z][a-zA-Z0-9_]{0,40}$/, "ids are letters, digits and _ and start with a letter");
91
+ export const itemColumnSchema = z.object({
92
+ id,
93
+ label: z.string().min(1),
94
+ kind: z.enum(["text", "number", "money"]),
95
+ });
96
+ export const fieldSchema = z.object({
97
+ id,
98
+ label: z.string().min(1),
99
+ kind: z.enum(FIELD_KINDS),
100
+ required: z.boolean().optional(),
101
+ placeholder: z.string().optional(),
102
+ help: z.string().optional(),
103
+ /** Choices for select / multiselect. */
104
+ options: z.array(z.string()).optional(),
105
+ default: z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]).optional(),
106
+ /** Line-item columns for `items`. A row's amount is the product of its number and money columns. */
107
+ columns: z.array(itemColumnSchema).optional(),
108
+ /** VAT for `items`: a fixed rate in percent, or the id of a number/select field holding it. */
109
+ vat: z.object({ rate: z.number().optional(), field: z.string().optional() }).optional(),
110
+ currency: z.string().optional(),
111
+ /** image / file: several files; the value is then a list, in the order the person put them. */
112
+ multiple: z.boolean().optional(),
113
+ /** image / file with `multiple`: how many files at least and at most (up to 30). */
114
+ min: z.number().int().min(1).max(MAX_FILES).optional(),
115
+ max: z.number().int().min(1).max(MAX_FILES).optional(),
116
+ /** file: also offer the camera, for documents the person only has on paper. Images always offer it. */
117
+ camera: z.boolean().optional(),
118
+ /** file: the camera also records short video clips. */
119
+ video: z.boolean().optional(),
120
+ /** text: also fill it by scanning a QR code or barcode with the camera. */
121
+ scan: z.boolean().optional(),
122
+ /** connection: id of the wizard connection the person connects here. */
123
+ connection: z.string().optional(),
124
+ /** list: id of the wizard list shown here for the person to check and correct. */
125
+ list: z.string().optional(),
126
+ /**
127
+ * What the field starts with, from an earlier step: "steps.<id>" or "steps.<id>.<key>". An
128
+ * `items` field takes a table whose columns are its column ids; the person corrects it.
129
+ */
130
+ prefill: z.string().optional(),
131
+ });
132
+ export const nextRuleSchema = z.object({
133
+ when: z.object({
134
+ field: z.string(),
135
+ op: z.enum(["equals", "notEquals", "in", "notEmpty", "empty"]),
136
+ value: z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]).optional(),
137
+ }),
138
+ /** A step id, or "end". */
139
+ goto: z.string(),
140
+ });
141
+ const stepBase = {
142
+ id,
143
+ title: z.string().min(1),
144
+ description: z.string().optional(),
145
+ next: z.array(nextRuleSchema).optional(),
146
+ };
147
+ export const outputFieldSchema = z
148
+ .object({
149
+ id,
150
+ kind: z.enum(["text", "number", "list", "table", "yesno", "choice"]),
151
+ /** What the field holds; for `yesno` and `choice` the question it answers. */
152
+ description: z.string().optional(),
153
+ /** Table only: the exact column keys every row has — what a widget reads. */
154
+ columns: z.array(z.string().min(1)).optional(),
155
+ /** Choice only: the options, one of which is the answer. */
156
+ options: z.array(z.string().min(1)).min(2).max(40).optional(),
157
+ })
158
+ .refine((field) => field.kind !== "choice" || Boolean(field.options?.length), {
159
+ message: "A choice field lists its options.",
160
+ path: ["options"],
161
+ });
162
+ export const pageStepSchema = z.object({
163
+ ...stepBase,
164
+ type: z.literal("page"),
165
+ fields: z.array(fieldSchema).min(1),
166
+ cta: z.string().optional(),
167
+ });
168
+ export const agentStepSchema = z.object({
169
+ ...stepBase,
170
+ type: z.literal("agent"),
171
+ /** What the agent does. A template: {{field}}, {{steps.id}}, {{brand.name}}. */
172
+ instructions: z.string().min(1),
173
+ tools: z.array(z.enum(TOOL_IDS)).default([]),
174
+ /** Ids of project MCP servers this step may use. */
175
+ mcp: z.array(z.string()).optional(),
176
+ /** Ids of wizard connections (the person's accounts) this step may use. */
177
+ connections: z.array(z.string()).optional(),
178
+ output: z
179
+ .object({
180
+ format: z.enum(["text", "markdown", "json"]),
181
+ fields: z.array(outputFieldSchema).optional(),
182
+ })
183
+ .default({ format: "markdown" }),
184
+ /** The kind of model the step needs; default `high`. The model itself is bound elsewhere. */
185
+ model: z.enum(TEXT_CLASSES).optional(),
186
+ effort: z.enum(EFFORTS).optional(),
187
+ /** Shown to the person while it works. */
188
+ working: z.string().optional(),
189
+ });
190
+ export const generateStepSchema = z.object({
191
+ ...stepBase,
192
+ type: z.literal("generate"),
193
+ asset: z.enum(ASSET_KINDS),
194
+ /** For image/video the visual prompt; for document/dashboard the brief. A template. */
195
+ prompt: z.string().min(1),
196
+ options: z
197
+ .object({
198
+ aspectRatio: z.enum(ASPECT_RATIOS).optional(),
199
+ /** Video length in seconds. */
200
+ duration: z.number().optional(),
201
+ /** Video: the height the model renders at; default 720p, the most there is. */
202
+ resolution: z.enum(VIDEO_RESOLUTIONS).optional(),
203
+ style: z.string().optional(),
204
+ /** Document flavour, steers the layout. */
205
+ template: z.enum(["invoice", "offer", "briefing", "letter", "report", "free"]).optional(),
206
+ })
207
+ .optional(),
208
+ /**
209
+ * The image the image/video model starts from: an earlier image field, or an earlier step
210
+ * that made images.
211
+ */
212
+ referenceImage: z.string().optional(),
213
+ /**
214
+ * Image / video: one result per entry instead of one. Names an image field with several
215
+ * uploads, an earlier step that made several images, or a table of an earlier agent step
216
+ * ("steps.<id>.<key>"). The prompt reads the entry as {{item.<column>}}, {{index}}, {{count}};
217
+ * `referenceImage` gives each entry its own image when it holds as many.
218
+ */
219
+ each: z.string().optional(),
220
+ /** For document/dashboard: the kind of model that writes it; default `high`. */
221
+ model: z.enum(TEXT_CLASSES).optional(),
222
+ effort: z.enum(EFFORTS).optional(),
223
+ working: z.string().optional(),
224
+ });
225
+ export const widgetStepSchema = z.object({
226
+ ...stepBase,
227
+ type: z.literal("widget"),
228
+ /** Workspace path of the widget's HTML. Written once; every run only brings new data. */
229
+ entry: z.string().min(1),
230
+ /** What the widget gets as `wizard.data`: key → a field id, steps.id, steps.id.key, brand.name or today. */
231
+ data: z.record(z.string(), z.string()).default({}),
232
+ /** Workspace path of example data (JSON) for previews before any run. */
233
+ sample: z.string().optional(),
234
+ /** The size the widget is designed for; previews scale it, PNG/PDF/MP4 render at it. */
235
+ size: z
236
+ .object({
237
+ width: z.number().int().min(200).max(3840),
238
+ height: z.number().int().min(200).max(3840),
239
+ })
240
+ .optional(),
241
+ /**
242
+ * The result is a film: the timeline is rendered to an MP4 with sound when the step runs,
243
+ * and that video is what the person sees and downloads.
244
+ */
245
+ video: z.boolean().optional(),
246
+ working: z.string().optional(),
247
+ });
248
+ export const reviewStepSchema = z.object({
249
+ ...stepBase,
250
+ type: z.literal("review"),
251
+ /** Step ids whose output is shown. */
252
+ show: z.array(z.string()).min(1),
253
+ edit: z.boolean().optional(),
254
+ regenerate: z.boolean().optional(),
255
+ });
256
+ export const deliverableSchema = z.object({
257
+ from: z.string(),
258
+ label: z.string().optional(),
259
+ formats: z.array(z.enum(FORMATS)).min(1),
260
+ });
261
+ export const resultStepSchema = z.object({
262
+ ...stepBase,
263
+ type: z.literal("result"),
264
+ message: z.string().optional(),
265
+ deliverables: z.array(deliverableSchema),
266
+ });
267
+ export const stepSchema = z.discriminatedUnion("type", [
268
+ pageStepSchema,
269
+ agentStepSchema,
270
+ generateStepSchema,
271
+ widgetStepSchema,
272
+ reviewStepSchema,
273
+ resultStepSchema,
274
+ ]);
275
+ /**
276
+ * A decision: a step on the classifier class that uses no tools and whose output is only
277
+ * yes/no and choice fields. It is answered in one call, which a model that only decides
278
+ * (no text) can take.
279
+ */
280
+ export function isDecisionStep(step) {
281
+ const fields = step.output.fields ?? [];
282
+ return (step.model === "classifier" &&
283
+ step.output.format === "json" &&
284
+ fields.length > 0 &&
285
+ fields.every((f) => f.kind === "yesno" || f.kind === "choice") &&
286
+ step.tools.length === 0 &&
287
+ !step.mcp?.length &&
288
+ !step.connections?.length);
289
+ }
290
+ export const listSchema = z.object({
291
+ id,
292
+ title: z.string().min(1),
293
+ description: z.string().optional(),
294
+ /** engenty data-table columns: `{ id, name, type, format }`. */
295
+ columns: tableColumnsSchema,
296
+ /** Column that identifies a row; saving a row with a known key updates it. */
297
+ key: z.string().optional(),
298
+ /**
299
+ * The person goes through the rows one by one in a review: `file` is the column holding the
300
+ * path of a kept file, shown beside the row; `status` a select column whose options are the
301
+ * answers — the first one means "not looked at yet".
302
+ */
303
+ check: z.object({ file: z.string().optional(), status: z.string().optional() }).optional(),
304
+ });
305
+ export const connectionSchema = z.object({
306
+ id,
307
+ /** A built-in kind of account … */
308
+ kind: z.enum(CONNECTION_KINDS).optional(),
309
+ /** … or the id of one of the project's imported connectors. */
310
+ connector: z.string().optional(),
311
+ /** Imported connectors: the actions steps may call. Default: every action that only reads. */
312
+ actions: z.array(z.string()).max(200).optional(),
313
+ /** Imported connectors, per action: "allow" runs it without asking, "ask" asks the person first. */
314
+ policy: z.record(z.string(), z.enum(["allow", "ask"])).optional(),
315
+ title: z.string().optional(),
316
+ description: z.string().optional(),
317
+ });
318
+ /** The version of the definition this app writes and reads; raised only by a breaking change. */
319
+ export const DEFINITION_VERSION = 1;
320
+ export const wizardSchema = z.object({
321
+ version: z.literal(DEFINITION_VERSION).default(DEFINITION_VERSION),
322
+ title: z.string().min(1),
323
+ description: z.string().default(""),
324
+ avatar: z.enum(ENGENTY_KINDS).default("round"),
325
+ /** Shown on the first page above the first question. */
326
+ intro: z.string().optional(),
327
+ /** Tabular data the wizard keeps between runs, per person. */
328
+ lists: z.array(listSchema).max(12).optional(),
329
+ /** Accounts the person connects; kept between runs, per person. */
330
+ connections: z.array(connectionSchema).max(12).optional(),
331
+ steps: z.array(stepSchema).min(1),
332
+ });
333
+ export const INTERACTIVE = new Set(["page", "review", "result"]);
334
+ export function isInteractive(step) {
335
+ return INTERACTIVE.has(step.type);
336
+ }
337
+ /** Which formats each producing step can be downloaded in. */
338
+ export function formatsFor(step) {
339
+ if (step.type === "generate") {
340
+ switch (step.asset) {
341
+ case "image":
342
+ return step.each ? ["png", "zip"] : ["png"];
343
+ case "video":
344
+ return step.each ? ["mp4", "zip"] : ["mp4"];
345
+ case "voice":
346
+ return ["mp3"];
347
+ case "document":
348
+ return ["pdf", "docx", "html", "md", "png"];
349
+ case "dashboard":
350
+ return ["html", "pdf", "png"];
351
+ }
352
+ }
353
+ if (step.type === "widget") {
354
+ return step.video ? ["mp4", "png"] : ["html", "png", "pdf", "mp4", "json"];
355
+ }
356
+ if (step.type === "agent") {
357
+ if (step.output.format === "json") {
358
+ return ["json", "csv", "xlsx", "md", "zip"];
359
+ }
360
+ return ["md", "txt", "docx", "pdf", "html", "zip"];
361
+ }
362
+ return [];
363
+ }
364
+ /** Formats a stored list can be downloaded in; zip = the kept files its rows name, with the list. */
365
+ export const LIST_FORMATS = ["xlsx", "csv", "json", "md", "zip"];
366
+ const LIST_REF = /^lists\.([a-zA-Z][a-zA-Z0-9_]*)$/;
367
+ /** "lists.providers" → "providers"; anything else → null. */
368
+ export function listRef(ref) {
369
+ return ref.match(LIST_REF)?.[1] ?? null;
370
+ }
371
+ export const DEFAULT_WIDGET_SIZE = { width: 1280, height: 720 };
372
+ export function widgetSize(step) {
373
+ return step.size ?? DEFAULT_WIDGET_SIZE;
374
+ }
375
+ /** A widget data reference without braces: "{{steps.x}}" and "steps.x" mean the same. */
376
+ export function dataRef(raw) {
377
+ return raw
378
+ .trim()
379
+ .replace(/^\{\{\s*/, "")
380
+ .replace(/\s*\}\}$/, "");
381
+ }
382
+ const TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
383
+ export function templateRefs(template) {
384
+ return [...template.matchAll(TEMPLATE_RE)].map((m) => m[1]);
385
+ }
386
+ /**
387
+ * Structural checks the schema cannot express: ids, references between steps,
388
+ * templates naming things that exist by the time the step runs.
389
+ */
390
+ export function validateWizard(def, files) {
391
+ const issues = [];
392
+ const stepIds = new Set();
393
+ const fieldIds = new Set();
394
+ const seenSteps = new Set();
395
+ const seenFields = new Set();
396
+ const listIds = new Set();
397
+ const connectionIds = new Set();
398
+ for (const list of def.lists ?? []) {
399
+ if (listIds.has(list.id)) {
400
+ issues.push({ message: `Duplicate list id "${list.id}".` });
401
+ }
402
+ listIds.add(list.id);
403
+ if (list.key && !list.columns.some((c) => c.id === list.key)) {
404
+ issues.push({ message: `List "${list.id}": key "${list.key}" is not one of its columns.` });
405
+ }
406
+ for (const [role, column] of Object.entries(list.check ?? {})) {
407
+ const found = list.columns.find((c) => c.id === column);
408
+ if (!found) {
409
+ issues.push({
410
+ message: `List "${list.id}": check.${role} "${column}" is not one of its columns.`,
411
+ });
412
+ }
413
+ else if (role === "status" && found.type !== "select") {
414
+ issues.push({ message: `List "${list.id}": check.status "${column}" must be a select.` });
415
+ }
416
+ }
417
+ }
418
+ for (const connection of def.connections ?? []) {
419
+ if (connectionIds.has(connection.id)) {
420
+ issues.push({ message: `Duplicate connection id "${connection.id}".` });
421
+ }
422
+ connectionIds.add(connection.id);
423
+ if (Boolean(connection.kind) === Boolean(connection.connector)) {
424
+ issues.push({
425
+ message: `Connection "${connection.id}" needs either "kind" or "connector", not both.`,
426
+ });
427
+ }
428
+ }
429
+ for (const step of def.steps) {
430
+ if (stepIds.has(step.id)) {
431
+ issues.push({ stepId: step.id, message: `Duplicate step id "${step.id}".` });
432
+ }
433
+ stepIds.add(step.id);
434
+ if (step.type === "page") {
435
+ for (const field of step.fields) {
436
+ if (fieldIds.has(field.id)) {
437
+ issues.push({ stepId: step.id, message: `Duplicate field id "${field.id}".` });
438
+ }
439
+ fieldIds.add(field.id);
440
+ if ((field.kind === "select" || field.kind === "multiselect") && !field.options?.length) {
441
+ issues.push({ stepId: step.id, message: `Field "${field.id}" needs options.` });
442
+ }
443
+ if (field.kind === "items" && !field.columns?.length) {
444
+ issues.push({ stepId: step.id, message: `Field "${field.id}" needs columns.` });
445
+ }
446
+ if (field.kind === "connection" && !connectionIds.has(field.connection ?? "")) {
447
+ issues.push({
448
+ stepId: step.id,
449
+ message: `Field "${field.id}" needs "connection": the id of one of the wizard's connections.`,
450
+ });
451
+ }
452
+ if (field.kind === "list" && !listIds.has(field.list ?? "")) {
453
+ issues.push({
454
+ stepId: step.id,
455
+ message: `Field "${field.id}" needs "list": the id of one of the wizard's lists.`,
456
+ });
457
+ }
458
+ if (field.min !== undefined || field.max !== undefined) {
459
+ if ((field.kind !== "image" && field.kind !== "file") || !field.multiple) {
460
+ issues.push({
461
+ stepId: step.id,
462
+ message: `Field "${field.id}": "min" and "max" count the files of an image or file field with "multiple".`,
463
+ });
464
+ }
465
+ else if (field.min !== undefined && field.max !== undefined && field.min > field.max) {
466
+ issues.push({
467
+ stepId: step.id,
468
+ message: `Field "${field.id}": min is larger than max.`,
469
+ });
470
+ }
471
+ }
472
+ if (field.scan && field.kind !== "text") {
473
+ issues.push({
474
+ stepId: step.id,
475
+ message: `Field "${field.id}": "scan" fills a text field; kind is "${field.kind}".`,
476
+ });
477
+ }
478
+ if (field.video && field.kind !== "file") {
479
+ issues.push({
480
+ stepId: step.id,
481
+ message: `Field "${field.id}": "video" belongs to a file field; kind is "${field.kind}".`,
482
+ });
483
+ }
484
+ }
485
+ }
486
+ }
487
+ const last = def.steps.at(-1);
488
+ if (last?.type !== "result") {
489
+ issues.push({ message: "The last step must be a result step." });
490
+ }
491
+ const checkTemplate = (step, template) => {
492
+ for (const ref of templateRefs(template)) {
493
+ const [head, second] = ref.split(".");
494
+ if (head === "steps") {
495
+ if (!second || !seenSteps.has(second)) {
496
+ issues.push({
497
+ stepId: step.id,
498
+ message: `"{{${ref}}}" refers to a step that has not run before "${step.id}".`,
499
+ });
500
+ }
501
+ }
502
+ else if (head === "brand" || head === "facts" || head === "today" || head === "notes") {
503
+ // provided by the runner
504
+ }
505
+ else if (head === "item" || head === "index" || head === "count") {
506
+ if (step.type !== "generate" || !step.each) {
507
+ issues.push({
508
+ stepId: step.id,
509
+ message: `"{{${ref}}}" only exists in a generate step with "each".`,
510
+ });
511
+ }
512
+ }
513
+ else if (head === "lists") {
514
+ if (!second || !listIds.has(second)) {
515
+ issues.push({ stepId: step.id, message: `"{{${ref}}}" refers to an unknown list.` });
516
+ }
517
+ }
518
+ else if (!seenFields.has(head)) {
519
+ issues.push({
520
+ stepId: step.id,
521
+ message: `"{{${ref}}}" refers to a field that is not asked before "${step.id}".`,
522
+ });
523
+ }
524
+ }
525
+ };
526
+ for (const step of def.steps) {
527
+ switch (step.type) {
528
+ case "page":
529
+ for (const field of step.fields) {
530
+ if (field.prefill) {
531
+ const ref = dataRef(field.prefill);
532
+ if (ref.split(".")[0] !== "steps") {
533
+ issues.push({
534
+ stepId: step.id,
535
+ message: `Field "${field.id}": prefill names an earlier step ("steps.<id>.<key>").`,
536
+ });
537
+ }
538
+ else {
539
+ checkTemplate(step, `{{${ref}}}`);
540
+ }
541
+ }
542
+ seenFields.add(field.id);
543
+ }
544
+ break;
545
+ case "agent":
546
+ checkTemplate(step, step.instructions);
547
+ for (const c of step.connections ?? []) {
548
+ if (!connectionIds.has(c)) {
549
+ issues.push({ stepId: step.id, message: `Step uses unknown connection "${c}".` });
550
+ }
551
+ }
552
+ break;
553
+ case "generate":
554
+ checkTemplate(step, step.prompt);
555
+ if (step.referenceImage &&
556
+ !seenFields.has(step.referenceImage) &&
557
+ !seenSteps.has(step.referenceImage)) {
558
+ issues.push({
559
+ stepId: step.id,
560
+ message: `referenceImage "${step.referenceImage}" is not an earlier field or step.`,
561
+ });
562
+ }
563
+ if (step.each) {
564
+ const ref = dataRef(step.each);
565
+ if (step.asset !== "image" && step.asset !== "video") {
566
+ issues.push({ stepId: step.id, message: `"each" is for image and video steps.` });
567
+ }
568
+ else if (ref.startsWith("steps.")) {
569
+ checkTemplate(step, `{{${ref}}}`);
570
+ }
571
+ else if (!seenFields.has(ref) && !seenSteps.has(ref)) {
572
+ issues.push({
573
+ stepId: step.id,
574
+ message: `each "${step.each}" is not an earlier field, step or "steps.<id>.<key>".`,
575
+ });
576
+ }
577
+ }
578
+ break;
579
+ case "widget":
580
+ for (const [key, ref] of Object.entries(step.data)) {
581
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
582
+ issues.push({
583
+ stepId: step.id,
584
+ message: `Widget data key "${key}" is not an identifier.`,
585
+ });
586
+ }
587
+ checkTemplate(step, `{{${dataRef(ref)}}}`);
588
+ }
589
+ if (files && !files.includes(step.entry)) {
590
+ issues.push({
591
+ stepId: step.id,
592
+ message: `Widget entry "${step.entry}" is not in the workspace.`,
593
+ });
594
+ }
595
+ if (files && step.sample && !files.includes(step.sample)) {
596
+ issues.push({
597
+ stepId: step.id,
598
+ message: `Widget sample "${step.sample}" is not in the workspace.`,
599
+ });
600
+ }
601
+ break;
602
+ case "review":
603
+ for (const ref of step.show) {
604
+ const list = listRef(ref);
605
+ if (list ? !listIds.has(list) : !seenSteps.has(ref)) {
606
+ issues.push({
607
+ stepId: step.id,
608
+ message: `Review shows unknown ${list ? "list" : "step"} "${ref}".`,
609
+ });
610
+ }
611
+ }
612
+ break;
613
+ case "result":
614
+ for (const d of step.deliverables) {
615
+ const list = listRef(d.from);
616
+ if (list ? !listIds.has(list) : !seenSteps.has(d.from)) {
617
+ issues.push({
618
+ stepId: step.id,
619
+ message: `Deliverable from unknown ${list ? "list" : "step"} "${d.from}".`,
620
+ });
621
+ }
622
+ }
623
+ break;
624
+ }
625
+ for (const rule of step.next ?? []) {
626
+ if (rule.goto !== "end" && !stepIds.has(rule.goto)) {
627
+ issues.push({ stepId: step.id, message: `Branch goes to unknown step "${rule.goto}".` });
628
+ }
629
+ // A branch reads what the person answered, or an output field of this or an earlier
630
+ // agent step as `steps.<step>.<field>`.
631
+ const [head, from, key, ...more] = rule.when.field.split(".");
632
+ const source = head === "steps" && key && more.length === 0 && (from === step.id || seenSteps.has(from))
633
+ ? def.steps.find((s) => s.id === from)
634
+ : undefined;
635
+ const known = fieldIds.has(rule.when.field) ||
636
+ (source?.type === "agent" && (source.output.fields ?? []).some((f) => f.id === key));
637
+ if (!known) {
638
+ issues.push({
639
+ stepId: step.id,
640
+ message: `Branch reads unknown field "${rule.when.field}".`,
641
+ });
642
+ }
643
+ }
644
+ seenSteps.add(step.id);
645
+ }
646
+ return issues;
647
+ }
648
+ /** Parse + validate in one go. */
649
+ export function parseWizard(input, files) {
650
+ const parsed = wizardSchema.safeParse(input);
651
+ if (!parsed.success) {
652
+ return {
653
+ ok: false,
654
+ issues: parsed.error.issues.map((i) => ({ message: `${i.path.join(".")}: ${i.message}` })),
655
+ };
656
+ }
657
+ const issues = validateWizard(parsed.data, files);
658
+ return { ok: true, wizard: parsed.data, issues };
659
+ }
660
+ /**
661
+ * What branch rules can read in a run: the person's answers by field id, and the structured
662
+ * output of the steps run so far as `steps.<step>.<field>`.
663
+ */
664
+ export function branchValues(values, outputs) {
665
+ const out = { ...values };
666
+ for (const [stepId, output] of Object.entries(outputs)) {
667
+ if (output.json && typeof output.json === "object" && !Array.isArray(output.json)) {
668
+ for (const [key, value] of Object.entries(output.json)) {
669
+ out[`steps.${stepId}.${key}`] = value;
670
+ }
671
+ }
672
+ }
673
+ return out;
674
+ }
675
+ /** The step after `step`, honouring its branch rules. `null` = the end. */
676
+ export function nextStepId(def, stepId, values) {
677
+ const index = def.steps.findIndex((s) => s.id === stepId);
678
+ const step = def.steps[index];
679
+ if (!step) {
680
+ return null;
681
+ }
682
+ for (const rule of step.next ?? []) {
683
+ if (matches(rule, values)) {
684
+ return rule.goto === "end" ? null : rule.goto;
685
+ }
686
+ }
687
+ return def.steps[index + 1]?.id ?? null;
688
+ }
689
+ function isEmptyValue(v) {
690
+ return v === undefined || v === null || v === "" || (Array.isArray(v) && v.length === 0);
691
+ }
692
+ function matches(rule, values) {
693
+ const v = values[rule.when.field];
694
+ switch (rule.when.op) {
695
+ case "empty":
696
+ return isEmptyValue(v);
697
+ case "notEmpty":
698
+ return !isEmptyValue(v);
699
+ case "equals":
700
+ return String(v) === String(rule.when.value);
701
+ case "notEquals":
702
+ return String(v) !== String(rule.when.value);
703
+ case "in":
704
+ return Array.isArray(rule.when.value) && rule.when.value.map(String).includes(String(v));
705
+ }
706
+ }
707
+ export function itemsTotals(field, value, values) {
708
+ const rows = Array.isArray(value) ? value : [];
709
+ const numeric = (field.columns ?? []).filter((c) => c.kind !== "text");
710
+ const withAmount = rows.map((row) => {
711
+ const amount = numeric.length ? numeric.reduce((acc, c) => acc * amountOf(row[c.id]), 1) : 0;
712
+ return { ...row, amount: round2(amount) };
713
+ });
714
+ const net = round2(withAmount.reduce((a, r) => a + r.amount, 0));
715
+ let vatRate = field.vat?.rate ?? 0;
716
+ if (field.vat?.field) {
717
+ const raw = values[field.vat.field];
718
+ const parsed = Number.parseFloat(String(raw ?? "").replace(",", "."));
719
+ if (Number.isFinite(parsed)) {
720
+ vatRate = parsed;
721
+ }
722
+ }
723
+ const vat = round2((net * vatRate) / 100);
724
+ return {
725
+ rows: withAmount,
726
+ net,
727
+ vatRate,
728
+ vat,
729
+ gross: round2(net + vat),
730
+ currency: field.currency ?? "EUR",
731
+ };
732
+ }
733
+ /** A typed amount as a number: a phone's number pad in German gives "89,90". */
734
+ function amountOf(v) {
735
+ const n = typeof v === "string" ? Number(v.replace(/\s/g, "").replace(",", ".")) : Number(v);
736
+ return Number.isFinite(n) ? n : 0;
737
+ }
738
+ function round2(n) {
739
+ return Math.round(n * 100) / 100;
740
+ }
741
+ export function allFields(def) {
742
+ return def.steps.flatMap((s) => (s.type === "page" ? s.fields : []));
743
+ }
744
+ // --- can this app read a definition? ---------------------------------------------------------
745
+ // The schema drops what it does not know. A definition written for a newer app would be read
746
+ // without complaint and run wrongly, so it counts only when nothing of it is lost.
747
+ function keepsAll(given, read) {
748
+ if (Array.isArray(given)) {
749
+ return Array.isArray(read) && given.every((v, i) => keepsAll(v, read[i]));
750
+ }
751
+ if (given && typeof given === "object") {
752
+ return (Boolean(read) &&
753
+ typeof read === "object" &&
754
+ Object.entries(given).every(([k, v]) => v === undefined || (k in read && keepsAll(v, read[k]))));
755
+ }
756
+ return true;
757
+ }
758
+ /** True when this app's schema reads the definition without losing anything of it. */
759
+ export function readable(definition) {
760
+ const parsed = wizardSchema.safeParse(definition);
761
+ return parsed.success && keepsAll(definition, parsed.data);
762
+ }