@engenty/wizards-runtime 0.0.0-stage → 0.7.5

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 (518) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +214 -2
  3. package/apps/runtime/dist/agents/architect.js +444 -0
  4. package/apps/runtime/dist/agents/studio-assistant.js +883 -0
  5. package/apps/runtime/dist/agents/subscription.js +207 -0
  6. package/apps/runtime/dist/agents/thought.js +21 -0
  7. package/apps/runtime/dist/agents/website.js +480 -0
  8. package/apps/runtime/dist/app.js +359 -0
  9. package/apps/runtime/dist/apple/wizards-apple +0 -0
  10. package/apps/runtime/dist/assistant/browser.js +26 -0
  11. package/apps/runtime/dist/assistant/docs.js +158 -0
  12. package/apps/runtime/dist/assistant/knows.js +353 -0
  13. package/apps/runtime/dist/assistant/primer.js +26 -0
  14. package/apps/runtime/dist/assistant/runs.js +24 -0
  15. package/apps/runtime/dist/auth/account.js +175 -0
  16. package/apps/runtime/dist/auth/device.js +112 -0
  17. package/apps/runtime/dist/auth/index.js +358 -0
  18. package/apps/runtime/dist/auth/keys.js +55 -0
  19. package/apps/runtime/dist/auth/local-ticket.js +26 -0
  20. package/apps/runtime/dist/authoring/example.js +74 -0
  21. package/apps/runtime/dist/authoring/guide.js +433 -0
  22. package/apps/runtime/dist/authoring/ops.js +117 -0
  23. package/apps/runtime/dist/browser/fast/action-space.js +160 -0
  24. package/apps/runtime/dist/browser/fast/classifier.js +91 -0
  25. package/apps/runtime/dist/browser/fast/decide.js +171 -0
  26. package/apps/runtime/dist/browser/fast/driver.js +141 -0
  27. package/apps/runtime/dist/browser/fast/instructions.js +33 -0
  28. package/apps/runtime/dist/browser/fast/run.js +249 -0
  29. package/apps/runtime/dist/browser/fast/span-picker.js +153 -0
  30. package/apps/runtime/dist/browser/fast/text-helper.js +178 -0
  31. package/apps/runtime/dist/browser/snapshot.js +335 -0
  32. package/apps/runtime/dist/cli/account.js +385 -0
  33. package/apps/runtime/dist/cli/autostart.js +182 -0
  34. package/apps/runtime/dist/cli/autoupdate.js +221 -0
  35. package/apps/runtime/dist/cli/clients.js +44 -0
  36. package/apps/runtime/dist/cli/connect.js +540 -0
  37. package/apps/runtime/dist/cli/environment.js +93 -0
  38. package/apps/runtime/dist/cli/exec.js +45 -0
  39. package/apps/runtime/dist/cli/features.js +50 -0
  40. package/apps/runtime/dist/cli/home.js +88 -0
  41. package/apps/runtime/dist/cli/install-env.js +33 -0
  42. package/apps/runtime/dist/cli/machine.js +107 -0
  43. package/apps/runtime/dist/cli/main.js +222 -0
  44. package/apps/runtime/dist/cli/mcp.js +136 -0
  45. package/apps/runtime/dist/cli/menu.js +202 -0
  46. package/apps/runtime/dist/cli/processes.js +72 -0
  47. package/apps/runtime/dist/cli/report.js +66 -0
  48. package/apps/runtime/dist/cli/setup.js +335 -0
  49. package/apps/runtime/dist/cli/start.js +276 -0
  50. package/apps/runtime/dist/cli/status.js +203 -0
  51. package/apps/runtime/dist/cli/ui.js +59 -0
  52. package/apps/runtime/dist/cli/uninstall-main.js +20 -0
  53. package/apps/runtime/dist/cli/uninstall.js +231 -0
  54. package/apps/runtime/dist/cli/update.js +86 -0
  55. package/apps/runtime/dist/cli/validate.js +75 -0
  56. package/apps/runtime/dist/cli/versions.js +123 -0
  57. package/apps/runtime/dist/connectors/builtin.js +86 -0
  58. package/apps/runtime/dist/connectors/external.js +409 -0
  59. package/apps/runtime/dist/connectors/gmail.js +107 -0
  60. package/apps/runtime/dist/connectors/imap.js +261 -0
  61. package/apps/runtime/dist/connectors/index.js +325 -0
  62. package/apps/runtime/dist/connectors/mail.js +72 -0
  63. package/apps/runtime/dist/connectors/outlook.js +90 -0
  64. package/apps/runtime/dist/connectors/plugin-connections.js +343 -0
  65. package/apps/runtime/dist/connectors/via-account.js +65 -0
  66. package/apps/runtime/dist/credits/credits.js +100 -0
  67. package/apps/runtime/dist/credits/estimate.js +152 -0
  68. package/apps/runtime/dist/db/adopt.js +149 -0
  69. package/apps/runtime/dist/db/client.js +173 -0
  70. package/apps/runtime/dist/db/control-schema.js +133 -0
  71. package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
  72. package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
  73. package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
  74. package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
  75. package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
  76. package/apps/runtime/dist/db/migrations/control/0005_chilly_oracle.sql +1 -0
  77. package/apps/runtime/dist/db/migrations/control/0006_sleepy_mojo.sql +1 -0
  78. package/apps/runtime/dist/db/migrations/control/0007_common_the_hand.sql +15 -0
  79. package/apps/runtime/dist/db/migrations/control/0008_runner.sql +8 -0
  80. package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
  81. package/apps/runtime/dist/db/migrations/control/0010_tan_snowbird.sql +6 -0
  82. package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
  83. package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
  84. package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
  85. package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
  86. package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
  87. package/apps/runtime/dist/db/migrations/control/meta/0005_snapshot.json +358 -0
  88. package/apps/runtime/dist/db/migrations/control/meta/0006_snapshot.json +365 -0
  89. package/apps/runtime/dist/db/migrations/control/meta/0007_snapshot.json +460 -0
  90. package/apps/runtime/dist/db/migrations/control/meta/0008_snapshot.json +505 -0
  91. package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
  92. package/apps/runtime/dist/db/migrations/control/meta/0010_snapshot.json +600 -0
  93. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +83 -0
  94. package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
  95. package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
  96. package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
  97. package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
  98. package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
  99. package/apps/runtime/dist/db/migrations/tenant/0005_reflective_forgotten_one.sql +2 -0
  100. package/apps/runtime/dist/db/migrations/tenant/0006_hard_war_machine.sql +30 -0
  101. package/apps/runtime/dist/db/migrations/tenant/0007_space_data.sql +43 -0
  102. package/apps/runtime/dist/db/migrations/tenant/0008_run_event_note.sql +1 -0
  103. package/apps/runtime/dist/db/migrations/tenant/0009_knowledge.sql +104 -0
  104. package/apps/runtime/dist/db/migrations/tenant/0010_plugin_connection.sql +15 -0
  105. package/apps/runtime/dist/db/migrations/tenant/0011_runners.sql +2 -0
  106. package/apps/runtime/dist/db/migrations/tenant/0012_assistant_chat.sql +40 -0
  107. package/apps/runtime/dist/db/migrations/tenant/0013_run_origin.sql +1 -0
  108. package/apps/runtime/dist/db/migrations/tenant/0014_run_answer.sql +2 -0
  109. package/apps/runtime/dist/db/migrations/tenant/0015_device_calls.sql +13 -0
  110. package/apps/runtime/dist/db/migrations/tenant/0016_even_trish_tilby.sql +12 -0
  111. package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
  112. package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
  113. package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
  114. package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
  115. package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
  116. package/apps/runtime/dist/db/migrations/tenant/meta/0005_snapshot.json +1659 -0
  117. package/apps/runtime/dist/db/migrations/tenant/meta/0006_snapshot.json +1696 -0
  118. package/apps/runtime/dist/db/migrations/tenant/meta/0007_snapshot.json +2012 -0
  119. package/apps/runtime/dist/db/migrations/tenant/meta/0008_snapshot.json +2019 -0
  120. package/apps/runtime/dist/db/migrations/tenant/meta/0009_snapshot.json +2837 -0
  121. package/apps/runtime/dist/db/migrations/tenant/meta/0010_snapshot.json +2942 -0
  122. package/apps/runtime/dist/db/migrations/tenant/meta/0011_snapshot.json +2956 -0
  123. package/apps/runtime/dist/db/migrations/tenant/meta/0012_snapshot.json +3132 -0
  124. package/apps/runtime/dist/db/migrations/tenant/meta/0013_snapshot.json +3139 -0
  125. package/apps/runtime/dist/db/migrations/tenant/meta/0014_snapshot.json +3154 -0
  126. package/apps/runtime/dist/db/migrations/tenant/meta/0015_snapshot.json +3243 -0
  127. package/apps/runtime/dist/db/migrations/tenant/meta/0016_snapshot.json +3326 -0
  128. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +125 -0
  129. package/apps/runtime/dist/db/schema.js +602 -0
  130. package/apps/runtime/dist/debug.js +196 -0
  131. package/apps/runtime/dist/docs/dev/index.md +41 -0
  132. package/apps/runtime/dist/docs/dev/meta.json +7 -0
  133. package/apps/runtime/dist/docs/dev/plugins/connections.md +74 -0
  134. package/apps/runtime/dist/docs/dev/plugins/events.md +89 -0
  135. package/apps/runtime/dist/docs/dev/plugins/examples.md +101 -0
  136. package/apps/runtime/dist/docs/dev/plugins/index.md +78 -0
  137. package/apps/runtime/dist/docs/dev/plugins/layout.md +90 -0
  138. package/apps/runtime/dist/docs/dev/plugins/meta.json +28 -0
  139. package/apps/runtime/dist/docs/dev/plugins/quick-start.md +94 -0
  140. package/apps/runtime/dist/docs/dev/plugins/reference.md +348 -0
  141. package/apps/runtime/dist/docs/dev/plugins/reloading.md +76 -0
  142. package/apps/runtime/dist/docs/dev/plugins/routes.md +185 -0
  143. package/apps/runtime/dist/docs/dev/plugins/runners.md +230 -0
  144. package/apps/runtime/dist/docs/dev/plugins/server.md +281 -0
  145. package/apps/runtime/dist/docs/dev/plugins/shipping.md +79 -0
  146. package/apps/runtime/dist/docs/dev/plugins/space.md +297 -0
  147. package/apps/runtime/dist/docs/dev/plugins/starters.md +70 -0
  148. package/apps/runtime/dist/docs/dev/plugins/studio.md +363 -0
  149. package/apps/runtime/dist/docs/dev/plugins/styling.md +129 -0
  150. package/apps/runtime/dist/docs/dev/plugins/tables.md +104 -0
  151. package/apps/runtime/dist/docs/dev/plugins/tools.md +139 -0
  152. package/apps/runtime/dist/docs/dev/wizard-format.md +125 -0
  153. package/apps/runtime/dist/docs/meta.json +3 -0
  154. package/apps/runtime/dist/docs/user/ai-apps.md +112 -0
  155. package/apps/runtime/dist/docs/user/assistant.md +77 -0
  156. package/apps/runtime/dist/docs/user/build-a-wizard.md +116 -0
  157. package/apps/runtime/dist/docs/user/command-line.md +83 -0
  158. package/apps/runtime/dist/docs/user/connectors.md +90 -0
  159. package/apps/runtime/dist/docs/user/create-account.md +52 -0
  160. package/apps/runtime/dist/docs/user/data.md +66 -0
  161. package/apps/runtime/dist/docs/user/first-start.md +60 -0
  162. package/apps/runtime/dist/docs/user/index.md +49 -0
  163. package/apps/runtime/dist/docs/user/install.md +123 -0
  164. package/apps/runtime/dist/docs/user/local-only.md +42 -0
  165. package/apps/runtime/dist/docs/user/meta.json +32 -0
  166. package/apps/runtime/dist/docs/user/models.md +156 -0
  167. package/apps/runtime/dist/docs/user/plugins.md +71 -0
  168. package/apps/runtime/dist/docs/user/run.md +98 -0
  169. package/apps/runtime/dist/docs/user/server.md +52 -0
  170. package/apps/runtime/dist/docs/user/space.md +126 -0
  171. package/apps/runtime/dist/docs/user/steps.md +220 -0
  172. package/apps/runtime/dist/docs/user/test-and-share.md +224 -0
  173. package/apps/runtime/dist/docs/user/troubleshooting.md +67 -0
  174. package/apps/runtime/dist/docs/user/wordpress.md +121 -0
  175. package/apps/runtime/dist/documents/parse.js +246 -0
  176. package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
  177. package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
  178. package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
  179. package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
  180. package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
  181. package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
  182. package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
  183. package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
  184. package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
  185. package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
  186. package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
  187. package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
  188. package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
  189. package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
  190. package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
  191. package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +381 -0
  192. package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
  193. package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
  194. package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
  195. package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
  196. package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
  197. package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
  198. package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
  199. package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
  200. package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
  201. package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +492 -0
  202. package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
  203. package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
  204. package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
  205. package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
  206. package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
  207. package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
  208. package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
  209. package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
  210. package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
  211. package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
  212. package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
  213. package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
  214. package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
  215. package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
  216. package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
  217. package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
  218. package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
  219. package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
  220. package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
  221. package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
  222. package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
  223. package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
  224. package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
  225. package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
  226. package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
  227. package/apps/runtime/dist/engenty/shims/web-ingest.js +19 -0
  228. package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
  229. package/apps/runtime/dist/engine/asks.js +82 -0
  230. package/apps/runtime/dist/engine/decide.js +82 -0
  231. package/apps/runtime/dist/engine/device.js +194 -0
  232. package/apps/runtime/dist/engine/events.js +128 -0
  233. package/apps/runtime/dist/engine/input.js +208 -0
  234. package/apps/runtime/dist/engine/owner.js +32 -0
  235. package/apps/runtime/dist/engine/prepare.js +90 -0
  236. package/apps/runtime/dist/engine/push.js +58 -0
  237. package/apps/runtime/dist/engine/requirements.js +355 -0
  238. package/apps/runtime/dist/engine/resources.js +198 -0
  239. package/apps/runtime/dist/engine/runner.js +970 -0
  240. package/apps/runtime/dist/engine/steps.js +1000 -0
  241. package/apps/runtime/dist/engine/surface.js +91 -0
  242. package/apps/runtime/dist/engine/template.js +240 -0
  243. package/apps/runtime/dist/engine/types.js +4 -0
  244. package/apps/runtime/dist/env.js +295 -0
  245. package/apps/runtime/dist/features.js +123 -0
  246. package/apps/runtime/dist/files/blobs.js +22 -0
  247. package/apps/runtime/dist/files/objects.js +86 -0
  248. package/apps/runtime/dist/files/storage.js +120 -0
  249. package/apps/runtime/dist/files/web-images.js +101 -0
  250. package/apps/runtime/dist/film/project.js +257 -0
  251. package/apps/runtime/dist/film/skills.js +91 -0
  252. package/apps/runtime/dist/film/sounds.js +122 -0
  253. package/apps/runtime/dist/film/step.js +289 -0
  254. package/apps/runtime/dist/film/tools.js +99 -0
  255. package/apps/runtime/dist/geocode.js +58 -0
  256. package/apps/runtime/dist/harness/antigravity.js +129 -0
  257. package/apps/runtime/dist/harness/apple.js +270 -0
  258. package/apps/runtime/dist/harness/claude.js +147 -0
  259. package/apps/runtime/dist/harness/codex.js +229 -0
  260. package/apps/runtime/dist/harness/cursor.js +93 -0
  261. package/apps/runtime/dist/harness/env.js +124 -0
  262. package/apps/runtime/dist/harness/index.js +95 -0
  263. package/apps/runtime/dist/harness/model.js +279 -0
  264. package/apps/runtime/dist/harness/prompt.js +121 -0
  265. package/apps/runtime/dist/harness/terminal.js +194 -0
  266. package/apps/runtime/dist/harness/types.js +7 -0
  267. package/apps/runtime/dist/index.js +113 -0
  268. package/apps/runtime/dist/limits.js +81 -0
  269. package/apps/runtime/dist/link-preview.js +60 -0
  270. package/apps/runtime/dist/log.js +106 -0
  271. package/apps/runtime/dist/manage.js +249 -0
  272. package/apps/runtime/dist/mcp/auth.js +161 -0
  273. package/apps/runtime/dist/mcp/bridge.js +59 -0
  274. package/apps/runtime/dist/mcp/flow-app.js +97 -0
  275. package/apps/runtime/dist/mcp/handler.js +54 -0
  276. package/apps/runtime/dist/mcp/scopes.js +14 -0
  277. package/apps/runtime/dist/mcp/seen.js +65 -0
  278. package/apps/runtime/dist/mcp/tools.js +572 -0
  279. package/apps/runtime/dist/media/conversation.js +167 -0
  280. package/apps/runtime/dist/media/ffmpeg.js +121 -0
  281. package/apps/runtime/dist/media/generate.js +120 -0
  282. package/apps/runtime/dist/media/marking.js +121 -0
  283. package/apps/runtime/dist/media/transcribe.js +198 -0
  284. package/apps/runtime/dist/model-errors.js +42 -0
  285. package/apps/runtime/dist/models.js +1164 -0
  286. package/apps/runtime/dist/plugin.js +61 -0
  287. package/apps/runtime/dist/plugins/channels.js +78 -0
  288. package/apps/runtime/dist/plugins/claims.js +56 -0
  289. package/apps/runtime/dist/plugins/discovery.js +155 -0
  290. package/apps/runtime/dist/plugins/events.js +63 -0
  291. package/apps/runtime/dist/plugins/jobs.js +64 -0
  292. package/apps/runtime/dist/plugins/loader.js +508 -0
  293. package/apps/runtime/dist/plugins/migrations.js +69 -0
  294. package/apps/runtime/dist/plugins/pro-keys.js +34 -0
  295. package/apps/runtime/dist/plugins/pro-state.js +83 -0
  296. package/apps/runtime/dist/plugins/pro.js +310 -0
  297. package/apps/runtime/dist/plugins/public.js +44 -0
  298. package/apps/runtime/dist/plugins/registry.js +122 -0
  299. package/apps/runtime/dist/plugins/runs.js +210 -0
  300. package/apps/runtime/dist/render/browser-path.js +54 -0
  301. package/apps/runtime/dist/render/chromium.js +98 -0
  302. package/apps/runtime/dist/render/convert.js +158 -0
  303. package/apps/runtime/dist/render/downloads.js +299 -0
  304. package/apps/runtime/dist/render/guard.js +25 -0
  305. package/apps/runtime/dist/render/shaped.js +137 -0
  306. package/apps/runtime/dist/routes/api.js +440 -0
  307. package/apps/runtime/dist/routes/debug.js +42 -0
  308. package/apps/runtime/dist/routes/delivery.js +90 -0
  309. package/apps/runtime/dist/routes/internal.js +81 -0
  310. package/apps/runtime/dist/routes/lang.js +25 -0
  311. package/apps/runtime/dist/routes/marketplace.js +43 -0
  312. package/apps/runtime/dist/routes/plugin-public.js +113 -0
  313. package/apps/runtime/dist/routes/plugins.js +212 -0
  314. package/apps/runtime/dist/routes/runs.js +1422 -0
  315. package/apps/runtime/dist/routes/studio.js +1629 -0
  316. package/apps/runtime/dist/routes/wizard-stream.js +30 -0
  317. package/apps/runtime/dist/running.js +39 -0
  318. package/apps/runtime/dist/sandbox/docker.js +75 -0
  319. package/apps/runtime/dist/sandbox/index.js +23 -0
  320. package/apps/runtime/dist/sandbox/types.js +1 -0
  321. package/apps/runtime/dist/secrets/crypto.js +28 -0
  322. package/apps/runtime/dist/secrets/signing.js +52 -0
  323. package/apps/runtime/dist/secrets/vault.js +59 -0
  324. package/apps/runtime/dist/services/access.js +88 -0
  325. package/apps/runtime/dist/services/api.js +248 -0
  326. package/apps/runtime/dist/services/architect.js +95 -0
  327. package/apps/runtime/dist/services/assistant-chats.js +311 -0
  328. package/apps/runtime/dist/services/brand.js +85 -0
  329. package/apps/runtime/dist/services/cloud.js +456 -0
  330. package/apps/runtime/dist/services/draft-events.js +27 -0
  331. package/apps/runtime/dist/services/engenty-shapes.js +71 -0
  332. package/apps/runtime/dist/services/errors.js +29 -0
  333. package/apps/runtime/dist/services/files.js +178 -0
  334. package/apps/runtime/dist/services/knowledge-model.js +559 -0
  335. package/apps/runtime/dist/services/knowledge.js +721 -0
  336. package/apps/runtime/dist/services/links.js +16 -0
  337. package/apps/runtime/dist/services/marketplace.js +492 -0
  338. package/apps/runtime/dist/services/onboarding.js +152 -0
  339. package/apps/runtime/dist/services/package-file.js +82 -0
  340. package/apps/runtime/dist/services/package.js +170 -0
  341. package/apps/runtime/dist/services/profile.js +51 -0
  342. package/apps/runtime/dist/services/project-files.js +439 -0
  343. package/apps/runtime/dist/services/project-index.js +707 -0
  344. package/apps/runtime/dist/services/projects.js +387 -0
  345. package/apps/runtime/dist/services/relevance.js +58 -0
  346. package/apps/runtime/dist/services/runners.js +178 -0
  347. package/apps/runtime/dist/services/runs.js +524 -0
  348. package/apps/runtime/dist/services/shares.js +130 -0
  349. package/apps/runtime/dist/services/site.js +270 -0
  350. package/apps/runtime/dist/services/space-categories.js +861 -0
  351. package/apps/runtime/dist/services/space-data.js +792 -0
  352. package/apps/runtime/dist/services/space-origin.js +207 -0
  353. package/apps/runtime/dist/services/spaces.js +656 -0
  354. package/apps/runtime/dist/services/translations.js +306 -0
  355. package/apps/runtime/dist/services/widgets.js +34 -0
  356. package/apps/runtime/dist/services/wizard-icon.js +157 -0
  357. package/apps/runtime/dist/services/wizards.js +420 -0
  358. package/apps/runtime/dist/settings.js +16 -0
  359. package/apps/runtime/dist/store/errors.js +4 -0
  360. package/apps/runtime/dist/store/index.js +299 -0
  361. package/apps/runtime/dist/store/shared-lists.js +123 -0
  362. package/apps/runtime/dist/tenants/control.js +238 -0
  363. package/apps/runtime/dist/tenants/tenant.js +26 -0
  364. package/apps/runtime/dist/texts.js +207 -0
  365. package/apps/runtime/dist/tools/browser.js +703 -0
  366. package/apps/runtime/dist/tools/connector.js +204 -0
  367. package/apps/runtime/dist/tools/index.js +291 -0
  368. package/apps/runtime/dist/tools/mail.js +104 -0
  369. package/apps/runtime/dist/tools/net-guard.js +170 -0
  370. package/apps/runtime/dist/tools/pages.js +81 -0
  371. package/apps/runtime/dist/tools/plugin.js +180 -0
  372. package/apps/runtime/dist/tools/project.js +154 -0
  373. package/apps/runtime/dist/tools/shared.js +69 -0
  374. package/apps/runtime/dist/tools/store.js +238 -0
  375. package/apps/runtime/dist/update.js +113 -0
  376. package/apps/runtime/dist/widgets/bundle.js +99 -0
  377. package/apps/runtime/dist/widgets/render.js +329 -0
  378. package/apps/runtime/dist/widgets/runtime.js +108 -0
  379. package/apps/runtime/dist/widgets/script.js +30 -0
  380. package/apps/runtime/dist/widgets/step.js +223 -0
  381. package/apps/web/dist/ai-labels/ai-black.svg +20 -0
  382. package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
  383. package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
  384. package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
  385. package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
  386. package/apps/web/dist/ai-labels/ai-white.svg +21 -0
  387. package/apps/web/dist/assets/AboutDialog-D4mvc-zu.js +1 -0
  388. package/apps/web/dist/assets/ChangelogDialog-DLqpJsF6.js +1 -0
  389. package/apps/web/dist/assets/ChatView-3laypRcE.js +10 -0
  390. package/apps/web/dist/assets/EditorPage-BalA5U2Y.js +3 -0
  391. package/apps/web/dist/assets/FlowPreview-CusP7ci9.js +1 -0
  392. package/apps/web/dist/assets/Harness-BrP-ENHg.css +1 -0
  393. package/apps/web/dist/assets/Harness-iYLtOLaK.js +16 -0
  394. package/apps/web/dist/assets/NewWizardChannels-CbwJ9A56.js +1 -0
  395. package/apps/web/dist/assets/NewWizardPage-BOBYiB9i.js +7 -0
  396. package/apps/web/dist/assets/OnboardingPage-CUwCiVIr.js +1 -0
  397. package/apps/web/dist/assets/ProjectPage-Bb16MdYG.js +1 -0
  398. package/apps/web/dist/assets/PublicRunner-BCt7Q6C-.js +1 -0
  399. package/apps/web/dist/assets/ReadOnly-kIHX2TEy.js +1 -0
  400. package/apps/web/dist/assets/RunDrawer-hZ9oeSXu.js +4 -0
  401. package/apps/web/dist/assets/RunnerView-DUS9fPmx.js +1 -0
  402. package/apps/web/dist/assets/SettingsPage-BzwR50fi.js +1 -0
  403. package/apps/web/dist/assets/SetupPage-gAp7rPxp.js +1 -0
  404. package/apps/web/dist/assets/SharePage-DqZWHva_.js +1 -0
  405. package/apps/web/dist/assets/changelog-DugmCNSf.js +1 -0
  406. package/apps/web/dist/assets/engenty-eXZ-EAwd.js +1 -0
  407. package/apps/web/dist/assets/esm-DXeMu8Lb.js +7 -0
  408. package/apps/web/dist/assets/fluffy-engenty-ClRou7yu.js +456 -0
  409. package/apps/web/dist/assets/index-AfUa8xKD.css +1 -0
  410. package/apps/web/dist/assets/index-C5Sm7tTR.js +14 -0
  411. package/apps/web/dist/assets/jsx-runtime-BNakU3Ej.js +1 -0
  412. package/apps/web/dist/assets/lock-R_CfU0Xo.js +1 -0
  413. package/apps/web/dist/assets/oss-credits-cZ9NUaLZ.js +1 -0
  414. package/apps/web/dist/assets/pdfmake-Cx9scTbD.js +228 -0
  415. package/apps/web/dist/assets/phone-CgrIS4_-.js +1 -0
  416. package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
  417. package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
  418. package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
  419. package/apps/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  420. package/apps/web/dist/assets/settings-menu-2IMOOlLl.js +1 -0
  421. package/apps/web/dist/assets/ui-dzGvc3xk.js +137 -0
  422. package/apps/web/dist/assets/upload-BSuTilFF.js +1 -0
  423. package/apps/web/dist/assets/vfs_fonts-DllDSYt1.js +1 -0
  424. package/apps/web/dist/embed.js +266 -0
  425. package/apps/web/dist/favicon.svg +1 -0
  426. package/apps/web/dist/icons/apple-touch-icon.png +0 -0
  427. package/apps/web/dist/icons/icon-192.png +0 -0
  428. package/apps/web/dist/icons/icon-512.png +0 -0
  429. package/apps/web/dist/icons/icon-maskable-512.png +0 -0
  430. package/apps/web/dist/index.html +48 -0
  431. package/apps/web/dist/install.sh +577 -0
  432. package/apps/web/dist/manifest.webmanifest +21 -0
  433. package/apps/web/dist/mcp/flow.html +980 -0
  434. package/apps/web/dist/sw.js +15 -0
  435. package/bin/wizards.mjs +288 -0
  436. package/modules/README.md +15 -0
  437. package/node_modules/@engenty/wizards/dist/plugin/index.d.ts +1150 -0
  438. package/node_modules/@engenty/wizards/dist/plugin/index.js +4 -0
  439. package/node_modules/@engenty/wizards/dist/plugin/studio.d.ts +322 -0
  440. package/node_modules/@engenty/wizards/dist/plugin/studio.js +4 -0
  441. package/node_modules/@engenty/wizards/dist/plugin/thread.d.ts +174 -0
  442. package/node_modules/@engenty/wizards/dist/plugin/thread.js +821 -0
  443. package/node_modules/@engenty/wizards/package.json +11 -0
  444. package/node_modules/@engenty-wizards/shared/dist/assistant.d.ts +174 -0
  445. package/node_modules/@engenty-wizards/shared/dist/assistant.js +65 -0
  446. package/node_modules/@engenty-wizards/shared/dist/changes.d.ts +22 -0
  447. package/node_modules/@engenty-wizards/shared/dist/changes.js +61 -0
  448. package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
  449. package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
  450. package/node_modules/@engenty-wizards/shared/dist/decision.d.ts +76 -0
  451. package/node_modules/@engenty-wizards/shared/dist/decision.js +147 -0
  452. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +2922 -0
  453. package/node_modules/@engenty-wizards/shared/dist/definition.js +1920 -0
  454. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
  455. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
  456. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
  457. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
  458. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
  459. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +217 -0
  460. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
  461. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
  462. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
  463. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
  464. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
  465. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
  466. package/node_modules/@engenty-wizards/shared/dist/feature-flags.d.ts +78 -0
  467. package/node_modules/@engenty-wizards/shared/dist/feature-flags.js +70 -0
  468. package/node_modules/@engenty-wizards/shared/dist/features.d.ts +41 -0
  469. package/node_modules/@engenty-wizards/shared/dist/features.js +89 -0
  470. package/node_modules/@engenty-wizards/shared/dist/fit.d.ts +71 -0
  471. package/node_modules/@engenty-wizards/shared/dist/fit.js +382 -0
  472. package/node_modules/@engenty-wizards/shared/dist/json-schema.d.ts +14 -0
  473. package/node_modules/@engenty-wizards/shared/dist/json-schema.js +66 -0
  474. package/node_modules/@engenty-wizards/shared/dist/knowledge.d.ts +199 -0
  475. package/node_modules/@engenty-wizards/shared/dist/knowledge.js +68 -0
  476. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
  477. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +107 -0
  478. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
  479. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +136 -0
  480. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +227 -0
  481. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +354 -0
  482. package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
  483. package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
  484. package/node_modules/@engenty-wizards/shared/dist/numbers.d.ts +24 -0
  485. package/node_modules/@engenty-wizards/shared/dist/numbers.js +54 -0
  486. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +224 -0
  487. package/node_modules/@engenty-wizards/shared/dist/projects.js +319 -0
  488. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +101 -0
  489. package/node_modules/@engenty-wizards/shared/dist/providers.js +390 -0
  490. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +680 -0
  491. package/node_modules/@engenty-wizards/shared/dist/run.js +620 -0
  492. package/node_modules/@engenty-wizards/shared/dist/runner-texts.d.ts +546 -0
  493. package/node_modules/@engenty-wizards/shared/dist/runner-texts.js +523 -0
  494. package/node_modules/@engenty-wizards/shared/dist/runners.d.ts +93 -0
  495. package/node_modules/@engenty-wizards/shared/dist/runners.js +146 -0
  496. package/node_modules/@engenty-wizards/shared/dist/shape.d.ts +102 -0
  497. package/node_modules/@engenty-wizards/shared/dist/shape.js +318 -0
  498. package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +89 -0
  499. package/node_modules/@engenty-wizards/shared/dist/space-data.js +47 -0
  500. package/node_modules/@engenty-wizards/shared/dist/store.d.ts +117 -0
  501. package/node_modules/@engenty-wizards/shared/dist/store.js +81 -0
  502. package/node_modules/@engenty-wizards/shared/dist/surface.d.ts +115 -0
  503. package/node_modules/@engenty-wizards/shared/dist/surface.js +442 -0
  504. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +45 -0
  505. package/node_modules/@engenty-wizards/shared/dist/texts.js +54 -0
  506. package/node_modules/@engenty-wizards/shared/dist/translation.d.ts +23 -0
  507. package/node_modules/@engenty-wizards/shared/dist/translation.js +154 -0
  508. package/node_modules/@engenty-wizards/shared/dist/wizard-package.d.ts +48 -0
  509. package/node_modules/@engenty-wizards/shared/dist/wizard-package.js +49 -0
  510. package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
  511. package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
  512. package/node_modules/@engenty-wizards/shared/package.json +10 -0
  513. package/package.json +77 -4
  514. package/plugin/.claude-plugin/plugin.json +9 -0
  515. package/plugin/.mcp.json +8 -0
  516. package/plugin/commands/wizard.md +8 -0
  517. package/plugin/skills/build-wizard/SKILL.md +27 -0
  518. package/plugin/skills/use-wizard/SKILL.md +18 -0
