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,259 @@
1
+ import { sandboxGuideLine } from "../sandbox/index.js";
2
+ import { EXAMPLE_WIZARD } from "./example.js";
3
+ /**
4
+ * How a wizard is written down and what makes a good one. The studio's architect and the MCP
5
+ * tools hand the same text to whichever model authors the wizard.
6
+ */
7
+ export const SCHEMA_DOC = `
8
+ type Wizard = {
9
+ version: 1
10
+ title: string // short, what the end user gets ("Rechnung erstellen")
11
+ description: string // one sentence
12
+ avatar: "round"|"drop"|"dome"|"flame"|"oval"|"bean"|"pebble"|"sprout"|"tower"|"wedge" // the engenty mascot shown to end users
13
+ intro?: string // one friendly sentence above the first page
14
+ lists?: List[] // tabular data the wizard KEEPS between runs — separately for each person who runs it
15
+ connections?: Connection[] // accounts the PERSON connects during a run (their mailbox); kept for their next run
16
+ steps: Step[] // run top to bottom; the LAST step is always a "result"
17
+ }
18
+
19
+ type List = {
20
+ id, title, description?,
21
+ key?: columnId // rows are matched on this column: saving a row with a known key updates that row
22
+ check?: { file?: columnId, status?: columnId }
23
+ // a review that shows the list lets the person go through it ROW BY ROW on a split screen:
24
+ // "file" = a text column holding the path of a kept file (PDF, photo), previewed beside the row;
25
+ // "status" = a select column whose options are the answer buttons (the FIRST option = not looked at yet)
26
+ columns: Column[] // { id (lowercase_slug), name (shown to the person), type, format?, required? }
27
+ }
28
+ // type "text": format? { style: "single"|"multiline" }
29
+ // type "number": format { style: "integer"|"decimal"|"percent"|"currency", currency?: "EUR" } // currency only with style "currency"
30
+ // type "date": format { kind: "date"|"datetime" } // values are YYYY-MM-DD
31
+ // type "select": format { options: { id, label }[], allowCustom: boolean }
32
+ // type "boolean"
33
+
34
+ type Connection =
35
+ | { id, kind: "mail", title?, description? }
36
+ // the person picks how to connect (Gmail, Outlook, or any mailbox over IMAP); steps only ever READ it
37
+ | { id, connector: connectorId, actions?: actionId[], policy?: { [actionId]: "allow"|"ask" }, title?, description? }
38
+ // connector = a service the wizard works in for the person: a built-in one (Gmail, Google Drive, Calendar and
39
+ // Contacts, Outlook, OneDrive, Slack, GitHub, HubSpot, S3) or one imported into the project from the
40
+ // integrations registry. The person connects their own account (OAuth or API key) on a page.
41
+ // kind "mail" reads any mailbox the same way and keeps attachments as files; use the gmail / outlook
42
+ // connectors only for what it cannot do (drafts, sending, labels, calendar).
43
+ // A step that lists the connection gets the connector's actions as tools named <toolPrefix>_<actionId>.
44
+ // actions: exactly the actions the wizard may use. Default: only those marked "read".
45
+ // policy: "allow" = runs without asking, "ask" = the person confirms each call first.
46
+ // Default: read → allow, write → ask. Deleting always asks. Set "allow" for an action marked
47
+ // "write" that in truth only looks something up (MCP servers often leave the mark out).
48
+
49
+ // Every step: { id (camelCase, unique), title, description?, next?: Branch[] }
50
+ // Branch = { when: { field, op: "equals"|"notEquals"|"in"|"notEmpty"|"empty", value? }, goto: stepId | "end" }
51
+ // field is a page field id, or an output field of this or an earlier agent step as "steps.<stepId>.<fieldId>"
52
+ // first matching branch wins, otherwise the next step in the list.
53
+
54
+ type PageStep = { type: "page", fields: Field[] (1–5 per page), cta?: string }
55
+ type Field = {
56
+ id (camelCase, unique across the WHOLE wizard), label, kind, required?, placeholder?, help?,
57
+ options?: string[] // select / multiselect
58
+ default?: string|number|boolean|string[]
59
+ columns?: { id, label, kind: "text"|"number"|"money" }[] // items only; row amount = product of number/money columns
60
+ vat?: { rate?: number, field?: fieldId } // items only; VAT % fixed or read from a field
61
+ currency?: "EUR"|... // items only
62
+ multiple?: boolean // image / file: several files, in the order the person puts them (they can drag them)
63
+ min?: number, max?: number // image / file with multiple: how many files at least / at most (up to 30). Set max wherever
64
+ // every file costs something later (a video clip per photo)
65
+ camera?: boolean // file: also offer the camera, for papers the person has not scanned (image always offers it)
66
+ video?: boolean // file: the camera also records a short clip (up to 30 s); the clip is a file like any other
67
+ scan?: boolean // text: a "scan" button fills it from a QR code or barcode (serial number, ticket, article number)
68
+ connection?: connectionId // kind "connection": the page shows "connect your account"; with required the person must connect
69
+ list?: listId // kind "list": the page shows that stored list for the person to check, correct and extend
70
+ prefill?: "steps.<id>.<key>" // what the field starts with, from an earlier step. An "items" field takes a table whose
71
+ // columns are exactly its column ids — the step proposes the positions, the person corrects them
72
+ }
73
+ // kinds: text textarea number select multiselect date email url toggle color image file items connection list
74
+ // location audio signature
75
+ // location: where the person is — the phone's position on a tap (with its accuracy), or an address they type.
76
+ // {{field}} → "Herrengasse 16, 8010 Graz (47.07071, 15.43950, ±12 m)" · {{field.lat}} {{field.lng}}
77
+ // {{field.accuracy}} (metres) · {{field.label}} (the address line) · {{field.map}} (a map link).
78
+ // A typed address has no lat/lng. Widgets take the numbers: data { lat: "place.lat", lng: "place.lng" }.
79
+ // audio: a voice note recorded on the page (up to 5 minutes). It is written down before the next step runs:
80
+ // {{field}} is what the person said, as text. {{field.seconds}} is its length.
81
+ // signature: drawn with the finger — a picture (dark ink, clear ground). A document step can place it:
82
+ // {{field}} → asset://ID, used as <img src="asset://ID">.
83
+
84
+ type AgentStep = {
85
+ type: "agent"
86
+ instructions: string // the task, with {{templates}}
87
+ tools: ("web_search"|"web_fetch"|"browser"|"sandbox"|"image"|"http")[]
88
+ mcp?: string[] // ids of the project's MCP servers this step may use
89
+ connections?: string[] // ids of wizard connections this step may read (mail → mail_search, mail_read, mail_save)
90
+ output: { format: "text"|"markdown"|"json", fields?: { id, kind: "text"|"number"|"list"|"table"|"yesno"|"choice", description, columns?: string[], options?: string[] }[] }
91
+ // a table with columns gives rows as objects with exactly those keys — what widgets read
92
+ // "yesno" answers the question in its description with true/false; "choice" picks one of its options
93
+ model?: "classifier"|"standard"|"high"|"highest" // the kind of model, see "Models"; default "high"
94
+ effort?: "low"|"medium"|"high" // how hard it should think; leave out unless it matters
95
+ working?: string // shown while it runs ("Recherchiert im Web …")
96
+ }
97
+ // Every agent step also has, without listing them: list_read / list_write (the wizard's lists), files_list,
98
+ // read_document (PDF, scans and photos, Word, Excel, CSV, saved mails) and scan_documents (invoice / receipt fields, many at once).
99
+ // "browser" brings: browser_open/click/type, browser_screenshot (the model looks at the page),
100
+ // browser_request_credentials (the PERSON types a login into the page — the model never sees it; sign-ins can be remembered),
101
+ // browser_request_user (the person solves a captcha or "continue with Google" in a picture of the page),
102
+ // browser_download (keeps a file from a signed-in site).
103
+
104
+ type GenerateStep = {
105
+ type: "generate"
106
+ asset: "image"|"video"|"voice"|"document"|"dashboard"
107
+ prompt: string // the brief, with {{templates}}; for "voice" the exact text that is read aloud
108
+ options?: { aspectRatio?: "1:1"|"16:9"|"9:16"|"4:5"|"3:2"|"2:3", duration?: 4–10 (video seconds; 4, 6 or 8 are safe),
109
+ resolution?: "720p"|"480p", // video: 720p by default and at most; 480p for drafts and many clips
110
+ style?: string, // image/video: the look · voice: how to speak ("ruhig, warm")
111
+ template?: "invoice"|"offer"|"briefing"|"letter"|"report"|"free" } // template for documents
112
+ referenceImage?: fieldId | stepId // the image the image/video starts from: an image field, or an earlier step that made images
113
+ each?: fieldId | stepId | "steps.<id>.<key>"
114
+ // image/video: ONE RESULT PER ENTRY (at most 8) — per upload of an image field (multiple), per image
115
+ // of an earlier step, or per row of an agent step's table. The prompt reads the row as
116
+ // {{item.<column>}} (plus {{index}}, {{count}}); referenceImage gives entry n its n-th image.
117
+ // With each the prompt goes to the model AS WRITTEN (no rewriting step): let the agent step
118
+ // that makes the table write complete visual descriptions.
119
+ // In a review the person can make single results again.
120
+ working?: string
121
+ }
122
+
123
+ type WidgetStep = {
124
+ type: "widget"
125
+ entry: string // workspace path of the widget's HTML, e.g. "weather/index.html"
126
+ data: { [key]: string } // what the widget gets as wizard.data: key → fieldId | steps.stepId | steps.stepId.key | brand.name | facts.key | today
127
+ sample?: string // workspace path of example data (same shape as data) for previews
128
+ size?: { width, height } // design size in px, default 1280×720 (9:16 → 1080×1920)
129
+ video?: boolean // a FILM: the timeline is rendered to an MP4 WITH SOUND when the step runs; the person sees and
130
+ // downloads that video. data may then name generate steps: "steps.<id>" of an image / video / voice
131
+ // step arrives as a URL (a list of URLs for a step with "each").
132
+ working?: string
133
+ }
134
+ // an interactive HTML app written ONCE into the workspace; every run only brings new data (no model call, no cost)
135
+
136
+ type ReviewStep = { type: "review", show: (stepId | "lists.<listId>")[], edit?: boolean, regenerate?: boolean }
137
+ // shows earlier outputs; the person accepts, edits text outputs (edit), or asks for a new version with a note (regenerate)
138
+ // a shown list is edited in place by the person
139
+
140
+ type ResultStep = { type: "result", message?: string, deliverables: { from: stepId | "lists.<listId>", label?, formats: Format[] }[] }
141
+ // formats per source: image→png · video→mp4 (several results: zip) · voice→mp3 · document→pdf docx html md png · dashboard→html pdf png
142
+ // film widget (video: true)→mp4 png
143
+ // widget→html png pdf mp4 json (mp4 only when the widget registers a timeline)
144
+ // agent text/markdown→md txt docx pdf html · agent json→json csv xlsx md (xlsx: one sheet per table)
145
+ // agent, any format→zip: the FILES the step kept with mail_save / browser_download
146
+ // lists.<id>→xlsx csv json md · zip (a list with check.file: the kept files its rows name, plus the list)
147
+
148
+ Templates (in instructions/prompt): {{fieldId}} · {{steps.stepId}} (whole output) · {{steps.stepId.key}} (json key)
149
+ · {{itemsField}} (line items as a table WITH computed net/VAT/total) · {{itemsField.net|vat|gross}} · {{today}}
150
+ · the project (its settings): {{brand.name}} {{brand.about}} {{brand.colors}} · {{facts.key}} (one fact: address, VAT id, …) · {{facts}} (all facts)
151
+ — every AI step already knows them, name one only where the text must carry it; agent steps can also search the project's documents
152
+ · {{lists.listId}} (the stored list as a table, as it is when the step starts) · {{fileField}} (the names of the uploads, for read_document)
153
+ · {{locationField}} {{locationField.lat|lng|accuracy|label|map}} · {{audioField}} (the transcript) · {{signatureField}} (asset://ID)
154
+ A template may only use fields asked and steps run EARLIER.`;
155
+ export const PRINCIPLES = `
156
+ How good wizards look:
157
+ - For non-technical end users. Plain words, friendly titles phrased as questions ("Wofür ist der Post?").
158
+ - Few pages, 1–5 fields each, one theme per page. Only ask what the result really needs; prefer selects with sensible defaults.
159
+ - Put a review step before anything expensive (video) and before the final result, so people can correct or regenerate.
160
+ - Money/totals: use an "items" field with vat — the runner computes totals; documents must use {{items}} verbatim. Never let a model compute totals.
161
+ - Facts from the web need an agent step with web_search + web_fetch BEFORE writing; documents then cite those notes.
162
+ - Live data with a free public JSON API (weather and wind: api.open-meteo.com; sunrise/sunset is in it too) → an agent step with the http tool and model "standard" that calls the API and returns json. Far cheaper and more exact than web research; use web research only when no API exists.
163
+ - Writing into other systems (CRM, database, spreadsheet, website): an agent step with the matching mcp server, http, or browser tool, preceded by a review step.
164
+ ${sandboxGuideLine()}
165
+ - Remembering between runs ("merke dir", "führe eine Liste"): declare a list and let an agent step save into it with list_write; a later run reads it as {{lists.id}} or list_read. Give it a key column so known rows are updated, not duplicated. Never make the person re-upload last time's result.
166
+ - The person's mail: one connection of kind "mail", a page field of kind "connection" early in the wizard, and the agent step lists it in "connections". Do not ask for the provider or the address in other fields — the connect field does that.
167
+ - Working in a service for the person (read their Notion, create an issue, look up a customer): import the service as a connector, declare a connection with "connector", put a page field of kind "connection" before the step, and list the connection in the step's "connections". Prefer a connector over the browser whenever the service has one.
168
+ - Logins on websites: never ask for passwords in page fields. An agent step with the browser tool asks the person at the moment it meets the login (browser_request_credentials); write that into its instructions, and that it must go on without the site when the person skips.
169
+ - Most people open a wizard on their phone. Keep pages short, and let the phone do the typing: photos with an "image" field (multiple) instead of descriptions, the place with a "location" field instead of an address form, what happened as an "audio" field instead of a long textarea (keep an optional textarea beside it), a sign-off as a "signature" field, a serial or ticket number as a "text" field with scan. Typical: damage report, handover protocol, site inspection, delivery note.
170
+ - Photos in documents: a document step can place the photos of an earlier "image" field and a "signature" (it gets them as asset:// images); say in its prompt where they go.
171
+ - Looking at photos: an agent step whose instructions name an image field ({{photos}}) gets up to 8 of its photos attached and sees them itself, in the person's order — do not tell it to call read_document for them. Photos in a "file" field, PDFs and scans are read with read_document / scan_documents.
172
+ - Papers and scans: a "file" field with multiple and camera. Agent steps read uploads with read_document; for invoices and receipts scan_documents returns the fields (vendor, number, date, totals, currency) exactly as printed.
173
+ - Collecting documents (invoices from mail or portals): the step keeps each file with mail_save / browser_download under a dated path ("invoices/2026-09/2026-09-03_Notion_INV-123.pdf"); its deliverable offers "zip". Split a big job into steps (mail, then statements, then portals) — one step manages about 60 tool calls.
174
+ - Going through documents one by one (receipts against payments): never one model step per document. One step reads them in batches (scan_documents takes 30 at once) and writes rows into a list with "check"; the review shows that list split-screen — the person answers each row beside its file, without any model call.
175
+ - Video that convinces is built in stages, each with its own review: idea and script (agent, json with a table of shots) → one still per shot (generate image with "each", starting from the person's product photo) → one clip per still (generate video with "each" and referenceImage = the stills step) → a voice-over (generate voice) → a film widget (video: true) that cuts clips, captions, voice and a closing card with the call to action. The starters "facebook-video-ad" and "property-film" carry such a widget (film/film.js) to copy. Offer a cheaper path without clips: the film animates the stills.
176
+ - Prices come from a file the admin keeps in the workspace (a CSV price list): the agent step looks them up with read_workspace_file and proposes positions as a table; the next page shows them in an "items" field with "prefill" for the person to correct; the runner computes the totals.
177
+ - AI media is marked for you (EU AI Act, Art. 50): images, clips, voice-overs and films carry the marking in their file metadata, and the pages lay the EU's "AI" label over them. Never ask an image step to draw such a label into the picture. A film is different — it leaves the page as a video file, so its widget shows the EU label in the picture: wizard.ai tells a widget whether the run's media in its data is "generated" or "edited" (film/film.js draws the matching icon).
178
+ - Every step a person sees later (review/result) must come from an earlier step id.
179
+ - Write all wizard texts in the admin's language.`;
180
+ export const WIDGET_GUIDE = `
181
+ Widgets — when to use: the result must look the same every run, animate, or be explored (a map with
182
+ animated arrows, a timeline with a scrubber, a dashboard with filters, a configurator). Documents and
183
+ one-off pages stay "generate" steps. A widget is code in the wizard's WORKSPACE (files), written once:
184
+ - Files: "<widget>/index.html" (+ optional app.js, style.css), libraries under "lib/", reference data
185
+ (GeoJSON shapes, station lists, price lists) as JSON/CSV files, and "<widget>/sample.json" with
186
+ example data in exactly the shape of the step's data.
187
+ - Its data usually comes from an agent step with output.format "json"; give its table fields fixed
188
+ "columns" so rows have known keys, and write sample.json in that exact shape.
189
+ - NO NETWORK when it runs: no CDN, no fetch, no external fonts or images — everything is inlined.
190
+ Put libraries into the workspace (cdnjs.cloudflare.com, cdn.jsdelivr.net) and include them with
191
+ <script src="lib/name.min.js">. Fetch reference data ONCE now (e.g. lake outlines
192
+ from nominatim.openstreetmap.org/search?q=…&format=json&polygon_geojson=1&polygon_threshold=0.0005)
193
+ and save it as a workspace file.
194
+ - Runtime, window.wizard: data · brand {name, accent, logo, colors [{name, value}], facts {key: value}} · ai "generated"|"edited"|null · mode "view"|"export" · file(path) ·
195
+ json(path) · url(path) (data URL for images/fonts) · ready() · timeline({ duration, seek, poster }).
196
+ - Animation: call wizard.timeline({ duration: seconds, seek: t => draw(t) }) and drive your own
197
+ play/pause and <input type=range> scrubber from it. seek(t) must draw time t synchronously — the MP4
198
+ export steps through it frame by frame. In mode "export" hide all controls and do not autoplay.
199
+ - A film (step.video): seek(t) may return a promise that resolves once the frame is drawn (set a clip's
200
+ currentTime, wait for "seeked", draw it to a canvas). Name the sound in the timeline:
201
+ audio: [{ src, start, duration?, volume? }] with the URLs from wizard.data (the voice-over, the clips
202
+ themselves for their own sound). Clips and images of the run arrive as URLs, not inlined.
203
+ - Call wizard.ready() once the first frame is drawn. Lay out for the step's size and scale to fit
204
+ the window on every "resize" event — the window can be 0×0 when your script first runs.
205
+ - Plain HTML/CSS/JS, SVG or canvas. Calm design, one accent colour (brand.accent), legible labels.
206
+ - After writing, ALWAYS run check_widget, read its errors and look at the screenshot; fix until clean.`;
207
+ export function connectorsLine(connectors) {
208
+ const lines = connectors.map((c) => {
209
+ const actions = c.actions
210
+ .slice(0, 40)
211
+ .map((a) => `${a.id}${a.group === "read" ? "" : ` (${a.group})`}`)
212
+ .join(", ");
213
+ const more = c.actions.length > 40 ? `, … ${c.actions.length - 40} more (list_connectors)` : "";
214
+ const state = c.usable
215
+ ? ""
216
+ : " — NOT SET UP on this server: tell the admin before you build on it";
217
+ return `- ${c.id} — ${c.name}${state}, tools ${c.toolPrefix}_*: ${actions || "actions appear once an account is connected"}${more}`;
218
+ });
219
+ return `Connectors a wizard of this project can use (built in, or imported from the integrations registry; find more with find_connectors + import_connector):\n${lines.join("\n")}`;
220
+ }
221
+ export function mcpServersLine(mcp) {
222
+ return `Project MCP servers available to agent steps: ${mcp.length ? mcp.map((m) => `${m.id} (${m.name})`).join(", ") : "none — if the admin wants to write into an external system that needs one, build the step with http or browser, and tell them they can connect a server in the project settings"}.`;
223
+ }
224
+ export function exampleWizard() {
225
+ return JSON.stringify(EXAMPLE_WIZARD, null, 2);
226
+ }
227
+ /** The guide an MCP client reads before it writes a wizard. */
228
+ export function authoringGuide(mcp, connectors = []) {
229
+ return `engenty wizards: a wizard is a page-by-page flow an end user walks through on a shared link. Pages ask questions; AI steps research, write, draw images, render video, build documents and dashboards, or write into other systems. You author the wizard as JSON; the person who asked you is the ADMIN, the people who later open the link are END USERS.
230
+
231
+ ${SCHEMA_DOC}
232
+ ${PRINCIPLES}
233
+ ${WIDGET_GUIDE}
234
+
235
+ ${mcpServersLine(mcp)}
236
+
237
+ ${connectorsLine(connectors)}
238
+
239
+ Models: a step names the KIND of model it needs, never a model. Pick the cheapest class that does the job:
240
+ - "classifier": routing, yes/no, picking from options, pulling a few values out of text.
241
+ A DECISION is a "classifier" step with no tools whose json output has only "yesno" and "choice" fields: it is answered in one fast, very cheap call. Write the question into each field's description, give the step everything it must judge in its instructions via {{templates}}, and branch on the answer with "next" (e.g. { when: { field: "steps.check.refund", op: "equals", value: true }, goto: "refund" }).
242
+ - "standard": short copy, calling an API, reformatting, simple summaries.
243
+ - "high": research with tools, long documents, reasoning over many sources (the default).
244
+ - "highest": hard reasoning or writing code; rare in a run, several times the cost of "high".
245
+ "effort" ("low" | "medium" | "high") tells the model how hard to think where it can be told; leave it out unless a step is clearly trivial ("low") or clearly hard ("high").
246
+ A generate step for a document or dashboard takes the same "model" (default "high"). Image and video steps use the image and video class; which model serves a class is set by the platform, not in the wizard.
247
+
248
+ Editing:
249
+ - Every write returns the new "revision" and the validator's "issues". Pass the revision you last saw as "baseRevision"; on revision_conflict re-read with get_wizard and apply your change again.
250
+ - Prefer edit_wizard with small ops; use replace_wizard only to rewrite most of the wizard.
251
+ - A draft may have issues while you work. publish_wizard and start_test_run refuse until there are none.
252
+ - Put a short "note" on writes (what changed, in the admin's language) — the admin sees it in the studio.
253
+ - Test runs and published runs spend the admin's credits (research ~150, video ~250, short copy with an image ~15).
254
+
255
+ Example of a complete wizard:
256
+ \`\`\`json
257
+ ${exampleWizard()}
258
+ \`\`\``;
259
+ }
@@ -0,0 +1,117 @@
1
+ import { ENGENTY_KINDS } from "@engenty-wizards/shared/definition";
2
+ import { z } from "zod";
3
+ const position = {
4
+ /** Place the step directly before this step id. */
5
+ before: z.string().optional(),
6
+ /** Place the step directly after this step id. */
7
+ after: z.string().optional(),
8
+ };
9
+ export const wizardOpSchema = z.discriminatedUnion("op", [
10
+ z.object({
11
+ op: z.literal("set_meta"),
12
+ title: z.string().min(1).optional(),
13
+ description: z.string().optional(),
14
+ avatar: z.enum(ENGENTY_KINDS).optional(),
15
+ /** null removes the intro. */
16
+ intro: z.string().nullable().optional(),
17
+ }),
18
+ z.object({
19
+ op: z.literal("upsert_step"),
20
+ /** The complete step. Replaces the step with the same id, otherwise inserts it. */
21
+ step: z.looseObject({ id: z.string(), type: z.string() }),
22
+ ...position,
23
+ }),
24
+ /** The wizard's lists, all of them: what it keeps between runs. An empty array removes them. */
25
+ z.object({ op: z.literal("set_lists"), lists: z.array(z.looseObject({ id: z.string() })) }),
26
+ /** The wizard's connections, all of them: accounts the person connects. */
27
+ z.object({
28
+ op: z.literal("set_connections"),
29
+ connections: z.array(z.looseObject({ id: z.string() })),
30
+ }),
31
+ z.object({ op: z.literal("remove_step"), stepId: z.string() }),
32
+ z.object({ op: z.literal("move_step"), stepId: z.string(), ...position }),
33
+ ]);
34
+ export class OpError extends Error {
35
+ }
36
+ function anchorIndex(steps, op, at) {
37
+ if (op.before && op.after) {
38
+ throw new OpError(`${at}: give "before" or "after", not both.`);
39
+ }
40
+ const ref = op.before ?? op.after;
41
+ if (!ref) {
42
+ return null;
43
+ }
44
+ const i = steps.findIndex((s) => s.id === ref);
45
+ if (i === -1) {
46
+ throw new OpError(`${at}: there is no step "${ref}".`);
47
+ }
48
+ return op.before ? i : i + 1;
49
+ }
50
+ /**
51
+ * Applies ops in order to a copy of the draft. The result is checked against the schema by the
52
+ * caller; here only the ops themselves can fail (unknown step, both anchors).
53
+ */
54
+ export function applyOps(def, ops) {
55
+ const next = structuredClone(def);
56
+ ops.forEach((op, n) => {
57
+ const at = `op ${n + 1} (${op.op})`;
58
+ const steps = next.steps;
59
+ switch (op.op) {
60
+ case "set_meta": {
61
+ const { op: _, intro, ...meta } = op;
62
+ Object.assign(next, Object.fromEntries(Object.entries(meta).filter(([, v]) => v !== undefined)));
63
+ if (intro === null) {
64
+ delete next.intro;
65
+ }
66
+ else if (intro !== undefined) {
67
+ next.intro = intro;
68
+ }
69
+ return;
70
+ }
71
+ case "set_lists":
72
+ Object.assign(next, { lists: op.lists.length ? op.lists : undefined });
73
+ return;
74
+ case "set_connections":
75
+ Object.assign(next, { connections: op.connections.length ? op.connections : undefined });
76
+ return;
77
+ case "upsert_step": {
78
+ const existing = steps.findIndex((s) => s.id === op.step.id);
79
+ const anchor = anchorIndex(steps, op, at);
80
+ if (existing !== -1 && anchor === null) {
81
+ steps[existing] = op.step;
82
+ return;
83
+ }
84
+ if (existing !== -1) {
85
+ steps.splice(existing, 1);
86
+ }
87
+ const target = anchorIndex(steps, op, at);
88
+ // New steps go before the closing result step unless placed explicitly.
89
+ const last = steps.length - 1;
90
+ const fallback = last >= 0 && steps[last].type === "result" ? last : steps.length;
91
+ steps.splice(target ?? fallback, 0, op.step);
92
+ return;
93
+ }
94
+ case "remove_step": {
95
+ const i = steps.findIndex((s) => s.id === op.stepId);
96
+ if (i === -1) {
97
+ throw new OpError(`${at}: there is no step "${op.stepId}".`);
98
+ }
99
+ steps.splice(i, 1);
100
+ return;
101
+ }
102
+ case "move_step": {
103
+ const i = steps.findIndex((s) => s.id === op.stepId);
104
+ if (i === -1) {
105
+ throw new OpError(`${at}: there is no step "${op.stepId}".`);
106
+ }
107
+ if (!op.before && !op.after) {
108
+ throw new OpError(`${at}: give "before" or "after".`);
109
+ }
110
+ const [step] = steps.splice(i, 1);
111
+ steps.splice(anchorIndex(steps, op, at), 0, step);
112
+ return;
113
+ }
114
+ }
115
+ });
116
+ return next;
117
+ }
@@ -0,0 +1,134 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { installedByScript } from "./home.js";
7
+ import { findOnPath } from "./machine.js";
8
+ const run = promisify(execFile);
9
+ /**
10
+ * engenty wizards starting by itself when the person logs in, so that links and AI clients
11
+ * reach it without anybody starting it: `wizards start --no-open`, on a Mac from a
12
+ * LaunchAgent, on Linux from a systemd user service. Only for an install made by install.sh,
13
+ * whose command stays where it is.
14
+ */
15
+ const LOGIN_LABEL = "com.engenty.wizards.login";
16
+ /** The LaunchAgent. */
17
+ export const LOGIN_ITEM = join(homedir(), "Library", "LaunchAgents", `${LOGIN_LABEL}.plist`);
18
+ const xml = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
19
+ /** The LaunchAgent: the command of this install, started again when it fails. */
20
+ export function loginItemText(command, log) {
21
+ const program = [command, "start", "--no-open"];
22
+ return `<?xml version="1.0" encoding="UTF-8"?>
23
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
24
+ <plist version="1.0">
25
+ <dict>
26
+ <key>Label</key>
27
+ <string>${LOGIN_LABEL}</string>
28
+ <key>ProgramArguments</key>
29
+ <array>
30
+ ${program.map((arg) => ` <string>${xml(arg)}</string>\n`).join("")} </array>
31
+ <key>RunAtLoad</key>
32
+ <true/>
33
+ <key>KeepAlive</key>
34
+ <dict>
35
+ <key>SuccessfulExit</key>
36
+ <false/>
37
+ </dict>
38
+ <key>StandardOutPath</key>
39
+ <string>${xml(log)}</string>
40
+ <key>StandardErrorPath</key>
41
+ <string>${xml(log)}</string>
42
+ <key>ProcessType</key>
43
+ <string>Interactive</string>
44
+ </dict>
45
+ </plist>
46
+ `;
47
+ }
48
+ const UNIT = "engenty-wizards.service";
49
+ export function unitFile(env = process.env) {
50
+ return join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "systemd", "user", UNIT);
51
+ }
52
+ /** The systemd user service: the command of this install, started again when it fails. */
53
+ export function unitText(command) {
54
+ return [
55
+ "[Unit]",
56
+ "Description=engenty wizards",
57
+ "Documentation=https://github.com/engenty/engenty-wizards",
58
+ "",
59
+ "[Service]",
60
+ `ExecStart="${command.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}" start --no-open`,
61
+ "Restart=on-failure",
62
+ "RestartSec=5",
63
+ "",
64
+ "[Install]",
65
+ "WantedBy=default.target",
66
+ "",
67
+ ].join("\n");
68
+ }
69
+ const MAC_HOW = `a LaunchAgent, ${LOGIN_LABEL}`;
70
+ const LINUX_HOW = `a systemd user service, ${UNIT}`;
71
+ async function systemdHere() {
72
+ if (!findOnPath("systemctl", process.env.PATH ?? "")) {
73
+ return false;
74
+ }
75
+ try {
76
+ await run("systemctl", ["--user", "show-environment"], { timeout: 10_000 });
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ export async function autostartState(paths) {
84
+ if (process.platform !== "darwin" && process.platform !== "linux") {
85
+ return { unavailable: `not on ${process.platform}` };
86
+ }
87
+ if (!installedByScript(paths)) {
88
+ return { unavailable: "only for an install made by install.sh" };
89
+ }
90
+ if (process.platform === "darwin") {
91
+ return { on: existsSync(LOGIN_ITEM), how: MAC_HOW };
92
+ }
93
+ if (!(await systemdHere())) {
94
+ return { unavailable: "this Linux has no systemd user session" };
95
+ }
96
+ try {
97
+ await run("systemctl", ["--user", "is-enabled", "--quiet", UNIT], { timeout: 10_000 });
98
+ return { on: true, how: LINUX_HOW };
99
+ }
100
+ catch {
101
+ return { on: false, how: LINUX_HOW };
102
+ }
103
+ }
104
+ /** Switches it on or off; what it is afterwards, or an error the person can read. */
105
+ export async function setAutostart(paths, on) {
106
+ const state = await autostartState(paths);
107
+ if ("unavailable" in state) {
108
+ return state;
109
+ }
110
+ if (process.platform === "darwin") {
111
+ // Takes effect at the next login; nothing is started now.
112
+ if (on) {
113
+ mkdirSync(dirname(LOGIN_ITEM), { recursive: true });
114
+ writeFileSync(LOGIN_ITEM, loginItemText(join(paths.home, "bin", "wizards"), join(paths.home, "logs", "autostart.log")));
115
+ }
116
+ else {
117
+ rmSync(LOGIN_ITEM, { force: true });
118
+ }
119
+ return { on: existsSync(LOGIN_ITEM), how: MAC_HOW };
120
+ }
121
+ const file = unitFile();
122
+ if (on) {
123
+ mkdirSync(dirname(file), { recursive: true });
124
+ writeFileSync(file, unitText(join(paths.home, "bin", "wizards")));
125
+ await run("systemctl", ["--user", "daemon-reload"]);
126
+ await run("systemctl", ["--user", "enable", "--now", UNIT]);
127
+ }
128
+ else {
129
+ await run("systemctl", ["--user", "disable", "--now", UNIT]).catch(() => undefined);
130
+ rmSync(file, { force: true });
131
+ await run("systemctl", ["--user", "daemon-reload"]).catch(() => undefined);
132
+ }
133
+ return autostartState(paths);
134
+ }
@@ -0,0 +1,44 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { delimiter, dirname, join } from "node:path";
3
+ import { runtimePath } from "./environment.js";
4
+ import { runLogged } from "./exec.js";
5
+ import { findOnPath } from "./machine.js";
6
+ /** A word for the shell: quoted when it holds anything but plain characters. */
7
+ const shellWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
8
+ /** The command the setup runs for a client, as the person would type it. */
9
+ export function installCommand(client, paths) {
10
+ return client.install.kind === "npm"
11
+ ? `npm install --global --prefix ${shellWord(paths.clients)} ${client.install.pkg}`
12
+ : `curl -fsSL ${client.install.url} | bash`;
13
+ }
14
+ /**
15
+ * Installs an AI client. One that comes from npm goes into the install's own prefix
16
+ * (`clients/`), with the npm of the Node that runs this command: nothing of the person's own
17
+ * Node, if they have one, is touched. The others bring their own installer.
18
+ */
19
+ export function installClient(client, paths, onLine) {
20
+ const logFile = join(paths.logs, "install.log");
21
+ const env = { ...process.env, PATH: runtimePath(paths, process.env) };
22
+ if (client.install.kind === "script") {
23
+ return runLogged("bash", ["-c", `set -o pipefail; curl -fsSL ${client.install.url} | bash`], {
24
+ env,
25
+ logFile,
26
+ onLine,
27
+ });
28
+ }
29
+ const npm = findOnPath("npm", [dirname(process.execPath), process.env.PATH ?? ""].join(delimiter));
30
+ if (!npm) {
31
+ return Promise.resolve({ code: 127, tail: "npm was not found next to this Node." });
32
+ }
33
+ mkdirSync(paths.clients, { recursive: true });
34
+ return runLogged(npm, [
35
+ "install",
36
+ "--global",
37
+ "--prefix",
38
+ paths.clients,
39
+ "--no-audit",
40
+ "--no-fund",
41
+ "--loglevel=error",
42
+ client.install.pkg,
43
+ ], { env, logFile, onLine });
44
+ }
@@ -0,0 +1,81 @@
1
+ import { delimiter, dirname, join } from "node:path";
2
+ /**
3
+ * Variables of the terminal the runtime inherits. Everything else it gets comes from the
4
+ * install's `.env` or is set by the command line. A key or a database address that the shell
5
+ * happens to export for another project never reaches it; in particular it never sees
6
+ * `MANAGE_URL`, which would make it a cloud runtime.
7
+ */
8
+ const INHERITED = new Set([
9
+ "HOME",
10
+ "USER",
11
+ "LOGNAME",
12
+ "SHELL",
13
+ "TMPDIR",
14
+ "LANG",
15
+ "LC_ALL",
16
+ "LC_CTYPE",
17
+ "TERM",
18
+ "COLORTERM",
19
+ "HTTP_PROXY",
20
+ "HTTPS_PROXY",
21
+ "NO_PROXY",
22
+ "http_proxy",
23
+ "https_proxy",
24
+ "no_proxy",
25
+ "SSL_CERT_FILE",
26
+ "NODE_EXTRA_CA_CERTS",
27
+ // Settings of this install somebody may give for one start: `API_PORT=8900 wizards`.
28
+ "API_HOST",
29
+ "APP_URL",
30
+ "ALLOWED_HOSTS",
31
+ "SECRETS",
32
+ "CHROME_PATH",
33
+ "FFMPEG_PATH",
34
+ "OLLAMA_URL",
35
+ "SANDBOX",
36
+ "SANDBOX_ENABLED",
37
+ "SANDBOX_IMAGE",
38
+ "ACCOUNT_URL",
39
+ "ACCOUNT_GATEWAY_URL",
40
+ "CLOUD_URL",
41
+ ]);
42
+ export function inherited(env) {
43
+ const out = {};
44
+ for (const [key, value] of Object.entries(env)) {
45
+ if (value !== undefined && (INHERITED.has(key) || key.startsWith("MODEL_"))) {
46
+ out[key] = value;
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /**
52
+ * The PATH of the runtime: AI clients the setup installed and the Node that runs this command
53
+ * come first — a client installed with npm is a script that asks for `node`.
54
+ */
55
+ export function runtimePath(paths, env, node = process.execPath) {
56
+ const entries = [
57
+ join(paths.clients, "bin"),
58
+ dirname(node),
59
+ ...(env.PATH ?? "").split(delimiter),
60
+ join(env.HOME ?? "", ".local", "bin"),
61
+ ];
62
+ return [...new Set(entries.filter(Boolean))].join(delimiter);
63
+ }
64
+ /**
65
+ * The environment the command line starts the runtime with. `fileEnv` is the install's `.env`:
66
+ * the runtime reads it itself, it is only looked at here so that a default does not override it.
67
+ */
68
+ export function runtimeEnv(paths, start, env = process.env, fileEnv = {}, platform = process.platform) {
69
+ const secretsGiven = env.SECRETS !== undefined || fileEnv.SECRETS !== undefined;
70
+ return {
71
+ ...inherited(env),
72
+ PATH: runtimePath(paths, env),
73
+ NODE_ENV: "production",
74
+ API_PORT: String(start.port),
75
+ APP_URL: start.url,
76
+ DATA_DIR: start.dataDir,
77
+ LOCAL_ACCESS_KEY: start.accessKey,
78
+ // Keys entered in the settings live in the Keychain, as with the desktop app.
79
+ ...(platform === "darwin" && !secretsGiven ? { SECRETS: "keychain" } : {}),
80
+ };
81
+ }