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,342 @@
1
+ import { validateWizard, wizardSchema, } from "@engenty-wizards/shared/definition";
2
+ import { and, desc, eq, sql } from "drizzle-orm";
3
+ import { nanoid } from "nanoid";
4
+ import { applyOps, OpError } from "../authoring/ops.js";
5
+ import { listConnectors } from "../connectors/external.js";
6
+ import { db, schema } from "../db/client.js";
7
+ import { env } from "../env.js";
8
+ import { dropLinks, putLink } from "../tenants/control.js";
9
+ import { currentTenant } from "../tenants/tenant.js";
10
+ import { changedSteps, emitDraftChanged } from "./draft-events.js";
11
+ import { notFound, ServiceError } from "./errors.js";
12
+ import { copyFiles, draftFiles, sameFiles, seedFiles } from "./files.js";
13
+ import { forgetWizardLinks } from "./links.js";
14
+ import { asLang, countInstall, marketplaceWizard } from "./marketplace.js";
15
+ import { defaultProject, ownedProject } from "./projects.js";
16
+ const BLANK = {
17
+ version: 1,
18
+ title: "Neuer Wizard",
19
+ description: "",
20
+ avatar: "round",
21
+ steps: [
22
+ {
23
+ id: "start",
24
+ type: "page",
25
+ title: "Los geht's",
26
+ fields: [{ id: "input", label: "Worum geht es?", kind: "textarea", required: true }],
27
+ },
28
+ { id: "done", type: "result", title: "Fertig", deliverables: [] },
29
+ ],
30
+ };
31
+ export const studioUrl = (wizardId) => `${env.appUrl}/studio/edit/${wizardId}`;
32
+ export const shareUrl = (token) => `${env.appUrl}/w/${token}`;
33
+ const newShareToken = () => nanoid(14);
34
+ export async function ownedWizard(_userId, wizardId) {
35
+ const w = await db.query.wizard.findFirst({
36
+ where: and(eq(schema.wizard.id, wizardId), eq(schema.wizard.tenantId, currentTenant())),
37
+ });
38
+ if (!w) {
39
+ throw notFound();
40
+ }
41
+ return w;
42
+ }
43
+ export function wizardSummary(w) {
44
+ return {
45
+ id: w.id,
46
+ projectId: w.projectId,
47
+ title: w.title,
48
+ description: w.draft.description,
49
+ avatar: w.draft.avatar,
50
+ revision: w.revision,
51
+ published: w.publishedVersion !== null,
52
+ publishedVersion: w.publishedVersion,
53
+ shareToken: w.shareToken,
54
+ shareEnabled: w.shareEnabled,
55
+ dailyRunLimit: w.dailyRunLimit,
56
+ stepCount: w.draft.steps.length,
57
+ updatedAt: w.updatedAt.toISOString(),
58
+ };
59
+ }
60
+ export async function listWizards(_userId, projectId) {
61
+ const rows = await db.query.wizard.findMany({
62
+ where: and(eq(schema.wizard.tenantId, currentTenant()), projectId ? eq(schema.wizard.projectId, projectId) : undefined),
63
+ orderBy: [desc(schema.wizard.updatedAt)],
64
+ });
65
+ return rows.map(wizardSummary);
66
+ }
67
+ async function publishedVersion(w) {
68
+ if (w.publishedVersion === null) {
69
+ return null;
70
+ }
71
+ const v = await db.query.wizardVersion.findFirst({
72
+ where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
73
+ });
74
+ return v ?? null;
75
+ }
76
+ /** Connections must name connectors the project has imported, and actions those connectors have. */
77
+ async function connectorIssues(projectId, draft) {
78
+ const wanted = (draft.connections ?? []).filter((c) => c.connector);
79
+ if (!wanted.length) {
80
+ return [];
81
+ }
82
+ const issues = [];
83
+ const connectors = await listConnectors(projectId);
84
+ for (const connection of wanted) {
85
+ const connector = connectors.find((c) => c.id === connection.connector);
86
+ if (!connector) {
87
+ issues.push({
88
+ message: `Connection "${connection.id}" uses connector "${connection.connector}", which this project has not imported.`,
89
+ });
90
+ continue;
91
+ }
92
+ const unknown = (connection.actions ?? []).filter((a) => !connector.actions.some((x) => x.id === a));
93
+ if (unknown.length && !connector.toolsPending) {
94
+ issues.push({
95
+ message: `Connection "${connection.id}": ${connector.name} has no action ${unknown.map((a) => `"${a}"`).join(", ")}.`,
96
+ });
97
+ }
98
+ }
99
+ return issues;
100
+ }
101
+ /** Everything wrong with a draft: its structure, its workspace files, its project's connectors. */
102
+ async function checkDraft(projectId, draft, files) {
103
+ return [...validateWizard(draft, files), ...(await connectorIssues(projectId, draft))];
104
+ }
105
+ /** Validator issues of the draft, checked against its workspace and the project's connectors. */
106
+ export async function draftIssues(w) {
107
+ const files = await draftFiles(w.id);
108
+ return checkDraft(w.projectId, w.draft, files.map((f) => f.path));
109
+ }
110
+ /** The draft with its validator issues and whether it differs from the published version. */
111
+ export async function wizardState(w) {
112
+ const published = await publishedVersion(w);
113
+ const files = await draftFiles(w.id);
114
+ return {
115
+ ...wizardSummary(w),
116
+ draft: w.draft,
117
+ files,
118
+ issues: await checkDraft(w.projectId, w.draft, files.map((f) => f.path)),
119
+ dirty: !published ||
120
+ JSON.stringify(published.definition) !== JSON.stringify(w.draft) ||
121
+ !sameFiles(published.files, files),
122
+ shareUrl: shareUrl(w.shareToken),
123
+ studioUrl: studioUrl(w.id),
124
+ };
125
+ }
126
+ export async function wizardMessages(wizardId) {
127
+ return db.query.wizardMessage.findMany({
128
+ where: eq(schema.wizardMessage.wizardId, wizardId),
129
+ orderBy: [schema.wizardMessage.createdAt],
130
+ });
131
+ }
132
+ export async function addMessage(wizardId, message, writer = { source: "studio" }) {
133
+ const id = nanoid(12);
134
+ await db.insert(schema.wizardMessage).values({
135
+ id,
136
+ wizardId,
137
+ role: message.role,
138
+ content: message.content,
139
+ changed: message.changed ?? false,
140
+ source: writer.source,
141
+ client: writer.source === "mcp" ? writer.client : null,
142
+ });
143
+ return id;
144
+ }
145
+ function stepLabel(raw, index) {
146
+ const steps = raw?.steps;
147
+ const id = steps?.[index]?.id;
148
+ return typeof id === "string" ? `steps[${index}] "${id}"` : `steps[${index}]`;
149
+ }
150
+ /** The schema check every write passes. Validator issues are allowed in a draft; a wrong shape is not. */
151
+ export function parseDraft(raw, files) {
152
+ const parsed = wizardSchema.safeParse(raw);
153
+ if (!parsed.success) {
154
+ throw new ServiceError("invalid", "The wizard does not match the schema.", {
155
+ issues: shapeIssues(parsed.error, raw),
156
+ });
157
+ }
158
+ return { draft: parsed.data, issues: validateWizard(parsed.data, files) };
159
+ }
160
+ export function shapeIssues(error, raw) {
161
+ return error.issues.map((i) => {
162
+ const [head, index, ...rest] = i.path;
163
+ const where = head === "steps" && typeof index === "number"
164
+ ? [stepLabel(raw, index), ...rest].join(".")
165
+ : i.path.join(".");
166
+ return { message: `${where || "wizard"}: ${i.message}` };
167
+ });
168
+ }
169
+ export async function createWizard(userId, input, writer = { source: "studio" }) {
170
+ const project = input.projectId
171
+ ? await ownedProject(userId, input.projectId)
172
+ : await defaultProject(userId);
173
+ const starter = input.starterId
174
+ ? await marketplaceWizard(input.starterId, asLang(input.lang))
175
+ : undefined;
176
+ if (input.starterId && !starter) {
177
+ throw new ServiceError("invalid", `There is no starter "${input.starterId}".`);
178
+ }
179
+ const { draft, issues } = input.definition !== undefined
180
+ ? parseDraft(input.definition)
181
+ : { draft: starter?.definition ?? structuredClone(BLANK), issues: [] };
182
+ const fromStarter = input.definition === undefined ? starter : undefined;
183
+ const id = nanoid(12);
184
+ const shareToken = newShareToken();
185
+ await db.insert(schema.wizard).values({
186
+ id,
187
+ projectId: project.id,
188
+ tenantId: currentTenant(),
189
+ title: draft.title,
190
+ draft,
191
+ shareToken,
192
+ dailyRunLimit: env.limits.defaultDailyRuns,
193
+ starter: fromStarter?.id ?? null,
194
+ starterRevision: fromStarter?.revision ?? null,
195
+ });
196
+ await putLink(shareToken, "wizard", id);
197
+ if (fromStarter) {
198
+ await seedFiles(id, fromStarter.files);
199
+ // Counted at the marketplace, beside the request.
200
+ void countInstall(fromStarter.id);
201
+ }
202
+ if (input.note) {
203
+ await addMessage(id, { role: "assistant", content: input.note, changed: true }, writer);
204
+ }
205
+ else if (fromStarter) {
206
+ await addMessage(id, {
207
+ role: "assistant",
208
+ content: fromStarter.language === "en"
209
+ ? `I have set up the template "${fromStarter.title}" for you. Test it at the top right – or tell me what should be different.`
210
+ : `Ich habe die Vorlage „${fromStarter.title}“ für dich angelegt. Teste sie rechts oben – oder sag mir, was anders sein soll.`,
211
+ });
212
+ }
213
+ return { id, revision: 0, issues, studioUrl: studioUrl(id) };
214
+ }
215
+ /**
216
+ * Replaces the draft if nobody wrote since `baseRevision`. Every draft write — studio inspector,
217
+ * architect, MCP — goes through here, so the revision counts all of them.
218
+ */
219
+ export async function writeDraft(userId, wizardId, input, writer = { source: "studio" }) {
220
+ const w = await ownedWizard(userId, wizardId);
221
+ const files = await draftFiles(w.id);
222
+ const paths = files.map((f) => f.path);
223
+ const { draft } = parseDraft(input.definition, paths);
224
+ const issues = await checkDraft(w.projectId, draft, paths);
225
+ const [row] = await db
226
+ .update(schema.wizard)
227
+ .set({
228
+ draft,
229
+ title: draft.title,
230
+ revision: sql `${schema.wizard.revision} + 1`,
231
+ updatedAt: new Date(),
232
+ })
233
+ .where(and(eq(schema.wizard.id, w.id), eq(schema.wizard.revision, input.baseRevision)))
234
+ .returning({ revision: schema.wizard.revision });
235
+ if (!row) {
236
+ const current = await ownedWizard(userId, wizardId);
237
+ throw new ServiceError("revision_conflict", "Der Wizard wurde inzwischen geändert.", {
238
+ revision: current.revision,
239
+ });
240
+ }
241
+ if (input.note) {
242
+ await addMessage(w.id, { role: "assistant", content: input.note, changed: true }, writer);
243
+ }
244
+ emitDraftChanged({
245
+ wizardId: w.id,
246
+ kind: "draft",
247
+ revision: row.revision,
248
+ source: writer.source,
249
+ client: writer.source === "mcp" ? writer.client : null,
250
+ note: input.note ?? null,
251
+ touched: changedSteps(w.draft, draft),
252
+ });
253
+ return { revision: row.revision, issues, draft };
254
+ }
255
+ export async function editWizard(userId, wizardId, input, writer) {
256
+ const w = await ownedWizard(userId, wizardId);
257
+ if (w.revision !== input.baseRevision) {
258
+ throw new ServiceError("revision_conflict", "Der Wizard wurde inzwischen geändert.", {
259
+ revision: w.revision,
260
+ });
261
+ }
262
+ let definition;
263
+ try {
264
+ definition = applyOps(w.draft, input.ops);
265
+ }
266
+ catch (err) {
267
+ if (err instanceof OpError) {
268
+ throw new ServiceError("invalid", err.message);
269
+ }
270
+ throw err;
271
+ }
272
+ return writeDraft(userId, wizardId, { definition, baseRevision: input.baseRevision, note: input.note }, writer);
273
+ }
274
+ export async function updateWizardSettings(userId, wizardId, patch) {
275
+ const w = await ownedWizard(userId, wizardId);
276
+ if (patch.projectId) {
277
+ await ownedProject(userId, patch.projectId);
278
+ }
279
+ await db
280
+ .update(schema.wizard)
281
+ .set({ ...patch, updatedAt: new Date() })
282
+ .where(eq(schema.wizard.id, w.id));
283
+ return wizardSummary(await ownedWizard(userId, wizardId));
284
+ }
285
+ export async function rotateShareLink(userId, wizardId) {
286
+ const w = await ownedWizard(userId, wizardId);
287
+ const token = newShareToken();
288
+ await db
289
+ .update(schema.wizard)
290
+ .set({ shareToken: token, updatedAt: new Date() })
291
+ .where(eq(schema.wizard.id, w.id));
292
+ await dropLinks([w.shareToken]);
293
+ await putLink(token, "wizard", w.id);
294
+ return { shareToken: token, shareUrl: shareUrl(token) };
295
+ }
296
+ export async function duplicateWizard(userId, wizardId) {
297
+ const w = await ownedWizard(userId, wizardId);
298
+ const id = nanoid(12);
299
+ const draft = { ...w.draft, title: `${w.draft.title} (Kopie)` };
300
+ const shareToken = newShareToken();
301
+ await db.insert(schema.wizard).values({
302
+ id,
303
+ projectId: w.projectId,
304
+ tenantId: currentTenant(),
305
+ title: draft.title,
306
+ draft,
307
+ shareToken,
308
+ dailyRunLimit: w.dailyRunLimit,
309
+ starter: w.starter,
310
+ });
311
+ await putLink(shareToken, "wizard", id);
312
+ await copyFiles(w.id, id);
313
+ return { id };
314
+ }
315
+ export async function deleteWizard(_userId, wizardId) {
316
+ await forgetWizardLinks(wizardId);
317
+ await db
318
+ .delete(schema.wizard)
319
+ .where(and(eq(schema.wizard.id, wizardId), eq(schema.wizard.tenantId, currentTenant())));
320
+ }
321
+ /** A wizard with validator issues is never published — same rule for studio and MCP. */
322
+ export async function requireClean(w) {
323
+ const files = await draftFiles(w.id);
324
+ const issues = await checkDraft(w.projectId, w.draft, files.map((f) => f.path));
325
+ if (issues.length) {
326
+ throw new ServiceError("has_issues", "Der Wizard hat noch Fehler.", { issues });
327
+ }
328
+ return { definition: w.draft, files };
329
+ }
330
+ export async function publishWizard(userId, wizardId) {
331
+ const w = await ownedWizard(userId, wizardId);
332
+ const { definition, files } = await requireClean(w);
333
+ const version = (w.publishedVersion ?? 0) + 1;
334
+ await db
335
+ .insert(schema.wizardVersion)
336
+ .values({ id: nanoid(12), wizardId: w.id, version, definition, files });
337
+ await db
338
+ .update(schema.wizard)
339
+ .set({ publishedVersion: version, updatedAt: new Date() })
340
+ .where(eq(schema.wizard.id, w.id));
341
+ return { version, shareUrl: shareUrl(w.shareToken), shareEnabled: w.shareEnabled };
342
+ }
@@ -0,0 +1,16 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { control, controlDb } from "./db/client.js";
3
+ /** Settings of a runtime that runs alone, kept in the control database. Never secrets. */
4
+ export async function readSetting(key) {
5
+ const row = await controlDb.query.setting.findFirst({ where: eq(control.setting.key, key) });
6
+ return row?.value ?? null;
7
+ }
8
+ export async function writeSetting(key, value) {
9
+ await controlDb
10
+ .insert(control.setting)
11
+ .values({ key, value })
12
+ .onConflictDoUpdate({ target: control.setting.key, set: { value } });
13
+ }
14
+ export async function deleteSetting(key) {
15
+ await controlDb.delete(control.setting).where(eq(control.setting.key, key));
16
+ }
@@ -0,0 +1,279 @@
1
+ import { newCells, patchCells, rowKey, STORE_LIMITS, } from "@engenty-wizards/shared/store";
2
+ import { cleanPath, mimeForPath } from "@engenty-wizards/shared/workspace";
3
+ import { and, asc, eq, inArray, like, lt, sql } from "drizzle-orm";
4
+ import { nanoid } from "nanoid";
5
+ import { db, schema } from "../db/client.js";
6
+ import { env } from "../env.js";
7
+ import { getBlob, putBlob } from "../files/blobs.js";
8
+ import { seal, unseal } from "../secrets/crypto.js";
9
+ export class StoreError extends Error {
10
+ }
11
+ /** The person a run belongs to: the signed-in admin, else the visitor of the shared link. */
12
+ export function holderOf(run) {
13
+ if (run.userId) {
14
+ return `u:${run.userId}`;
15
+ }
16
+ return run.visitorId ? `v:${run.visitorId}` : `r:${run.id}`;
17
+ }
18
+ export function scopeOf(run) {
19
+ return { wizardId: run.wizardId, holder: holderOf(run) };
20
+ }
21
+ /** Marks the store as in use; stores nobody came back to are cleaned up. */
22
+ export async function touchStore(scope) {
23
+ await db
24
+ .insert(schema.storeHolder)
25
+ .values({ ...scope, usedAt: new Date() })
26
+ .onConflictDoUpdate({
27
+ target: [schema.storeHolder.wizardId, schema.storeHolder.holder],
28
+ set: { usedAt: new Date() },
29
+ });
30
+ }
31
+ // --- lists -------------------------------------------------------------------
32
+ const inList = (scope, list) => and(eq(schema.storeRow.wizardId, scope.wizardId), eq(schema.storeRow.holder, scope.holder), eq(schema.storeRow.list, list));
33
+ function toRow(r) {
34
+ return { id: r.id, cells: r.cells, updatedAt: r.updatedAt.toISOString() };
35
+ }
36
+ export async function listRows(scope, list) {
37
+ const rows = await db.query.storeRow.findMany({
38
+ where: inList(scope, list),
39
+ orderBy: [asc(schema.storeRow.createdAt), asc(schema.storeRow.id)],
40
+ });
41
+ return rows.map(toRow);
42
+ }
43
+ /**
44
+ * Saves rows into a list. With a key column, a row whose key is already there is updated — only
45
+ * the columns given change, so a step can add "how to get the invoice" without wiping the rest.
46
+ * A row with a bad value is refused as a whole (`TableColumnValueError` names the column).
47
+ */
48
+ export async function saveRows(scope, def, input) {
49
+ let added = 0;
50
+ let updated = 0;
51
+ const existing = await db.query.storeRow.findMany({ where: inList(scope, def.id) });
52
+ const byKey = new Map(existing.filter((r) => r.key).map((r) => [r.key, r]));
53
+ let count = existing.length;
54
+ for (const raw of input) {
55
+ const key = rowKey(def, raw);
56
+ const hit = key ? byKey.get(key) : undefined;
57
+ if (hit) {
58
+ const cells = patchCells(def, hit.cells, raw);
59
+ await db
60
+ .update(schema.storeRow)
61
+ .set({ cells, updatedAt: new Date() })
62
+ .where(eq(schema.storeRow.id, hit.id));
63
+ hit.cells = cells;
64
+ updated++;
65
+ continue;
66
+ }
67
+ const cells = newCells(def, raw);
68
+ if (Object.values(cells).every((v) => v === null)) {
69
+ continue;
70
+ }
71
+ if (count >= STORE_LIMITS.rowsPerList) {
72
+ throw new StoreError(`List "${def.id}" is full (${STORE_LIMITS.rowsPerList} rows).`);
73
+ }
74
+ const row = { id: nanoid(14), ...scope, list: def.id, key, cells };
75
+ await db.insert(schema.storeRow).values(row);
76
+ if (key) {
77
+ byKey.set(key, { ...row, createdAt: new Date(), updatedAt: new Date() });
78
+ }
79
+ count++;
80
+ added++;
81
+ }
82
+ await touchStore(scope);
83
+ return { added, updated };
84
+ }
85
+ /** Changes cells of one row (the person edited it). */
86
+ export async function updateRow(scope, def, rowId, patch) {
87
+ const row = await db.query.storeRow.findFirst({
88
+ where: and(inList(scope, def.id), eq(schema.storeRow.id, rowId)),
89
+ });
90
+ if (!row) {
91
+ return false;
92
+ }
93
+ const cells = patchCells(def, row.cells, patch);
94
+ const key = rowKey(def, cells);
95
+ const clash = key && key !== row.key
96
+ ? await db.query.storeRow.findFirst({
97
+ where: and(inList(scope, def.id), eq(schema.storeRow.key, key)),
98
+ })
99
+ : null;
100
+ if (clash) {
101
+ throw new StoreError("Diesen Eintrag gibt es schon.");
102
+ }
103
+ await db
104
+ .update(schema.storeRow)
105
+ .set({ cells, key, updatedAt: new Date() })
106
+ .where(eq(schema.storeRow.id, rowId));
107
+ return true;
108
+ }
109
+ /** Deletes rows by id, or by the value of the key column. */
110
+ export async function deleteRows(scope, def, idsOrKeys) {
111
+ if (!idsOrKeys.length) {
112
+ return 0;
113
+ }
114
+ const keys = idsOrKeys.map((k) => k.trim().toLowerCase());
115
+ const byId = await db
116
+ .delete(schema.storeRow)
117
+ .where(and(inList(scope, def.id), inArray(schema.storeRow.id, idsOrKeys)))
118
+ .returning({ id: schema.storeRow.id });
119
+ const byKey = await db
120
+ .delete(schema.storeRow)
121
+ .where(and(inList(scope, def.id), inArray(schema.storeRow.key, keys)))
122
+ .returning({ id: schema.storeRow.id });
123
+ return byId.length + byKey.length;
124
+ }
125
+ // --- files -------------------------------------------------------------------
126
+ const ownFiles = (scope) => and(eq(schema.storeFile.wizardId, scope.wizardId), eq(schema.storeFile.holder, scope.holder));
127
+ function toFile(r) {
128
+ return {
129
+ path: r.path,
130
+ mime: r.mime,
131
+ size: r.size,
132
+ source: r.source,
133
+ updatedAt: r.updatedAt.toISOString(),
134
+ };
135
+ }
136
+ /** Any name a model or a mail server comes up with, as a path the store accepts. */
137
+ export function storePath(raw) {
138
+ const path = raw
139
+ .normalize("NFKD")
140
+ .replace(/[̀-ͯ]/g, "")
141
+ .replace(/ß/g, "ss")
142
+ .split("/")
143
+ .map((part) => part
144
+ .trim()
145
+ .replace(/[^A-Za-z0-9_.-]+/g, "_")
146
+ .replace(/^[._]+/, "")
147
+ .slice(0, 80))
148
+ .filter(Boolean)
149
+ .join("/");
150
+ const clean = cleanPath(path);
151
+ if (!clean) {
152
+ throw new StoreError(`"${raw}" is not a usable file name.`);
153
+ }
154
+ return clean;
155
+ }
156
+ export async function storeFiles(scope, folder) {
157
+ const rows = await db.query.storeFile.findMany({
158
+ where: folder
159
+ ? and(ownFiles(scope), like(schema.storeFile.path, `${folder.replace(/\/+$/, "")}/%`))
160
+ : ownFiles(scope),
161
+ orderBy: [asc(schema.storeFile.path)],
162
+ });
163
+ return rows.map(toFile);
164
+ }
165
+ export async function readStoreFile(scope, rawPath) {
166
+ const path = cleanPath(rawPath);
167
+ const row = path
168
+ ? await db.query.storeFile.findFirst({
169
+ where: and(ownFiles(scope), eq(schema.storeFile.path, path)),
170
+ })
171
+ : undefined;
172
+ return row ? { file: toFile(row), data: await getBlob(row.hash) } : null;
173
+ }
174
+ export async function writeStoreFile(scope, rawPath, content, meta = {}) {
175
+ const path = storePath(rawPath);
176
+ const data = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
177
+ if (data.byteLength > STORE_LIMITS.fileBytes) {
178
+ throw new StoreError(`${path} is larger than 15 MB.`);
179
+ }
180
+ const [usage] = await db
181
+ .select({
182
+ files: sql `count(*)`,
183
+ bytes: sql `coalesce(sum(${schema.storeFile.size}), 0)`,
184
+ })
185
+ .from(schema.storeFile)
186
+ .where(ownFiles(scope));
187
+ if (usage.files >= STORE_LIMITS.files ||
188
+ usage.bytes + data.byteLength > STORE_LIMITS.totalBytes) {
189
+ throw new StoreError("The wizard's storage is full.");
190
+ }
191
+ const hash = await putBlob(data);
192
+ const mime = meta.mime && meta.mime !== "application/octet-stream" ? meta.mime : mimeForPath(path);
193
+ const updatedAt = new Date();
194
+ await db
195
+ .insert(schema.storeFile)
196
+ .values({ ...scope, path, hash, mime, size: data.byteLength, source: meta.source ?? null })
197
+ .onConflictDoUpdate({
198
+ target: [schema.storeFile.wizardId, schema.storeFile.holder, schema.storeFile.path],
199
+ set: { hash, mime, size: data.byteLength, source: meta.source ?? null, updatedAt },
200
+ });
201
+ await touchStore(scope);
202
+ return {
203
+ path,
204
+ mime,
205
+ size: data.byteLength,
206
+ source: meta.source ?? null,
207
+ updatedAt: updatedAt.toISOString(),
208
+ };
209
+ }
210
+ export async function deleteStoreFile(scope, rawPath) {
211
+ const path = cleanPath(rawPath);
212
+ if (!path) {
213
+ return false;
214
+ }
215
+ const gone = await db
216
+ .delete(schema.storeFile)
217
+ .where(and(ownFiles(scope), eq(schema.storeFile.path, path)))
218
+ .returning({ path: schema.storeFile.path });
219
+ return gone.length > 0;
220
+ }
221
+ // --- secrets -----------------------------------------------------------------
222
+ const ownSecrets = (scope) => and(eq(schema.storeSecret.wizardId, scope.wizardId), eq(schema.storeSecret.holder, scope.holder));
223
+ export async function getSecret(scope, slot) {
224
+ const row = await db.query.storeSecret.findFirst({
225
+ where: and(ownSecrets(scope), eq(schema.storeSecret.slot, slot)),
226
+ });
227
+ const data = row ? unseal(row.data) : null;
228
+ return row && data
229
+ ? { provider: row.provider, label: row.label, data, createdAt: row.createdAt }
230
+ : null;
231
+ }
232
+ export async function putSecret(scope, slot, provider, label, data) {
233
+ const sealed = seal(data);
234
+ await db
235
+ .insert(schema.storeSecret)
236
+ .values({ id: nanoid(14), ...scope, slot, provider, label, data: sealed })
237
+ .onConflictDoUpdate({
238
+ target: [schema.storeSecret.wizardId, schema.storeSecret.holder, schema.storeSecret.slot],
239
+ set: { provider, label, data: sealed, updatedAt: new Date() },
240
+ });
241
+ await touchStore(scope);
242
+ }
243
+ export async function deleteSecret(scope, slot) {
244
+ await db
245
+ .delete(schema.storeSecret)
246
+ .where(and(ownSecrets(scope), eq(schema.storeSecret.slot, slot)));
247
+ }
248
+ export async function listSecrets(scope) {
249
+ const rows = await db.query.storeSecret.findMany({ where: ownSecrets(scope) });
250
+ return rows.map((r) => ({
251
+ slot: r.slot,
252
+ provider: r.provider,
253
+ label: r.label,
254
+ createdAt: r.createdAt.toISOString(),
255
+ }));
256
+ }
257
+ // --- everything --------------------------------------------------------------
258
+ /** Deletes everything the wizard keeps for this person. Blobs stay: other files may share them. */
259
+ export async function clearStore(scope) {
260
+ await db
261
+ .delete(schema.storeRow)
262
+ .where(and(eq(schema.storeRow.wizardId, scope.wizardId), eq(schema.storeRow.holder, scope.holder)));
263
+ await db.delete(schema.storeFile).where(ownFiles(scope));
264
+ await db.delete(schema.storeSecret).where(ownSecrets(scope));
265
+ await db
266
+ .delete(schema.storeHolder)
267
+ .where(and(eq(schema.storeHolder.wizardId, scope.wizardId), eq(schema.storeHolder.holder, scope.holder)));
268
+ }
269
+ /** Stores nobody used for STORE_TTL_DAYS are deleted — a visitor's cookie is long gone by then. */
270
+ export async function purgeUnusedStores() {
271
+ const cutoff = new Date(Date.now() - env.limits.storeTtlDays * 24 * 60 * 60 * 1000);
272
+ const stale = await db.query.storeHolder.findMany({
273
+ where: lt(schema.storeHolder.usedAt, cutoff),
274
+ });
275
+ for (const scope of stale) {
276
+ await clearStore(scope);
277
+ }
278
+ return stale.length;
279
+ }