@runeditrun/rer 0.0.0-stage → 0.1.0

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 (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/bin/rer.mjs +3 -0
  4. package/cli/README.md +243 -0
  5. package/cli/adapter.mjs +10 -0
  6. package/cli/ai-local.mjs +65 -0
  7. package/cli/archive-path.mjs +52 -0
  8. package/cli/archive-vite.mjs +55 -0
  9. package/cli/archive.mjs +8 -0
  10. package/cli/auth-copy.mjs +80 -0
  11. package/cli/bounded.mjs +70 -0
  12. package/cli/cache-doctor.mjs +63 -0
  13. package/cli/container-transfer.mjs +35 -0
  14. package/cli/container.mjs +62 -0
  15. package/cli/contract.mjs +25 -0
  16. package/cli/dependencies.mjs +45 -0
  17. package/cli/dependency-approval.mjs +33 -0
  18. package/cli/dependency-doctor.mjs +34 -0
  19. package/cli/dependency-input.mjs +43 -0
  20. package/cli/deploy-config.mjs +118 -0
  21. package/cli/deploy.mjs +37 -0
  22. package/cli/dev-identity.mjs +80 -0
  23. package/cli/errors.mjs +14 -0
  24. package/cli/export.mjs +42 -0
  25. package/cli/font-cache.mjs +29 -0
  26. package/cli/hosted-output.mjs +110 -0
  27. package/cli/independent.mjs +29 -0
  28. package/cli/inspect.mjs +174 -0
  29. package/cli/jsonc.mjs +111 -0
  30. package/cli/lifecycle.mjs +148 -0
  31. package/cli/local.mjs +100 -0
  32. package/cli/main.mjs +57 -0
  33. package/cli/managed-server.mjs +143 -0
  34. package/cli/node-runtime-cache.mjs +68 -0
  35. package/cli/node-runtime-discovery.mjs +56 -0
  36. package/cli/node-runtime-download.mjs +59 -0
  37. package/cli/node-runtime.mjs +50 -0
  38. package/cli/output.mjs +22 -0
  39. package/cli/parse.mjs +77 -0
  40. package/cli/port.mjs +52 -0
  41. package/cli/portability-worker.mjs +56 -0
  42. package/cli/portability.mjs +66 -0
  43. package/cli/preview-cache.mjs +99 -0
  44. package/cli/project.mjs +48 -0
  45. package/cli/release.mjs +24 -0
  46. package/cli/runtime-front-door.mjs +46 -0
  47. package/cli/scheduled.mjs +81 -0
  48. package/cli/server-activity.mjs +12 -0
  49. package/cli/server-proxy.mjs +93 -0
  50. package/cli/server-registry.mjs +177 -0
  51. package/cli/server-resources.mjs +22 -0
  52. package/cli/settings.mjs +15 -0
  53. package/cli/setup-answers.mjs +29 -0
  54. package/cli/setup-input.mjs +113 -0
  55. package/cli/shared.mjs +7 -0
  56. package/cli/transfers.mjs +33 -0
  57. package/cli/version.mjs +36 -0
  58. package/composition/git-source.mjs +111 -0
  59. package/edition-authoring/files.mjs +81 -0
  60. package/edition-authoring/manifest.mjs +65 -0
  61. package/global/README.md +155 -0
  62. package/global/checkout.mjs +72 -0
  63. package/global/cli/command.mjs +13 -0
  64. package/global/cli/install.mjs +75 -0
  65. package/global/cli/layout.mjs +45 -0
  66. package/global/cli/status.mjs +54 -0
  67. package/global/cli/uninstall.mjs +36 -0
  68. package/global/delegate.mjs +13 -0
  69. package/global/launch/capacity.mjs +72 -0
  70. package/global/launch/dev.mjs +129 -0
  71. package/global/launch/facts.mjs +93 -0
  72. package/global/launch/failure.mjs +5 -0
  73. package/global/launch/local-defaults.mjs +4 -0
  74. package/global/launch/prepare.mjs +61 -0
  75. package/global/launch/render.mjs +44 -0
  76. package/global/launch/state.mjs +97 -0
  77. package/global/launch/steps.mjs +81 -0
  78. package/global/launch/worker.mjs +43 -0
  79. package/global/list.mjs +59 -0
  80. package/global/main.mjs +53 -0
  81. package/global/package-info.mjs +38 -0
  82. package/global/registry.mjs +24 -0
  83. package/global/rer.mjs +29 -0
  84. package/global/update/archive.mjs +89 -0
  85. package/global/update/command.mjs +90 -0
  86. package/global/update/latest.mjs +6 -0
  87. package/global/update/notice.mjs +58 -0
  88. package/global/update/registry.mjs +47 -0
  89. package/global/version.mjs +29 -0
  90. package/install.sh +144 -0
  91. package/modules/ai/LICENSE +21 -0
  92. package/modules/ai/README.md +90 -0
  93. package/modules/ai/client.mjs +71 -0
  94. package/modules/ai/config.mjs +39 -0
  95. package/modules/ai/dev/index.d.mts +6 -0
  96. package/modules/ai/dev/index.mjs +64 -0
  97. package/modules/ai/dev/outbox.mjs +33 -0
  98. package/modules/ai/errors.mjs +28 -0
  99. package/modules/ai/fake/index.d.mts +3 -0
  100. package/modules/ai/fake/index.mjs +82 -0
  101. package/modules/ai/index.d.mts +10 -0
  102. package/modules/ai/index.mjs +3 -0
  103. package/modules/ai/package.json +6 -0
  104. package/modules/container/README.md +156 -0
  105. package/modules/container/adapters/d1.mjs +34 -0
  106. package/modules/container/adapters/egress.mjs +40 -0
  107. package/modules/container/adapters/jobs.mjs +47 -0
  108. package/modules/container/adapters/objects.mjs +84 -0
  109. package/modules/container/adapters/retained.mjs +14 -0
  110. package/modules/container/adapters/sqlite-error.mjs +7 -0
  111. package/modules/container/adapters/sqlite-worker.mjs +26 -0
  112. package/modules/container/assets.mjs +14 -0
  113. package/modules/container/bindings.mjs +1 -0
  114. package/modules/container/boundary.mjs +38 -0
  115. package/modules/container/build.mjs +65 -0
  116. package/modules/container/deploy-input.mjs +17 -0
  117. package/modules/container/entrypoint.sh +5 -0
  118. package/modules/container/host.mjs +43 -0
  119. package/modules/container/ingress.mjs +23 -0
  120. package/modules/container/lock.mjs +11 -0
  121. package/modules/container/migrations.mjs +16 -0
  122. package/modules/container/node-engine.mjs +39 -0
  123. package/modules/container/portability-schema.mjs +14 -0
  124. package/modules/container/remote/deploy.py +76 -0
  125. package/modules/container/remote/files.py +275 -0
  126. package/modules/container/remote/resources.py +157 -0
  127. package/modules/container/runner.mjs +10 -0
  128. package/modules/container/ssh-plan.mjs +44 -0
  129. package/modules/container/ssh.mjs +63 -0
  130. package/modules/container/transfer.mjs +27 -0
  131. package/modules/container/worker-engine.mjs +32 -0
  132. package/modules/dependencies/README.md +211 -0
  133. package/modules/dependencies/approval.mjs +51 -0
  134. package/modules/dependencies/auth.mjs +40 -0
  135. package/modules/dependencies/authority.mjs +14 -0
  136. package/modules/dependencies/crypto.mjs +13 -0
  137. package/modules/dependencies/index.d.mts +23 -0
  138. package/modules/dependencies/index.mjs +113 -0
  139. package/modules/dependencies/logout.mjs +42 -0
  140. package/modules/dependencies/native.mjs +9 -0
  141. package/modules/dependencies/protocol.mjs +23 -0
  142. package/modules/dependencies/schema/approval-attempt.sql +3 -0
  143. package/modules/dependencies/schema/dependencies.sql +15 -0
  144. package/modules/dependencies/store.mjs +19 -0
  145. package/modules/deploy-kit/README.md +308 -0
  146. package/modules/deploy-kit/artifact.mjs +47 -0
  147. package/modules/deploy-kit/build-modules.mjs +21 -0
  148. package/modules/deploy-kit/check.mjs +13 -0
  149. package/modules/deploy-kit/cli.mjs +50 -0
  150. package/modules/deploy-kit/container-config.mjs +8 -0
  151. package/modules/deploy-kit/default-stage.mjs +14 -0
  152. package/modules/deploy-kit/deploy.mjs +80 -0
  153. package/modules/deploy-kit/health-request.mjs +86 -0
  154. package/modules/deploy-kit/health.mjs +99 -0
  155. package/modules/deploy-kit/index.mjs +12 -0
  156. package/modules/deploy-kit/local-only.mjs +10 -0
  157. package/modules/deploy-kit/migrations.mjs +48 -0
  158. package/modules/deploy-kit/owner-cli.mjs +63 -0
  159. package/modules/deploy-kit/owner.mjs +297 -0
  160. package/modules/deploy-kit/prepare.mjs +62 -0
  161. package/modules/deploy-kit/provider.mjs +99 -0
  162. package/modules/deploy-kit/queue-consumer.mjs +10 -0
  163. package/modules/deploy-kit/receipt.mjs +16 -0
  164. package/modules/deploy-kit/render.mjs +47 -0
  165. package/modules/deploy-kit/resources.mjs +106 -0
  166. package/modules/deploy-kit/service-grants.mjs +25 -0
  167. package/modules/deploy-kit/sql-safety.mjs +94 -0
  168. package/modules/deploy-kit/sql-tokens.mjs +49 -0
  169. package/modules/deploy-kit/teardown.mjs +100 -0
  170. package/modules/deploy-kit/templates.mjs +31 -0
  171. package/modules/deploy-kit/validation.mjs +58 -0
  172. package/modules/deploy-kit/worker.mjs +105 -0
  173. package/modules/erasure/CONTRACT.md +90 -0
  174. package/modules/erasure/README.md +221 -0
  175. package/modules/erasure/audit.mjs +23 -0
  176. package/modules/erasure/blobs.mjs +45 -0
  177. package/modules/erasure/cascade.mjs +91 -0
  178. package/modules/erasure/completion.mjs +62 -0
  179. package/modules/erasure/declaration.mjs +51 -0
  180. package/modules/erasure/fence.mjs +19 -0
  181. package/modules/erasure/index.d.mts +62 -0
  182. package/modules/erasure/index.mjs +85 -0
  183. package/modules/erasure/order.mjs +56 -0
  184. package/modules/erasure/plan.mjs +121 -0
  185. package/modules/erasure/rate.mjs +57 -0
  186. package/modules/erasure/receipt.mjs +32 -0
  187. package/modules/erasure/root.mjs +21 -0
  188. package/modules/erasure/schema/erasure-rates.sql +5 -0
  189. package/modules/erasure/schema/erasure.sql +27 -0
  190. package/modules/erasure/scope.mjs +29 -0
  191. package/modules/erasure/sql.mjs +23 -0
  192. package/modules/erasure/verification.mjs +69 -0
  193. package/modules/integrations/core/CONTRACT.md +50 -0
  194. package/modules/integrations/core/README.md +27 -0
  195. package/modules/integrations/core/crypto.ts +19 -0
  196. package/modules/integrations/core/database.ts +21 -0
  197. package/modules/integrations/core/delivery.ts +97 -0
  198. package/modules/integrations/core/destination.ts +13 -0
  199. package/modules/integrations/core/index.ts +7 -0
  200. package/modules/integrations/core/node-transport.ts +72 -0
  201. package/modules/integrations/core/public-address.ts +57 -0
  202. package/modules/integrations/core/repository.ts +74 -0
  203. package/modules/integrations/core/schema.sql +36 -0
  204. package/modules/integrations/core/sqlite.ts +20 -0
  205. package/modules/integrations/core/timestamped-body-signature.ts +38 -0
  206. package/modules/integrations/core/tokens.ts +71 -0
  207. package/modules/integrations/core/types.ts +52 -0
  208. package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
  209. package/modules/integrations/core/webhook.ts +73 -0
  210. package/modules/job-queue/README.md +173 -0
  211. package/modules/job-queue/cli.mjs +64 -0
  212. package/modules/job-queue/command.mjs +64 -0
  213. package/modules/job-queue/config.mjs +48 -0
  214. package/modules/job-queue/identity.mjs +48 -0
  215. package/modules/job-queue/index.mjs +135 -0
  216. package/modules/job-queue/membership.mjs +30 -0
  217. package/modules/job-queue/module.json +10 -0
  218. package/modules/job-queue/policy.mjs +29 -0
  219. package/modules/job-queue/processes.mjs +80 -0
  220. package/modules/job-queue/resources.mjs +37 -0
  221. package/modules/job-queue/storage.mjs +64 -0
  222. package/modules/job-queue/supervisor.mjs +30 -0
  223. package/modules/job-queue/waiting.mjs +16 -0
  224. package/modules/mail/LICENSE +21 -0
  225. package/modules/mail/README.md +336 -0
  226. package/modules/mail/doctor.mjs +81 -0
  227. package/modules/mail/index.mjs +8 -0
  228. package/modules/mail/live-proof/README.md +113 -0
  229. package/modules/mail/live-proof/run.mjs +55 -0
  230. package/modules/mail/package.json +19 -0
  231. package/modules/mail/pnpm-lock.yaml +537 -0
  232. package/modules/mail/src/adapters/cloudflare/README.md +84 -0
  233. package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
  234. package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
  235. package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
  236. package/modules/mail/src/adapters/messaging/README.md +92 -0
  237. package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
  238. package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
  239. package/modules/mail/src/adapters/messaging/index.mjs +61 -0
  240. package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
  241. package/modules/mail/src/adapters/messaging/request.mjs +52 -0
  242. package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
  243. package/modules/mail/src/adapters/resend/README.md +120 -0
  244. package/modules/mail/src/adapters/resend/index.mjs +22 -0
  245. package/modules/mail/src/adapters/resend/response.mjs +54 -0
  246. package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
  247. package/modules/mail/src/adapters/resend/size.mjs +99 -0
  248. package/modules/mail/src/adapters/sendgrid/README.md +75 -0
  249. package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
  250. package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
  251. package/modules/mail/src/core/errors.mjs +9 -0
  252. package/modules/mail/src/core/transport.mjs +33 -0
  253. package/modules/mail/src/core/validation.mjs +172 -0
  254. package/modules/mail/src/development/index.d.mts +37 -0
  255. package/modules/mail/src/development/index.mjs +35 -0
  256. package/modules/mail/src/development/outbox.mjs +78 -0
  257. package/modules/mail/src/development/schema.sql +9 -0
  258. package/modules/mail/src/development/viewer-html.mjs +42 -0
  259. package/modules/mail/src/development/viewer.mjs +65 -0
  260. package/modules/mail/src/doctor/README.md +107 -0
  261. package/modules/mail/src/doctor/common.mjs +64 -0
  262. package/modules/mail/src/doctor/dns.mjs +102 -0
  263. package/modules/mail/src/doctor/index.mjs +28 -0
  264. package/modules/mail/src/doctor/probe.mjs +66 -0
  265. package/modules/mail/src/doctor/receive.mjs +81 -0
  266. package/modules/mail/src/doctor/records.mjs +45 -0
  267. package/modules/mail/src/events/README.md +268 -0
  268. package/modules/mail/src/events/d1-schema.sql +25 -0
  269. package/modules/mail/src/events/d1-storage.mjs +76 -0
  270. package/modules/mail/src/events/index.mjs +3 -0
  271. package/modules/mail/src/events/intake.mjs +201 -0
  272. package/modules/mail/src/events/storage.mjs +52 -0
  273. package/modules/mail/src/events/verify.mjs +92 -0
  274. package/modules/mail/src/inbound/README.md +137 -0
  275. package/modules/mail/src/inbound/bytes.mjs +91 -0
  276. package/modules/mail/src/inbound/headers.mjs +143 -0
  277. package/modules/mail/src/inbound/index.mjs +29 -0
  278. package/modules/mail/src/inbound/mime.mjs +36 -0
  279. package/modules/mail/src/inbound/multipart.mjs +27 -0
  280. package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
  281. package/modules/mail/src/inbound/resend.mjs +54 -0
  282. package/modules/mail/src/inbound/sendgrid.mjs +60 -0
  283. package/modules/mail/src/sender.mjs +33 -0
  284. package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
  285. package/modules/mail/src/sendgrid-email/index.mjs +86 -0
  286. package/modules/mail/src/unsubscribe/index.mjs +142 -0
  287. package/modules/operator-auth/README.md +599 -0
  288. package/modules/operator-auth/accounts/adapter.mjs +41 -0
  289. package/modules/operator-auth/accounts/admin.mjs +159 -0
  290. package/modules/operator-auth/accounts/credentials.mjs +27 -0
  291. package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
  292. package/modules/operator-auth/accounts/index.d.mts +9 -0
  293. package/modules/operator-auth/accounts/index.mjs +9 -0
  294. package/modules/operator-auth/accounts/native.d.mts +38 -0
  295. package/modules/operator-auth/accounts/native.mjs +171 -0
  296. package/modules/operator-auth/accounts/oa.mjs +90 -0
  297. package/modules/operator-auth/accounts/provider-control.mjs +50 -0
  298. package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
  299. package/modules/operator-auth/accounts/roles.mjs +26 -0
  300. package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
  301. package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
  302. package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
  303. package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
  304. package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
  305. package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
  306. package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
  307. package/modules/operator-auth/accounts/settings-page.mjs +4 -0
  308. package/modules/operator-auth/accounts/store.mjs +246 -0
  309. package/modules/operator-auth/branding.mjs +66 -0
  310. package/modules/operator-auth/config.mjs +35 -0
  311. package/modules/operator-auth/conformance/README.md +81 -0
  312. package/modules/operator-auth/conformance/index.d.mts +43 -0
  313. package/modules/operator-auth/conformance/index.mjs +300 -0
  314. package/modules/operator-auth/conformance/presentation.mjs +57 -0
  315. package/modules/operator-auth/crypto.mjs +17 -0
  316. package/modules/operator-auth/dev/badge.mjs +62 -0
  317. package/modules/operator-auth/dev/index.d.mts +14 -0
  318. package/modules/operator-auth/dev/index.mjs +137 -0
  319. package/modules/operator-auth/dev/personas.mjs +17 -0
  320. package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
  321. package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
  322. package/modules/operator-auth/dev-admission.d.mts +4 -0
  323. package/modules/operator-auth/dev-admission.mjs +10 -0
  324. package/modules/operator-auth/dev-gate.mjs +86 -0
  325. package/modules/operator-auth/docs/README.md +78 -0
  326. package/modules/operator-auth/docs/native-accounts.md +313 -0
  327. package/modules/operator-auth/docs/tanstack-start.md +218 -0
  328. package/modules/operator-auth/docs/vite-spa.md +144 -0
  329. package/modules/operator-auth/errors.mjs +9 -0
  330. package/modules/operator-auth/http.mjs +42 -0
  331. package/modules/operator-auth/identity/admission.mjs +105 -0
  332. package/modules/operator-auth/identity/after-confirm.mjs +18 -0
  333. package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
  334. package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
  335. package/modules/operator-auth/identity/email-link/index.mjs +128 -0
  336. package/modules/operator-auth/identity/email-link/message.mjs +16 -0
  337. package/modules/operator-auth/identity/index.d.mts +7 -0
  338. package/modules/operator-auth/identity/index.mjs +5 -0
  339. package/modules/operator-auth/identity/oidc/README.md +171 -0
  340. package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
  341. package/modules/operator-auth/identity/oidc/config.mjs +102 -0
  342. package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
  343. package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
  344. package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
  345. package/modules/operator-auth/identity/oidc/index.mjs +287 -0
  346. package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
  347. package/modules/operator-auth/identity/oidc/network.mjs +93 -0
  348. package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
  349. package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
  350. package/modules/operator-auth/identity/oidc/store.mjs +68 -0
  351. package/modules/operator-auth/identity/principal.mjs +28 -0
  352. package/modules/operator-auth/index.d.mts +223 -0
  353. package/modules/operator-auth/index.mjs +11 -0
  354. package/modules/operator-auth/input.mjs +55 -0
  355. package/modules/operator-auth/local-cli.mjs +16 -0
  356. package/modules/operator-auth/page.mjs +34 -0
  357. package/modules/operator-auth/policy.mjs +20 -0
  358. package/modules/operator-auth/routes.mjs +91 -0
  359. package/modules/operator-auth/service.mjs +152 -0
  360. package/modules/operator-auth/signin/index.mjs +67 -0
  361. package/modules/owner-claim/README.md +137 -0
  362. package/modules/owner-claim/idp.mjs +40 -0
  363. package/modules/owner-claim/index.d.mts +27 -0
  364. package/modules/owner-claim/index.mjs +39 -0
  365. package/modules/owner-claim/page.mjs +11 -0
  366. package/modules/owner-claim/schema/idp.sql +4 -0
  367. package/modules/owner-claim/schema/owner-claim.sql +18 -0
  368. package/modules/owner-claim/transaction.mjs +87 -0
  369. package/modules/owner-claim/verify.mjs +80 -0
  370. package/modules/portability/README.md +444 -0
  371. package/modules/portability/bridge.mjs +102 -0
  372. package/modules/portability/catalogue.mjs +118 -0
  373. package/modules/portability/cli-options.mjs +38 -0
  374. package/modules/portability/cli.mjs +56 -0
  375. package/modules/portability/copies.mjs +68 -0
  376. package/modules/portability/dev-data.mjs +32 -0
  377. package/modules/portability/diff.mjs +33 -0
  378. package/modules/portability/export.mjs +129 -0
  379. package/modules/portability/files.mjs +65 -0
  380. package/modules/portability/format.mjs +122 -0
  381. package/modules/portability/generated.mjs +60 -0
  382. package/modules/portability/hash.mjs +79 -0
  383. package/modules/portability/http.mjs +42 -0
  384. package/modules/portability/import.mjs +185 -0
  385. package/modules/portability/index.d.mts +58 -0
  386. package/modules/portability/index.mjs +22 -0
  387. package/modules/portability/journal.mjs +49 -0
  388. package/modules/portability/legacy-files.mjs +43 -0
  389. package/modules/portability/legacy-safety.mjs +27 -0
  390. package/modules/portability/lines.mjs +36 -0
  391. package/modules/portability/migration-history.mjs +18 -0
  392. package/modules/portability/migration-statements.mjs +23 -0
  393. package/modules/portability/payloads.mjs +52 -0
  394. package/modules/portability/provider-data.mjs +25 -0
  395. package/modules/portability/restore-safety.mjs +30 -0
  396. package/modules/portability/schema/portability.sql +25 -0
  397. package/modules/portability/storage.mjs +49 -0
  398. package/modules/release/README.md +212 -0
  399. package/modules/release/catalogue.mjs +79 -0
  400. package/modules/release/export.mjs +51 -0
  401. package/modules/release/produce.mjs +136 -0
  402. package/modules/release/public-child-transport-state.cjs +29 -0
  403. package/modules/release/public-child.mjs +71 -0
  404. package/modules/release/public-git.mjs +21 -0
  405. package/modules/release/publication-scan.mjs +22 -0
  406. package/modules/release/publish-handoff.mjs +37 -0
  407. package/modules/release/source.mjs +71 -0
  408. package/modules/release/test-summary.mjs +50 -0
  409. package/onboarding/README.md +163 -0
  410. package/onboarding/activity.mjs +31 -0
  411. package/onboarding/adapter-auth.mjs +88 -0
  412. package/onboarding/adapter-hosting.mjs +35 -0
  413. package/onboarding/adapter-labels.mjs +13 -0
  414. package/onboarding/adapter-settings.mjs +129 -0
  415. package/onboarding/adapter.mjs +238 -0
  416. package/onboarding/agent-handoff.mjs +9 -0
  417. package/onboarding/app-processes.mjs +21 -0
  418. package/onboarding/archive-output.d.mts +3 -0
  419. package/onboarding/archive-output.mjs +54 -0
  420. package/onboarding/command-custody.mjs +24 -0
  421. package/onboarding/command-recovery.mjs +8 -0
  422. package/onboarding/commands.mjs +166 -0
  423. package/onboarding/dependency-approval.mjs +36 -0
  424. package/onboarding/dev-identity.mjs +23 -0
  425. package/onboarding/email.mjs +24 -0
  426. package/onboarding/failures.mjs +42 -0
  427. package/onboarding/input-limits.mjs +7 -0
  428. package/onboarding/installer-inputs.mjs +24 -0
  429. package/onboarding/launcher.mjs +34 -0
  430. package/onboarding/local/build-files.mjs +64 -0
  431. package/onboarding/local/build.mjs +49 -0
  432. package/onboarding/local/environment.mjs +30 -0
  433. package/onboarding/local/files.mjs +35 -0
  434. package/onboarding/local/operation.mjs +166 -0
  435. package/onboarding/local/owner.mjs +34 -0
  436. package/onboarding/local/process-table.mjs +114 -0
  437. package/onboarding/local/recovery.mjs +79 -0
  438. package/onboarding/local/state.mjs +31 -0
  439. package/onboarding/metadata.mjs +7 -0
  440. package/onboarding/operator-install.mjs +45 -0
  441. package/onboarding/operator-installer.mjs +39 -0
  442. package/onboarding/operator-prerequisites.mjs +27 -0
  443. package/onboarding/package.json +6 -0
  444. package/onboarding/prefill.mjs +16 -0
  445. package/onboarding/private-files.mjs +97 -0
  446. package/onboarding/private.mjs +35 -0
  447. package/onboarding/process-cleanup.mjs +134 -0
  448. package/onboarding/process-diagnostics.mjs +91 -0
  449. package/onboarding/process-scope.mjs +118 -0
  450. package/onboarding/process-table.mjs +118 -0
  451. package/onboarding/process-tree.mjs +36 -0
  452. package/onboarding/production-modules.mjs +22 -0
  453. package/onboarding/production-options.mjs +40 -0
  454. package/onboarding/production-plan.mjs +8 -0
  455. package/onboarding/production-runtime.mjs +30 -0
  456. package/onboarding/project-vite.mjs +14 -0
  457. package/onboarding/providers.mjs +289 -0
  458. package/onboarding/queue.mjs +53 -0
  459. package/onboarding/queued-entry.mjs +34 -0
  460. package/onboarding/readiness.mjs +59 -0
  461. package/onboarding/redaction-jwt.mjs +32 -0
  462. package/onboarding/redaction.mjs +298 -0
  463. package/onboarding/schema/SETUP.schema.json +514 -0
  464. package/onboarding/schema/dependencies.mjs +35 -0
  465. package/onboarding/schema/json-schema.mjs +202 -0
  466. package/onboarding/schema/setup-validation.mjs +325 -0
  467. package/onboarding/secrets.mjs +21 -0
  468. package/onboarding/server.mjs +188 -0
  469. package/onboarding/setup-manifest.mjs +116 -0
  470. package/onboarding/state.mjs +46 -0
  471. package/onboarding/theme-files.mjs +37 -0
  472. package/onboarding/theme.mjs +118 -0
  473. package/onboarding/ui/activity.js +98 -0
  474. package/onboarding/ui/address-client.mjs +33 -0
  475. package/onboarding/ui/app.js +377 -0
  476. package/onboarding/ui/index.html +1 -0
  477. package/onboarding/ui/model-core.mjs +127 -0
  478. package/onboarding/ui/model.mjs +5 -0
  479. package/onboarding/ui/style.css +63 -0
  480. package/onboarding/ui/theme.css +37 -0
  481. package/onboarding/ui/workspace-art.js +30 -0
  482. package/onboarding/wrangler-auth.mjs +43 -0
  483. package/operations/README.md +246 -0
  484. package/operations/operations.mjs +168 -0
  485. package/operations/project-operations.mjs +157 -0
  486. package/package.json +11 -4
  487. package/scripts/publish-check/README.md +72 -0
  488. package/scripts/publish-check/checksum_maps.py +52 -0
  489. package/scripts/publish-check/cli.py +34 -0
  490. package/scripts/publish-check/command_arguments.py +51 -0
  491. package/scripts/publish-check/command_shell_quotes.py +41 -0
  492. package/scripts/publish-check/command_source_quotes.py +120 -0
  493. package/scripts/publish-check/content_identities.py +250 -0
  494. package/scripts/publish-check/copy_identities.py +41 -0
  495. package/scripts/publish-check/der.py +61 -0
  496. package/scripts/publish-check/destination.py +117 -0
  497. package/scripts/publish-check/env_values.py +74 -0
  498. package/scripts/publish-check/font_licence.py +23 -0
  499. package/scripts/publish-check/git_history.py +115 -0
  500. package/scripts/publish-check/github_refs.py +217 -0
  501. package/scripts/publish-check/gitleaks.py +72 -0
  502. package/scripts/publish-check/gitleaks.toml +40 -0
  503. package/scripts/publish-check/history_scan.py +176 -0
  504. package/scripts/publish-check/hit_location.py +18 -0
  505. package/scripts/publish-check/hygiene_config.py +42 -0
  506. package/scripts/publish-check/hygiene_text.py +11 -0
  507. package/scripts/publish-check/identity-recipe.mjs +54 -0
  508. package/scripts/publish-check/json_identity.py +79 -0
  509. package/scripts/publish-check/license-exceptions.json +11 -0
  510. package/scripts/publish-check/licenses.py +49 -0
  511. package/scripts/publish-check/policy.py +18 -0
  512. package/scripts/publish-check/project_identity.py +89 -0
  513. package/scripts/publish-check/provenance.py +91 -0
  514. package/scripts/publish-check/provenance_pins.py +33 -0
  515. package/scripts/publish-check/public_base.py +63 -0
  516. package/scripts/publish-check/public_hygiene.py +80 -0
  517. package/scripts/publish-check/publication-config.json +12 -0
  518. package/scripts/publish-check/publication_config.py +34 -0
  519. package/scripts/publish-check/report.py +52 -0
  520. package/scripts/publish-check/repository_commands.py +75 -0
  521. package/scripts/publish-check/repository_intent.py +52 -0
  522. package/scripts/publish-check/reviewed.py +41 -0
  523. package/scripts/publish-check/source_hashes.mjs +6 -0
  524. package/scripts/publish-check/tree_scan.py +88 -0
  525. package/scripts/publish-check/trust_bundle.py +87 -0
  526. package/scripts/publish-check.mjs +58 -0
@@ -0,0 +1,98 @@
1
+ // This view owns only the sidebar status; polling never replaces form controls.
2
+ export function activityStatus(container, { request, clipboard = container.ownerDocument.defaultView?.navigator?.clipboard } = {}) {
3
+ const document = container.ownerDocument;
4
+ const node = (tag, className, text) => {
5
+ const element = document.createElement(tag);
6
+ element.className = className;
7
+ if (text !== undefined) element.textContent = text;
8
+ return element;
9
+ };
10
+ container.className = 'agent-activity known-group';
11
+ container.hidden = true;
12
+ const header = node('div', 'known-group-header');
13
+ const title = node('h2', 'known-group-title', 'Preparing setup');
14
+ header.append(title);
15
+ container.setAttribute('aria-label', 'Preparing setup');
16
+ const latest = node('p', 'agent-activity-message', '');
17
+ latest.hidden = true;
18
+ latest.setAttribute('role', 'status');
19
+ latest.setAttribute('aria-live', 'polite');
20
+ latest.setAttribute('aria-atomic', 'true');
21
+ const history = node('details', 'agent-activity-history');
22
+ history.hidden = true;
23
+ history.append(node('summary', '', 'Recent updates'));
24
+ const list = node('ul', 'agent-activity-events');
25
+ history.append(list);
26
+ const connect = node('button', 'agent-connect', 'Connect an agent');
27
+ connect.type = 'button'; connect.hidden = true;
28
+ const working = node('span', 'agent-working'); working.hidden = true;
29
+ const spinner = node('span', 'agent-working-spinner'); spinner.setAttribute('aria-hidden', 'true');
30
+ working.append(spinner, node('span', '', 'Working'));
31
+ header.append(connect, working);
32
+ const dialog = node('dialog', 'agent-handoff');
33
+ dialog.setAttribute('aria-labelledby', 'agent-handoff-title');
34
+ dialog.setAttribute('aria-describedby', 'agent-handoff-description');
35
+ const dialogTitle = node('h2', '', 'Connect an agent'); dialogTitle.id = 'agent-handoff-title';
36
+ const description = node('p', 'help', 'Give this prompt to a coding agent running on this computer to connect it to this setup session.'); description.id = 'agent-handoff-description';
37
+ const label = node('label', 'visually-hidden', 'Connection prompt'); label.htmlFor = 'agent-handoff-prompt';
38
+ const prompt = node('textarea', 'input'); prompt.id = 'agent-handoff-prompt'; prompt.readOnly = true; prompt.rows = 8; prompt.spellcheck = false;
39
+ const notice = node('p', 'status'); notice.setAttribute('role', 'status'); notice.setAttribute('aria-live', 'polite');
40
+ const actions = node('div', 'agent-handoff-actions');
41
+ const copy = node('button', 'primary', 'Copy prompt'); copy.type = 'button'; copy.disabled = true;
42
+ const close = node('button', 'secondary', 'Close'); close.type = 'button'; close.autofocus = true;
43
+ actions.append(copy, close); dialog.append(dialogTitle, description, label, prompt, notice, actions);
44
+ container.append(header, latest, history, dialog);
45
+ connect.onclick = async () => {
46
+ prompt.value = ''; copy.disabled = true; notice.textContent = 'Loading connection prompt…';
47
+ dialog.showModal();
48
+ try {
49
+ const result = await request('/api/agent-handoff');
50
+ if (typeof result.prompt !== 'string' || !result.prompt.trim()) throw Error('The installer did not return a connection prompt.');
51
+ prompt.value = result.prompt; copy.disabled = false; notice.textContent = '';
52
+ } catch (cause) { notice.textContent = cause.message || 'Unable to load the connection prompt.'; }
53
+ };
54
+ copy.onclick = async () => {
55
+ try {
56
+ if (!clipboard?.writeText) throw Error('Clipboard access is unavailable. Select and copy the prompt above.');
57
+ await clipboard.writeText(prompt.value); notice.textContent = 'Prompt copied. Paste it into your coding agent.';
58
+ } catch { notice.textContent = 'Could not copy automatically. Select and copy the prompt above.'; prompt.focus(); prompt.select(); }
59
+ };
60
+ close.onclick = () => dialog.close();
61
+ let historySignature = '', latestId = -1;
62
+ return (activity, agent, { showManual = false } = {}) => {
63
+ const attached = agent?.mode === 'connected' && agent.attached === true;
64
+ const manual = agent?.mode === 'manual';
65
+ const connectingAgent = agent?.mode === 'connecting';
66
+ const visible = attached || connectingAgent || (manual && showManual);
67
+ container.hidden = !visible;
68
+ if (!visible) { working.hidden = true; latest.hidden = true; history.hidden = true; return; }
69
+ const heading = attached ? 'Agent status' : manual ? 'Manual setup' : 'Preparing setup';
70
+ if (title.textContent !== heading) { title.textContent = heading; container.setAttribute('aria-label', heading); }
71
+ connect.hidden = !manual; latest.hidden = !attached && !manual && agent?.mode !== 'connecting';
72
+ if (!attached) {
73
+ working.hidden = true; header.setAttribute('aria-busy', 'false');
74
+ container.dataset.phase = manual ? 'manual' : agent?.mode === 'connecting' ? 'connecting' : 'preparing';
75
+ const message = manual ? 'Complete the setup steps to get started.' : agent?.mode === 'connecting' ? 'Waiting for your agent' : '';
76
+ if (latest.textContent !== message) latest.textContent = message;
77
+ history.hidden = true; return;
78
+ }
79
+ const events = Array.isArray(activity) ? activity.filter(event => event && typeof event.message === 'string' && event.message.trim()) : [];
80
+ const current = events.at(-1);
81
+ if (latestId >= 0 && (!Number.isFinite(current?.id) || current.id < latestId)) return;
82
+ if (Number.isFinite(current?.id)) latestId = current.id;
83
+ const message = current?.message || 'Waiting for setup updates';
84
+ // Keep the live region unchanged on identical polls, including new IDs with identical text.
85
+ if (latest.textContent !== message) latest.textContent = message;
86
+ const phase = current?.phase || 'waiting';
87
+ container.dataset.phase = phase;
88
+ working.hidden = phase !== 'checking';
89
+ header.setAttribute('aria-busy', String(phase === 'checking'));
90
+ const recent = events.slice(-4, -1).reverse();
91
+ const signature = JSON.stringify(recent.map(event => [event.id, event.message]));
92
+ if (signature !== historySignature) {
93
+ list.replaceChildren(...recent.map(event => node('li', '', event.message)));
94
+ historySignature = signature;
95
+ }
96
+ history.hidden = !recent.length;
97
+ };
98
+ }
@@ -0,0 +1,33 @@
1
+
2
+ // Resolve an address from real Cloudflare account information, never a guessed URL.
3
+ export function addressResolver({ read, request, answer, changed, model: {fields,derivePrefix,secretReady,nameKey} }) {
4
+ let timer, lastKey = '', generation = 0;
5
+ const status = { pending: false, error: '' };
6
+ function schedule(force = false) {
7
+ const { draft, known } = read();
8
+ const prefix = draft.prefixMode === 'custom' ? draft.prefix : derivePrefix(draft[nameKey]);
9
+ if (draft.prefixMode !== 'custom' && draft.prefix !== prefix) answer('prefix', prefix);
10
+ if (draft.target !== 'remote' || draft.addressMode === 'custom') { clearTimeout(timer); generation++; lastKey = ''; status.pending = false; status.error = ''; return; }
11
+ const ready = fields[nameKey]?.valid(draft[nameKey]) && fields.accountId.valid(draft.accountId) && (known.providers?.cloudflare?.authenticated || draft.apiToken || secretReady('apiToken', draft, known));
12
+ if (!ready) { clearTimeout(timer); generation++; lastKey = ''; status.pending = false; status.error = ''; if (draft.appOrigin) answer('appOrigin', ''); return; }
13
+ const key = JSON.stringify([draft[nameKey], draft.accountId, prefix, draft.apiToken || '', known.providers?.cloudflare?.authenticated, secretReady('apiToken', draft, known)]);
14
+ if (!force && key === lastKey) return;
15
+ clearTimeout(timer); lastKey = key; const version = ++generation;
16
+ if (draft.appOrigin) answer('appOrigin', '');
17
+ status.pending = true; status.error = ''; changed();
18
+ timer = setTimeout(async () => {
19
+ try {
20
+ const payload = { name: draft[nameKey], accountId: draft.accountId };
21
+ if (draft.prefixMode === 'custom') payload.prefix = draft.prefix;
22
+ if (draft.apiToken) payload.apiToken = draft.apiToken;
23
+ const result = await request('/api/address', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
24
+ const current = read().draft;
25
+ if (version !== generation || current.addressMode === 'custom' || current.target !== 'remote') return;
26
+ if (current.prefixMode !== 'custom') answer('prefix', result.prefix);
27
+ answer('appOrigin', result.appOrigin);
28
+ } catch (cause) { if (version === generation) status.error = cause.message; }
29
+ finally { if (version === generation) { status.pending = false; changed(); } }
30
+ }, 400);
31
+ }
32
+ return { schedule, status };
33
+ }
@@ -0,0 +1,377 @@
1
+ import { activityStatus } from './activity.js';
2
+ import { workspaceArt } from './workspace-art.js';
3
+ import { addressResolver } from './address-client.mjs';
4
+ import { product, branding, features, summaryDefaults, readySummary, fields, normalizeWebsite, initialDraft, setupModel, safeDraft, installInput, displayValue, summaryKeys, failureDetails, mergeAgentDraft, secretReady, hostingModel, normalizeAutomaticDraft, derivePrefix, setupGroups, setupProgress, fieldVisible, fieldDefinition, groupDefinition, groupKeys, secretScopes, nameKey } from './model.mjs';
5
+ const $ = selector => document.querySelector(selector);
6
+ document.title = 'Install ' + product.name;
7
+ $('.brand').textContent = branding.name || product.name;
8
+ $('.panel').setAttribute('aria-label', 'Install ' + product.name);
9
+ $('#open').textContent = 'Open ' + product.name + ' →';
10
+ const updateActivity = activityStatus($('#activity'), { request });
11
+ const tokenMeta = $('meta[name="installer-token"]'), token = tokenMeta?.content || '';
12
+ tokenMeta?.remove();
13
+ let draft = {}, state = {}, initialized = false, busy = false, stopping = false, timer, saveTimer, activeGroup = null, agentRevision = 0, tokenEditing = false;
14
+ const resolver = features.hosting ? addressResolver({ model: {fields,derivePrefix,secretReady,nameKey}, read: () => ({ draft, known: state.known || {} }), request, answer: setAnswer, changed: () => { if (initialized) renderPreservingFocus(); } }) : null;
15
+ const art = features.artwork ? workspaceArt({ model: {fields}, request, read: () => draft, save }) : null;
16
+ const changed = new Set(), pendingSave = new Set(), history = [], visitedGroups = [], reviewedDefaultGroups = new Set();
17
+ let editingGroup = false;
18
+ let visibleKeys = new Set(), editingSecrets = new Set(), hostingAccountShown = false;
19
+ let followLogs = true;
20
+ $('#logs').addEventListener('scroll', () => {
21
+ const logs = $('#logs');
22
+ followLogs = logs.scrollHeight - logs.clientHeight - logs.scrollTop <= 2;
23
+ });
24
+ function selectGroup(id, edit = false, editing = edit) {
25
+ editingGroup = editing;
26
+ if (id && !editing && !visitedGroups.includes(id)) visitedGroups.push(id);
27
+ activeGroup = id; editingSecrets.clear(); hostingAccountShown = false;
28
+ const group = setupGroups.find(group => group.id === id), missing = setupModel(draft, state.known).missing;
29
+ visibleKeys = new Set((group ? groupKeys(group,draft).filter(key => edit || fieldDefinition(key,draft).optional || missing.includes(key)) : []) || []);
30
+ }
31
+ function hasAnswered(key) {
32
+ return changed.has(key) || state.answered?.includes(key) || ['agent', 'browser', 'saved'].includes(state.sources?.[key]);
33
+ }
34
+ function hasAnswers() {
35
+ return Boolean(state.answered?.length || Object.keys(state.sources || {}).length || changed.size || reviewedDefaultGroups.size);
36
+ }
37
+ function groupNeedsOwnerReview(group) {
38
+ return features.localTrialWithoutEmail === true && features.hosting && group?.kind === 'hosting' && !hasAnswered('target');
39
+ }
40
+ function nextSetupGroup() {
41
+ return setupGroups.find(group => !group.complete(draft, state.known) || (groupNeedsOwnerReview(group) && !reviewedDefaultGroups.has(group.id)))?.id || null;
42
+ }
43
+ function node(tag, className, text) { const result = document.createElement(tag); if (className) result.className = className; if (text !== undefined) result.textContent = text; return result; }
44
+ function error(message = '') { $('#error').textContent = message; $('#error').hidden = !message; }
45
+ async function request(path, options = {}) {
46
+ const response = await fetch(path, { ...options, headers: { ...options.headers, 'X-Installer-Token': token }, credentials: 'same-origin', cache: 'no-store' });
47
+ const body = await response.json(); if (!response.ok) throw Error(body.error || `Installer request failed (${response.status}).`); return body;
48
+ }
49
+ async function save() {
50
+ clearTimeout(saveTimer); const values = safeDraft(Object.fromEntries([...pendingSave].map(key => [key, draft[key]]))); if (!Object.keys(values).length) return;
51
+ await request('/api/draft', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values) });
52
+ for (const [key, value] of Object.entries(values)) if (draft[key] === value) pendingSave.delete(key);
53
+ }
54
+ function scheduleSave() { clearTimeout(saveTimer); saveTimer = setTimeout(() => { save().catch(cause => error(cause.message)); }, 600); }
55
+ function inputFor(key, value) {
56
+ const definition = fieldDefinition(key,draft), accounts = state.known?.providers?.cloudflare?.accounts || [];
57
+ const options = key === 'accountId' && accounts.length ? accounts.map(account => [account.id, account.name || account.id]) : definition.options;
58
+ const input = node(options ? 'select' : definition.type === 'textarea' ? 'textarea' : 'input', 'input'); input.id = key; input.name = key;
59
+ if (options) { const placeholder = node('option', '', 'Choose…'); placeholder.value = ''; input.append(placeholder); for (const [value, title] of options) { const option = node('option', '', title); option.value = value; input.append(option); } }
60
+ else if (definition.type === 'textarea') { input.rows = 3; }
61
+ else { input.type = definition.type || 'text'; input.autocomplete = definition.autocomplete || 'off'; if (definition.inputMode) input.inputMode = definition.inputMode; if (['email', 'url', 'password'].includes(input.type)) { input.spellcheck = false; input.setAttribute('autocapitalize', 'none'); } }
62
+ if (definition.placeholder) input.placeholder = definition.placeholder;
63
+ if (definition.maxLength) input.maxLength = definition.maxLength;
64
+ input.value = definition.type === 'color' ? (definition.valid(value) ? value : '#7c3aed') : value || ''; input.required = !definition.optional;
65
+ if (input.type === 'email') input.maxLength = 320;
66
+ return input;
67
+ }
68
+ function appendInput(container, key, input, value) {
69
+ if (input.type === 'color') {
70
+ const wrap = node('div', 'colour-control'), selected = fields[key].valid(value), caption = node('span', 'colour-caption', selected ? value : 'Choose a colour');
71
+ wrap.append(input, caption); input.addEventListener('input', () => { caption.textContent = input.value; });
72
+ container.append(wrap);
73
+ } else if (input.tagName === 'SELECT') { const wrap = node('div', 'select-wrap'); wrap.append(input); container.append(wrap); }
74
+ else container.append(input);
75
+ }
76
+ function setAnswer(key, value) {
77
+ if (draft[key] !== value) art?.edited(key);
78
+ if (key === 'accountId' && fields.accountId?.valid(draft.accountId) && draft.accountId !== value) draft.apiToken = '';
79
+ for(const [secret,dependencies]of Object.entries(secretScopes))if(dependencies.includes(key)&&draft[key]!==value){draft[secret]='';const input=document.getElementById(secret);if(input)input.value='';}
80
+ draft[key] = value; changed.add(key); pendingSave.add(key); scheduleSave();
81
+ if ([nameKey, 'accountId', 'apiToken', 'target', 'prefixMode', 'addressMode'].includes(key) || (key === 'prefix' && draft.prefixMode === 'custom')) queueMicrotask(() => resolver?.schedule());
82
+ }
83
+ function missingField(key, container = $('#fields')) {
84
+ const definition = fieldDefinition(key,draft), card = node('div', 'field'), label = node('label', '', definition.label), input = inputFor(key, draft[key]);
85
+ label.htmlFor = key; card.dataset.field = key; if (definition.heading) card.append(node('h3', 'field-title', definition.heading)); card.append(label); appendInput(card, key, input, draft[key]);
86
+ if (definition.help) { const help = node('p', 'help', definition.help); help.id = key + '-help'; card.append(help); input.setAttribute('aria-describedby', help.id); }
87
+ input.addEventListener('input', () => { setAnswer(key, input.value); refreshStatus(); });
88
+ if (definition.artwork || definition.type === 'url') input.addEventListener('blur', () => {
89
+ if (definition.type === 'url') {
90
+ let value = normalizeWebsite(input.value);
91
+ if (definition.valid(value)) value = new URL(value).origin;
92
+ if (value !== input.value) { input.value = value; setAnswer(key, value); refreshStatus(); }
93
+ }
94
+ if (definition.artwork) void art?.commit()?.catch(cause => error(cause.message));
95
+ });
96
+ input.addEventListener('change', () => {
97
+ if (definition.type === 'url') {
98
+ input.value = normalizeWebsite(input.value);
99
+ if (definition.valid(input.value)) input.value = new URL(input.value).origin;
100
+ }
101
+ setAnswer(key, input.value);
102
+ if (definition.artwork) void art?.commit()?.catch(cause => error(cause.message));
103
+ const dependency=Object.values(fields).some(field=>field.when?.field===key||field.presentations?.some(item=>item.when.field===key))||setupGroups.some(group=>group.presentations?.some(item=>item.when.field===key));
104
+ if(dependency||['target','accountId','apiToken'].includes(key))renderPreservingFocus();else refreshStatus();
105
+ });
106
+ container.append(card);
107
+ }
108
+ function knownRow(container, key, value, source) {
109
+ const row = node('div', 'known-row'), display = node('div', 'known-value');
110
+ if (fields[key].type === 'color' && fields[key].valid(value)) {
111
+ const swatch = node('span', 'colour-swatch'); swatch.style.backgroundColor = value; swatch.setAttribute('aria-hidden', 'true'); display.append(swatch);
112
+ } else if (fields[key].type === 'email') {
113
+ const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
114
+ icon.classList.add('value-icon'); icon.setAttribute('aria-hidden', 'true'); use.setAttribute('href', '#group-email'); icon.append(use); display.append(icon);
115
+ }
116
+ display.append(node('span', 'known-value-text', value)); row.append(node('div', 'known-label', fields[key]?.summaryLabel || fields[key]?.label || key), display);
117
+ if (source) { row.title = source; row.append(node('span', 'visually-hidden', source)); if (source === 'Edition default') row.append(node('span', 'known-source', source)); }
118
+ container.append(row);
119
+ return row;
120
+ }
121
+ function targetChoices(container) {
122
+ const choices = node('fieldset', 'target-choices'); choices.append(node('legend', 'known-label', `Where should ${product.name} run?`));
123
+ for (const [value, title] of fields.target.options) {
124
+ const label = node('label', 'target-choice'), input = node('input'); input.type = 'radio'; input.name = 'installation-target'; input.value = value; input.checked = draft.target === value;
125
+ input.onchange = () => { setAnswer('target', value); renderPreservingFocus(); $('#target-' + value)?.focus(); }; input.id = 'target-' + value;
126
+ const tick = node('span', 'target-tick', '✓'); tick.setAttribute('aria-hidden', 'true'); label.append(input, tick);
127
+ if (value === 'remote') {
128
+ const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); icon.classList.add('value-icon'); icon.setAttribute('aria-hidden', 'true'); use.setAttribute('href', '#value-cloudflare'); icon.append(use); label.append(icon);
129
+ }
130
+ const copy = node('span', 'choice-copy', title); if (container.closest('#fields')) copy.append(node('small', 'help', value === 'local' ? 'Uses the machine running this installer, for testing. Keep its terminal open.' : `Publish ${product.name} online for your team.`)); label.append(copy); choices.append(label);
131
+ }
132
+ container.append(choices);
133
+ }
134
+ function openGroup(id) {
135
+ if (activeGroup !== id) history.push({ id: activeGroup, editing: editingGroup });
136
+ selectGroup(id, true); tokenEditing = false;
137
+ render(); focusGroup();
138
+ }
139
+ function focusGroup() { $('#heading').focus(); }
140
+ function renderKnown() {
141
+ const container = $('#known'); container.replaceChildren();
142
+ const reviewedDefaults = setupGroups.filter(group => reviewedDefaultGroups.has(group.id)).flatMap(group => group.keys.filter(key => fields[key]?.valid(draft[key])));
143
+ const answered = [...new Set([...(state.answered || []), ...changed, ...reviewedDefaults])];
144
+ const visibleAnswers = new Set(summaryKeys(draft, { ...state, answered }));
145
+ for (const baseDefinition of setupGroups) {
146
+ const definition=groupDefinition(baseDefinition,draft);
147
+ const keys = groupKeys(definition,draft).filter(key => visibleAnswers.has(key) && !(definition.id === activeGroup && (definition.kind === 'hosting' || visibleKeys.has(key))) && !['prefix', 'apiToken'].includes(key));
148
+ const optionalAvailable=definition.kind!=='hosting'&&definition.id!==activeGroup&&groupKeys(definition,draft).some(key=>fieldDefinition(key,draft).optional);
149
+ if (!keys.length && !optionalAvailable) continue;
150
+ const group = node('section', 'known-group ' + definition.id); group.setAttribute('aria-label', definition.title);
151
+ const header = node('header', 'known-group-header'), icon = node('span', 'category-icon'); icon.setAttribute('aria-hidden', 'true');
152
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttribute('href', '#group-' + definition.id); svg.append(use); icon.append(svg);
153
+ const edit = node('button', 'edit', 'Edit'); edit.type = 'button'; edit.setAttribute('aria-label', 'Edit ' + definition.title); edit.onclick = () => openGroup(definition.id);
154
+ header.append(icon, node('h3', 'known-group-title', definition.title), edit);
155
+ const body = node('div', 'known-group-body'); group.append(header, body);
156
+ for (const key of keys) {
157
+ let value = displayValue(key, draft[key]);
158
+ if (!value) continue;
159
+ if (fields[key].type === 'password') continue;
160
+ knownRow(body, key, value, !hasAnswered(key) && summaryDefaults.includes(key) ? 'Edition default' : '', false);
161
+ }
162
+ if (body.children.length || optionalAvailable) container.append(group);
163
+ }
164
+ const summary = $('#known-summary');
165
+ const optionalGroupsAvailable=setupGroups.some(group=>group.kind!=='hosting'&&group.id!==activeGroup&&groupKeys(group,draft).some(key=>fieldDefinition(key,draft).optional));
166
+ summary.hidden = state.phase !== 'ready' || (!hasAnswers()&&!optionalGroupsAvailable) || !container.children.length;
167
+ if (summary.hidden) summary.open = false;
168
+ }
169
+ function refreshSetupChrome() {
170
+ const ready = state.phase === 'ready';
171
+ $('#progress').hidden = !ready;
172
+ updateActivity(state.activity, state.agent, { showManual: hasAnswers() });
173
+ if (!ready) $('#activity').hidden = true;
174
+ renderKnown();
175
+ }
176
+
177
+ function providerCard(provider, container = $('#fields')) {
178
+ const info = state.known?.providers?.[provider] || {}, connection = state.connection?.provider === provider ? state.connection : null;
179
+ const card = node('div', 'field connection-row'), title = provider === 'github' ? (info.authenticated ? 'Change GitHub connection' : 'GitHub (optional)') : info.authenticated ? 'Change Cloudflare connection' : 'Connect Cloudflare'; card.dataset.provider = provider; card.append(node('h3', 'field-title', title));
180
+ if (provider === 'github') {
181
+ if (info.authenticated) card.append(node('p', 'help', 'This account comes from GitHub CLI sign-in. Sign in with the account you want to use; this updates the active GitHub CLI connection on this computer.'));
182
+ else if (info.installed === true) card.append(node('p', 'help', 'You can connect GitHub later. Connecting lets this installer identify the GitHub account for setup.'));
183
+ else {
184
+ const help = node('p', 'help', 'You can connect GitHub later. Install GitHub CLI, then run gh auth login. ');
185
+ const link = node('a', 'help-link', 'cli.github.com'); link.href = 'https://cli.github.com'; link.target = '_blank'; link.rel = 'noopener noreferrer'; help.append(link); card.append(help);
186
+ }
187
+ } else if (info.authenticated) card.append(node('p', 'help', 'Sign in with the Cloudflare account you want to use. This updates the Wrangler connection on this computer.'));
188
+ else if (info.installed === false) card.append(node('p', 'help', 'Wrangler is not installed. Run npm install --global wrangler, then connect your Cloudflare account. For online setup, you can use an API token instead.'));
189
+ else card.append(node('p', 'help', draft.target === 'local' ? 'For Cloudflare hosting. Not needed to run on this computer.' : draft.target === 'remote' ? 'Connect your account or supply an API token to install on Cloudflare.' : 'Required if you install on Cloudflare.'));
190
+ const connecting = connection && ['starting', 'awaiting_browser', 'refreshing', 'running'].includes(connection.state), actions = node('div', 'connection-actions');
191
+ const canConnect = provider === 'github' ? info.installed === true : info.installed !== false;
192
+ if (canConnect) {
193
+ const button = node('button', 'secondary', connecting ? 'Connecting…' : info.authenticated ? provider === 'github' ? 'Sign in to GitHub' : 'Sign in to Cloudflare' : 'Connect'); button.type = 'button'; button.id = 'connect-' + provider; button.disabled = Boolean(connecting);
194
+ button.onclick = async () => { button.disabled = true; error(); try { await request('/api/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider }) }); await poll(); } catch (cause) { error(cause.message); button.disabled = false; } }; actions.append(button);
195
+ }
196
+ if (provider === 'cloudflare' && draft.target === 'remote') { const alternative = node('button', 'edit', 'Use an API token'); alternative.type = 'button'; alternative.onclick = () => { tokenEditing = true; render(); $('#apiToken')?.focus(); }; actions.append(alternative); }
197
+ card.append(actions);
198
+ if (connection) {
199
+ const text = { starting: 'Starting sign-in…', awaiting_browser: 'Complete sign-in in your browser.', refreshing: 'Checking the connection…', connected: 'Connection confirmed.', auth_required: 'Sign-in was not confirmed. Try connecting again.', failed: 'Connection failed. Check your installer terminal.' }[connection.state] || connection.state;
200
+ card.append(node('p', 'status', text));
201
+ if (connection.deviceCode) { card.append(node('p', '', `One-time code: ${connection.deviceCode}`)); if (connection.verificationUrl === 'https://github.com/login/device') { const link = node('a', 'edit', 'Open GitHub device sign-in ↗'); link.href = connection.verificationUrl; link.target = '_blank'; link.rel = 'noopener noreferrer'; card.append(link); } }
202
+ }
203
+ if (provider === 'cloudflare' && tokenEditing) missingField('apiToken', card);
204
+ container.append(card);
205
+ }
206
+ function renderHosting() {
207
+ const hosting = hostingModel(draft, state.known);
208
+ const unit = node('section', 'hosting-unit target-question'); unit.setAttribute('aria-label', 'Hosting & connections'); $('#fields').append(unit);
209
+ {
210
+ unit.dataset.editing = 'true'; targetChoices(unit);
211
+ if (draft.target === 'remote') {
212
+ if (!hosting.accessReady || tokenEditing) providerCard('cloudflare', unit);
213
+ else { unit.append(node('p', 'status', 'Cloudflare connected')); const change = node('button', 'edit', 'Change Cloudflare connection'); change.type = 'button'; change.onclick = () => { tokenEditing = true; renderPreservingFocus(); }; unit.append(change); }
214
+ if (hosting.accessReady) hostingAccountShown = true;
215
+ if (hostingAccountShown) {
216
+ missingField('accountId', unit);
217
+ if (resolver.status.pending) { const progress = node('p', 'status', 'Finding your team workspace address…'); progress.setAttribute('role', 'status'); unit.append(progress); }
218
+ if (resolver.status.error) {
219
+ const notice = node('p', 'error', resolver.status.error); notice.setAttribute('role', 'alert'); unit.append(notice);
220
+ const retry = node('button', 'edit', 'Try again'); retry.type = 'button'; retry.onclick = () => resolver?.schedule(true); unit.append(retry);
221
+ }
222
+ if (draft.appOrigin) unit.append(node('p', 'status', 'Team workspace address: ' + draft.appOrigin));
223
+ const advanced = node('details', 'advanced'); advanced.append(node('summary', '', 'Advanced settings'));
224
+ for (const [key, mode, title] of [['prefix', 'prefixMode', 'Use a custom resource name'], ['appOrigin', 'addressMode', 'Use a custom team workspace address']]) {
225
+ const label = node('label', 'advanced-toggle'), toggle = node('input'); toggle.type = 'checkbox'; toggle.id = mode; toggle.checked = draft[mode] === 'custom';
226
+ toggle.onchange = () => { setAnswer(mode, toggle.checked ? 'custom' : 'automatic'); if (!toggle.checked && key === 'appOrigin') setAnswer(key, ''); renderPreservingFocus(); resolver?.schedule(true); };
227
+ label.append(toggle, node('span', '', title)); advanced.append(label);
228
+ if (toggle.checked) missingField(key, advanced);
229
+ }
230
+ advanced.open = draft.prefixMode === 'custom' || draft.addressMode === 'custom'; unit.append(advanced);
231
+ }
232
+ }
233
+ }
234
+ if (features.github) providerCard('github', unit);
235
+ }
236
+ function choiceGroup(container, legend, options, selected, onChange) {
237
+ const group = node('fieldset', 'target-choices'); group.append(node('legend', '', legend));
238
+ for (const [value, text] of options) {
239
+ const label = node('label', 'target-choice'), input = node('input'); input.type = 'radio'; input.name = legend; input.value = value; input.id = 'choice-' + value; input.checked = value === selected;
240
+ input.onchange = () => { onChange(value); renderPreservingFocus(); }; const tick = node('span', 'target-tick', '✓'); tick.setAttribute('aria-hidden', 'true'); label.append(input, tick, node('span', '', text)); group.append(label);
241
+ }
242
+ container.append(group);
243
+ }
244
+ function refreshStatus() {
245
+ const model = setupModel(draft, state.known), baseGroup = setupGroups.find(group => group.id === activeGroup), group = baseGroup && groupDefinition(baseGroup,draft);
246
+ $('#continue').hidden = !group; $('#approve').hidden = Boolean(group);
247
+ $('#continue').disabled = busy || !group?.complete(draft, state.known);
248
+ $('#approve').disabled = busy || !model.ready;
249
+ $('#fields .review-correction')?.remove();
250
+ if (!activeGroup && !model.ready) { const fix = node('button', 'secondary review-correction', 'Complete missing details'); fix.type = 'button'; fix.onclick = () => { selectGroup(nextSetupGroup()); render(); focusGroup(); }; $('#fields').append(fix); }
251
+ $('#back').hidden = !history.length;
252
+ $('#progress').textContent = setupProgress(draft, state.known, { active: activeGroup, visited: visitedGroups, editing: editingGroup });
253
+ refreshSetupChrome();
254
+ }
255
+ function render() {
256
+ const container = $('#fields'); container.replaceChildren();
257
+ const baseGroup = setupGroups.find(group => group.id === activeGroup), group = baseGroup && groupDefinition(baseGroup,draft);
258
+ $('#heading').textContent = group?.title || 'Ready to install';
259
+ if(group?.help)container.append(node('p','help',group.help));
260
+ if (group && group.kind !== 'hosting') for (const key of groupKeys(group,draft)) {
261
+ if (setupModel(draft, state.known).missing.includes(key)) { visibleKeys.add(key); if (fields[key].type === 'password') editingSecrets.add(key); }
262
+ if (!visibleKeys.has(key)) continue;
263
+ if (fields[key].type === 'password' && secretReady(key, draft, state.known) && !draft[key] && !editingSecrets.has(key)) {
264
+ const row = node('div', 'status', fields[key].label + ' securely provided');
265
+ const change = node('button', 'edit', 'Change ' + fields[key].label.toLowerCase()); change.type = 'button'; change.onclick = () => { editingSecrets.add(key); render(); document.getElementById(key).focus(); }; row.append(change); container.append(row);
266
+ } else missingField(key);
267
+ }
268
+ if (group?.kind === 'hosting') { if (features.hosting) renderHosting(); }
269
+ if (!group) container.append(node('p', 'ready-summary', readySummary(draft)));
270
+ refreshStatus();
271
+ }
272
+ function renderPreservingFocus() {
273
+ const active = document.activeElement, focused = active?.id, start = active?.selectionStart, end = active?.selectionEnd;
274
+ render(); const next = focused && document.getElementById(focused);
275
+ if (next) { next.focus(); if (start !== null && start !== undefined) { try { next.setSelectionRange(start, end); } catch {} } }
276
+ else if (focused) ($('#accountId') || $('#fields input, #fields select, #fields button') || $('#approve'))?.focus();
277
+ }
278
+ function applyState(next) {
279
+ art?.update(next);
280
+ const changedProviders = JSON.stringify([state.known?.providers, state.connection, state.known?.secretReadiness]) !== JSON.stringify([next.known?.providers, next.connection, next.known?.secretReadiness]); state = next; $('#loading').hidden = true;
281
+ const agentDraft = mergeAgentDraft(draft, next, agentRevision, pendingSave), agentChanged = initialized && agentDraft !== draft;
282
+ if (Number.isFinite(next.lastUpdate?.revision)) agentRevision = Math.max(agentRevision, next.lastUpdate.revision);
283
+ if (!initialized) { draft = initialDraft(next); for (const key of ['prefixMode', 'addressMode']) if (features.hosting && draft[key] !== next.draft?.[key]) pendingSave.add(key); if (pendingSave.size) scheduleSave(); initialized = true; selectGroup(nextSetupGroup()); render(); resolver?.schedule(); }
284
+ else if (agentChanged && next.phase === 'ready') {
285
+ const appliedKeys = Object.keys(agentDraft).filter(key => agentDraft[key] !== draft[key]);
286
+ for(const [secret,dependencies]of Object.entries(secretScopes))if(dependencies.some(key=>agentDraft[key]!==draft[key])){
287
+ agentDraft[secret]='';
288
+ // Clearing the focused control also prevents its blur/change event from
289
+ // restoring stale private bytes while render replaces the old form.
290
+ const input=document.getElementById(secret);if(input)input.value='';
291
+ }
292
+ draft = normalizeAutomaticDraft(agentDraft); for (const key of appliedKeys) changed.delete(key); renderPreservingFocus();
293
+ }
294
+ else if (changedProviders && next.phase === 'ready') {
295
+ const provider = next.known?.providers?.cloudflare, accounts = provider?.accounts || [];
296
+ if (provider?.authenticated && accounts.length && draft.accountId && !accounts.some(account => account.id === draft.accountId)) setAnswer('accountId', '');
297
+ if (features.hosting && !draft.accountId && accounts.length === 1) setAnswer('accountId', accounts[0].id);
298
+ renderPreservingFocus();
299
+ }
300
+ resolver?.schedule();
301
+ const active = next.phase !== 'ready'; $('#form').hidden = active; $('#installation').hidden = !active;
302
+ refreshSetupChrome();
303
+ if (!active) { error(next.error || ''); return; }
304
+ busy = true; $('#heading').hidden = false;
305
+ const stopped = next.phase === 'failed' && /\bcancelled\b/i.test(typeof next.error === 'string' ? next.error.split('\n', 1)[0] : '');
306
+ $('#heading').textContent = next.phase === 'complete' ? 'Your workspace is ready' : stopped ? 'Stopped' : next.phase === 'failed' ? 'Installation needs attention' : 'Making a home for your team';
307
+ const stillStarting = (next.logs || []).slice().reverse().find(line => line.startsWith(`${next.step}: `) && /\bStill starting \(\d+ s\)$/.test(line));
308
+ $('#current-step').textContent = next.phase === 'running' && stillStarting ? stillStarting : next.step || (next.phase === 'running' ? 'Installation is running.' : next.phase === 'failed' ? 'Installation stopped.' : 'Installation complete.');
309
+ const logLines = [...(next.logs || [])];
310
+ const failure = failureDetails(next);
311
+ if (failure.output && !logLines.join('\n').includes(failure.output)) logLines.push(failure.output);
312
+ const logs = $('#logs'), scrollTop = logs.scrollTop;
313
+ logs.textContent = logLines.join('\n');
314
+ if (followLogs) logs.scrollTop = logs.scrollHeight;
315
+ else logs.scrollTop = scrollTop;
316
+ $('#failed-note').hidden = next.phase !== 'failed';
317
+ $('#failure-log-path').hidden = next.phase !== 'failed' || !failure.logPath;
318
+ $('#failure-log-path').textContent = failure.logPath ? `Installer log: ${failure.logPath}` : '';
319
+ $('#retry-installation').hidden = next.phase !== 'failed';
320
+ if (next.phase === 'failed') {
321
+ const diagnostic = failure.output ? `Output from ${failure.step} is shown in the log above.` : `Installation stopped during ${failure.step}. Review the installation activity above.`;
322
+ $('#failed-note').textContent = `${diagnostic} Choose Review saved answers to return to approval; after correcting the issue, choose Approve and install to try again.`;
323
+ }
324
+ $('#stop-installation').hidden = next.phase !== 'running';
325
+ if (next.phase !== 'running') { stopping = false; $('#stop-installation').disabled = false; $('#stop-installation').textContent = 'Stop installation'; }
326
+ if (stopped) error();
327
+ else if (next.phase === 'failed') error(failureHeadline(next.error, failure.step));
328
+ else error();
329
+ if (next.phase === 'complete' && next.appUrl) { try { const url = new URL(next.appUrl); if (['http:', 'https:'].includes(url.protocol)) { $('#open').href = url.href; $('#open').hidden = false; } } catch { error('The application address is invalid. Check the installer terminal.'); } }
330
+ // A dev-identity app's outbox opens through its key exchange; the key stays out of saved state.
331
+ if (next.phase === 'complete' && next.devOpen && !$('#open').dataset.devOpen) { $('#open').dataset.devOpen = 'pending'; request('/api/open-app').then(({ url }) => { if (new URL(url).origin === new URL(next.appUrl).origin) { $('#open').href = url; $('#open').dataset.devOpen = 'ready'; } }).catch(() => { delete $('#open').dataset.devOpen; }); }
332
+ }
333
+ function failureHeadline(message, step) {
334
+ const firstLine = typeof message === 'string' ? message.split('\n', 1)[0] : '';
335
+ const label = typeof step === 'string' && step.trim() ? step.replace(/\s+/g, ' ').trim() : 'Installation';
336
+ const conciseStep = label.length > 29 ? `${label.slice(0, 28).trimEnd()}…` : label;
337
+ const exit = firstLine.match(/\bexit (\d+)\b/i);
338
+ if (exit) return `${conciseStep}: exit ${exit[1]}.`;
339
+ const signal = firstLine.match(/\bsignal (?!none\b)([A-Z0-9]+)\b/i);
340
+ if (signal) return `${conciseStep}: signal ${signal[1]}.`;
341
+ if (/could not start/i.test(firstLine)) return `${conciseStep}: command could not start.`;
342
+ if (/readiness deadline exceeded/i.test(firstLine)) return `${conciseStep}: startup timed out.`;
343
+ return `${conciseStep}: failed.`;
344
+ }
345
+ function reviewSavedAnswers() {
346
+ if (state.phase !== 'failed') return;
347
+ busy = false; stopping = false;
348
+ state = { ...state, phase: 'ready', error: undefined };
349
+ $('#installation').hidden = true; $('#form').hidden = false; $('#open').hidden = true;
350
+ selectGroup(null); error();
351
+ render(); focusGroup();
352
+ }
353
+ async function poll() { clearTimeout(timer); try { const next = await request('/api/state'); applyState(next); if (!['complete', 'failed'].includes(next.phase)) timer = setTimeout(poll, 1500); } catch (cause) { error(cause.message + ' Keep the installer terminal open.'); timer = setTimeout(poll, 3000); } }
354
+ $('#form').onsubmit = async event => {
355
+ event.preventDefault(); if (busy) return; error();
356
+ try { await art?.commit(); } catch (cause) { error(cause.message); return; }
357
+ if (activeGroup) {
358
+ const baseGroup = setupGroups.find(group => group.id === activeGroup), group = baseGroup && groupDefinition(baseGroup,draft);
359
+ if (!group.complete(draft, state.known)) return;
360
+ history.push({ id: activeGroup, editing: editingGroup }); reviewedDefaultGroups.add(activeGroup); selectGroup(nextSetupGroup()); tokenEditing = false; render(); focusGroup(); return;
361
+ }
362
+ const model = setupModel(draft, state.known); if (!model.ready) { selectGroup(nextSetupGroup()); render(); (model.missing[0] === 'target' ? $('#target-local') : $('#' + model.missing[0]))?.focus(); return; }
363
+ busy = true; $('#approve').disabled = true; clearTimeout(saveTimer); const data = installInput(draft);
364
+ try { await request('/api/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); for (const key of Object.keys(fields).filter(key => fields[key].type === 'password')) draft[key] = ''; await poll(); }
365
+ catch (cause) { error(cause.message); busy = false; refreshStatus(); }
366
+ finally { for (const key of Object.keys(fields).filter(key => fields[key].type === 'password')) { data[key] = ''; draft[key] = ''; } }
367
+ };
368
+ poll();
369
+
370
+ $('#back').onclick = () => { const previous = history.pop(); selectGroup(previous.id, true, previous.editing); render(); focusGroup(); };
371
+ $('#retry-installation').onclick = reviewSavedAnswers;
372
+ $('#stop-installation').onclick = async () => {
373
+ if (stopping) return;
374
+ stopping = true; $('#stop-installation').disabled = true; $('#stop-installation').textContent = 'Stopping…'; error();
375
+ try { await request('/api/stop', { method: 'POST' }); await poll(); }
376
+ catch (cause) { error(cause.message); stopping = false; $('#stop-installation').disabled = false; $('#stop-installation').textContent = 'Stop installation'; }
377
+ };
@@ -0,0 +1 @@
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>Install {{product}}</title><link rel="stylesheet" href="/style.css"><script type="module" src="/app.js"></script></head><body><svg class="group-symbols" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><symbol id="group-owner" viewBox="0 0 24 24"><circle cx="12" cy="7" r="4"/><path d="M4 21v-3a8 6 0 0 1 16 0v3Z"/></symbol><symbol id="group-workspace" viewBox="0 0 24 24"><rect x="3" y="7" width="18" height="14" rx="1"/><path d="M8 7V4h8v3M3 12q9 5 18 0M10 13h4"/></symbol><symbol id="group-hosting" viewBox="0 0 24 24"><path d="M6 18a4 4 0 0 1-1-8 6 6 0 0 1 11-2 5 5 0 0 1 1 10Z"/></symbol><symbol id="group-email" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="1"/><path d="m4 7 8 6 8-6"/></symbol><symbol id="value-github" viewBox="0 0 24 24"><path fill="currentColor" stroke="none" d="M12 .8a11.2 11.2 0 0 0-3.54 21.83c.56.1.77-.24.77-.54v-2.1c-3.13.68-3.79-1.33-3.79-1.33-.51-1.3-1.25-1.64-1.25-1.64-1.02-.7.08-.69.08-.69 1.13.08 1.72 1.16 1.72 1.16 1 1.71 2.62 1.22 3.26.93.1-.72.39-1.22.71-1.5-2.5-.29-5.13-1.25-5.13-5.54 0-1.22.44-2.22 1.15-3-.12-.28-.5-1.42.11-2.96 0 0 .94-.3 3.08 1.15a10.8 10.8 0 0 1 5.6 0c2.14-1.45 3.08-1.15 3.08-1.15.61 1.54.23 2.68.11 2.97.71.77 1.15 1.77 1.15 2.99 0 4.3-2.63 5.25-5.14 5.53.4.35.76 1.03.76 2.08v3.1c0 .3.2.65.77.54A11.2 11.2 0 0 0 12 .8Z"/></symbol><!-- Cloudflare mark: Simple Icons, CC0; https://github.com/simple-icons/simple-icons/blob/develop/icons/cloudflare.svg --><symbol id="value-cloudflare" viewBox="0 0 24 24"><path fill="currentColor" stroke="none" d="M16.5088 16.8447c.1475-.5068.0908-.9707-.1553-1.3154-.2246-.3164-.6045-.499-1.0615-.5205l-8.6592-.1123a.1559.1559 0 0 1-.1333-.0713c-.0283-.042-.0351-.0986-.021-.1553.0278-.084.1123-.1484.2036-.1562l8.7359-.1123c1.0351-.0489 2.1601-.8868 2.5537-1.9136l.499-1.3013c.0215-.0561.0293-.1128.0147-.168-.5625-2.5463-2.835-4.4453-5.5499-4.4453-2.5039 0-4.6284 1.6177-5.3876 3.8614-.4927-.3658-1.1187-.5625-1.794-.499-1.2026.119-2.1665 1.083-2.2861 2.2856-.0283.31-.0069.6128.0635.894C1.5683 13.171 0 14.7754 0 16.752c0 .1748.0142.3515.0352.5273.0141.083.0844.1475.1689.1475h15.9814c.0909 0 .1758-.0645.2032-.1553l.12-.4268zm2.7568-5.5634c-.0771 0-.1611 0-.2383.0112-.0566 0-.1054.0415-.127.0976l-.3378 1.1744c-.1475.5068-.0918.9707.1543 1.3164.2256.3164.6055.498 1.0625.5195l1.8437.1133c.0557 0 .1055.0263.1329.0703.0283.043.0351.1074.0214.1562-.0283.084-.1132.1485-.204.1553l-1.921.1123c-1.041.0488-2.1582.8867-2.5527 1.914l-.1406.3585c-.0283.0713.0215.1416.0986.1416h6.5977c.0771 0 .1474-.0489.169-.126.1122-.4082.1757-.837.1757-1.2803 0-2.6025-2.125-4.727-4.7344-4.727"/></symbol></svg><main class="scene"><div class="layout"><div class="brand">{{product}}</div><aside class="sidebar"><section id="activity" class="agent-activity" aria-label="Preparing setup"></section></aside><section class="panel" aria-label="Install {{product}}"><p id="progress" class="step-progress" aria-live="polite"></p><h1 id="heading" tabindex="-1">Let’s get you set up</h1><p id="error" class="error" role="alert" hidden></p><p id="loading" role="status">Connecting to your installer…</p><form id="form" hidden novalidate><div id="fields" class="fields"></div><footer class="actions"><button id="back" class="secondary" type="button" hidden>Back</button><button id="continue" class="primary" type="submit" hidden>Continue →</button><button id="approve" class="primary" type="submit" disabled>Approve and install →</button></footer></form><details id="known-summary" class="known-summary" hidden><summary>Your answers</summary><div id="known"></div></details><section id="installation" hidden><p id="current-step" role="status" aria-live="polite"></p><pre id="logs" class="logs" tabindex="0" aria-label="Installation log"></pre><p id="failure-log-path" class="help failure-log-path" hidden></p><a id="open" class="primary" hidden>Open {{product}} →</a><button id="retry-installation" class="secondary" type="button" hidden>Review saved answers</button><button id="stop-installation" class="secondary" type="button" hidden>Stop installation</button><p id="failed-note" class="help" hidden></p></section></section></div></main></body></html>