wizards 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +170 -0
  3. package/apps/runtime/dist/agents/architect.js +453 -0
  4. package/apps/runtime/dist/agents/project-assistant.js +354 -0
  5. package/apps/runtime/dist/agents/subscription.js +206 -0
  6. package/apps/runtime/dist/agents/thought.js +21 -0
  7. package/apps/runtime/dist/agents/website.js +197 -0
  8. package/apps/runtime/dist/app.js +153 -0
  9. package/apps/runtime/dist/auth/account.js +121 -0
  10. package/apps/runtime/dist/auth/index.js +329 -0
  11. package/apps/runtime/dist/auth/keys.js +55 -0
  12. package/apps/runtime/dist/auth/local-ticket.js +23 -0
  13. package/apps/runtime/dist/authoring/example.js +73 -0
  14. package/apps/runtime/dist/authoring/guide.js +259 -0
  15. package/apps/runtime/dist/authoring/ops.js +117 -0
  16. package/apps/runtime/dist/cli/autostart.js +134 -0
  17. package/apps/runtime/dist/cli/clients.js +44 -0
  18. package/apps/runtime/dist/cli/environment.js +81 -0
  19. package/apps/runtime/dist/cli/exec.js +45 -0
  20. package/apps/runtime/dist/cli/home.js +74 -0
  21. package/apps/runtime/dist/cli/machine.js +145 -0
  22. package/apps/runtime/dist/cli/main.js +125 -0
  23. package/apps/runtime/dist/cli/setup.js +273 -0
  24. package/apps/runtime/dist/cli/start.js +190 -0
  25. package/apps/runtime/dist/cli/status.js +120 -0
  26. package/apps/runtime/dist/cli/ui.js +12 -0
  27. package/apps/runtime/dist/cli/update.js +58 -0
  28. package/apps/runtime/dist/connectors/builtin.js +70 -0
  29. package/apps/runtime/dist/connectors/external.js +400 -0
  30. package/apps/runtime/dist/connectors/gmail.js +107 -0
  31. package/apps/runtime/dist/connectors/imap.js +217 -0
  32. package/apps/runtime/dist/connectors/index.js +266 -0
  33. package/apps/runtime/dist/connectors/mail.js +72 -0
  34. package/apps/runtime/dist/connectors/outlook.js +90 -0
  35. package/apps/runtime/dist/credits/credits.js +103 -0
  36. package/apps/runtime/dist/credits/estimate.js +132 -0
  37. package/apps/runtime/dist/db/adopt.js +149 -0
  38. package/apps/runtime/dist/db/client.js +146 -0
  39. package/apps/runtime/dist/db/control-schema.js +70 -0
  40. package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
  41. package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
  42. package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
  43. package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
  44. package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
  45. package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
  46. package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
  47. package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
  48. package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
  49. package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
  50. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
  51. package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
  52. package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
  53. package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
  54. package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
  55. package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
  56. package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
  57. package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
  58. package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
  59. package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
  60. package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
  61. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
  62. package/apps/runtime/dist/db/schema.js +278 -0
  63. package/apps/runtime/dist/documents/parse.js +230 -0
  64. package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
  65. package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
  66. package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
  67. package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
  68. package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
  69. package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
  70. package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
  71. package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
  72. package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
  73. package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
  74. package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
  75. package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
  76. package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
  77. package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
  78. package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
  79. package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
  80. package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
  81. package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
  82. package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
  83. package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
  84. package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
  85. package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
  86. package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
  87. package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
  88. package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
  89. package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
  90. package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
  91. package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
  92. package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
  93. package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
  94. package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
  95. package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
  96. package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
  97. package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
  98. package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
  99. package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
  100. package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
  101. package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
  102. package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
  103. package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
  104. package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
  105. package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
  106. package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
  107. package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
  108. package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
  109. package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
  110. package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
  111. package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
  112. package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
  113. package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
  114. package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
  115. package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
  116. package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
  117. package/apps/runtime/dist/engine/asks.js +60 -0
  118. package/apps/runtime/dist/engine/events.js +46 -0
  119. package/apps/runtime/dist/engine/input.js +170 -0
  120. package/apps/runtime/dist/engine/prepare.js +51 -0
  121. package/apps/runtime/dist/engine/requirements.js +124 -0
  122. package/apps/runtime/dist/engine/resources.js +89 -0
  123. package/apps/runtime/dist/engine/runner.js +560 -0
  124. package/apps/runtime/dist/engine/steps.js +598 -0
  125. package/apps/runtime/dist/engine/template.js +183 -0
  126. package/apps/runtime/dist/engine/types.js +3 -0
  127. package/apps/runtime/dist/env.js +224 -0
  128. package/apps/runtime/dist/files/blobs.js +22 -0
  129. package/apps/runtime/dist/files/objects.js +86 -0
  130. package/apps/runtime/dist/files/storage.js +95 -0
  131. package/apps/runtime/dist/geocode.js +58 -0
  132. package/apps/runtime/dist/harness/claude.js +138 -0
  133. package/apps/runtime/dist/harness/codex.js +228 -0
  134. package/apps/runtime/dist/harness/cursor.js +93 -0
  135. package/apps/runtime/dist/harness/env.js +124 -0
  136. package/apps/runtime/dist/harness/gemini.js +126 -0
  137. package/apps/runtime/dist/harness/index.js +99 -0
  138. package/apps/runtime/dist/harness/model.js +267 -0
  139. package/apps/runtime/dist/harness/prompt.js +121 -0
  140. package/apps/runtime/dist/harness/terminal.js +193 -0
  141. package/apps/runtime/dist/harness/types.js +7 -0
  142. package/apps/runtime/dist/index.js +76 -0
  143. package/apps/runtime/dist/limits.js +57 -0
  144. package/apps/runtime/dist/link-preview.js +59 -0
  145. package/apps/runtime/dist/manage.js +151 -0
  146. package/apps/runtime/dist/mcp/auth.js +102 -0
  147. package/apps/runtime/dist/mcp/bridge.js +59 -0
  148. package/apps/runtime/dist/mcp/handler.js +42 -0
  149. package/apps/runtime/dist/mcp/scopes.js +5 -0
  150. package/apps/runtime/dist/mcp/tools.js +401 -0
  151. package/apps/runtime/dist/media/ffmpeg.js +121 -0
  152. package/apps/runtime/dist/media/generate.js +120 -0
  153. package/apps/runtime/dist/media/marking.js +121 -0
  154. package/apps/runtime/dist/media/transcribe.js +30 -0
  155. package/apps/runtime/dist/model-errors.js +3 -0
  156. package/apps/runtime/dist/models.js +489 -0
  157. package/apps/runtime/dist/plugin.js +61 -0
  158. package/apps/runtime/dist/render/chromium.js +85 -0
  159. package/apps/runtime/dist/render/convert.js +161 -0
  160. package/apps/runtime/dist/render/downloads.js +277 -0
  161. package/apps/runtime/dist/render/guard.js +25 -0
  162. package/apps/runtime/dist/routes/api.js +74 -0
  163. package/apps/runtime/dist/routes/delivery.js +52 -0
  164. package/apps/runtime/dist/routes/internal.js +49 -0
  165. package/apps/runtime/dist/routes/marketplace.js +36 -0
  166. package/apps/runtime/dist/routes/runs.js +805 -0
  167. package/apps/runtime/dist/routes/studio.js +702 -0
  168. package/apps/runtime/dist/routes/wizard-stream.js +30 -0
  169. package/apps/runtime/dist/running.js +39 -0
  170. package/apps/runtime/dist/sandbox/agentos.js +191 -0
  171. package/apps/runtime/dist/sandbox/docker.js +77 -0
  172. package/apps/runtime/dist/sandbox/index.js +30 -0
  173. package/apps/runtime/dist/sandbox/types.js +1 -0
  174. package/apps/runtime/dist/secrets/crypto.js +28 -0
  175. package/apps/runtime/dist/secrets/signing.js +34 -0
  176. package/apps/runtime/dist/secrets/vault.js +59 -0
  177. package/apps/runtime/dist/services/architect.js +92 -0
  178. package/apps/runtime/dist/services/brand.js +20 -0
  179. package/apps/runtime/dist/services/cloud.js +52 -0
  180. package/apps/runtime/dist/services/draft-events.js +27 -0
  181. package/apps/runtime/dist/services/errors.js +24 -0
  182. package/apps/runtime/dist/services/files.js +129 -0
  183. package/apps/runtime/dist/services/links.js +16 -0
  184. package/apps/runtime/dist/services/marketplace.js +292 -0
  185. package/apps/runtime/dist/services/package.js +202 -0
  186. package/apps/runtime/dist/services/profile.js +23 -0
  187. package/apps/runtime/dist/services/project-files.js +266 -0
  188. package/apps/runtime/dist/services/project-index.js +109 -0
  189. package/apps/runtime/dist/services/projects.js +181 -0
  190. package/apps/runtime/dist/services/runs.js +224 -0
  191. package/apps/runtime/dist/services/shares.js +129 -0
  192. package/apps/runtime/dist/services/widgets.js +34 -0
  193. package/apps/runtime/dist/services/wizards.js +342 -0
  194. package/apps/runtime/dist/settings.js +16 -0
  195. package/apps/runtime/dist/store/index.js +279 -0
  196. package/apps/runtime/dist/tenants/control.js +106 -0
  197. package/apps/runtime/dist/tenants/tenant.js +26 -0
  198. package/apps/runtime/dist/tools/browser.js +380 -0
  199. package/apps/runtime/dist/tools/connector.js +204 -0
  200. package/apps/runtime/dist/tools/index.js +234 -0
  201. package/apps/runtime/dist/tools/mail.js +104 -0
  202. package/apps/runtime/dist/tools/net-guard.js +59 -0
  203. package/apps/runtime/dist/tools/project.js +53 -0
  204. package/apps/runtime/dist/tools/shared.js +42 -0
  205. package/apps/runtime/dist/tools/store.js +238 -0
  206. package/apps/runtime/dist/update.js +118 -0
  207. package/apps/runtime/dist/widgets/bundle.js +99 -0
  208. package/apps/runtime/dist/widgets/render.js +312 -0
  209. package/apps/runtime/dist/widgets/runtime.js +84 -0
  210. package/apps/runtime/dist/widgets/script.js +30 -0
  211. package/apps/runtime/dist/widgets/step.js +192 -0
  212. package/apps/web/dist/ai-labels/ai-black.svg +20 -0
  213. package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
  214. package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
  215. package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
  216. package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
  217. package/apps/web/dist/ai-labels/ai-white.svg +21 -0
  218. package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
  219. package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
  220. package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
  221. package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
  222. package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
  223. package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
  224. package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
  225. package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
  226. package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
  227. package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
  228. package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
  229. package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
  230. package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
  231. package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
  232. package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
  233. package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
  234. package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
  235. package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
  236. package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
  237. package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
  238. package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
  239. package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
  240. package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
  241. package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
  242. package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
  243. package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
  244. package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
  245. package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
  246. package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
  247. package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
  248. package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
  249. package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
  250. package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
  251. package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
  252. package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
  253. package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
  254. package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
  255. package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
  256. package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
  257. package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
  258. package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
  259. package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
  260. package/apps/web/dist/embed.js +102 -0
  261. package/apps/web/dist/favicon.svg +1 -0
  262. package/apps/web/dist/icons/apple-touch-icon.png +0 -0
  263. package/apps/web/dist/icons/icon-192.png +0 -0
  264. package/apps/web/dist/icons/icon-512.png +0 -0
  265. package/apps/web/dist/icons/icon-maskable-512.png +0 -0
  266. package/apps/web/dist/index.html +46 -0
  267. package/apps/web/dist/install.sh +532 -0
  268. package/apps/web/dist/manifest.webmanifest +20 -0
  269. package/apps/web/dist/sw.js +15 -0
  270. package/bin/wizards.mjs +13 -0
  271. package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
  272. package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
  273. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
  274. package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
  275. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
  276. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
  277. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
  278. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
  279. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
  280. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
  281. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
  282. package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
  283. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
  284. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
  285. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
  286. package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
  287. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
  288. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
  289. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
  290. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
  291. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
  292. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
  293. package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
  294. package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
  295. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
  296. package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
  297. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
  298. package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
  299. package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
  300. package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
  301. package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
  302. package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
  303. package/node_modules/@engenty-wizards/shared/package.json +10 -0
  304. package/package.json +62 -0
  305. package/plugin/.claude-plugin/plugin.json +9 -0
  306. package/plugin/.mcp.json +8 -0
  307. package/plugin/commands/wizard.md +8 -0
  308. package/plugin/skills/build-wizard/SKILL.md +27 -0
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import { documentTypeDescription, documentTypeSchema, lineItemSchema, taxTypeSchema, } from "./shared.js";
3
+ /**
4
+ * Receipt extraction schema.
5
+ * Uses Zod `.describe()` annotations as LLM instructions (midday pattern).
6
+ * Designed for multi-modal OCR extraction via Mistral/Gemini.
7
+ */
8
+ export const receiptSchema = z.object({
9
+ document_type: documentTypeSchema.describe(documentTypeDescription),
10
+ date: z
11
+ .string()
12
+ .nullable()
13
+ .describe("Date of receipt in ISO 8601 format (YYYY-MM-DD)"),
14
+ total_amount: z
15
+ .number()
16
+ .nullable()
17
+ .describe("Total amount including tax in EUR. Null if document_type is 'other'."),
18
+ subtotal_amount: z.number().nullable().describe("Subtotal amount before tax"),
19
+ tax_amount: z
20
+ .number()
21
+ .nullable()
22
+ .describe("Tax amount. Null if document_type is 'other'."),
23
+ tax_rate: z
24
+ .number()
25
+ .nullable()
26
+ .optional()
27
+ .describe("Tax rate as a percentage value (e.g., 20 for 20%)"),
28
+ tax_type: taxTypeSchema
29
+ .nullable()
30
+ .describe("The type of tax applied: VAT, Sales Tax, GST, Withholding Tax, Service Tax, Excise Tax, Reverse Charge, or Custom Tax. Infer from country/context if not specified."),
31
+ vendor_name: z
32
+ .string()
33
+ .nullable()
34
+ .describe("Name of the store/merchant/vendor"),
35
+ vendor_address: z
36
+ .string()
37
+ .nullable()
38
+ .describe("Complete address of the vendor"),
39
+ vendor_website: z
40
+ .string()
41
+ .nullable()
42
+ .describe("Vendor website URL — look directly on the receipt, or infer from email/store name."),
43
+ vendor_email: z
44
+ .string()
45
+ .nullable()
46
+ .describe("Email of the vendor/store if present"),
47
+ vendor_vat_id: z
48
+ .string()
49
+ .nullable()
50
+ .describe("VAT ID of the vendor (e.g., ATU12345678, DE123456789). Look for UID, USt-IdNr., or similar."),
51
+ payment_method: z
52
+ .string()
53
+ .nullable()
54
+ .describe("Method of payment (e.g., cash, credit card, debit card)"),
55
+ items: z
56
+ .array(lineItemSchema)
57
+ .describe("Array of items purchased on the receipt"),
58
+ notes: z.string().nullable().describe("Additional notes or comments"),
59
+ language: z
60
+ .string()
61
+ .nullable()
62
+ .describe("The language of the document (e.g., 'english', 'german', 'french')"),
63
+ });
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Tax type enum — covers major international tax systems.
4
+ * Modeled after midday's taxTypeSchema.
5
+ */
6
+ export const taxTypeSchema = z.enum([
7
+ "vat",
8
+ "sales_tax",
9
+ "gst",
10
+ "withholding_tax",
11
+ "service_tax",
12
+ "excise_tax",
13
+ "reverse_charge",
14
+ "custom_tax",
15
+ ]);
16
+ /** Document type classifier */
17
+ export const documentTypeSchema = z.enum(["invoice", "receipt", "other"]);
18
+ const documentTypeDescription = `Classify this document type FIRST before extracting data:
19
+ - 'invoice': A bill requesting payment with amounts due, from vendor to customer
20
+ - 'receipt': Proof of completed purchase showing items and payment made
21
+ - 'other': Any non-financial document (contracts, agreements, newsletters, shipping notifications, confirmations without amounts, terms of service, correspondence)
22
+ If 'other', financial fields (amount, currency, etc.) may be left as null.`;
23
+ /** Shared line item schema */
24
+ export const lineItemSchema = z.object({
25
+ description: z.string().nullable().describe("Description of the item"),
26
+ quantity: z.number().nullable().describe("Quantity of items"),
27
+ unit_price: z.number().nullable().describe("Price per unit"),
28
+ total_price: z.number().nullable().describe("Total price for this line item"),
29
+ tax_rate: z
30
+ .number()
31
+ .nullable()
32
+ .optional()
33
+ .describe("Tax rate percentage for this item (e.g., 20 for 20%)"),
34
+ discount: z
35
+ .number()
36
+ .nullable()
37
+ .optional()
38
+ .describe("Discount amount applied to this item if any"),
39
+ });
40
+ export { documentTypeDescription };
@@ -0,0 +1,164 @@
1
+ // Chunking helpers shared by all providers.
2
+ //
3
+ // `mode: "fixed"` slices on character boundaries (cheap, predictable).
4
+ // `mode: "paragraph"` accumulates `\n\n`-separated paragraphs up to
5
+ // `max_chunk_length` with optional word-overlap between consecutive chunks
6
+ // (matches KB's historical chunkText so we can drop the bespoke copy).
7
+ const CHUNK_ID_SEPARATOR = "::chunk::";
8
+ export function createSearchChunkId(docId, chunkIndex) {
9
+ if (!docId.trim()) {
10
+ throw new Error("docId is required");
11
+ }
12
+ if (!Number.isInteger(chunkIndex) || chunkIndex < 0) {
13
+ throw new Error("chunkIndex must be a non-negative integer");
14
+ }
15
+ return `${docId}${CHUNK_ID_SEPARATOR}${chunkIndex}`;
16
+ }
17
+ export function parseSearchChunkId(chunkId) {
18
+ const separatorIndex = chunkId.lastIndexOf(CHUNK_ID_SEPARATOR);
19
+ if (separatorIndex <= 0) {
20
+ return null;
21
+ }
22
+ const docId = chunkId.slice(0, separatorIndex);
23
+ const rawIndex = chunkId.slice(separatorIndex + CHUNK_ID_SEPARATOR.length);
24
+ const chunkIndex = Number.parseInt(rawIndex, 10);
25
+ if (!Number.isInteger(chunkIndex) || chunkIndex < 0) {
26
+ return null;
27
+ }
28
+ return {
29
+ chunk_index: chunkIndex,
30
+ doc_id: docId,
31
+ };
32
+ }
33
+ export function compactSearchText(parts, separator = "\n\n") {
34
+ return parts
35
+ .map((part) => part?.trim() ?? "")
36
+ .filter(Boolean)
37
+ .join(separator)
38
+ .trim();
39
+ }
40
+ export function createSearchChunks(params) {
41
+ const mode = params.mode ?? "fixed";
42
+ if (mode === "paragraph") {
43
+ return createParagraphChunks(params);
44
+ }
45
+ return createFixedChunks(params);
46
+ }
47
+ function createFixedChunks(params) {
48
+ const maxChunkLength = Math.max(params.max_chunk_length ?? 4000, 1);
49
+ const chunks = [];
50
+ let index = 0;
51
+ for (let start = 0; start < params.text.length; start += maxChunkLength) {
52
+ const end = Math.min(start + maxChunkLength, params.text.length);
53
+ const slice = params.text.slice(start, end);
54
+ const trimmed = slice.trim();
55
+ if (!trimmed) {
56
+ continue;
57
+ }
58
+ const sourceOffset = { end, start };
59
+ chunks.push(buildChunk(params, trimmed, sourceOffset, index));
60
+ index += 1;
61
+ }
62
+ return chunks;
63
+ }
64
+ function createParagraphChunks(params) {
65
+ const maxChunkLength = Math.max(params.max_chunk_length ?? 1000, 1);
66
+ const overlap = Math.max(params.overlap ?? 0, 0);
67
+ if (!params.text.trim()) {
68
+ return [];
69
+ }
70
+ const paragraphs = splitParagraphsWithOffsets(params.text);
71
+ const chunks = [];
72
+ let buffer = "";
73
+ let bufferStart = null;
74
+ let bufferEnd = 0;
75
+ let index = 0;
76
+ const flush = () => {
77
+ if (!buffer.trim() || bufferStart === null) {
78
+ return;
79
+ }
80
+ const sourceOffset = { end: bufferEnd, start: bufferStart };
81
+ chunks.push(buildChunk(params, buffer.trim(), sourceOffset, index));
82
+ index += 1;
83
+ };
84
+ for (const para of paragraphs) {
85
+ const tentativeLength = buffer.length + para.text.length + (buffer.length > 0 ? 2 : 0);
86
+ if (tentativeLength > maxChunkLength && buffer.length > 0) {
87
+ flush();
88
+ const overlapText = overlap > 0 ? takeWordsFromEnd(buffer, overlap) : "";
89
+ buffer = overlapText ? `${overlapText}\n\n${para.text}` : para.text;
90
+ bufferStart = para.start;
91
+ bufferEnd = para.end;
92
+ }
93
+ else {
94
+ if (buffer.length === 0) {
95
+ bufferStart = para.start;
96
+ }
97
+ buffer += (buffer ? "\n\n" : "") + para.text;
98
+ bufferEnd = para.end;
99
+ }
100
+ }
101
+ flush();
102
+ return chunks;
103
+ }
104
+ function splitParagraphsWithOffsets(text) {
105
+ const spans = [];
106
+ const re = /\n{2,}/g;
107
+ let lastIndex = 0;
108
+ for (let m = re.exec(text); m !== null; m = re.exec(text)) {
109
+ pushSpan(spans, text, lastIndex, m.index);
110
+ lastIndex = m.index + m[0].length;
111
+ }
112
+ pushSpan(spans, text, lastIndex, text.length);
113
+ return spans;
114
+ }
115
+ function pushSpan(spans, text, rawStart, rawEnd) {
116
+ if (rawEnd <= rawStart) {
117
+ return;
118
+ }
119
+ const slice = text.slice(rawStart, rawEnd);
120
+ const leading = slice.match(/^\s*/)?.[0].length ?? 0;
121
+ const trailing = slice.match(/\s*$/)?.[0].length ?? 0;
122
+ const start = rawStart + leading;
123
+ const end = rawEnd - trailing;
124
+ if (end <= start) {
125
+ return;
126
+ }
127
+ spans.push({ end, start, text: text.slice(start, end) });
128
+ }
129
+ function takeWordsFromEnd(text, approxChars) {
130
+ const words = text.split(/\s+/).filter(Boolean);
131
+ if (words.length === 0) {
132
+ return "";
133
+ }
134
+ const wordCount = Math.max(1, Math.ceil(approxChars / 6));
135
+ return words.slice(-wordCount).join(" ");
136
+ }
137
+ function buildChunk(params, text, sourceOffset, index) {
138
+ const chunk = {
139
+ chunk_id: createSearchChunkId(params.doc_id, index),
140
+ chunk_index: index,
141
+ doc_id: params.doc_id,
142
+ ...(params.metadata ? { metadata: params.metadata } : {}),
143
+ source_offset: sourceOffset,
144
+ text,
145
+ };
146
+ if (params.track_lines) {
147
+ chunk.source_lines = computeSourceLines(params.text, sourceOffset);
148
+ }
149
+ return chunk;
150
+ }
151
+ function computeSourceLines(text, offset) {
152
+ const start = countLines(text, 0, offset.start) + 1;
153
+ const end = countLines(text, 0, Math.max(offset.end - 1, offset.start)) + 1;
154
+ return { end: Math.max(end, start), start };
155
+ }
156
+ function countLines(text, from, to) {
157
+ let count = 0;
158
+ for (let i = from; i < to; i += 1) {
159
+ if (text.charCodeAt(i) === 10) {
160
+ count += 1;
161
+ }
162
+ }
163
+ return count;
164
+ }
@@ -0,0 +1,9 @@
1
+ // Core search-index contracts.
2
+ //
3
+ // `SearchDocument` is the wire shape every provider stores; `SearchIndexProvider`
4
+ // is the contract every search backend implements (chat-search, kb, contacts,
5
+ // api-catalog). Providers carry a stable `id` so the host can route auto-tools,
6
+ // declarative re-index events, and operator status/backfill by name.
7
+ // Sentinel for tenant-less / system-scope providers (e.g. core api-catalog).
8
+ // Documents use a real tenant id when scoped per tenant; otherwise this value.
9
+ export const SEARCH_INDEX_SYSTEM_TENANT_ID = "__system__";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Stand-in for `@engenty/connections-sdk`: the contracts, OAuth2 and registry are engenty's own
3
+ * files; what engenty binds to its database and plugin host is provided here.
4
+ */
5
+ import { seal, unseal } from "../../secrets/crypto.js";
6
+ import { filesCapabilityActions } from "../connections-sdk/files-capability.js";
7
+ import { storageCapabilityActions } from "../connections-sdk/storage-capability.js";
8
+ export * from "../connections-sdk/oauth2.js";
9
+ export * from "../connections-sdk/registry.js";
10
+ export * from "../connections-sdk/types.js";
11
+ /** Secrets of imported connectors (OAuth client id and secret) at rest. */
12
+ export function encryptToken(plain) {
13
+ return seal(plain);
14
+ }
15
+ export function decryptToken(value) {
16
+ const plain = unseal(value);
17
+ if (plain === null) {
18
+ throw new Error("stored connector secret cannot be read");
19
+ }
20
+ return plain;
21
+ }
22
+ /**
23
+ * Finalises a connector definition as engenty's runtime does: a `files` capability becomes the
24
+ * read actions `files_list` / `files_read` / `files_stat` / `files_search`, a `storage`
25
+ * capability the write actions `files_write` / `files_delete` / `files_move`.
26
+ */
27
+ export function defineConnector(def) {
28
+ if (!(def.files || def.storage)) {
29
+ return def;
30
+ }
31
+ return {
32
+ ...def,
33
+ actions: [
34
+ ...def.actions,
35
+ ...(def.files ? filesCapabilityActions(def.files, def.filesProviderScopes) : []),
36
+ ...(def.storage ? storageCapabilityActions(def.storage, def.storageProviderScopes) : []),
37
+ ],
38
+ };
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ /** Stand-in for `@engenty/web-ingest`: its host guard, and this product's HTML to Markdown. */
2
+ export { htmlToMarkdown } from "../../render/convert.js";
3
+ export { assertPublicHttpHost, isBlockedIp } from "../web-ingest/ssrf.js";
@@ -0,0 +1,277 @@
1
+ /**
2
+ * SSRF protection for server-side URL fetching.
3
+ *
4
+ * `assertPublicHttpHost` is the ONE guard — it resolves the hostname via DNS and
5
+ * validates every returned address with `isBlockedIp`. The literal-hostname
6
+ * check is its internal fast path, deliberately NOT exported: a string-only
7
+ * check passes any name that resolves to a private address, so exporting it
8
+ * invites call sites that look guarded and aren't (it was the sole guard on two
9
+ * fetch paths until 2026-08-04 — see TRK-01).
10
+ *
11
+ * Residual risk: DNS rebinding / TOCTOU window between resolution here and the
12
+ * actual TCP connect. The proper mitigation is to hook into the HTTP client's
13
+ * connect phase (e.g. undici `connect.lookup`). That is tracked as a follow-up.
14
+ */
15
+ // No top-level node:* imports: this module reaches browser bundles through the
16
+ // `@engenty/web-ingest` barrel (document-sources adapters → module UI plugins),
17
+ // so IP classification is implemented in pure JS instead of `node:net.isIP`.
18
+ const IPV4_PATTERN = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
19
+ function isValidIpv6(ip) {
20
+ const halves = ip.split("::");
21
+ if (halves.length > 2) {
22
+ return false;
23
+ }
24
+ const hasDoubleColon = halves.length === 2;
25
+ const head = halves[0] === "" ? [] : halves[0].split(":");
26
+ const tail = hasDoubleColon && halves[1] !== "" ? halves[1].split(":") : [];
27
+ const groups = [...head, ...tail];
28
+ let wordCount = 0;
29
+ for (let i = 0; i < groups.length; i++) {
30
+ const group = groups[i];
31
+ // Embedded IPv4 (e.g. ::ffff:192.168.1.1) is only valid as the last group.
32
+ if (i === groups.length - 1 && group.includes(".")) {
33
+ if (!IPV4_PATTERN.test(group)) {
34
+ return false;
35
+ }
36
+ wordCount += 2;
37
+ continue;
38
+ }
39
+ if (!/^[0-9a-fA-F]{1,4}$/.test(group)) {
40
+ return false;
41
+ }
42
+ wordCount += 1;
43
+ }
44
+ return hasDoubleColon ? wordCount < 8 : wordCount === 8;
45
+ }
46
+ /** Pure equivalent of `node:net.isIP`: 4, 6, or 0 when not a valid IP literal. */
47
+ function isIP(ip) {
48
+ if (IPV4_PATTERN.test(ip)) {
49
+ return 4;
50
+ }
51
+ return isValidIpv6(ip) ? 6 : 0;
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Literal-hostname fast path — internal to assertPublicHttpHost, never exported
55
+ // ---------------------------------------------------------------------------
56
+ /** Block obvious SSRF targets by name, before any DNS work. */
57
+ function isBlockedHostname(hostname) {
58
+ const h = hostname.toLowerCase();
59
+ if (h === "localhost" || h.endsWith(".localhost")) {
60
+ return true;
61
+ }
62
+ if (h === "metadata" || h === "metadata.google.internal") {
63
+ return true;
64
+ }
65
+ if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) {
66
+ return true;
67
+ }
68
+ if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) {
69
+ return true;
70
+ }
71
+ if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(h)) {
72
+ return true;
73
+ }
74
+ if (/^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(h)) {
75
+ return true;
76
+ }
77
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(h)) {
78
+ return true;
79
+ }
80
+ if (h === "[::1]" || h === "::1") {
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // IP-level range check
87
+ // ---------------------------------------------------------------------------
88
+ function ipv4ToNumber(ip) {
89
+ const parts = ip.split(".");
90
+ if (parts.length !== 4) {
91
+ return null;
92
+ }
93
+ let n = 0;
94
+ for (const part of parts) {
95
+ const octet = Number(part);
96
+ if (!Number.isInteger(octet) || octet < 0 || octet > 255) {
97
+ return null;
98
+ }
99
+ n = n * 256 + octet;
100
+ }
101
+ return n;
102
+ }
103
+ function inCidr(ip, base, prefixLen) {
104
+ const a = ipv4ToNumber(ip);
105
+ const b = ipv4ToNumber(base);
106
+ if (a === null || b === null) {
107
+ return false;
108
+ }
109
+ // biome-ignore lint/suspicious/noBitwiseOperators: intentional — CIDR mask arithmetic requires bitwise ops
110
+ const mask = prefixLen === 0 ? 0 : (~0 << (32 - prefixLen)) >>> 0;
111
+ // biome-ignore lint/suspicious/noBitwiseOperators: intentional — CIDR mask arithmetic requires bitwise ops
112
+ return (a & mask) >>> 0 === (b & mask) >>> 0;
113
+ }
114
+ /**
115
+ * Returns true if the given IP address (v4 or v6) falls within a range that
116
+ * must not be reached from a server-side fetch.
117
+ */
118
+ export function isBlockedIp(ip) {
119
+ const kind = isIP(ip);
120
+ if (kind === 4) {
121
+ // 0.0.0.0/8
122
+ if (inCidr(ip, "0.0.0.0", 8)) {
123
+ return true;
124
+ }
125
+ // 10.0.0.0/8
126
+ if (inCidr(ip, "10.0.0.0", 8)) {
127
+ return true;
128
+ }
129
+ // 100.64.0.0/10 (carrier-grade NAT)
130
+ if (inCidr(ip, "100.64.0.0", 10)) {
131
+ return true;
132
+ }
133
+ // 127.0.0.0/8 (loopback)
134
+ if (inCidr(ip, "127.0.0.0", 8)) {
135
+ return true;
136
+ }
137
+ // 169.254.0.0/16 (link-local / cloud metadata)
138
+ if (inCidr(ip, "169.254.0.0", 16)) {
139
+ return true;
140
+ }
141
+ // 172.16.0.0/12
142
+ if (inCidr(ip, "172.16.0.0", 12)) {
143
+ return true;
144
+ }
145
+ // 192.168.0.0/16
146
+ if (inCidr(ip, "192.168.0.0", 16)) {
147
+ return true;
148
+ }
149
+ // 198.18.0.0/15 (benchmark / testing)
150
+ if (inCidr(ip, "198.18.0.0", 15)) {
151
+ return true;
152
+ }
153
+ // 224.0.0.0/3 (multicast + reserved: 224–255.x.x.x)
154
+ if (inCidr(ip, "224.0.0.0", 3)) {
155
+ return true;
156
+ }
157
+ return false;
158
+ }
159
+ if (kind === 6) {
160
+ const lower = ip.toLowerCase();
161
+ // ::1 (loopback)
162
+ if (lower === "::1") {
163
+ return true;
164
+ }
165
+ // :: (unspecified)
166
+ if (lower === "::") {
167
+ return true;
168
+ }
169
+ // fc00::/7 (unique-local: fc00:: – fdff::)
170
+ // First 7 bits are 1111110x — covers fc00:: and fd00:: prefixes
171
+ if (lower.startsWith("fc") || lower.startsWith("fd")) {
172
+ return true;
173
+ }
174
+ // fe80::/10 (link-local)
175
+ if (lower.startsWith("fe8") ||
176
+ lower.startsWith("fe9") ||
177
+ lower.startsWith("fea") ||
178
+ lower.startsWith("feb")) {
179
+ return true;
180
+ }
181
+ // ::ffff:0:0/96 — IPv4-mapped; extract and re-check the embedded v4 address
182
+ if (lower.startsWith("::ffff:")) {
183
+ const v4Part = lower.slice("::ffff:".length);
184
+ // Could be dotted-quad (::ffff:192.168.1.1) or hex word pair (::ffff:c0a8:0101)
185
+ if (v4Part.includes(".")) {
186
+ if (isBlockedIp(v4Part)) {
187
+ return true;
188
+ }
189
+ }
190
+ else {
191
+ // Parse two hex words into dotted-quad
192
+ const words = v4Part.split(":");
193
+ if (words.length === 2) {
194
+ const hi = Number.parseInt(words[0], 16);
195
+ const lo = Number.parseInt(words[1], 16);
196
+ if (!(Number.isNaN(hi) || Number.isNaN(lo))) {
197
+ // biome-ignore lint/suspicious/noBitwiseOperators: intentional — extracting octets from 16-bit words
198
+ const dotted = [hi >>> 8, hi & 0xff, lo >>> 8, lo & 0xff].join(".");
199
+ if (isBlockedIp(dotted)) {
200
+ return true;
201
+ }
202
+ }
203
+ }
204
+ }
205
+ }
206
+ return false;
207
+ }
208
+ // Not a valid IP — treat as blocked to be safe
209
+ return true;
210
+ }
211
+ /**
212
+ * Validates `urlString` is a safe public HTTP(S) target:
213
+ * - Must be http: or https:
214
+ * - Hostname must pass `isBlockedHostname` (fast-path, no DNS)
215
+ * - If hostname is an IP literal, `isBlockedIp` is called directly
216
+ * - Otherwise DNS is resolved and *every* returned address is checked with `isBlockedIp`
217
+ *
218
+ * Returns the parsed URL and the list of resolved addresses on success.
219
+ * Throws a descriptive Error on any violation.
220
+ *
221
+ * `lookupImpl` can be injected for testing; defaults to `dns/promises` lookup.
222
+ */
223
+ export async function assertPublicHttpHost(urlString, lookupImpl) {
224
+ let url;
225
+ try {
226
+ url = new URL(urlString);
227
+ }
228
+ catch {
229
+ throw new Error(`Invalid URL: ${urlString}`);
230
+ }
231
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
232
+ throw new Error("Only http and https URLs are allowed");
233
+ }
234
+ const hostname = url.hostname;
235
+ // Strip IPv6 brackets that URL parsing leaves in
236
+ const hostForCheck = hostname.startsWith("[") && hostname.endsWith("]")
237
+ ? hostname.slice(1, -1)
238
+ : hostname;
239
+ // Fast-path: blocked hostnames by name
240
+ if (isBlockedHostname(hostname)) {
241
+ throw new Error(`URL host is not allowed for fetch: ${hostname}`);
242
+ }
243
+ // If the hostname is an IP literal, skip DNS and check it directly
244
+ if (isIP(hostForCheck) !== 0) {
245
+ if (isBlockedIp(hostForCheck)) {
246
+ throw new Error(`URL host is not allowed for fetch: ${hostname}`);
247
+ }
248
+ return { url, addresses: [hostForCheck] };
249
+ }
250
+ // Resolve via DNS and check every returned address
251
+ let lookup;
252
+ if (lookupImpl) {
253
+ lookup = lookupImpl;
254
+ }
255
+ else {
256
+ const dns = await import("node:dns/promises");
257
+ lookup = dns.lookup;
258
+ }
259
+ let entries;
260
+ try {
261
+ entries = await lookup(hostname, { all: true, verbatim: true });
262
+ }
263
+ catch (e) {
264
+ throw new Error(`DNS resolution failed for ${hostname}: ${e instanceof Error ? e.message : String(e)}`);
265
+ }
266
+ if (entries.length === 0) {
267
+ throw new Error(`No DNS records found for ${hostname}`);
268
+ }
269
+ const addresses = [];
270
+ for (const entry of entries) {
271
+ if (isBlockedIp(entry.address)) {
272
+ throw new Error(`URL host resolves to a blocked IP (${entry.address}): ${hostname}`);
273
+ }
274
+ addresses.push(entry.address);
275
+ }
276
+ return { url, addresses };
277
+ }
@@ -0,0 +1,60 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { nanoid } from "nanoid";
3
+ import { db, schema } from "../db/client.js";
4
+ import { signalChanged } from "./events.js";
5
+ /** How long a step waits for the person before it goes on without them. */
6
+ const ASK_TIMEOUT_MS = 15 * 60_000;
7
+ /** Runs nobody watches (an MCP client drives them): a question there would wait for no one. */
8
+ export const unattended = new Set();
9
+ const waiting = new Map();
10
+ async function setAsk(runId, ask) {
11
+ await db.update(schema.run).set({ ask, updatedAt: new Date() }).where(eq(schema.run.id, runId));
12
+ signalChanged(runId);
13
+ }
14
+ /**
15
+ * Puts a question to the person and waits. The run stays "running": the step is in the middle
16
+ * of its work and goes on with the answer. One question per run at a time.
17
+ */
18
+ export async function askPerson(runId, ask, signal) {
19
+ if (waiting.has(runId)) {
20
+ throw new Error("The wizard is already waiting for the person.");
21
+ }
22
+ const full = { ...ask, id: nanoid(10), at: new Date().toISOString() };
23
+ const result = new Promise((resolve) => {
24
+ const finish = (r) => {
25
+ clearTimeout(timer);
26
+ signal.removeEventListener("abort", onAbort);
27
+ resolve(r);
28
+ };
29
+ const onAbort = () => finish({ type: "skip" });
30
+ const timer = setTimeout(() => finish({ type: "timeout" }), ASK_TIMEOUT_MS);
31
+ signal.addEventListener("abort", onAbort, { once: true });
32
+ waiting.set(runId, { ask: full, settle: finish });
33
+ });
34
+ await setAsk(runId, full);
35
+ try {
36
+ return await result;
37
+ }
38
+ finally {
39
+ waiting.delete(runId);
40
+ await setAsk(runId, null).catch(() => undefined);
41
+ }
42
+ }
43
+ /** The question a run is waiting on, if any. */
44
+ export function pendingAsk(runId) {
45
+ return waiting.get(runId)?.ask ?? null;
46
+ }
47
+ export function answerAsk(runId, askId, answer) {
48
+ const entry = waiting.get(runId);
49
+ if (!entry || entry.ask.id !== askId) {
50
+ return false;
51
+ }
52
+ entry.settle(answer);
53
+ return true;
54
+ }
55
+ /** A question left in the row by a server that stopped: nobody is waiting for it any more. */
56
+ export async function clearStaleAsk(runId) {
57
+ if (!waiting.has(runId)) {
58
+ await db.update(schema.run).set({ ask: null }).where(eq(schema.run.id, runId));
59
+ }
60
+ }