@@ -0,0 +1,1629 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { ASSISTANT_LAYOUTS, ASSISTANT_PAGE_BYTES, assistantSignalSchema, assistantTurnSchema, } from "@engenty-wizards/shared/assistant";
4
+ import { LANGS, MODEL_CLASSES, TEXT_CLASSES, wizardLang, } from "@engenty-wizards/shared/definition";
5
+ import { CATEGORY_TYPES, whereSchema } from "@engenty-wizards/shared/knowledge";
6
+ import { PROJECT_FILE_KINDS } from "@engenty-wizards/shared/projects";
7
+ import { isCreditsRef } from "@engenty-wizards/shared/providers";
8
+ import { note } from "@engenty-wizards/shared/run";
9
+ import { wizardTexts } from "@engenty-wizards/shared/translation";
10
+ import { PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKAGE_MIME, } from "@engenty-wizards/shared/wizard-package";
11
+ import { generateText } from "ai";
12
+ import { Hono } from "hono";
13
+ import { streamSSE } from "hono/streaming";
14
+ import { nanoid } from "nanoid";
15
+ import { z } from "zod";
16
+ import { runStudioAssistant, signalText } from "../agents/studio-assistant.js";
17
+ import { chatEngine, setChatEngine, subscriptionClients } from "../agents/subscription.js";
18
+ import { scanColors, scanFonts, scanImages, scanLogos } from "../agents/website.js";
19
+ import { answerBrowser, awaitBrowser } from "../assistant/browser.js";
20
+ import { endRun, signalRun, startRun, takeSignals } from "../assistant/runs.js";
21
+ import { accountOverview, linkedAccount, startLink, unlink } from "../auth/account.js";
22
+ import { createLocalKey, deleteLocalKey, listLocalKeys } from "../auth/keys.js";
23
+ import { appStates, connectApps } from "../cli/connect.js";
24
+ import { layout } from "../cli/home.js";
25
+ import { connectorImportSchema, importConnector, listConnectors, previewSource, refreshConnector, registryService, removeConnector, searchRegistry, } from "../connectors/external.js";
26
+ import { balanceCredits, canSpend } from "../credits/credits.js";
27
+ import { estimateRun } from "../credits/estimate.js";
28
+ import { missingModels, stepsWithoutModel } from "../engine/requirements.js";
29
+ import { env } from "../env.js";
30
+ import { feature, features } from "../features.js";
31
+ import { appleStatus } from "../harness/apple.js";
32
+ import { freshAuth, loginEnv } from "../harness/env.js";
33
+ import { detectHarness, detectHarnesses, HARNESS_IDS, harness, installSpec, } from "../harness/index.js";
34
+ import { closeTerminal, openTerminal, resizeTerminal, subscribeTerminal, writeTerminal, } from "../harness/terminal.js";
35
+ import { installMail, managed, renameTenant, saveTeamProfile, tenantInfo, tenantMembers, userInfo, } from "../manage.js";
36
+ import { seenApps } from "../mcp/seen.js";
37
+ import { generateImageMedia, generateSpeechMedia, generateVideoMedia } from "../media/generate.js";
38
+ import { transcribeAudio } from "../media/transcribe.js";
39
+ import { errorText, saidOf } from "../model-errors.js";
40
+ import { checkKey, classProblem, classWays, embeddingModel, hasTextModel, LOCAL_KEYS, localModelSettings, ModelUnavailableError, modelSettings, saveLocalModels, saveModels, setUpKey, setUpOllama, storedKey, textModel, } from "../models.js";
41
+ import { refreshPro } from "../plugins/pro.js";
42
+ import { HTML_RESPONSE_CSP } from "../render/guard.js";
43
+ import { isAdminRole, mayBuild, mayCreate, projectWritable, requireAdmin, requireWritable, } from "../services/access.js";
44
+ import { architectTurn } from "../services/architect.js";
45
+ import { addMessage, assistantLayout, currentChat, earlierChats, foldChat, newChat, openChat, readChat, turnHistory, } from "../services/assistant-chats.js";
46
+ import { describeGradient } from "../services/brand.js";
47
+ import { checkForCloud, cloudLinked, cloudSpaces, cloudState, forgetCloud, publishAndSync, pushSharing, removeFromCloud, replaceCloudSpaces, rotateInCloud, syncToCloud, } from "../services/cloud.js";
48
+ import { notFound, ServiceError } from "../services/errors.js";
49
+ import { deleteFile, listFiles, readFile, writeFile } from "../services/files.js";
50
+ import { categoryContents, listKnowledge, resolvePath, valueContents, } from "../services/knowledge.js";
51
+ import { writeSummary } from "../services/knowledge-model.js";
52
+ import { applyWebsite, finishOnboarding, needsOnboarding } from "../services/onboarding.js";
53
+ import { exportWizard, importWizard } from "../services/package.js";
54
+ import { profileSchema, saveProfile, saveUserLang, userLang, userProfile, } from "../services/profile.js";
55
+ import { addProjectFile, addProjectFileFromUrl, fileView, orderProjectFiles, projectFile, projectFileContent, projectFiles, reindexProjectFile, removeProjectFile, updateProjectFile, } from "../services/project-files.js";
56
+ import { documentText, queueIndex, searchKnowledge } from "../services/project-index.js";
57
+ import { createProject, deleteProject, gradientSchema, listProjects, maskedServers, ownedProject, projectLimitOrNull, projectPatchSchema, updateProject, } from "../services/projects.js";
58
+ import { runnersOf, wizardRunners } from "../services/runners.js";
59
+ import { listResults, listRuns, startTestRun } from "../services/runs.js";
60
+ import { addCategoryValue, categoryRow, categoryValue, deleteCategory, deleteCategoryValue, mergeCategoryValues, orderCategoryValues, putCategory, renameCategoryValue, setItemCategory, updateCategory, } from "../services/space-categories.js";
61
+ import { addTableRow, createPage, createTable, deletePage, deleteTable, deleteTableRows, getPage, getTable, listSpaceData, orderSubPages, updatePage, updateTable, updateTableRow, } from "../services/space-data.js";
62
+ import { deleteTranslation, editTranslation, isLangCode, publishedDefinition, translationsOf, } from "../services/translations.js";
63
+ import { draftWidgetPreview } from "../services/widgets.js";
64
+ import { createWizard, deleteWizard, draftChanges, duplicateWizard, listWizards, ownedWizard, rotateShareLink, updateWizardSettings, wizardMessages, wizardState, writableWizard, writeDraft, } from "../services/wizards.js";
65
+ import { readSetting, writeSetting } from "../settings.js";
66
+ import { codeOf } from "../tenants/control.js";
67
+ import { currentTenant } from "../tenants/tenant.js";
68
+ import { serverText } from "../texts.js";
69
+ import { applyUpdate, updateStatus } from "../update.js";
70
+ import { byteRange } from "./delivery.js";
71
+ import { notFoundAnswer, requestLang, say } from "./lang.js";
72
+ /** The model sources a test call has worked on: the setup is done for those, not for the others. */
73
+ async function testedSources() {
74
+ const tested = await readSetting("setup-done");
75
+ return Array.isArray(tested) ? tested : [];
76
+ }
77
+ async function markTested(source) {
78
+ const tested = await testedSources();
79
+ if (!tested.includes(source)) {
80
+ await writeSetting("setup-done", [...tested, source]);
81
+ }
82
+ }
83
+ /**
84
+ * The chosen source has passed a test call and can still answer: its client is there and signed
85
+ * in. Where Apple Intelligence thinks the short classes, the test ran there; a client behind High
86
+ * and Highest is no condition then.
87
+ */
88
+ async function setupDone() {
89
+ const { source, bindings } = localModelSettings();
90
+ if (!(await testedSources()).includes(source)) {
91
+ return false;
92
+ }
93
+ const client = bindings.standard.startsWith("apple:") ? null : harness(source);
94
+ if (client) {
95
+ const status = await detectHarness(client.id);
96
+ return Boolean(status?.version) && status?.auth !== "none";
97
+ }
98
+ return hasTextModel();
99
+ }
100
+ const modelsInput = z.object({
101
+ source: z.enum([...HARNESS_IDS, "account", "own"]).optional(),
102
+ bindings: z.partialRecord(z.enum(MODEL_CLASSES), z.string().max(160)).optional(),
103
+ ollamaUrl: z.string().max(200).optional(),
104
+ creditFallback: z.boolean().optional(),
105
+ keys: z.partialRecord(z.enum(LOCAL_KEYS), z.string().max(400)).optional(),
106
+ });
107
+ /** Alone the person changes everything; in a team its owner and admins. */
108
+ function mayChangeModels(user) {
109
+ return !managed || user.role === "owner" || user.role === "admin";
110
+ }
111
+ /** A binding the cloud can run: a provider's model or the credits, no client, no local model. */
112
+ function cloudBinding(ref) {
113
+ return (isCreditsRef(ref) || /^(openai|anthropic|google|fal|elevenlabs|replicate|gateway):/.test(ref));
114
+ }
115
+ const TEST_TEXT = {
116
+ de: {
117
+ image: "Ein kleiner, freundlicher Zauberhut aus orangem Filz auf einem Holztisch, weiches Licht, flache Illustration.",
118
+ speech: "Hallo! So klingt die Stimme deiner Wizards.",
119
+ video: "Ein orangefarbener Zauberhut dreht sich langsam auf einem Holztisch, weiches Licht.",
120
+ },
121
+ en: {
122
+ image: "A small, friendly wizard hat of orange felt on a wooden table, soft light, flat illustration.",
123
+ speech: "Hello! This is how your wizards sound.",
124
+ video: "An orange wizard hat slowly turns on a wooden table, soft light.",
125
+ },
126
+ };
127
+ const asDataUrl = (bytes, mime) => `data:${mime};base64,${Buffer.from(bytes).toString("base64")}`;
128
+ /** One second of silence as WAV: what a voice note test listens to where nothing speaks. */
129
+ function silentWav() {
130
+ const rate = 16_000;
131
+ const samples = rate;
132
+ const buf = Buffer.alloc(44 + samples * 2);
133
+ buf.write("RIFF", 0);
134
+ buf.writeUInt32LE(36 + samples * 2, 4);
135
+ buf.write("WAVEfmt ", 8);
136
+ buf.writeUInt32LE(16, 16);
137
+ buf.writeUInt16LE(1, 20);
138
+ buf.writeUInt16LE(1, 22);
139
+ buf.writeUInt32LE(rate, 24);
140
+ buf.writeUInt32LE(rate * 2, 28);
141
+ buf.writeUInt16LE(2, 32);
142
+ buf.writeUInt16LE(16, 34);
143
+ buf.write("data", 36);
144
+ buf.writeUInt32LE(samples * 2, 40);
145
+ return new Uint8Array(buf);
146
+ }
147
+ async function testClass(cls, lang) {
148
+ const started = Date.now();
149
+ const signal = AbortSignal.timeout(cls === "video" ? 300_000 : 120_000);
150
+ const done = (result) => ({
151
+ ok: true,
152
+ ms: Date.now() - started,
153
+ ...result,
154
+ });
155
+ try {
156
+ if (TEXT_CLASSES.includes(cls)) {
157
+ const resolved = await textModel(cls);
158
+ const result = await generateText({
159
+ model: resolved.model,
160
+ prompt: "Reply with the single word: OK",
161
+ maxOutputTokens: 20,
162
+ abortSignal: signal,
163
+ });
164
+ return done({ ref: resolved.ref, reply: result.text.trim().slice(0, 200) });
165
+ }
166
+ if (cls === "image") {
167
+ const image = await generateImageMedia({
168
+ prompt: TEST_TEXT[lang].image,
169
+ aspectRatio: "1:1",
170
+ abortSignal: signal,
171
+ });
172
+ return done({ ref: image.system, media: asDataUrl(image.bytes, image.mime) });
173
+ }
174
+ if (cls === "speech") {
175
+ const voice = await generateSpeechMedia({
176
+ text: TEST_TEXT[lang].speech,
177
+ abortSignal: signal,
178
+ });
179
+ return done({ ref: voice.system, media: asDataUrl(voice.bytes, voice.mime) });
180
+ }
181
+ if (cls === "video") {
182
+ const clip = await generateVideoMedia({
183
+ prompt: TEST_TEXT[lang].video,
184
+ aspectRatio: "16:9",
185
+ duration: 4,
186
+ resolution: "480p",
187
+ abortSignal: signal,
188
+ });
189
+ return done({ ref: clip.system, media: asDataUrl(clip.bytes, clip.mime) });
190
+ }
191
+ // A voice note: the test voice where one speaks here, else a second of silence.
192
+ const voice = (await classProblem("speech"))
193
+ ? null
194
+ : await generateSpeechMedia({ text: TEST_TEXT[lang].speech, abortSignal: signal }).catch(() => null);
195
+ const heard = await transcribeAudio({
196
+ bytes: voice?.bytes ?? silentWav(),
197
+ mediaType: voice?.mime ?? "audio/wav",
198
+ abortSignal: signal,
199
+ });
200
+ return done({ reply: heard.text.slice(0, 300) });
201
+ }
202
+ catch (err) {
203
+ const message = err?.message ?? String(err);
204
+ return { ok: false, ms: Date.now() - started, error: message.slice(0, 500) };
205
+ }
206
+ }
207
+ /** The size of an inline terminal, as the page lays it out. */
208
+ /** Whether the studio assistant's developer mode is offered to this person. */
209
+ async function assistantDevFor(user) {
210
+ return managed ? (await userInfo(user.id)).admin : env.assistantDev;
211
+ }
212
+ const terminalSize = z.object({
213
+ cols: z.number().int().min(10).max(500).optional(),
214
+ rows: z.number().int().min(4).max(200).optional(),
215
+ });
216
+ export const studio = new Hono()
217
+ .get("/me", async (c) => {
218
+ const user = c.get("user");
219
+ // A runtime that runs alone may be linked to an account: its credits and the cloud to publish to.
220
+ const linked = managed ? null : await linkedAccount();
221
+ const overview = linked ? await accountOverview() : null;
222
+ const profile = await userProfile(user);
223
+ // What the Manage-App says of the tenant: its plan and feature switches; and of the person.
224
+ const info = managed ? await tenantInfo(user.tenantId).catch(() => null) : null;
225
+ const person = managed ? await userInfo(user.id) : null;
226
+ return c.json({
227
+ user: { id: user.id, name: profile.name, email: profile.email, image: user.image ?? null },
228
+ /** What the person says about themselves; name and e-mail are the account's when managed. */
229
+ profile,
230
+ /** The language the person picked, wherever they picked it; null until they do. */
231
+ lang: await userLang(user),
232
+ tenant: {
233
+ id: user.tenantId,
234
+ role: user.role,
235
+ /** The plan the team is on; null alone, or where the Manage-App names none. */
236
+ plan: info?.plan ?? null,
237
+ },
238
+ /** The feature switches: the code's defaults, what the Manage-App says of the team and the person, this install's file. */
239
+ features: features(managed ? { ...info?.features, ...person?.features } : overview?.tenant.features),
240
+ /** The studio assistant's developer mode may be switched on: ASSISTANT_DEV here, the platform's admins when managed. */
241
+ assistantDev: await assistantDevFor(user),
242
+ mode: managed ? "managed" : "local",
243
+ /** The tenant's balance; null where the runtime resolves models itself. */
244
+ credits: managed ? await balanceCredits() : null,
245
+ /** Where the account, members, API keys and credits are managed. */
246
+ manageUrl: managed ? env.manage.url : null,
247
+ account: linked
248
+ ? {
249
+ name: linked.name,
250
+ email: linked.email,
251
+ /** The team the account's space belongs to, as the Manage-App names it. */
252
+ team: overview?.tenant.name ?? null,
253
+ credits: overview?.tenant.balanceCredits ?? null,
254
+ /** What of the credits ends on a date, the nearest first. */
255
+ expiring: overview?.tenant.expiring ?? [],
256
+ url: env.local.accountUrl,
257
+ cloudUrl: env.local.cloudUrl,
258
+ signedIn: Boolean(overview),
259
+ }
260
+ : null,
261
+ models: managed ? null : localModelSettings(),
262
+ /** Apple Intelligence on this Mac: offered where the machine can run it (harness/apple.ts). */
263
+ apple: managed ? null : await appleStatus(),
264
+ /** The AI clients this runtime can think with — installed or not, and how each is signed in. */
265
+ harnesses: managed ? [] : await detectHarnesses(),
266
+ /** Installed AI clients whose subscription can answer the studio chat. */
267
+ subscriptions: managed ? [] : await subscriptionClients(),
268
+ /** A runtime that runs alone keeps its setup in front until a test call has worked on the chosen source and the way is still open. */
269
+ setupDone: managed ? true : await setupDone(),
270
+ /** The cloud studio's first visit: the short wizard that names the team and fills the space (services/onboarding.ts). */
271
+ onboarding: await needsOnboarding(user),
272
+ /** What answers the studio chat: a model of class `highest`, or the admin's own subscription. */
273
+ chatEngine: managed ? "models" : await chatEngine(await hasTextModel()),
274
+ aiReady: (await hasTextModel()) || (!managed && (await subscriptionClients()).length > 0),
275
+ mcpUrl: `${env.appUrl}/api/mcp`,
276
+ /**
277
+ * One project: the studio shows no project switcher; null: as many as wanted. `build:
278
+ * false`: nothing is made or changed here; the studio shows what the person's local
279
+ * install synced. `create`: the person makes and deletes here (builds, and is no mere
280
+ * member). `members`: the people the plan allows, null for no limit.
281
+ */
282
+ limits: {
283
+ projects: await projectLimitOrNull(),
284
+ build: await mayBuild(),
285
+ create: await mayCreate(),
286
+ members: info?.limits.members ?? null,
287
+ },
288
+ });
289
+ })
290
+ // The team as the Manage-App keeps it: its people, who is invited, the places the plan gives.
291
+ .get("/team", async (c) => {
292
+ if (!managed) {
293
+ return c.json({
294
+ error: say(c, "onlyAvailableCloud"),
295
+ code: "not_managed",
296
+ }, 404);
297
+ }
298
+ const user = c.get("user");
299
+ const [info, team] = await Promise.all([
300
+ tenantInfo(user.tenantId).catch(() => null),
301
+ tenantMembers(currentTenant()),
302
+ ]);
303
+ return c.json({
304
+ ...team,
305
+ plan: info?.plan ?? null,
306
+ /** Where members are invited and roles changed: the account pages of the Manage-App. */
307
+ manageUrl: env.manage.url,
308
+ /** Whether this person manages the team there. */
309
+ canManage: isAdminRole(user.role),
310
+ });
311
+ })
312
+ // A runtime that runs alone says when a newer release is out and can run the update itself;
313
+ // `?fresh=1` asks GitHub now instead of within the hour.
314
+ .get("/update", async (c) => managed
315
+ ? c.json({
316
+ error: say(c, "onlyAvailableComputer"),
317
+ code: "not_local",
318
+ }, 404)
319
+ : c.json(await updateStatus(c.req.query("fresh") === "1")))
320
+ .post("/update", async (c) => {
321
+ if (managed) {
322
+ return c.json({
323
+ error: say(c, "onlyAvailableComputer"),
324
+ code: "not_local",
325
+ }, 404);
326
+ }
327
+ applyUpdate();
328
+ return c.json(await updateStatus());
329
+ })
330
+ .put("/profile", async (c) => c.json(await saveProfile(c.get("user"), profileSchema.parse(await c.req.json()))))
331
+ .put("/lang", async (c) => {
332
+ const { lang } = z.object({ lang: z.enum(LANGS) }).parse(await c.req.json());
333
+ await saveUserLang(c.get("user"), lang);
334
+ return c.json({ lang });
335
+ })
336
+ // --- the studio's assistant -------------------------------------------------
337
+ // One open chat per person and tenant, on every page; turns stream as SSE events shaped like
338
+ // AG-UI's (packages/shared/src/assistant.ts).
339
+ .get("/assistant/chat", async (c) => c.json(await currentChat(c.get("user"))))
340
+ .post("/assistant/chat/new", async (c) => c.json(await newChat(c.get("user"))))
341
+ .get("/assistant/chats", async (c) => c.json({ chats: await earlierChats(c.get("user")) }))
342
+ .get("/assistant/chats/:id", async (c) => c.json(await readChat(c.get("user"), c.req.param("id"))))
343
+ .get("/assistant/layout", async (c) => c.json({ layout: await assistantLayout(c.get("user")) }))
344
+ .put("/assistant/layout", async (c) => {
345
+ const { layout } = z.object({ layout: z.enum(ASSISTANT_LAYOUTS) }).parse(await c.req.json());
346
+ await writeSetting(`assistant-layout:${c.get("user").tenantId}:${c.get("user").id}`, layout);
347
+ return c.json({ layout });
348
+ })
349
+ // Live chat reads answers aloud with the speech model where one is set up; else the browser's voices.
350
+ .get("/assistant/voice", async (c) => c.json({ way: (await classWays()).speech.kind }))
351
+ .post("/assistant/speak", async (c) => {
352
+ const { text } = z.object({ text: z.string().min(1).max(4000) }).parse(await c.req.json());
353
+ if ((await classWays()).speech.kind === "none") {
354
+ throw new ServiceError("refused", note("noLanguageModelSet"));
355
+ }
356
+ if (!(await canSpend())) {
357
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
358
+ }
359
+ const voice = await generateSpeechMedia({ text, abortSignal: c.req.raw.signal });
360
+ return c.body(new Uint8Array(voice.bytes), 200, { "content-type": voice.mime });
361
+ })
362
+ // A message written while a turn runs: the turn takes it before its next step or after its answer.
363
+ .post("/assistant/run/:runId/signal", async (c) => {
364
+ const body = assistantSignalSchema.parse(await c.req.json());
365
+ const taken = signalRun(c.get("user").id, c.req.param("runId"), body);
366
+ return c.json({ ok: taken }, taken ? 200 : 404);
367
+ })
368
+ // A browser tool's result: the turn that waits for it goes on.
369
+ .post("/assistant/tool/:callId", async (c) => {
370
+ const body = z.object({ result: z.unknown() }).parse(await c.req.json());
371
+ const taken = answerBrowser(c.get("user").id, c.req.param("callId"), body.result ?? null);
372
+ return c.json({ ok: taken }, taken ? 200 : 404);
373
+ })
374
+ .post("/assistant/turn", async (c) => {
375
+ const user = c.get("user");
376
+ const body = assistantTurnSchema
377
+ .extend({ lang: z.enum(LANGS).default("en") })
378
+ .parse(await c.req.json());
379
+ if (!(await canSpend())) {
380
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
381
+ }
382
+ const project = body.projectId ? await ownedProject(user.id, body.projectId) : null;
383
+ const writable = project ? await projectWritable(project) : false;
384
+ // The page's snapshot is cut, not refused: a page that says too much still gets an answer.
385
+ const page = body.page && JSON.stringify(body.page).length > ASSISTANT_PAGE_BYTES
386
+ ? { route: body.page.route, title: body.page.title, view: body.page.view }
387
+ : (body.page ?? null);
388
+ const chat = await openChat(user);
389
+ const debug = Boolean(body.debug) && (await assistantDevFor(user));
390
+ let history = await turnHistory(user, chat);
391
+ const runId = nanoid(10);
392
+ const userMessageId = await addMessage(chat.id, {
393
+ role: "user",
394
+ content: body.message,
395
+ page: page ? { route: page.route, title: page.title } : null,
396
+ });
397
+ startRun(runId, user.id);
398
+ return streamSSE(c, async (stream) => {
399
+ const abort = new AbortController();
400
+ stream.onAbort(() => abort.abort());
401
+ const send = (event, data) => stream.writeSSE({ event, data: JSON.stringify(data) });
402
+ let messageId = nanoid(14);
403
+ await send("run_started", { threadId: chat.id, runId, messageId: userMessageId });
404
+ // The answer's text since the last message written meanwhile.
405
+ let reply = "";
406
+ let changed = false;
407
+ let steps = 0;
408
+ /** What the answer said and did up to messages written meanwhile; null when nothing. */
409
+ const storePart = async (before) => {
410
+ changed ||= before.changed;
411
+ return before.reply || before.toolCalls.length || before.cards.length
412
+ ? await addMessage(chat.id, { role: "assistant", content: before.reply, ...before })
413
+ : null;
414
+ };
415
+ /** The messages written meanwhile, after the part of the answer before them. */
416
+ const storeSignals = async (taken, answerId) => {
417
+ for (const [i, s] of taken.entries()) {
418
+ const stored = await addMessage(chat.id, { role: "user", content: s.message });
419
+ await send("signal", {
420
+ ref: s.ref,
421
+ messageId: stored,
422
+ content: s.message,
423
+ answerId: i === 0 ? answerId : null,
424
+ });
425
+ }
426
+ reply = "";
427
+ messageId = nanoid(14);
428
+ };
429
+ try {
430
+ let message = body.message;
431
+ for (;;) {
432
+ const result = await runStudioAssistant({
433
+ user,
434
+ chat,
435
+ history,
436
+ project,
437
+ writable,
438
+ message,
439
+ page,
440
+ browserTools: body.tools,
441
+ lang: body.lang,
442
+ signal: abort.signal,
443
+ onText: (delta) => {
444
+ reply += delta;
445
+ void send("text", { messageId, delta });
446
+ },
447
+ onActivity: (label) => void send("activity", { label }),
448
+ onChanged: () => void send("changed", {}),
449
+ onCard: (card) => void send("card", card),
450
+ onToolCall: (call) => void send("tool_call", call),
451
+ onToolResult: (result) => void send("tool_result", result),
452
+ callBrowser: (call) => awaitBrowser(user.id, call.toolCallId, abort.signal),
453
+ takeSignals: () => takeSignals(runId),
454
+ onSignal: async (taken, before) => storeSignals(taken, await storePart(before)),
455
+ ...(debug
456
+ ? {
457
+ onPrompt: (prompt) => void send("prompt", prompt),
458
+ onStep: (step) => void send("step", { ...step, step: ++steps }),
459
+ }
460
+ : {}),
461
+ });
462
+ // Written after the answer's last step: the turn goes on with it.
463
+ const left = takeSignals(runId);
464
+ if (!left.length || abort.signal.aborted) {
465
+ changed ||= result.changed;
466
+ reply = result.reply || (changed ? serverText(body.lang, "done") : "");
467
+ const stored = await addMessage(chat.id, {
468
+ role: "assistant",
469
+ content: reply,
470
+ cards: result.cards,
471
+ toolCalls: result.toolCalls,
472
+ changed: result.changed,
473
+ });
474
+ await send("done", { messageId: stored, reply, changed });
475
+ break;
476
+ }
477
+ // The next answer knows this one: it is stored before the history is read again.
478
+ const answerId = await storePart(result);
479
+ history = await turnHistory(user, chat);
480
+ await storeSignals(left, answerId);
481
+ message = signalText(left);
482
+ }
483
+ }
484
+ catch (err) {
485
+ console.error("[studio-assistant]", err);
486
+ if (reply.trim()) {
487
+ await addMessage(chat.id, { role: "assistant", content: reply.trim() }).catch(() => null);
488
+ }
489
+ await send("error", {
490
+ message: err instanceof ServiceError || err instanceof ModelUnavailableError
491
+ ? errorText(err, body.lang)
492
+ : serverText(body.lang, "somethingWrong"),
493
+ });
494
+ }
495
+ endRun(runId);
496
+ // Older messages fold into the chat's running summary once enough have piled up.
497
+ void foldChat(chat.id).catch((err) => console.error("[studio-assistant] fold:", err));
498
+ });
499
+ })
500
+ // --- projects --------------------------------------------------------------
501
+ .get("/projects", async (c) => {
502
+ const projects = await listProjects(c.get("user").id, requestLang(c));
503
+ const build = await mayBuild();
504
+ return c.json(projects.map((p) => ({
505
+ id: p.id,
506
+ name: p.name,
507
+ brand: p.brand,
508
+ facts: p.facts,
509
+ mcpServers: maskedServers(p),
510
+ wizardCount: p.wizardCount,
511
+ /** `local`: synced from a local install, where it is changed. */
512
+ origin: p.origin,
513
+ syncedAt: p.syncedAt?.toISOString() ?? null,
514
+ readOnly: p.origin === "local" || !build,
515
+ })));
516
+ })
517
+ .post("/projects", async (c) => {
518
+ const body = z.object({ name: z.string().min(1).max(80) }).parse(await c.req.json());
519
+ return c.json(await createProject(c.get("user").id, body.name));
520
+ })
521
+ .patch("/projects/:id", async (c) => {
522
+ const patch = projectPatchSchema.parse(await c.req.json());
523
+ await updateProject(c.get("user").id, c.req.param("id"), patch);
524
+ return c.json({ ok: true });
525
+ })
526
+ // --- what a project holds for all its wizards: logos, assets, documents -------
527
+ .get("/projects/:id/files", async (c) => {
528
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
529
+ return c.json({
530
+ files: (await projectFiles(project.id)).map(fileView),
531
+ /** Whether documents get vectors, or the index works on keywords alone. */
532
+ embeddings: Boolean(await embeddingModel().catch(() => null)),
533
+ });
534
+ })
535
+ .post("/projects/:id/files", async (c) => {
536
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
537
+ const kind = z.enum(PROJECT_FILE_KINDS).parse(c.req.query("kind"));
538
+ const file = (await c.req.formData()).get("file");
539
+ if (!(file instanceof File)) {
540
+ throw new ServiceError("invalid", note("chooseFile"));
541
+ }
542
+ const row = await addProjectFile(project.id, {
543
+ kind,
544
+ name: file.name,
545
+ mime: file.type.split(";")[0].trim().toLowerCase(),
546
+ data: new Uint8Array(await file.arrayBuffer()),
547
+ });
548
+ return c.json(fileView(row));
549
+ })
550
+ .post("/projects/:id/files/url", async (c) => {
551
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
552
+ const input = z
553
+ .object({ url: z.string().min(1).max(500_000), kind: z.enum(PROJECT_FILE_KINDS) })
554
+ .parse(await c.req.json());
555
+ const url = /^(https?:\/\/|data:)/i.test(input.url) ? input.url : `https://${input.url}`;
556
+ try {
557
+ return c.json({ file: fileView(await addProjectFileFromUrl(project.id, { ...input, url })) });
558
+ }
559
+ catch (err) {
560
+ // A web page where a logo or an asset was asked for: its logos or pictures, to pick from.
561
+ if (err instanceof ServiceError && err.data.reason === "page") {
562
+ const page = String(err.data.url ?? url);
563
+ const scan = input.kind === "logo" ? scanLogos : scanImages;
564
+ return c.json({ candidates: await scan(page) });
565
+ }
566
+ throw err;
567
+ }
568
+ })
569
+ .post("/projects/:id/colors/scan", async (c) => {
570
+ await ownedProject(c.get("user").id, c.req.param("id"));
571
+ const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
572
+ const colors = await scanColors(url).catch(() => {
573
+ throw new ServiceError("invalid", note("pageNotReached"));
574
+ });
575
+ return c.json({ colors });
576
+ })
577
+ // A gradient from the admin's words, or theirs changed as they say. Nothing is saved.
578
+ .post("/projects/:id/gradients/describe", async (c) => {
579
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
580
+ const { text, gradient } = z
581
+ .object({
582
+ text: z.string().trim().min(1).max(1000),
583
+ gradient: gradientSchema
584
+ .pick({ name: true, type: true, angle: true, stops: true })
585
+ .optional(),
586
+ })
587
+ .parse(await c.req.json());
588
+ if (!(await canSpend())) {
589
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
590
+ }
591
+ try {
592
+ return c.json({
593
+ gradient: await describeGradient(project.brand.colors ?? [], text, gradient),
594
+ });
595
+ }
596
+ catch (err) {
597
+ if (err instanceof ModelUnavailableError) {
598
+ throw new ServiceError("refused", saidOf(err));
599
+ }
600
+ throw err;
601
+ }
602
+ })
603
+ .post("/projects/:id/fonts/scan", async (c) => {
604
+ await ownedProject(c.get("user").id, c.req.param("id"));
605
+ const { url } = z.object({ url: z.string().min(1).max(2000) }).parse(await c.req.json());
606
+ const fonts = await scanFonts(url).catch(() => {
607
+ throw new ServiceError("invalid", note("pageNotReached"));
608
+ });
609
+ return c.json({ fonts });
610
+ })
611
+ .put("/projects/:id/files/order", async (c) => {
612
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
613
+ const body = z
614
+ .object({ kind: z.enum(PROJECT_FILE_KINDS), ids: z.array(z.string()).max(200) })
615
+ .parse(await c.req.json());
616
+ await orderProjectFiles(project.id, body.kind, body.ids);
617
+ return c.json({ ok: true });
618
+ })
619
+ .patch("/projects/:id/files/:fileId", async (c) => {
620
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
621
+ const patch = z
622
+ .object({ name: z.string().max(120).optional(), description: z.string().max(600).optional() })
623
+ .parse(await c.req.json());
624
+ return c.json(fileView(await updateProjectFile(project.id, c.req.param("fileId"), patch)));
625
+ })
626
+ .post("/projects/:id/files/:fileId/reindex", async (c) => {
627
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
628
+ return c.json(fileView(await reindexProjectFile(project.id, c.req.param("fileId"))));
629
+ })
630
+ .delete("/projects/:id/files/:fileId", async (c) => {
631
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
632
+ await removeProjectFile(project.id, c.req.param("fileId"));
633
+ return c.json({ ok: true });
634
+ })
635
+ .get("/projects/:id/files/:fileId/content", async (c) => {
636
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
637
+ const { row, data } = await projectFileContent(project.id, c.req.param("fileId"));
638
+ const headers = {
639
+ "content-type": row.mime,
640
+ "cache-control": "private, max-age=3600",
641
+ "x-content-type-options": "nosniff",
642
+ // An uploaded SVG or HTML file is shown, never run. A PDF is left to the browser's own
643
+ // viewer, which a sandbox would keep from starting.
644
+ ...(row.mime === "application/pdf" ? {} : { "content-security-policy": "sandbox" }),
645
+ };
646
+ if (/^(audio|video)\//.test(row.mime)) {
647
+ headers["accept-ranges"] = "bytes";
648
+ const range = byteRange(c.req.header("range"), data.byteLength);
649
+ if (range === "unsatisfiable") {
650
+ return c.body(null, 416, { "content-range": `bytes */${data.byteLength}` });
651
+ }
652
+ if (range) {
653
+ headers["content-range"] = `bytes ${range.start}-${range.end}/${data.byteLength}`;
654
+ return c.body(new Uint8Array(data.subarray(range.start, range.end + 1)), 206, headers);
655
+ }
656
+ }
657
+ return c.body(new Uint8Array(data), 200, headers);
658
+ })
659
+ // A document as the models read it: its text as Markdown, or the start of it.
660
+ .get("/projects/:id/files/:fileId/text", async (c) => {
661
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
662
+ const row = await projectFile(project.id, c.req.param("fileId"));
663
+ const limit = z.coerce
664
+ .number()
665
+ .int()
666
+ .min(100)
667
+ .max(400_000)
668
+ .catch(200_000)
669
+ .parse(c.req.query("limit"));
670
+ const text = await documentText(row);
671
+ return c.json({ text: text.slice(0, limit), chars: text.length });
672
+ })
673
+ // What a search of Knowledge finds for a question: the items an agent step would get.
674
+ .post("/projects/:id/search", async (c) => {
675
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
676
+ const body = z
677
+ .object({ query: z.string().min(1).max(400), where: whereSchema.optional() })
678
+ .parse(await c.req.json());
679
+ return c.json(await searchKnowledge(project.id, body.query, { where: body.where }));
680
+ })
681
+ .delete("/projects/:id", async (c) => {
682
+ await deleteProject(c.get("user").id, c.req.param("id"));
683
+ return c.json({ ok: true });
684
+ })
685
+ // --- connectors: services imported from the integrations registry ------------
686
+ .post("/connectors/search", async (c) => {
687
+ const { query } = z.object({ query: z.string().min(1).max(200) }).parse(await c.req.json());
688
+ return c.json(await searchRegistry(query));
689
+ })
690
+ .get("/connectors/registry/:domain", async (c) => c.json(await registryService(c.req.param("domain"))))
691
+ .post("/connectors/preview", async (c) => {
692
+ const body = z
693
+ .object({
694
+ domain: z.string().min(1).max(253).optional(),
695
+ sourceKind: z.enum(["openapi", "mcp"]),
696
+ sourceUrl: z.string().url(),
697
+ })
698
+ .parse(await c.req.json());
699
+ return c.json(await previewSource(body));
700
+ })
701
+ .get("/projects/:id/connectors", async (c) => {
702
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
703
+ return c.json(await listConnectors(project.id));
704
+ })
705
+ .post("/projects/:id/connectors", async (c) => {
706
+ const user = c.get("user");
707
+ const project = await ownedProject(user.id, c.req.param("id"));
708
+ return c.json(await importConnector(user.id, project.id, connectorImportSchema.parse(await c.req.json())));
709
+ })
710
+ .post("/projects/:id/connectors/:connectorId/refresh", async (c) => {
711
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
712
+ return c.json(await refreshConnector(project.id, c.req.param("connectorId"), null));
713
+ })
714
+ .delete("/projects/:id/connectors/:connectorId", async (c) => {
715
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
716
+ await removeConnector(project.id, c.req.param("connectorId"));
717
+ return c.json({ ok: true });
718
+ })
719
+ // The runs of the space's wizards that reached their result: of one wizard, or of all.
720
+ .get("/projects/:id/results", async (c) => {
721
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
722
+ const query = z
723
+ .object({
724
+ wizard: z.string().optional(),
725
+ q: z.string().max(200).optional(),
726
+ mode: z.enum(["live", "test"]).optional(),
727
+ days: z.coerce.number().int().min(1).max(3650).optional(),
728
+ })
729
+ .parse(c.req.query());
730
+ return c.json(await listResults(project.id, {
731
+ wizardId: query.wizard || undefined,
732
+ q: query.q,
733
+ mode: query.mode,
734
+ days: query.days,
735
+ }));
736
+ })
737
+ // --- Knowledge: its items and categories ------------------------------------
738
+ .get("/projects/:id/knowledge", async (c) => c.json(await listKnowledge(c.get("user").id, c.req.param("id"))))
739
+ // What a link in a page names (`pages/bgb/p-281`): the studio opens it.
740
+ .get("/projects/:id/resolve", async (c) => {
741
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
742
+ const found = await resolvePath(project.id, z.string().max(600).parse(c.req.query("path")));
743
+ if (!found) {
744
+ throw notFound();
745
+ }
746
+ return c.json(found);
747
+ })
748
+ .post("/projects/:id/categories", async (c) => {
749
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
750
+ await requireWritable(project);
751
+ const body = z
752
+ .object({
753
+ name: z.string().min(1).max(80),
754
+ type: z.enum(CATEGORY_TYPES),
755
+ unit: z.string().max(20).nullish(),
756
+ multiple: z.boolean().optional(),
757
+ ordered: z.boolean().optional(),
758
+ hint: z.string().max(120).nullish(),
759
+ values: z.array(z.string().max(200)).max(200).optional(),
760
+ tableId: z.string().nullish(),
761
+ columnId: z.string().nullish(),
762
+ })
763
+ .parse(await c.req.json());
764
+ const row = await putCategory(project.id, body);
765
+ return c.json({ id: row.id });
766
+ })
767
+ .patch("/categories/:id", async (c) => {
768
+ const row = await categoryRow(c.req.param("id"));
769
+ await ownedProject(c.get("user").id, row.projectId);
770
+ const body = z
771
+ .object({
772
+ name: z.string().min(1).max(80).optional(),
773
+ unit: z.string().max(20).nullish(),
774
+ multiple: z.boolean().optional(),
775
+ ordered: z.boolean().optional(),
776
+ hint: z.string().max(120).nullish(),
777
+ proposed: z.literal(false).optional(),
778
+ position: z.number().int().min(0).optional(),
779
+ })
780
+ .parse(await c.req.json());
781
+ await updateCategory(row.id, body);
782
+ return c.json({ ok: true });
783
+ })
784
+ .delete("/categories/:id", async (c) => {
785
+ const row = await categoryRow(c.req.param("id"));
786
+ await ownedProject(c.get("user").id, row.projectId);
787
+ await deleteCategory(row.id);
788
+ return c.json({ ok: true });
789
+ })
790
+ .post("/categories/:id/values", async (c) => {
791
+ const row = await categoryRow(c.req.param("id"));
792
+ await ownedProject(c.get("user").id, row.projectId);
793
+ const { value } = z.object({ value: z.string().min(1).max(200) }).parse(await c.req.json());
794
+ return c.json(await addCategoryValue(row.id, value));
795
+ })
796
+ .put("/categories/:id/values/order", async (c) => {
797
+ const row = await categoryRow(c.req.param("id"));
798
+ await ownedProject(c.get("user").id, row.projectId);
799
+ const { ids } = z.object({ ids: z.array(z.string()).max(500) }).parse(await c.req.json());
800
+ await orderCategoryValues(row.id, ids);
801
+ return c.json({ ok: true });
802
+ })
803
+ .get("/values/:id", async (c) => c.json(await valueContents(c.get("user").id, c.req.param("id"))))
804
+ // Everything that has a category: the entries its page lists.
805
+ .get("/categories/:id/items", async (c) => c.json(await categoryContents(c.get("user").id, c.req.param("id"))))
806
+ .patch("/values/:id", async (c) => {
807
+ const { category } = await categoryValue(c.req.param("id"));
808
+ await ownedProject(c.get("user").id, category.projectId);
809
+ const { value } = z.object({ value: z.string().min(1).max(200) }).parse(await c.req.json());
810
+ await renameCategoryValue(c.req.param("id"), value);
811
+ return c.json({ ok: true });
812
+ })
813
+ .post("/values/:id/merge", async (c) => {
814
+ const { category } = await categoryValue(c.req.param("id"));
815
+ await ownedProject(c.get("user").id, category.projectId);
816
+ const { into } = z.object({ into: z.string() }).parse(await c.req.json());
817
+ await mergeCategoryValues(c.req.param("id"), into);
818
+ return c.json({ ok: true });
819
+ })
820
+ .delete("/values/:id", async (c) => {
821
+ const { category } = await categoryValue(c.req.param("id"));
822
+ await ownedProject(c.get("user").id, category.projectId);
823
+ await deleteCategoryValue(c.req.param("id"));
824
+ return c.json({ ok: true });
825
+ })
826
+ // The summary of a value: a model writes it from the value's items.
827
+ .post("/values/:id/summary", async (c) => {
828
+ const { category } = await categoryValue(c.req.param("id"));
829
+ const project = await ownedProject(c.get("user").id, category.projectId);
830
+ await requireWritable(project);
831
+ return c.json({ summary: await writeSummary(c.req.param("id")) });
832
+ })
833
+ // A category a person sets on an item: `p:<page>`, `t:<table>`, `r:<row>`, `f:<file>`.
834
+ .put("/projects/:id/items/:key/categories/:categoryId", async (c) => {
835
+ const project = await ownedProject(c.get("user").id, c.req.param("id"));
836
+ await requireWritable(project);
837
+ const key = c.req.param("key");
838
+ const id = key.slice(2);
839
+ const ref = key[0] === "p"
840
+ ? { pageId: id }
841
+ : key[0] === "t"
842
+ ? { tableId: id }
843
+ : key[0] === "r"
844
+ ? { rowId: id }
845
+ : key[0] === "f"
846
+ ? { fileId: id }
847
+ : null;
848
+ if (!ref || key[1] !== ":") {
849
+ throw new ServiceError("invalid", note("unknownEntry"));
850
+ }
851
+ const { values } = z
852
+ .object({ values: z.array(z.union([z.string().max(300), z.number(), z.boolean()])).max(50) })
853
+ .parse(await c.req.json());
854
+ if (await setItemCategory(project.id, ref, c.req.param("categoryId"), values)) {
855
+ queueIndex(key[0] === "p" ? `P:${id}` : key);
856
+ }
857
+ return c.json({ ok: true });
858
+ })
859
+ // --- the space's tables and pages --------------------------------------------
860
+ .get("/projects/:id/data", async (c) => c.json(await listSpaceData(c.get("user").id, c.req.param("id"))))
861
+ .post("/projects/:id/tables", async (c) => {
862
+ const body = z
863
+ .object({
864
+ title: z.string().max(120),
865
+ wizardId: z.string().nullish(),
866
+ columns: z.unknown().optional(),
867
+ format: z.enum(["faq"]).nullish(),
868
+ })
869
+ .parse(await c.req.json());
870
+ return c.json(await createTable(c.get("user").id, c.req.param("id"), body, requestLang(c)));
871
+ })
872
+ .get("/tables/:id", async (c) => c.json(await getTable(c.get("user").id, c.req.param("id"))))
873
+ .patch("/tables/:id", async (c) => {
874
+ const body = z
875
+ .object({
876
+ title: z.string().max(120).optional(),
877
+ wizardId: z.string().nullish(),
878
+ columns: z.unknown().optional(),
879
+ format: z.enum(["faq"]).nullish(),
880
+ review: z.null().optional(),
881
+ })
882
+ .parse(await c.req.json());
883
+ await updateTable(c.get("user").id, c.req.param("id"), body);
884
+ return c.json({ ok: true });
885
+ })
886
+ .delete("/tables/:id", async (c) => {
887
+ await deleteTable(c.get("user").id, c.req.param("id"));
888
+ return c.json({ ok: true });
889
+ })
890
+ .post("/tables/:id/rows", async (c) => {
891
+ const body = z
892
+ .object({ cells: z.record(z.string(), z.unknown()).optional() })
893
+ .parse(await c.req.json());
894
+ return c.json(await addTableRow(c.get("user").id, c.req.param("id"), body.cells));
895
+ })
896
+ .patch("/tables/:id/rows/:rowId", async (c) => {
897
+ const body = z.object({ cells: z.record(z.string(), z.unknown()) }).parse(await c.req.json());
898
+ return c.json(await updateTableRow(c.get("user").id, c.req.param("id"), c.req.param("rowId"), body.cells));
899
+ })
900
+ .post("/tables/:id/rows/delete", async (c) => {
901
+ const body = z.object({ ids: z.array(z.string()).max(5000) }).parse(await c.req.json());
902
+ await deleteTableRows(c.get("user").id, c.req.param("id"), body.ids);
903
+ return c.json({ ok: true });
904
+ })
905
+ .post("/projects/:id/pages", async (c) => {
906
+ const body = z
907
+ .object({
908
+ title: z.string().max(200),
909
+ wizardId: z.string().nullish(),
910
+ parentId: z.string().nullish(),
911
+ markdown: z.string().optional(),
912
+ })
913
+ .parse(await c.req.json());
914
+ return c.json(await createPage(c.get("user").id, c.req.param("id"), body));
915
+ })
916
+ .get("/pages/:id", async (c) => c.json(await getPage(c.get("user").id, c.req.param("id"))))
917
+ .patch("/pages/:id", async (c) => {
918
+ const body = z
919
+ .object({
920
+ title: z.string().max(200).optional(),
921
+ wizardId: z.string().nullish(),
922
+ parentId: z.string().nullish(),
923
+ markdown: z.string().optional(),
924
+ review: z.null().optional(),
925
+ })
926
+ .parse(await c.req.json());
927
+ await updatePage(c.get("user").id, c.req.param("id"), body);
928
+ return c.json({ ok: true });
929
+ })
930
+ .put("/pages/:id/order", async (c) => {
931
+ const { ids } = z.object({ ids: z.array(z.string()).max(5000) }).parse(await c.req.json());
932
+ await orderSubPages(c.get("user").id, c.req.param("id"), ids);
933
+ return c.json({ ok: true });
934
+ })
935
+ .delete("/pages/:id", async (c) => {
936
+ await deletePage(c.get("user").id, c.req.param("id"));
937
+ return c.json({ ok: true });
938
+ })
939
+ // --- wizards ---------------------------------------------------------------
940
+ .get("/projects/:id/wizards", async (c) => {
941
+ const user = c.get("user");
942
+ await ownedProject(user.id, c.req.param("id"));
943
+ const wizards = await listWizards(user.id, c.req.param("id"));
944
+ // A local install with an account: where each wizard stands in its cloud, for the cards.
945
+ const linked = await cloudLinked();
946
+ return c.json(await Promise.all(wizards.map(async (w) => ({ ...w, cloud: linked ? await cloudState(w.id) : null }))));
947
+ })
948
+ .post("/wizards", async (c) => {
949
+ const body = z
950
+ .object({
951
+ projectId: z.string(),
952
+ starterId: z.string().optional(),
953
+ lang: z.string().optional(),
954
+ })
955
+ .parse(await c.req.json());
956
+ const { id } = await createWizard(c.get("user").id, body);
957
+ return c.json({ id });
958
+ })
959
+ // A wizard as a file, and a new wizard made of such a file.
960
+ .post("/projects/:id/wizards/import", async (c) => {
961
+ const file = (await c.req.formData()).get("file");
962
+ if (!(file instanceof File) || !file.size || file.size > PACKAGE_MAX_BYTES) {
963
+ throw new ServiceError("invalid", note("chooseWizardFileMb", { PACKAGE_EXTENSION }));
964
+ }
965
+ return c.json(await importWizard(c.get("user").id, c.req.param("id"), new Uint8Array(await file.arrayBuffer()), file.name, requestLang(c)));
966
+ })
967
+ .get("/wizards/:id/export", async (c) => {
968
+ const found = await exportWizard(c.get("user").id, c.req.param("id"), {
969
+ published: c.req.query("published") === "1",
970
+ knowledge: c.req.query("knowledge") === "1",
971
+ });
972
+ if (!found) {
973
+ return c.json({
974
+ error: say(c, "wizardNotPublishedYet"),
975
+ }, 404);
976
+ }
977
+ const { name, zip } = found;
978
+ return c.body(new Uint8Array(zip), 200, {
979
+ "content-type": PACKAGE_MIME,
980
+ "content-disposition": `attachment; filename="${name}"`,
981
+ "cache-control": "private, no-store",
982
+ });
983
+ })
984
+ .get("/wizards/:id", async (c) => {
985
+ const user = c.get("user");
986
+ const w = await ownedWizard(user.id, c.req.param("id"));
987
+ const messages = await wizardMessages(w.id);
988
+ const project = await ownedProject(user.id, w.projectId);
989
+ return c.json({
990
+ ...(await wizardState(w)),
991
+ /** Shown and run here, changed elsewhere: a local install's wizard, or nothing is built here. */
992
+ readOnly: !(await projectWritable(project)),
993
+ blank: messages.length === 0 && !w.starter,
994
+ messages: messages.map((m) => ({
995
+ id: m.id,
996
+ role: m.role,
997
+ content: m.content,
998
+ changed: m.changed,
999
+ source: m.source,
1000
+ client: m.client,
1001
+ })),
1002
+ mcpServers: project.mcpServers.map((s) => ({ id: s.id, name: s.name })),
1003
+ });
1004
+ })
1005
+ .put("/wizards/:id/draft", async (c) => {
1006
+ const body = z
1007
+ .object({ definition: z.unknown(), baseRevision: z.number().int().min(0) })
1008
+ .parse(await c.req.json());
1009
+ const { revision, issues } = await writeDraft(c.get("user").id, c.req.param("id"), body);
1010
+ return c.json({ ok: true, revision, issues });
1011
+ })
1012
+ .patch("/wizards/:id", async (c) => {
1013
+ const patch = z
1014
+ .object({
1015
+ shareEnabled: z.boolean().optional(),
1016
+ dailyRunLimit: z.number().int().min(1).max(10_000).optional(),
1017
+ projectId: z.string().optional(),
1018
+ runners: z.object({ default: z.string(), enabled: z.array(z.string()).max(20) }).optional(),
1019
+ translate: z.boolean().optional(),
1020
+ })
1021
+ .parse(await c.req.json());
1022
+ await updateWizardSettings(c.get("user").id, c.req.param("id"), patch);
1023
+ // The copy in the cloud of a linked account is shared the same way.
1024
+ if (patch.shareEnabled !== undefined ||
1025
+ patch.dailyRunLimit !== undefined ||
1026
+ patch.runners !== undefined ||
1027
+ patch.translate !== undefined) {
1028
+ await pushSharing(c.get("user").id, c.req.param("id"));
1029
+ }
1030
+ return c.json({ ok: true });
1031
+ })
1032
+ // The languages the published wizard is kept in, each original text with its translation.
1033
+ .get("/wizards/:id/translations", async (c) => {
1034
+ const w = await ownedWizard(c.get("user").id, c.req.param("id"));
1035
+ const def = await publishedDefinition(w);
1036
+ const kept = await translationsOf(w.id);
1037
+ return c.json({
1038
+ lang: def ? wizardLang(def) : wizardLang(w.draft),
1039
+ texts: def ? wizardTexts(def) : [],
1040
+ translations: kept.map((t) => ({
1041
+ lang: t.lang,
1042
+ texts: t.texts,
1043
+ updatedAt: t.updatedAt.toISOString(),
1044
+ })),
1045
+ });
1046
+ })
1047
+ // The author's own lines for a language: they replace what a model wrote.
1048
+ .put("/wizards/:id/translations/:lang", async (c) => {
1049
+ const w = await writableWizard(c.get("user").id, c.req.param("id"));
1050
+ const def = await publishedDefinition(w);
1051
+ const lang = c.req.param("lang");
1052
+ if (!(def && isLangCode(lang))) {
1053
+ return c.json(notFoundAnswer(c), 404);
1054
+ }
1055
+ const { lines } = z
1056
+ .object({ lines: z.record(z.string(), z.string().max(4000)) })
1057
+ .parse(await c.req.json());
1058
+ await editTranslation(w.id, def, lang, lines);
1059
+ return c.json({ ok: true });
1060
+ })
1061
+ .delete("/wizards/:id/translations/:lang", async (c) => {
1062
+ const w = await writableWizard(c.get("user").id, c.req.param("id"));
1063
+ await deleteTranslation(w.id, c.req.param("lang"));
1064
+ return c.json({ ok: true });
1065
+ })
1066
+ // The wizard's ID for the mobile app, beside its link.
1067
+ .get("/wizards/:id/code", async (c) => {
1068
+ const w = await ownedWizard(c.get("user").id, c.req.param("id"));
1069
+ return c.json({ code: await codeOf(w.shareToken) });
1070
+ })
1071
+ // A new link here, and for the copy in the cloud of a linked account: the old ones stop.
1072
+ .post("/wizards/:id/rotate-link", async (c) => {
1073
+ const rotated = await rotateShareLink(c.get("user").id, c.req.param("id"));
1074
+ const cloud = managed ? null : await rotateInCloud(c.get("user").id, c.req.param("id"));
1075
+ return c.json({ ...rotated, cloud });
1076
+ })
1077
+ .post("/wizards/:id/duplicate", async (c) => c.json(await duplicateWizard(c.get("user").id, c.req.param("id"))))
1078
+ .delete("/wizards/:id", async (c) => {
1079
+ if (!managed) {
1080
+ await removeFromCloud(c.get("user").id, c.req.param("id"));
1081
+ }
1082
+ await deleteWizard(c.get("user").id, c.req.param("id"));
1083
+ return c.json({ ok: true });
1084
+ })
1085
+ // With an account linked the published version also goes to its cloud: `cloud` says how that went.
1086
+ .post("/wizards/:id/publish", async (c) => c.json(await publishAndSync(c.get("user").id, c.req.param("id"))))
1087
+ // What publishing the draft would change: the steps and files, its last test, the cost of a run before and after.
1088
+ .get("/wizards/:id/changes", async (c) => {
1089
+ const w = await ownedWizard(c.get("user").id, c.req.param("id"));
1090
+ const { published, ...found } = await draftChanges(w);
1091
+ const cost = async (def) => estimateRun(w.id, null, def, await stepsWithoutModel(def));
1092
+ const [before, after] = published ? await Promise.all([cost(published), cost(w.draft)]) : [];
1093
+ return c.json({
1094
+ ...found,
1095
+ cost: before?.available && after?.available
1096
+ ? { before: before.credits, after: after.credits }
1097
+ : null,
1098
+ });
1099
+ })
1100
+ // What a run of the draft is expected to cost, per step and in total.
1101
+ .get("/wizards/:id/estimate", async (c) => {
1102
+ const w = await ownedWizard(c.get("user").id, c.req.param("id"));
1103
+ return c.json(await estimateRun(w.id, null, w.draft, await stepsWithoutModel(w.draft)));
1104
+ })
1105
+ // The runners this tenant has: the home's channel filter and the icons on its cards.
1106
+ .get("/runners", async (c) => c.json(await runnersOf()))
1107
+ // The runners this tenant has, how each fits the draft, and how the wizard is offered.
1108
+ .get("/wizards/:id/runners", async (c) => {
1109
+ const w = await ownedWizard(c.get("user").id, c.req.param("id"));
1110
+ return c.json(await wizardRunners(w));
1111
+ })
1112
+ // The models the draft needs that this runtime cannot serve, by class, with the steps that call them.
1113
+ .get("/wizards/:id/models", async (c) => {
1114
+ const w = await ownedWizard(c.get("user").id, c.req.param("id"));
1115
+ return c.json({ missing: await missingModels(w.draft, requestLang(c)) });
1116
+ })
1117
+ // A runtime that runs alone: the wizard's copy in the cloud of the linked account.
1118
+ .get("/wizards/:id/cloud", async (c) => {
1119
+ const w = await ownedWizard(c.get("user").id, c.req.param("id"));
1120
+ const linked = await cloudLinked();
1121
+ return c.json({ linked, ...(linked ? await cloudState(w.id) : { copy: null, error: null }) });
1122
+ })
1123
+ // Sends the published version again: after a try that failed, or a change of the project.
1124
+ .post("/wizards/:id/cloud", async (c) => {
1125
+ if (!(await cloudLinked())) {
1126
+ return c.notFound();
1127
+ }
1128
+ return c.json({
1129
+ linked: true,
1130
+ ...(await syncToCloud(c.get("user").id, c.req.param("id"))),
1131
+ });
1132
+ })
1133
+ // What the cloud would lack for the draft, before it is published.
1134
+ .get("/wizards/:id/cloud/check", async (c) => {
1135
+ if (!(await cloudLinked())) {
1136
+ return c.notFound();
1137
+ }
1138
+ return c.json(await checkForCloud(c.get("user").id, c.req.param("id")));
1139
+ })
1140
+ // The projects of local installs the account's cloud holds.
1141
+ .get("/cloud/spaces", async (c) => {
1142
+ if (!(await cloudLinked())) {
1143
+ return c.notFound();
1144
+ }
1145
+ return c.json({ spaces: await cloudSpaces() });
1146
+ })
1147
+ // Another install's project is in the way: it goes, and what is published here is sent.
1148
+ .post("/cloud/replace", async (c) => {
1149
+ if (!(await cloudLinked())) {
1150
+ return c.notFound();
1151
+ }
1152
+ return c.json(await replaceCloudSpaces(c.get("user").id));
1153
+ })
1154
+ .post("/wizards/:id/chat", async (c) => {
1155
+ const user = c.get("user");
1156
+ const wizardId = c.req.param("id");
1157
+ const { message } = z
1158
+ .object({ message: z.string().min(1).max(8000) })
1159
+ .parse(await c.req.json());
1160
+ await writableWizard(user.id, wizardId);
1161
+ if (!(await canSpend())) {
1162
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
1163
+ }
1164
+ return streamSSE(c, async (stream) => {
1165
+ const abort = new AbortController();
1166
+ stream.onAbort(() => abort.abort());
1167
+ try {
1168
+ const result = await architectTurn(user.id, wizardId, message, requestLang(c), {
1169
+ signal: abort.signal,
1170
+ onText: (delta) => {
1171
+ void stream.writeSSE({ event: "text", data: JSON.stringify(delta) });
1172
+ },
1173
+ onActivity: (label) => {
1174
+ void stream.writeSSE({ event: "activity", data: JSON.stringify(label) });
1175
+ },
1176
+ onThought: (line) => {
1177
+ void stream.writeSSE({ event: "thought", data: JSON.stringify(line) });
1178
+ },
1179
+ onBuilding: () => {
1180
+ void stream.writeSSE({ event: "building", data: "1" });
1181
+ },
1182
+ });
1183
+ await stream.writeSSE({ event: "done", data: JSON.stringify(result) });
1184
+ }
1185
+ catch (err) {
1186
+ console.error("[architect]", err);
1187
+ await stream.writeSSE({
1188
+ event: "error",
1189
+ data: JSON.stringify({
1190
+ message: err instanceof ServiceError
1191
+ ? errorText(err, requestLang(c))
1192
+ : say(c, "somethingWentWrongTry"),
1193
+ }),
1194
+ });
1195
+ }
1196
+ });
1197
+ })
1198
+ // What the admin says into the composer, written down — where the browser cannot do it itself.
1199
+ .post("/transcribe", async (c) => {
1200
+ const file = (await c.req.formData()).get("file");
1201
+ if (!(file instanceof File) || !file.size || file.size > 10_000_000) {
1202
+ throw new ServiceError("invalid", note("recordingMbMost"));
1203
+ }
1204
+ if (!(await canSpend())) {
1205
+ throw new ServiceError("no_credits", note("creditsUsedUp"));
1206
+ }
1207
+ try {
1208
+ const { text } = await transcribeAudio({
1209
+ bytes: new Uint8Array(await file.arrayBuffer()),
1210
+ mediaType: file.type || "audio/webm",
1211
+ });
1212
+ return c.json({ text });
1213
+ }
1214
+ catch (err) {
1215
+ if (err instanceof ModelUnavailableError) {
1216
+ throw new ServiceError("refused", saidOf(err));
1217
+ }
1218
+ throw err;
1219
+ }
1220
+ })
1221
+ .post("/wizards/:id/test-runs", async (c) => c.json(await startTestRun(c.get("user").id, c.req.param("id"))))
1222
+ .get("/wizards/:id/runs", async (c) => c.json(await listRuns(c.get("user").id, c.req.param("id"))))
1223
+ // --- workspace -------------------------------------------------------------
1224
+ .get("/wizards/:id/files", async (c) => c.json(await listFiles(c.get("user").id, c.req.param("id"))))
1225
+ .get("/wizards/:id/files/:path{.+}", async (c) => {
1226
+ const { file, data } = await readFile(c.get("user").id, c.req.param("id"), c.req.param("path"));
1227
+ return c.body(new Uint8Array(data), 200, {
1228
+ "content-type": file.mime,
1229
+ "content-disposition": `attachment; filename="${file.path.split("/").pop()}"`,
1230
+ "cache-control": "private, no-cache",
1231
+ });
1232
+ })
1233
+ .put("/wizards/:id/files/:path{.+}", async (c) => {
1234
+ const data = new Uint8Array(await c.req.arrayBuffer());
1235
+ const file = await writeFile(c.get("user").id, c.req.param("id"), c.req.param("path"), data, c.req.header("content-type")?.split(";")[0]);
1236
+ return c.json(file);
1237
+ })
1238
+ .delete("/wizards/:id/files/:path{.+}", async (c) => {
1239
+ await deleteFile(c.get("user").id, c.req.param("id"), c.req.param("path"));
1240
+ return c.json({ ok: true });
1241
+ })
1242
+ .get("/wizards/:id/widgets/:stepId/preview", async (c) => {
1243
+ const html = await draftWidgetPreview(c.get("user").id, c.req.param("id"), c.req.param("stepId"));
1244
+ return c.body(new TextEncoder().encode(html), 200, {
1245
+ "content-type": "text/html; charset=utf-8",
1246
+ "content-security-policy": HTML_RESPONSE_CSP,
1247
+ "cache-control": "no-store",
1248
+ });
1249
+ })
1250
+ // --- a runtime that runs alone: its keys, models and linked account -----------
1251
+ .get("/api-keys", async (c) => (managed ? c.notFound() : c.json(await listLocalKeys())))
1252
+ .post("/api-keys", async (c) => {
1253
+ if (managed) {
1254
+ return c.notFound();
1255
+ }
1256
+ const { name } = z.object({ name: z.string().min(1).max(32) }).parse(await c.req.json());
1257
+ return c.json(await createLocalKey(name));
1258
+ })
1259
+ .delete("/api-keys/:id", async (c) => {
1260
+ if (managed) {
1261
+ return c.notFound();
1262
+ }
1263
+ await deleteLocalKey(c.req.param("id"));
1264
+ return c.json({ ok: true });
1265
+ })
1266
+ // The person's AI apps on this machine (Claude Desktop, Cursor, Codex …) and whether they
1267
+ // have this install's MCP server: the same as `wizards connect`.
1268
+ .get("/local/apps", async (c) => (managed ? c.notFound() : c.json(await appStates(layout()))))
1269
+ .post("/local/apps/:id", async (c) => {
1270
+ if (managed) {
1271
+ return c.notFound();
1272
+ }
1273
+ const { connect } = z.object({ connect: z.boolean() }).parse(await c.req.json());
1274
+ const [done] = await connectApps(layout(), [c.req.param("id")], !connect);
1275
+ if (!done) {
1276
+ return c.json({ error: say(c, "unknownApp"), code: "not_found" }, 404);
1277
+ }
1278
+ // Not done is an answer too: the app's file is no plain JSON, here is what to paste.
1279
+ return c.json(done.outcome);
1280
+ })
1281
+ // What each AI client did with the MCP server since the start: the test on "Integrate".
1282
+ .get("/integrations/seen", (c) => c.json(seenApps(c.get("user").tenantId)))
1283
+ .get("/local/models", (c) => (managed ? c.notFound() : c.json(localModelSettings())))
1284
+ .put("/local/models", async (c) => {
1285
+ if (managed) {
1286
+ return c.notFound();
1287
+ }
1288
+ await saveLocalModels(modelsInput.parse(await c.req.json()));
1289
+ return c.json(localModelSettings());
1290
+ })
1291
+ // --- models: where each class runs, own API keys, the credits behind them -----------------
1292
+ // Alone for this machine; on a Manage-App's runtime for the team, whose admins change it.
1293
+ .get("/models", async (c) => c.json(await modelSettings(requestLang(c))))
1294
+ .put("/models", async (c) => {
1295
+ if (!mayChangeModels(c.get("user"))) {
1296
+ return c.json({
1297
+ error: say(c, "onlyTeamAdminsChange"),
1298
+ code: "forbidden",
1299
+ }, 403);
1300
+ }
1301
+ const input = modelsInput.parse(await c.req.json());
1302
+ // In the cloud nothing runs on a machine's client or local model.
1303
+ if (managed &&
1304
+ (input.source ||
1305
+ input.ollamaUrl ||
1306
+ Object.values(input.bindings ?? {}).some((ref) => ref && !cloudBinding(ref)))) {
1307
+ return c.json({
1308
+ error: say(c, "notAvailableCloud"),
1309
+ code: "not_local",
1310
+ }, 400);
1311
+ }
1312
+ // Own keys and bindings to a provider's model are a feature of the team's plan.
1313
+ // When the Manage-App can't say what the plan holds, own keys wait: nothing is taken unchecked.
1314
+ const own = Object.values(input.keys ?? {}).some((key) => key?.trim()) ||
1315
+ Object.values(input.bindings ?? {}).some((ref) => ref && !isCreditsRef(ref));
1316
+ if (managed && own) {
1317
+ const info = await tenantInfo(c.get("user").tenantId).catch((err) => {
1318
+ console.error("[models] plan of the tenant:", err);
1319
+ return null;
1320
+ });
1321
+ if (!info) {
1322
+ return c.json({
1323
+ error: say(c, "planUnavailable"),
1324
+ code: "manage_unavailable",
1325
+ }, 503);
1326
+ }
1327
+ if (!feature("ownKeys", info.features)) {
1328
+ return c.json({
1329
+ error: info.plan?.name
1330
+ ? say(c, "ownKeysNotInPlan", { plan: info.plan.name })
1331
+ : say(c, "ownKeysNotInTeamPlan"),
1332
+ code: "plan",
1333
+ }, 403);
1334
+ }
1335
+ }
1336
+ await saveModels(input);
1337
+ return c.json(await modelSettings(requestLang(c)));
1338
+ })
1339
+ // Whether a provider takes a key — the one typed, or the one stored. Nothing is generated.
1340
+ .post("/models/keys/:provider/check", async (c) => {
1341
+ const provider = z.enum(LOCAL_KEYS).parse(c.req.param("provider"));
1342
+ const { key } = z
1343
+ .object({ key: z.string().max(400).optional() })
1344
+ .parse(await c.req.json().catch(() => ({})));
1345
+ const value = key?.trim() || (await storedKey(provider));
1346
+ if (!value) {
1347
+ return c.json({
1348
+ ok: false,
1349
+ message: say(c, "noApiKeySet"),
1350
+ });
1351
+ }
1352
+ return c.json(await checkKey(provider, value, requestLang(c)));
1353
+ })
1354
+ // One call on a class, as a run makes it: text answers a word, an image, a voice or a short
1355
+ // clip comes back to look at, a voice note comes back as text. Paid like any call.
1356
+ .post("/models/test", async (c) => {
1357
+ if (!mayChangeModels(c.get("user"))) {
1358
+ return c.json({
1359
+ error: say(c, "onlyTeamAdminsTest"),
1360
+ code: "forbidden",
1361
+ }, 403);
1362
+ }
1363
+ const { cls, lang } = z
1364
+ .object({ cls: z.enum(MODEL_CLASSES), lang: z.enum(LANGS).default("en") })
1365
+ .parse(await c.req.json());
1366
+ return c.json(await testClass(cls, lang));
1367
+ })
1368
+ // The setup's test of one key, or of Ollama: a working one is put to use and ends the setup.
1369
+ .post("/local/keys/:provider", async (c) => {
1370
+ if (managed) {
1371
+ return c.notFound();
1372
+ }
1373
+ const provider = z.enum(LOCAL_KEYS).parse(c.req.param("provider"));
1374
+ const { key } = z
1375
+ .object({ key: z.string().max(400).optional() })
1376
+ .parse(await c.req.json().catch(() => ({})));
1377
+ const result = await setUpKey(provider, key, requestLang(c));
1378
+ if (result.ok && result.text) {
1379
+ await markTested("own");
1380
+ }
1381
+ return c.json(result);
1382
+ })
1383
+ .post("/local/ollama", async (c) => {
1384
+ if (managed) {
1385
+ return c.notFound();
1386
+ }
1387
+ const { url } = z.object({ url: z.string().url().max(200) }).parse(await c.req.json());
1388
+ const result = await setUpOllama(url, requestLang(c));
1389
+ if (result.ok) {
1390
+ await markTested("own");
1391
+ }
1392
+ return c.json(result);
1393
+ })
1394
+ // One short call on a class, as a run would make it: shows that the way to the model is open.
1395
+ .post("/local/models/test", async (c) => {
1396
+ if (managed) {
1397
+ return c.notFound();
1398
+ }
1399
+ const { cls } = z
1400
+ .object({ cls: z.enum(TEXT_CLASSES).default("classifier") })
1401
+ .parse(await c.req.json().catch(() => ({})));
1402
+ const started = Date.now();
1403
+ // The source this call runs on: the person may pick another one while it is under way.
1404
+ const source = localModelSettings().source;
1405
+ try {
1406
+ const resolved = await textModel(cls);
1407
+ const result = await generateText({
1408
+ model: resolved.model,
1409
+ prompt: "Reply with the single word: OK",
1410
+ maxOutputTokens: 20,
1411
+ abortSignal: AbortSignal.timeout(120_000),
1412
+ });
1413
+ // A working call is what ends the setup — for the source it ran on.
1414
+ await markTested(source);
1415
+ return c.json({
1416
+ ok: true,
1417
+ ref: resolved.ref,
1418
+ reply: result.text.trim().slice(0, 200),
1419
+ ms: Date.now() - started,
1420
+ });
1421
+ }
1422
+ catch (err) {
1423
+ const message = err?.message ?? String(err);
1424
+ return c.json({ ok: false, error: message.slice(0, 500), ms: Date.now() - started });
1425
+ }
1426
+ })
1427
+ // --- the AI clients on this machine: looked at afresh, signed in through the inline terminal ----
1428
+ .post("/local/harness/:id/detect", async (c) => {
1429
+ const id = c.req.param("id");
1430
+ if (managed || !harness(id)) {
1431
+ return c.notFound();
1432
+ }
1433
+ return c.json({ harness: await detectHarness(id, true) });
1434
+ })
1435
+ .post("/local/harness/:id/install", async (c) => {
1436
+ const spec = managed ? null : await installSpec(c.req.param("id"));
1437
+ if (!spec) {
1438
+ return c.notFound();
1439
+ }
1440
+ const { cols, rows } = terminalSize.parse(await c.req.json().catch(() => ({})));
1441
+ const cwd = join(env.dataDir, "harness");
1442
+ mkdirSync(cwd, { recursive: true });
1443
+ // The page asks again once the command is over: then the client is there, or its output says why not.
1444
+ const terminal = await openTerminal({ ...spec, cwd, cols, rows });
1445
+ return c.json({ terminal });
1446
+ })
1447
+ .post("/local/harness/:id/login", async (c) => {
1448
+ const id = c.req.param("id");
1449
+ const client = harness(id);
1450
+ if (managed || !client) {
1451
+ return c.notFound();
1452
+ }
1453
+ const { cols, rows } = terminalSize.parse(await c.req.json().catch(() => ({})));
1454
+ const cwd = join(env.dataDir, "harness");
1455
+ mkdirSync(cwd, { recursive: true });
1456
+ const terminal = await openTerminal({
1457
+ bin: client.bin,
1458
+ args: client.login.args,
1459
+ cwd,
1460
+ env: { ...(await loginEnv(client)), ...client.login.env },
1461
+ cols,
1462
+ rows,
1463
+ // Signed in: the client says so on its own, and the settings learn it right away.
1464
+ until: async () => (await freshAuth(client)) !== "none",
1465
+ onDone: () => void detectHarness(id, true),
1466
+ });
1467
+ return c.json({ terminal });
1468
+ })
1469
+ .get("/local/terminal/:id/stream", (c) => {
1470
+ if (managed) {
1471
+ return c.notFound();
1472
+ }
1473
+ const id = c.req.param("id");
1474
+ return streamSSE(c, async (stream) => {
1475
+ let ended = false;
1476
+ // One write after the other, and all of them out before the stream ends: the last one is
1477
+ // the exit, and a page that comes after the end gets everything at once.
1478
+ let written = Promise.resolve();
1479
+ const done = new Promise((resolve) => {
1480
+ const unsubscribe = subscribeTerminal(id, (event) => {
1481
+ written = written.then(() => stream.writeSSE({ event: event.type, data: JSON.stringify(event) }));
1482
+ if (event.type === "exit") {
1483
+ ended = true;
1484
+ resolve();
1485
+ }
1486
+ });
1487
+ stream.onAbort(() => {
1488
+ unsubscribe();
1489
+ resolve();
1490
+ });
1491
+ });
1492
+ // The page keeps the stream open while the terminal lives; a heartbeat keeps proxies from closing it.
1493
+ const beat = setInterval(() => {
1494
+ if (!ended) {
1495
+ void stream.writeSSE({ event: "ping", data: "" });
1496
+ }
1497
+ }, 15_000);
1498
+ await done;
1499
+ clearInterval(beat);
1500
+ await written;
1501
+ });
1502
+ })
1503
+ .post("/local/terminal/:id/input", async (c) => {
1504
+ if (managed) {
1505
+ return c.notFound();
1506
+ }
1507
+ const { data } = z.object({ data: z.string().max(10_000) }).parse(await c.req.json());
1508
+ writeTerminal(c.req.param("id"), data);
1509
+ return c.json({ ok: true });
1510
+ })
1511
+ .post("/local/terminal/:id/resize", async (c) => {
1512
+ if (managed) {
1513
+ return c.notFound();
1514
+ }
1515
+ const { cols, rows } = z
1516
+ .object({ cols: z.number().int().min(10).max(500), rows: z.number().int().min(4).max(200) })
1517
+ .parse(await c.req.json());
1518
+ resizeTerminal(c.req.param("id"), cols, rows);
1519
+ return c.json({ ok: true });
1520
+ })
1521
+ .delete("/local/terminal/:id", (c) => {
1522
+ if (managed) {
1523
+ return c.notFound();
1524
+ }
1525
+ closeTerminal(c.req.param("id"));
1526
+ return c.json({ ok: true });
1527
+ })
1528
+ .put("/local/chat", async (c) => {
1529
+ if (managed) {
1530
+ return c.notFound();
1531
+ }
1532
+ const { engine } = z.object({ engine: z.enum(["models", "claude"]) }).parse(await c.req.json());
1533
+ await setChatEngine(engine);
1534
+ return c.json({ ok: true });
1535
+ })
1536
+ // The address the system browser opens: the account's sign-in, or — for someone without an
1537
+ // account — its sign-up page with the invitation code, which then goes on to the sign-in.
1538
+ // --- the first visit of a cloud studio: who the team is, its website, first documents -----
1539
+ .put("/team", async (c) => {
1540
+ if (!managed) {
1541
+ return c.json({
1542
+ error: say(c, "onlyAvailableCloud"),
1543
+ code: "not_managed",
1544
+ }, 404);
1545
+ }
1546
+ await requireAdmin();
1547
+ const { name } = z.object({ name: z.string().trim().min(1).max(80) }).parse(await c.req.json());
1548
+ const info = await renameTenant(c.get("user").tenantId, name);
1549
+ return c.json({ name: info.name });
1550
+ })
1551
+ // What the team uses engenty for, and the questions that fit: kept at the Manage-App.
1552
+ .put("/team/profile", async (c) => {
1553
+ if (!managed) {
1554
+ return c.json({
1555
+ error: say(c, "onlyAvailableCloud"),
1556
+ code: "not_managed",
1557
+ }, 404);
1558
+ }
1559
+ await requireAdmin();
1560
+ const field = (max) => z.string().trim().max(max).nullable().optional();
1561
+ const profile = z
1562
+ .object({
1563
+ kind: z.enum(["personal", "club", "small", "corp"]),
1564
+ size: field(40),
1565
+ sector: field(120),
1566
+ role: field(120),
1567
+ need: field(2000),
1568
+ legalName: field(200),
1569
+ phone: field(40),
1570
+ website: field(400),
1571
+ })
1572
+ .parse(await c.req.json());
1573
+ const user = c.get("user");
1574
+ await saveTeamProfile(user.tenantId, user.id, requestLang(c) === "de" ? "de" : "en", profile);
1575
+ return c.json({ ok: true });
1576
+ })
1577
+ .post("/onboarding/done", async (c) => {
1578
+ if (!managed) {
1579
+ return c.json({
1580
+ error: say(c, "onlyAvailableCloud"),
1581
+ code: "not_managed",
1582
+ }, 404);
1583
+ }
1584
+ await finishOnboarding(c.get("user").tenantId);
1585
+ return c.json({ ok: true });
1586
+ })
1587
+ .post("/projects/:id/website", async (c) => {
1588
+ const user = c.get("user");
1589
+ const project = await ownedProject(user.id, c.req.param("id"));
1590
+ const { url } = z.object({ url: z.string().trim().min(3).max(400) }).parse(await c.req.json());
1591
+ return c.json(await applyWebsite(user.id, project.id, url));
1592
+ })
1593
+ // The install command and the first steps, mailed to the signed-in person by the Manage-App.
1594
+ .post("/install-mail", async (c) => {
1595
+ if (!managed) {
1596
+ return c.json({
1597
+ error: say(c, "onlyAvailableCloud"),
1598
+ code: "not_managed",
1599
+ }, 404);
1600
+ }
1601
+ const { lang } = z
1602
+ .object({ lang: z.enum(LANGS).default("en") })
1603
+ .parse(await c.req.json().catch(() => ({})));
1604
+ await installMail(c.get("user").email, lang);
1605
+ return c.json({ ok: true });
1606
+ })
1607
+ .post("/account/link", async (c) => {
1608
+ if (managed) {
1609
+ return c.notFound();
1610
+ }
1611
+ const input = z
1612
+ .object({ signup: z.boolean().optional(), code: z.string().max(64).optional() })
1613
+ .parse(await c.req.json().catch(() => ({})));
1614
+ return c.json({ url: await startLink(input) });
1615
+ })
1616
+ .delete("/account", async (c) => {
1617
+ if (managed) {
1618
+ return c.notFound();
1619
+ }
1620
+ await unlink();
1621
+ await forgetCloud();
1622
+ // Pro modules went with the account's plan; installed ones stay on disk.
1623
+ await refreshPro();
1624
+ // Models that ran on the account's credits have nothing to run on now; another source stays.
1625
+ if (localModelSettings().source === "account") {
1626
+ await saveLocalModels({ source: "own" });
1627
+ }
1628
+ return c.json({ ok: true });
1629
+ });