@omega.js/manager 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 (416) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/LICENSE +98 -0
  3. package/README.md +165 -0
  4. package/bin/omega +2 -0
  5. package/bin/omg +2 -0
  6. package/claude-plugin/.claude-plugin/plugin.json +11 -0
  7. package/claude-plugin/.mcp.json +9 -0
  8. package/claude-plugin/README.md +139 -0
  9. package/claude-plugin/hooks/gate/mark.sh +58 -0
  10. package/claude-plugin/hooks/gate/run.sh +97 -0
  11. package/claude-plugin/hooks/guard/run.sh +249 -0
  12. package/claude-plugin/hooks/hooks.json +63 -0
  13. package/claude-plugin/hooks/inject/run.sh +127 -0
  14. package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
  15. package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
  16. package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
  17. package/claude-plugin/hooks/quality/run.sh +132 -0
  18. package/claude-plugin/hooks/shape/run.sh +45 -0
  19. package/claude-plugin/mcp-router-launch.js +29 -0
  20. package/claude-plugin/skills/README.md +48 -0
  21. package/claude-plugin/skills/accessibility/SKILL.md +31 -0
  22. package/claude-plugin/skills/analytics/SKILL.md +30 -0
  23. package/claude-plugin/skills/backend/SKILL.md +25 -0
  24. package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
  25. package/claude-plugin/skills/browser/SKILL.md +64 -0
  26. package/claude-plugin/skills/client/SKILL.md +24 -0
  27. package/claude-plugin/skills/desktop/SKILL.md +25 -0
  28. package/claude-plugin/skills/extension/SKILL.md +25 -0
  29. package/claude-plugin/skills/main/SKILL.md +54 -0
  30. package/claude-plugin/skills/manager/SKILL.md +25 -0
  31. package/claude-plugin/skills/seo/SKILL.md +36 -0
  32. package/claude-plugin/skills/theme/SKILL.md +33 -0
  33. package/claude-plugin/skills/web/SKILL.md +29 -0
  34. package/dist/cli-run.js +34 -0
  35. package/dist/cli.js +29 -0
  36. package/dist/commands/build.js +17 -0
  37. package/dist/commands/clean.js +16 -0
  38. package/dist/commands/company.js +46 -0
  39. package/dist/commands/deploy.js +143 -0
  40. package/dist/commands/dev.js +437 -0
  41. package/dist/commands/devlog.js +25 -0
  42. package/dist/commands/help.js +48 -0
  43. package/dist/commands/manage.js +53 -0
  44. package/dist/commands/migrate.js +76 -0
  45. package/dist/commands/onboard.js +31 -0
  46. package/dist/commands/pipeline.js +319 -0
  47. package/dist/commands/test.js +380 -0
  48. package/dist/commands/update.js +128 -0
  49. package/dist/commands/version.js +8 -0
  50. package/dist/company-init.js +167 -0
  51. package/dist/company.js +283 -0
  52. package/dist/config.js +1359 -0
  53. package/dist/devlog/index.js +226 -0
  54. package/dist/devlog/lib/collect-commits.js +136 -0
  55. package/dist/devlog/lib/generate-post.js +201 -0
  56. package/dist/devlog/lib/ghostii.js +122 -0
  57. package/dist/devlog/lib/project-map.js +98 -0
  58. package/dist/devlog/lib/publish-website.js +88 -0
  59. package/dist/index.js +45 -0
  60. package/dist/lib/agents-md.js +201 -0
  61. package/dist/lib/analytics-secret.js +22 -0
  62. package/dist/lib/argv.js +21 -0
  63. package/dist/lib/auth-admin.js +120 -0
  64. package/dist/lib/automation-client.js +210 -0
  65. package/dist/lib/backend-marketing.js +49 -0
  66. package/dist/lib/brand.js +240 -0
  67. package/dist/lib/bundle-id.js +53 -0
  68. package/dist/lib/claude-settings.js +125 -0
  69. package/dist/lib/company-scaffold.js +213 -0
  70. package/dist/lib/company.js +227 -0
  71. package/dist/lib/config-flow.js +350 -0
  72. package/dist/lib/config-write.js +41 -0
  73. package/dist/lib/custom-target.js +75 -0
  74. package/dist/lib/domain-utils.js +19 -0
  75. package/dist/lib/duration.js +39 -0
  76. package/dist/lib/env-order.js +236 -0
  77. package/dist/lib/env-secret.js +78 -0
  78. package/dist/lib/firestore-rest.js +326 -0
  79. package/dist/lib/framework-bin.js +156 -0
  80. package/dist/lib/gitignore.js +63 -0
  81. package/dist/lib/google-auth.js +495 -0
  82. package/dist/lib/google-token.js +66 -0
  83. package/dist/lib/jwt.js +31 -0
  84. package/dist/lib/legacy-oauth.js +59 -0
  85. package/dist/lib/node-version.js +127 -0
  86. package/dist/lib/owner-plan.js +78 -0
  87. package/dist/lib/package-scripts.js +152 -0
  88. package/dist/lib/preflight.js +470 -0
  89. package/dist/lib/product-create.js +144 -0
  90. package/dist/lib/run-command.js +74 -0
  91. package/dist/lib/run-gates.js +70 -0
  92. package/dist/lib/run-output.js +42 -0
  93. package/dist/lib/run-summary.js +384 -0
  94. package/dist/lib/scaffold.js +427 -0
  95. package/dist/lib/service-input.js +207 -0
  96. package/dist/lib/service-runner.js +466 -0
  97. package/dist/lib/stale.js +24 -0
  98. package/dist/lib/target-selection.js +155 -0
  99. package/dist/lib/verb-fanout.js +128 -0
  100. package/dist/lib/verify-live.js +188 -0
  101. package/dist/manage.js +311 -0
  102. package/dist/omega-bin.js +7 -0
  103. package/dist/onboard.js +532 -0
  104. package/dist/services/account/ensure/users.js +259 -0
  105. package/dist/services/account/index.js +97 -0
  106. package/dist/services/account/lib/backend-client.js +107 -0
  107. package/dist/services/account/lib/password.js +36 -0
  108. package/dist/services/account/lib/resolve-password.js +100 -0
  109. package/dist/services/advertising/ensure/sites.js +101 -0
  110. package/dist/services/advertising/index.js +101 -0
  111. package/dist/services/advertising/lib/adsense-api.js +41 -0
  112. package/dist/services/ai/ensure/keys.js +21 -0
  113. package/dist/services/ai/index.js +36 -0
  114. package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
  115. package/dist/services/analytics/ensure/google-streams.js +256 -0
  116. package/dist/services/analytics/ensure/meta-pixel.js +22 -0
  117. package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
  118. package/dist/services/analytics/index.js +134 -0
  119. package/dist/services/analytics/lib/analytics-api.js +156 -0
  120. package/dist/services/analytics/lib/meta-api.js +83 -0
  121. package/dist/services/analytics/lib/pixel-account.js +78 -0
  122. package/dist/services/analytics/lib/pixel-provision.js +152 -0
  123. package/dist/services/analytics/lib/pixel-specs.js +57 -0
  124. package/dist/services/analytics/lib/pixel-token.js +88 -0
  125. package/dist/services/analytics/lib/property-flow.js +77 -0
  126. package/dist/services/analytics/lib/tiktok-api.js +157 -0
  127. package/dist/services/analytics/lib/tiktok-auth.js +153 -0
  128. package/dist/services/assets/ensure/logo-gen.js +68 -0
  129. package/dist/services/assets/index.js +109 -0
  130. package/dist/services/assets/lib/assets-config.js +168 -0
  131. package/dist/services/assets/lib/brandmark-api.js +160 -0
  132. package/dist/services/assets/lib/derived.js +113 -0
  133. package/dist/services/assets/lib/font-loader.js +67 -0
  134. package/dist/services/assets/lib/reconcile.js +122 -0
  135. package/dist/services/assets/lib/reset.js +118 -0
  136. package/dist/services/assets/lib/svg-logo-generator.js +154 -0
  137. package/dist/services/assets/lib/svg-to-black.js +32 -0
  138. package/dist/services/assets/write/favicons.js +82 -0
  139. package/dist/services/assets/write/icons.js +64 -0
  140. package/dist/services/assets/write/process.js +96 -0
  141. package/dist/services/assets/write/reconcile.js +30 -0
  142. package/dist/services/assets/write/social-icons.js +108 -0
  143. package/dist/services/assets/write/templates.js +345 -0
  144. package/dist/services/bookmark/ensure/sync.js +255 -0
  145. package/dist/services/bookmark/index.js +17 -0
  146. package/dist/services/campaigns/ensure/contact-person.js +40 -0
  147. package/dist/services/campaigns/ensure/custom-fields.js +84 -0
  148. package/dist/services/campaigns/ensure/domain-auth.js +92 -0
  149. package/dist/services/campaigns/ensure/event-webhook.js +89 -0
  150. package/dist/services/campaigns/ensure/link-branding.js +187 -0
  151. package/dist/services/campaigns/ensure/list.js +50 -0
  152. package/dist/services/campaigns/ensure/segments.js +109 -0
  153. package/dist/services/campaigns/ensure/sender-identity.js +153 -0
  154. package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
  155. package/dist/services/campaigns/index.js +79 -0
  156. package/dist/services/campaigns/lib/dns-sync.js +81 -0
  157. package/dist/services/campaigns/lib/segment-query.js +138 -0
  158. package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
  159. package/dist/services/captcha/ensure/site-key.js +83 -0
  160. package/dist/services/captcha/index.js +78 -0
  161. package/dist/services/captcha/lib/console-url.js +45 -0
  162. package/dist/services/captcha/lib/recaptcha-api.js +36 -0
  163. package/dist/services/certificates/ensure/api-key.js +22 -0
  164. package/dist/services/certificates/ensure/bundle-ids.js +115 -0
  165. package/dist/services/certificates/ensure/certificates.js +187 -0
  166. package/dist/services/certificates/ensure/profiles.js +143 -0
  167. package/dist/services/certificates/index.js +225 -0
  168. package/dist/services/certificates/lib/apple-api.js +183 -0
  169. package/dist/services/certificates/lib/certificate-manager.js +252 -0
  170. package/dist/services/certificates/lib/identifier-manager.js +107 -0
  171. package/dist/services/certificates/lib/keychain.js +135 -0
  172. package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
  173. package/dist/services/certificates/lib/profile-manager.js +115 -0
  174. package/dist/services/chat/data/baseline-knowledge.md +96 -0
  175. package/dist/services/chat/ensure/chat.js +106 -0
  176. package/dist/services/chat/ensure/user.js +45 -0
  177. package/dist/services/chat/index.js +139 -0
  178. package/dist/services/chat/lib/baseline-knowledge.js +137 -0
  179. package/dist/services/cloud/ensure/authentication.js +267 -0
  180. package/dist/services/cloud/ensure/billing.js +120 -0
  181. package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
  182. package/dist/services/cloud/ensure/database.js +60 -0
  183. package/dist/services/cloud/ensure/firestore.js +79 -0
  184. package/dist/services/cloud/ensure/functions.js +49 -0
  185. package/dist/services/cloud/ensure/hosting.js +398 -0
  186. package/dist/services/cloud/ensure/oauth-consent.js +248 -0
  187. package/dist/services/cloud/ensure/project-settings.js +72 -0
  188. package/dist/services/cloud/ensure/sdk-config.js +95 -0
  189. package/dist/services/cloud/ensure/service-account.js +152 -0
  190. package/dist/services/cloud/ensure/services.js +103 -0
  191. package/dist/services/cloud/ensure/storage.js +47 -0
  192. package/dist/services/cloud/index.js +130 -0
  193. package/dist/services/cloud/lib/access-heal.js +130 -0
  194. package/dist/services/cloud/lib/firebase-api.js +662 -0
  195. package/dist/services/cloud/lib/project-flow.js +102 -0
  196. package/dist/services/directory/ensure/entry.js +48 -0
  197. package/dist/services/directory/index.js +64 -0
  198. package/dist/services/directory/lib/blocks.js +104 -0
  199. package/dist/services/disperse/index.js +45 -0
  200. package/dist/services/disperse/write/certs.js +117 -0
  201. package/dist/services/domain/ensure/nameservers.js +179 -0
  202. package/dist/services/domain/index.js +69 -0
  203. package/dist/services/domain/lib/namecheap-api.js +167 -0
  204. package/dist/services/domain/lib/registrars.js +46 -0
  205. package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
  206. package/dist/services/edge/ensure/cache-rules.js +105 -0
  207. package/dist/services/edge/ensure/dns-records.js +304 -0
  208. package/dist/services/edge/ensure/email-routing.js +292 -0
  209. package/dist/services/edge/ensure/rules-configuration.js +98 -0
  210. package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
  211. package/dist/services/edge/ensure/rules-redirect.js +107 -0
  212. package/dist/services/edge/ensure/rules-response-headers.js +90 -0
  213. package/dist/services/edge/ensure/rules-security.js +142 -0
  214. package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
  215. package/dist/services/edge/ensure/workers.js +219 -0
  216. package/dist/services/edge/ensure/zone-settings.js +108 -0
  217. package/dist/services/edge/ensure/zone.js +183 -0
  218. package/dist/services/edge/index.js +73 -0
  219. package/dist/services/edge/lib/cloudflare-api.js +84 -0
  220. package/dist/services/edge/lib/dns-records-helpers.js +518 -0
  221. package/dist/services/edge/lib/read-cache.js +16 -0
  222. package/dist/services/edge/lib/ruleset-helper.js +108 -0
  223. package/dist/services/edge/workers/omega-api-proxy.js +43 -0
  224. package/dist/services/email/data/baseline-filter.md +4 -0
  225. package/dist/services/email/data/baseline-knowledge.md +86 -0
  226. package/dist/services/email/ensure/agent.js +107 -0
  227. package/dist/services/email/ensure/user.js +45 -0
  228. package/dist/services/email/index.js +138 -0
  229. package/dist/services/email/lib/baseline.js +82 -0
  230. package/dist/services/email/lib/knowledge-file.js +53 -0
  231. package/dist/services/forms/ensure/form.js +46 -0
  232. package/dist/services/forms/ensure/user.js +45 -0
  233. package/dist/services/forms/index.js +138 -0
  234. package/dist/services/migrations/ensure/notifications.js +189 -0
  235. package/dist/services/migrations/ensure/orders.js +34 -0
  236. package/dist/services/migrations/ensure/payment-provider.js +132 -0
  237. package/dist/services/migrations/ensure/payments-intents.js +34 -0
  238. package/dist/services/migrations/ensure/state-retirement.js +266 -0
  239. package/dist/services/migrations/ensure/targets-rename.js +124 -0
  240. package/dist/services/migrations/ensure/users.js +1176 -0
  241. package/dist/services/migrations/index.js +101 -0
  242. package/dist/services/migrations/lib/attribution-touch.js +64 -0
  243. package/dist/services/migrations/lib/ensure-metadata.js +144 -0
  244. package/dist/services/migrations/lib/migration-runner.js +473 -0
  245. package/dist/services/migrations/lib/sanitize-strings.js +112 -0
  246. package/dist/services/migrations/lib/schema-validator.js +154 -0
  247. package/dist/services/monitoring/ensure/dsn.js +54 -0
  248. package/dist/services/monitoring/ensure/projects.js +135 -0
  249. package/dist/services/monitoring/index.js +54 -0
  250. package/dist/services/monitoring/lib/sentry-api.js +94 -0
  251. package/dist/services/newsletter/ensure/custom-fields.js +90 -0
  252. package/dist/services/newsletter/ensure/publication.js +94 -0
  253. package/dist/services/newsletter/ensure/segments.js +140 -0
  254. package/dist/services/newsletter/ensure/webhook.js +108 -0
  255. package/dist/services/newsletter/index.js +64 -0
  256. package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
  257. package/dist/services/newsletter/lib/segment-automation.js +577 -0
  258. package/dist/services/payment/ensure/chargebee-account.js +30 -0
  259. package/dist/services/payment/ensure/chargebee-products.js +359 -0
  260. package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
  261. package/dist/services/payment/ensure/paypal-account.js +36 -0
  262. package/dist/services/payment/ensure/paypal-products.js +374 -0
  263. package/dist/services/payment/ensure/paypal-webhook.js +128 -0
  264. package/dist/services/payment/ensure/stripe-account.js +93 -0
  265. package/dist/services/payment/ensure/stripe-disputes.js +56 -0
  266. package/dist/services/payment/ensure/stripe-products.js +245 -0
  267. package/dist/services/payment/ensure/stripe-radar.js +74 -0
  268. package/dist/services/payment/ensure/stripe-webhook.js +130 -0
  269. package/dist/services/payment/index.js +163 -0
  270. package/dist/services/payment/lib/chargebee-api.js +338 -0
  271. package/dist/services/payment/lib/payment-utils.js +116 -0
  272. package/dist/services/payment/lib/paypal-api.js +354 -0
  273. package/dist/services/payment/lib/provider-setup.js +134 -0
  274. package/dist/services/payment/lib/stripe-api.js +152 -0
  275. package/dist/services/repo/ensure/org.js +70 -0
  276. package/dist/services/repo/ensure/pages.js +72 -0
  277. package/dist/services/repo/ensure/repo.js +90 -0
  278. package/dist/services/repo/index.js +52 -0
  279. package/dist/services/repo/lib/github-api.js +196 -0
  280. package/dist/services/search/ensure/ga-link.js +57 -0
  281. package/dist/services/search/ensure/property.js +137 -0
  282. package/dist/services/search/ensure/sitemaps.js +70 -0
  283. package/dist/services/search/index.js +61 -0
  284. package/dist/services/search/lib/search-console-api.js +92 -0
  285. package/dist/services/seo/ensure/github-repos.js +271 -0
  286. package/dist/services/seo/index.js +55 -0
  287. package/dist/services/seo/lib/gh-api.js +161 -0
  288. package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
  289. package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
  290. package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
  291. package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
  292. package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
  293. package/dist/services/seo/templates/index.js +57 -0
  294. package/dist/services/server/ensure/brands.js +63 -0
  295. package/dist/services/server/index.js +42 -0
  296. package/dist/services/testing/ensure/target-checks.js +97 -0
  297. package/dist/services/testing/index.js +11 -0
  298. package/dist/services/testing/lib/checks.js +467 -0
  299. package/dist/services/update/index.js +20 -0
  300. package/dist/services/update/lib/cache.js +59 -0
  301. package/dist/services/update/lib/fingerprint.js +157 -0
  302. package/dist/services/update/write/targets.js +206 -0
  303. package/dist/services/workspace/ensure/agents.js +42 -0
  304. package/dist/services/workspace/ensure/claude-settings.js +34 -0
  305. package/dist/services/workspace/ensure/config.js +54 -0
  306. package/dist/services/workspace/ensure/defaults.js +60 -0
  307. package/dist/services/workspace/ensure/env-keys.js +55 -0
  308. package/dist/services/workspace/ensure/env-order.js +66 -0
  309. package/dist/services/workspace/ensure/env-rules.js +97 -0
  310. package/dist/services/workspace/ensure/gitignore.js +18 -0
  311. package/dist/services/workspace/ensure/scripts.js +126 -0
  312. package/dist/services/workspace/ensure/structure.js +97 -0
  313. package/dist/services/workspace/ensure/translation-sdk.js +148 -0
  314. package/dist/services/workspace/ensure/workflows.js +54 -0
  315. package/dist/services/workspace/index.js +8 -0
  316. package/dist/vendor/account/engine.js +182 -0
  317. package/dist/vendor/account/features.js +220 -0
  318. package/dist/vendor/account/index.js +53 -0
  319. package/dist/vendor/account/schema.js +272 -0
  320. package/dist/vendor/account/subscription.js +38 -0
  321. package/dist/vendor/config/company.js +31 -0
  322. package/dist/vendor/config/defaults.js +173 -0
  323. package/dist/vendor/config/demo.js +18 -0
  324. package/dist/vendor/config/desktop-artifacts.js +110 -0
  325. package/dist/vendor/config/edit.js +769 -0
  326. package/dist/vendor/config/env-delivery.js +145 -0
  327. package/dist/vendor/config/env-rules.js +93 -0
  328. package/dist/vendor/config/env-schema.js +1078 -0
  329. package/dist/vendor/config/env.js +445 -0
  330. package/dist/vendor/config/hooks.js +97 -0
  331. package/dist/vendor/config/index.js +237 -0
  332. package/dist/vendor/config/instances.js +208 -0
  333. package/dist/vendor/config/load.js +490 -0
  334. package/dist/vendor/config/merge.js +68 -0
  335. package/dist/vendor/config/order.js +139 -0
  336. package/dist/vendor/config/ports.js +374 -0
  337. package/dist/vendor/config/providers.js +32 -0
  338. package/dist/vendor/config/repo.js +142 -0
  339. package/dist/vendor/config/retired-keys.js +430 -0
  340. package/dist/vendor/config/schema.js +1610 -0
  341. package/dist/vendor/config/secrets.js +50 -0
  342. package/dist/vendor/config/seed.js +34 -0
  343. package/dist/vendor/config/site-global.js +205 -0
  344. package/dist/vendor/config/validate.js +554 -0
  345. package/dist/vendor/config/winback.js +61 -0
  346. package/dist/vendor/devkit/attach-log-file.js +262 -0
  347. package/dist/vendor/devkit/certs.js +199 -0
  348. package/dist/vendor/devkit/ci-workflows.js +520 -0
  349. package/dist/vendor/devkit/cli-router.js +156 -0
  350. package/dist/vendor/devkit/command-path.js +46 -0
  351. package/dist/vendor/devkit/deploy-record.js +180 -0
  352. package/dist/vendor/devkit/flows.js +317 -0
  353. package/dist/vendor/devkit/local-https.js +360 -0
  354. package/dist/vendor/devkit/local.js +1905 -0
  355. package/dist/vendor/devkit/logger.js +128 -0
  356. package/dist/vendor/devkit/omega-bin.js +345 -0
  357. package/dist/vendor/devkit/prompt.js +187 -0
  358. package/dist/vendor/devkit/safe-install.js +18 -0
  359. package/dist/vendor/devkit/stop-signals.js +28 -0
  360. package/dist/vendor/devkit/test/scope.js +162 -0
  361. package/dist/vendor/devkit/translate/cache.js +84 -0
  362. package/dist/vendor/devkit/translate/engine.js +243 -0
  363. package/dist/vendor/devkit/translate/index.js +49 -0
  364. package/dist/vendor/devkit/translate/languages.js +134 -0
  365. package/dist/vendor/devkit/translate/providers.js +188 -0
  366. package/dist/vendor/devkit/update.js +569 -0
  367. package/docs/AGENTS.md +200 -0
  368. package/docs/account.md +45 -0
  369. package/docs/advertising.md +46 -0
  370. package/docs/ai.md +34 -0
  371. package/docs/analytics.md +69 -0
  372. package/docs/assets.md +57 -0
  373. package/docs/bookmark.md +26 -0
  374. package/docs/brand.md +151 -0
  375. package/docs/campaigns.md +96 -0
  376. package/docs/captcha.md +46 -0
  377. package/docs/certificates.md +68 -0
  378. package/docs/chat.md +47 -0
  379. package/docs/cloud.md +110 -0
  380. package/docs/company.md +64 -0
  381. package/docs/directory.md +140 -0
  382. package/docs/disperse.md +46 -0
  383. package/docs/domain.md +56 -0
  384. package/docs/edge.md +234 -0
  385. package/docs/email.md +42 -0
  386. package/docs/forms.md +48 -0
  387. package/docs/index.md +109 -0
  388. package/docs/migration.md +203 -0
  389. package/docs/migrations.md +56 -0
  390. package/docs/monitoring.md +41 -0
  391. package/docs/newsletter.md +48 -0
  392. package/docs/payment.md +83 -0
  393. package/docs/repo.md +48 -0
  394. package/docs/search.md +44 -0
  395. package/docs/seo.md +34 -0
  396. package/docs/server.md +37 -0
  397. package/docs/shared/agent-docs.md +89 -0
  398. package/docs/shared/analytics.md +612 -0
  399. package/docs/shared/brands.md +51 -0
  400. package/docs/shared/breaking-changes.md +497 -0
  401. package/docs/shared/config.md +1387 -0
  402. package/docs/shared/deploys.md +215 -0
  403. package/docs/shared/icons.md +201 -0
  404. package/docs/shared/local-dev.md +147 -0
  405. package/docs/shared/logging.md +202 -0
  406. package/docs/shared/monitoring.md +153 -0
  407. package/docs/shared/publishing.md +183 -0
  408. package/docs/shared/rulings.md +34 -0
  409. package/docs/shared/testing.md +147 -0
  410. package/docs/shared/theming.md +604 -0
  411. package/docs/shared/translation.md +291 -0
  412. package/docs/shared/updates.md +61 -0
  413. package/docs/testing.md +34 -0
  414. package/docs/update.md +36 -0
  415. package/docs/workspace.md +64 -0
  416. package/package.json +88 -0
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Preflight — the REQUIRES registry check run before any service. Collects
3
+ * the enabled services' declared REQUIRED inputs (env var names + Google OAuth
4
+ * scopes, src/config.js REQUIRES; an entry marked `gates: false` is optional
5
+ * and belongs to the mid-run ask, not here), checks what IS knowable up front —
6
+ * process.env presence (names only, values never printed) and the token
7
+ * store's granted-scopes record — and prints ONE consolidated fix
8
+ * walkthrough (cp236's 403-diagnostics tone: what's missing, which service,
9
+ * why, the exact fix, then the rerun verb) instead of N mid-run skips.
10
+ *
11
+ * Verdicts per failing service (Ian's ruling — absorb, never crash):
12
+ * run - interactive and the run itself collects the fix (the cp114
13
+ * setup-contract paste flow / the Google consent flow) — the
14
+ * service proceeds and asks.
15
+ * skip - the fix needs the operator (non-interactive, or an env var no
16
+ * flow collects) — the service skips with the walkthrough printed
17
+ * and the cycle continues; the skip carries the machine-readable
18
+ * missingEnv list the run summary's 🔑 section aggregates.
19
+ * error - --strict: every preflight failure fails hard instead.
20
+ *
21
+ * The scope leg's boundary: the token store records what the last consent
22
+ * GRANTED — that is what's knowable before a call. The live grant is only
23
+ * proven at call time; the google-auth 403 diagnostics are the backstop.
24
+ *
25
+ * The file's OTHER gate is assertFamilyVersions (#794), the lockstep check:
26
+ * same place in the run (before any service, from `runManage` and `omega dev`
27
+ * alike) and the opposite verdict — a mixed @omega.js family is REFUSED, not
28
+ * absorbed, because every service below it would reconcile on top of two
29
+ * copies of the runtime.
30
+ */
31
+ const fs = require('node:fs');
32
+ const path = require('node:path');
33
+ const chalk = require('chalk').default;
34
+
35
+ const { REQUIRES, TARGET_FRAMEWORKS } = require('../config.js');
36
+ const { canPrompt } = require('./run-gates.js');
37
+ const { googleTokenStorePath } = require('./google-auth.js');
38
+
39
+ // The family's ONE version (#794) — the manager's own, read at run time. A
40
+ // published brand installs the manager and its frameworks from the same
41
+ // release, so the manager's number IS what every target must carry.
42
+ const FAMILY_VERSION = require('../../package.json').version;
43
+
44
+ // The client rides inside every framework (web/backend/desktop/extension all
45
+ // depend on it), so a drifted client is the second way one brand ends up
46
+ // running two copies of the runtime — checked per target beside the framework.
47
+ const CLIENT_PACKAGE = '@omega.js/client';
48
+
49
+ /** Shorten a Google scope URL to its trailing name for display. */
50
+ function shortScope(scope) {
51
+ return scope.replace('https://www.googleapis.com/auth/', '');
52
+ }
53
+
54
+ /**
55
+ * Read the ONE Google token store's granted-scopes record. Stores from
56
+ * before scope tracking (no `scopes` array) count as granting nothing —
57
+ * the same rule google-auth's getAccessToken applies.
58
+ *
59
+ * @param {string} brandRoot - Brand monorepo root
60
+ * @returns {{ exists: boolean, scopes: string[], accountEmail: string|null, storePath: string }}
61
+ */
62
+ function readTokenStore(brandRoot) {
63
+ const storePath = googleTokenStorePath(brandRoot);
64
+
65
+ try {
66
+ const stored = JSON.parse(fs.readFileSync(storePath, 'utf8'));
67
+ return {
68
+ exists: true,
69
+ scopes: Array.isArray(stored.scopes) ? stored.scopes : [],
70
+ accountEmail: stored.account_email || null,
71
+ storePath,
72
+ };
73
+ } catch {
74
+ return { exists: false, scopes: [], accountEmail: null, storePath };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Check one service's declaration against the environment + token store.
80
+ *
81
+ * @param {string} serviceName - Service being checked
82
+ * @param {object} declaration - REQUIRES entry ({ why, when?, env, scopes })
83
+ * @param {object} brandConfig - Merged brand config (gates the when clauses)
84
+ * @param {object} tokenStore - readTokenStore() result
85
+ * @returns {object|null} Finding ({ service, why, missingEnv, missingScopes,
86
+ * noConsent }) or null when the service's requirements are met (or don't
87
+ * apply to this brand)
88
+ */
89
+ function checkService(serviceName, declaration, brandConfig, tokenStore) {
90
+ if (declaration.when && !declaration.when(brandConfig)) {
91
+ return null;
92
+ }
93
+
94
+ // `gates: false` entries are OPTIONAL inputs (#608): the service runs
95
+ // without them, so their absence is not a preflight finding — the operation
96
+ // that needs one asks for it in place, through the shared setup contract.
97
+ const envEntries = (declaration.env || [])
98
+ .filter((entry) => entry.gates !== false)
99
+ .filter((entry) => !entry.when || entry.when(brandConfig));
100
+ const missingEnv = envEntries.filter((entry) => !process.env[entry.name]);
101
+
102
+ const requiredScopes = declaration.scopes || [];
103
+ let missingScopes = [];
104
+ let noConsent = false;
105
+
106
+ if (requiredScopes.length > 0) {
107
+ if (!tokenStore.exists) {
108
+ noConsent = true;
109
+ } else {
110
+ missingScopes = requiredScopes.filter((scope) => !tokenStore.scopes.includes(scope));
111
+ }
112
+ }
113
+
114
+ if (missingEnv.length === 0 && missingScopes.length === 0 && !noConsent) {
115
+ return null;
116
+ }
117
+
118
+ return {
119
+ service: serviceName,
120
+ why: declaration.why || null,
121
+ missingEnv,
122
+ missingScopes,
123
+ noConsent,
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Build the machine-readable skip/error reason for a finding — always names
129
+ * the exact vars/scopes (never values) so run records and the 🔑 aggregate
130
+ * say precisely what to add where.
131
+ */
132
+ function buildReason(finding) {
133
+ const parts = [];
134
+
135
+ if (finding.missingEnv.length > 0) {
136
+ parts.push(`missing ${finding.missingEnv.map((entry) => entry.name).join(', ')} — add to the brand .env, or rerun interactively to paste`);
137
+ }
138
+ parts.push(...consentParts(finding));
139
+
140
+ return `preflight: ${parts.join('; ')}`;
141
+ }
142
+
143
+ /** The consent/scope half of a finding's wording — ONE home for both users. */
144
+ function consentParts(finding) {
145
+ const parts = [];
146
+
147
+ if (finding.noConsent) {
148
+ parts.push('Google consent not granted yet — rerun interactively once to consent');
149
+ }
150
+ if (finding.missingScopes.length > 0) {
151
+ parts.push(`Google token missing scopes ${finding.missingScopes.map(shortScope).join(', ')} — rerun interactively for the one re-consent`);
152
+ }
153
+
154
+ return parts;
155
+ }
156
+
157
+ /**
158
+ * The gate's human-pending summary (#228) — what the run summary's ⚑ section
159
+ * prints for this skip, so the item is NAMED with a rerun hint instead of
160
+ * counting as an anonymous "skipped". Consent/scope gaps only: a missing
161
+ * secret already rides the 🔑 section, and double-listing is noise.
162
+ *
163
+ * @param {object} finding - A checkService finding.
164
+ * @returns {string|null} The pending line, or null for env-only gaps.
165
+ */
166
+ function buildPending(finding) {
167
+ const parts = consentParts(finding);
168
+ return parts.length > 0 ? parts.join('; ') : null;
169
+ }
170
+
171
+ /**
172
+ * Print the consolidated walkthrough (cp236 tone) — one block per failing
173
+ * service: what's missing, why the service needs it, the exact fix, then
174
+ * the rerun verb. Env VALUES never appear — names, labels, and mint URLs only.
175
+ */
176
+ function printWalkthrough(findings, gates, tokenStore, strict) {
177
+ console.log('');
178
+ console.log(` ${chalk.yellow('⚑ Preflight')} — ${findings.length} service${findings.length === 1 ? ' is' : 's are'} missing requirements${strict ? chalk.red(' (--strict: failing hard)') : ''}:`);
179
+
180
+ for (const finding of findings) {
181
+ const gate = gates[finding.service];
182
+ const missingBits = [
183
+ ...finding.missingEnv.map((entry) => entry.name),
184
+ ...(finding.noConsent ? ['Google consent'] : []),
185
+ ...finding.missingScopes.map((scope) => `scope:${shortScope(scope)}`),
186
+ ];
187
+
188
+ console.log('');
189
+ console.log(` ${chalk.yellow('✗')} ${chalk.bold(finding.service)} — missing ${chalk.bold(missingBits.join(', '))}`);
190
+ if (finding.why) {
191
+ console.log(` ${chalk.dim('needed:')} ${finding.why}`);
192
+ }
193
+
194
+ for (const entry of finding.missingEnv) {
195
+ console.log(` ${chalk.dim('fix:')} add ${chalk.bold(`${entry.name}=<value>`)} to the brand .env${entry.label ? chalk.dim(` — ${entry.label}`) : ''}`);
196
+ if (entry.url) {
197
+ console.log(` ${chalk.dim(`→ mint it at ${entry.url}`)}`);
198
+ }
199
+ if (entry.hint) {
200
+ console.log(` ${chalk.dim(entry.hint)}`);
201
+ }
202
+ }
203
+
204
+ if (finding.noConsent) {
205
+ console.log(` ${chalk.dim('fix:')} run interactively once — the Google consent flow opens, and every later run works from the cached grant`);
206
+ } else if (finding.missingScopes.length > 0) {
207
+ console.log(` ${chalk.dim('acting identity:')} ${tokenStore.accountEmail || 'unknown'} ${chalk.dim(`(token store: ${tokenStore.storePath})`)}`);
208
+ console.log(` ${chalk.dim('fix:')} rerun interactively — one re-consent upgrades the cached token with the new scopes`);
209
+ console.log(` ${chalk.dim('→ or delete the token store to consent as a different account')}`);
210
+ }
211
+
212
+ if (gate.action === 'run') {
213
+ console.log(` ${chalk.dim('→ this run is interactive — it will ask, then continue')}`);
214
+ } else {
215
+ console.log(` ${chalk.dim('then:')} npm run manage -- --service=${finding.service} ${chalk.dim('(from the brand root)')}`);
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Run the preflight over the services about to run.
222
+ *
223
+ * @param {object} params
224
+ * @param {string[]} params.services - Services this manage run will walk
225
+ * @param {object} params.brandConfig - Merged brand config
226
+ * @param {string} params.brandRoot - Brand monorepo root
227
+ * @param {object} params.options - Run options ({ strict?, dryRun?, … })
228
+ * @param {object} [deps] - Test seams: { requires } replaces the REQUIRES
229
+ * registry, { canPrompt } the interactivity gate
230
+ * @returns {{ findings: object[], gates: Object<string, { action: 'run'|'skip'|'error', reason: string, missingEnv: string[], needsInteractive?: string }> }}
231
+ * gates only contains entries for services with findings — everything
232
+ * else runs untouched
233
+ */
234
+ function runPreflight({ services, brandConfig, brandRoot, options = {} }, deps = {}) {
235
+ const requires = deps.requires || REQUIRES;
236
+ const interactive = (deps.canPrompt || canPrompt)(options);
237
+ const strict = Boolean(options.strict);
238
+ const tokenStore = readTokenStore(brandRoot);
239
+
240
+ const findings = [];
241
+ for (const serviceName of services) {
242
+ const declaration = requires[serviceName];
243
+ if (!declaration) {
244
+ continue;
245
+ }
246
+
247
+ const finding = checkService(serviceName, declaration, brandConfig, tokenStore);
248
+ if (finding) {
249
+ findings.push(finding);
250
+ }
251
+ }
252
+
253
+ const gates = {};
254
+ for (const finding of findings) {
255
+ // Scope gaps always self-heal on a TTY (the consent flow); env gaps only
256
+ // when every missing entry rides the setup-contract paste flow
257
+ const selfHealing = finding.missingEnv.every((entry) => entry.prompted === true);
258
+ const action = strict ? 'error' : (interactive && selfHealing ? 'run' : 'skip');
259
+
260
+ const pending = buildPending(finding);
261
+
262
+ gates[finding.service] = {
263
+ action,
264
+ reason: buildReason(finding),
265
+ missingEnv: finding.missingEnv.map((entry) => entry.name),
266
+ ...(pending ? { needsInteractive: pending } : {}),
267
+ };
268
+ }
269
+
270
+ if (findings.length > 0) {
271
+ printWalkthrough(findings, gates, tokenStore, strict);
272
+ }
273
+
274
+ return { findings, gates };
275
+ }
276
+
277
+ // ─── The lockstep boot check (#794) ─────────────────────────────────────────
278
+
279
+ /**
280
+ * The nearest installed copy of a package, climbing from a target dir toward
281
+ * the brand root — npm hoists a workspace tree's deps to the root, so the
282
+ * target's own node_modules is the first place to look and never the only
283
+ * one (the same "nearest, climbing" resolution `omega update` reports from).
284
+ *
285
+ * @param {string} fromDir - Where to start looking (a target dir).
286
+ * @param {string} brandRoot - Where to stop climbing.
287
+ * @param {string} packageName - Full package name (`@omega.js/web`).
288
+ * @returns {string|null} The installed package dir, or null when nothing is installed.
289
+ */
290
+ function findInstalled(fromDir, brandRoot, packageName) {
291
+ let current = path.resolve(fromDir);
292
+ const stop = path.resolve(brandRoot);
293
+
294
+ for (;;) {
295
+ const candidate = path.join(current, 'node_modules', packageName);
296
+ if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate;
297
+
298
+ const parent = path.dirname(current);
299
+ if (current === stop || parent === current) return null;
300
+ current = parent;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Is this install a LINK out of node_modules — the local era's `file:` spec,
306
+ * or an npm workspace link? Its version is the monorepo's by construction, so
307
+ * comparing it to a published number says nothing.
308
+ */
309
+ function isLinkedInstall(installedDir) {
310
+ try {
311
+ return fs.lstatSync(installedDir).isSymbolicLink();
312
+ } catch {
313
+ return false;
314
+ }
315
+ }
316
+
317
+ /**
318
+ * The installed version, or the PROBLEM that stopped us reading it. A manifest
319
+ * that exists but cannot be parsed (or carries no version) is never a pass:
320
+ * the whole point of the gate is knowing what is installed, and "unknown"
321
+ * answers that question with silence.
322
+ *
323
+ * @param {string} installedDir - An installed package dir.
324
+ * @returns {{ version: string|null, problem: string|null }}
325
+ */
326
+ function readInstalledVersion(installedDir) {
327
+ const manifest = path.join(installedDir, 'package.json');
328
+ let raw;
329
+
330
+ try {
331
+ raw = fs.readFileSync(manifest, 'utf8');
332
+ } catch {
333
+ return { version: null, problem: 'cannot be read' };
334
+ }
335
+
336
+ let parsed;
337
+ try {
338
+ parsed = JSON.parse(raw);
339
+ } catch {
340
+ return { version: null, problem: 'is not valid JSON' };
341
+ }
342
+
343
+ if (typeof parsed.version !== 'string' || parsed.version === '') {
344
+ return { version: null, problem: 'declares no version' };
345
+ }
346
+
347
+ return { version: parsed.version, problem: null };
348
+ }
349
+
350
+ /** An Error the CLI prints as its message alone — a refusal is not a bug (#706). */
351
+ function refusal(message) {
352
+ const error = new Error(message);
353
+ error.refusal = true;
354
+ return error;
355
+ }
356
+
357
+ /** The framework spec a target's own manifest declares, or null. */
358
+ function declaredSpec(targetPath, packageName) {
359
+ try {
360
+ const pkg = JSON.parse(fs.readFileSync(path.join(targetPath, 'package.json'), 'utf8'));
361
+ return pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName] || null;
362
+ } catch {
363
+ return null;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * The lockstep gate (#794) — the @omega.js family ships ONE version, so every
369
+ * target's installed framework (and the `@omega.js/client` under it) must be
370
+ * the manager's own version. Run before any service, by `runManage` and by
371
+ * `omega dev` alike: a brand running a backend from one release beside a
372
+ * client from another validates its config with two validators and serves two
373
+ * copies of the runtime, and nothing downstream can tell.
374
+ *
375
+ * What is NOT a mismatch: a target linked with a `file:` spec (the local era —
376
+ * its version is the monorepo's by construction), and a target with nothing
377
+ * installed yet (the install itself is the fix, so it skips with a line).
378
+ *
379
+ * @param {object} params
380
+ * @param {string} params.brandRoot - Brand monorepo root.
381
+ * @param {Array<{ name, dir, path, target }>} params.targets - Discovered target entries.
382
+ * @param {string} [params.version] - The family version (defaults to the manager's own).
383
+ * @returns {{ checked: object[], mismatched: object[], exempt: object[], skipped: object[], unreadable: object[] }}
384
+ * @throws {Error} ONE refusal (`error.refusal`, so the CLI prints the message
385
+ * alone) naming every mismatched target and the fix — or, first, every
386
+ * installed manifest that cannot be read.
387
+ */
388
+ function assertFamilyVersions({ brandRoot, targets = [], version = FAMILY_VERSION }) {
389
+ const checked = [];
390
+ const mismatched = [];
391
+ const exempt = [];
392
+ const skipped = [];
393
+ const unreadable = [];
394
+
395
+ for (const entry of targets) {
396
+ const framework = TARGET_FRAMEWORKS[entry.target];
397
+ // A custom target (#603) maps to no framework — there is nothing to compare
398
+ if (!framework) continue;
399
+
400
+ const spec = declaredSpec(entry.path, framework);
401
+ if (spec && /^(file|link):/.test(spec)) {
402
+ exempt.push({ dir: entry.name, framework, reason: spec });
403
+ continue;
404
+ }
405
+
406
+ const installedDir = findInstalled(entry.path, brandRoot, framework);
407
+ if (!installedDir) {
408
+ skipped.push({ dir: entry.name, framework });
409
+ continue;
410
+ }
411
+ if (isLinkedInstall(installedDir)) {
412
+ exempt.push({ dir: entry.name, framework, reason: 'linked install' });
413
+ continue;
414
+ }
415
+
416
+ // The client is checked wherever npm placed it: nested under the
417
+ // framework when the ranges stopped overlapping (the second-copy case
418
+ // this check exists for), otherwise the hoisted copy the climb finds.
419
+ const nestedClient = path.join(installedDir, 'node_modules', CLIENT_PACKAGE);
420
+ const clientDir = fs.existsSync(path.join(nestedClient, 'package.json'))
421
+ ? nestedClient
422
+ : findInstalled(entry.path, brandRoot, CLIENT_PACKAGE);
423
+
424
+ const reads = [{ name: framework, dir: installedDir, ...readInstalledVersion(installedDir) }];
425
+ if (clientDir && !isLinkedInstall(clientDir)) {
426
+ reads.push({ name: CLIENT_PACKAGE, dir: clientDir, ...readInstalledVersion(clientDir) });
427
+ }
428
+
429
+ const broken = reads.filter((read) => read.problem);
430
+ if (broken.length > 0) {
431
+ unreadable.push(...broken.map((read) => ({ dir: entry.name, name: read.name, manifest: path.join(read.dir, 'package.json'), problem: read.problem })));
432
+ continue;
433
+ }
434
+
435
+ const packages = reads.map((read) => ({ name: read.name, version: read.version }));
436
+ checked.push({ dir: entry.name, packages });
437
+
438
+ const drifted = packages.filter((pkg) => pkg.version !== version);
439
+ if (drifted.length > 0) {
440
+ mismatched.push({ dir: entry.name, packages: drifted });
441
+ }
442
+ }
443
+
444
+ for (const entry of skipped) {
445
+ console.log(` ${chalk.dim(`⊘ ${entry.dir}: ${entry.framework} is not installed yet — skipped (npm install, or \`omega update --apply\`, is the fix)`)}`);
446
+ }
447
+
448
+ // An install we cannot READ is checked first: it answers "which version is
449
+ // this" with silence, and a gate that shrugs at silence is not a gate.
450
+ if (unreadable.length > 0) {
451
+ const lines = unreadable.map((entry) => ` ${entry.dir}: ${entry.manifest} ${entry.problem}`);
452
+ throw refusal(
453
+ `an installed @omega.js package cannot be read (#794):\n${lines.join('\n')}\n`
454
+ + ' fix: reinstall the brand — `npm install` at the brand root',
455
+ );
456
+ }
457
+
458
+ if (mismatched.length > 0) {
459
+ const lines = mismatched.map((entry) => ` ${entry.dir}: ${entry.packages.map((pkg) => `${pkg.name} ${pkg.version}`).join(', ')}`);
460
+ throw refusal(
461
+ `the @omega.js family ships ONE version — this brand is mixed (#794):\n${lines.join('\n')}\n`
462
+ + ` this manager is ${version}\n`
463
+ + ' fix: run `omega update --apply` at the brand root — it moves every target together',
464
+ );
465
+ }
466
+
467
+ return { checked, mismatched, exempt, skipped, unreadable };
468
+ }
469
+
470
+ module.exports = { runPreflight, checkService, readTokenStore, assertFamilyVersions };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Create-on-missing for the ITW product services — forms (Slapform contact
3
+ * form), chat (Chatsy agent), email (Replyify agent). When a brand configures no
4
+ * asset id, the OPERATOR service account mints a brand-OWNED asset instead
5
+ * of the brand borrowing another brand's (2b, Ian 2026-07-13: "this shit
6
+ * was supposed to create new accounts for the brand"):
7
+ *
8
+ * 1. Product USER — the brand's own account on the product, minted via
9
+ * the product project's Identity Toolkit (email = brand contact
10
+ * email; password through the account service's owner channels:
11
+ * env pin → owner hook → derived seed). The users/{uid} doc writes
12
+ * the CANONICAL @omega.js/account shape (the BEM golden master the
13
+ * products run) with real generated credentials — including
14
+ * api.privateKey, the user API key the products accept.
15
+ * 2. The ASSET doc — shape-templated from an existing doc (the config's
16
+ * template id — typically the company's own richest example), with
17
+ * `owner` pointed at the new user. The service's normal ensures then
18
+ * converge every managed field (name, settings, knowledge, plan) in
19
+ * the SAME run, so the mint stays minimal and the convergence logic
20
+ * stays in one place.
21
+ *
22
+ * Auth tiers (Ian's call): operator SA = full create/manage (this lib) ·
23
+ * user API key = recognized, manage via the product APIs lands when those
24
+ * routes are verified · no credentials = the interactive dashboard
25
+ * paste-back every service already ships.
26
+ *
27
+ * Idempotent by construction: the minted id writes back into omega.json5
28
+ * (comment-preserving), so a rerun takes the normal converge path; an
29
+ * existing product user is reused by email lookup.
30
+ */
31
+ const { randomBytes, randomUUID } = require('node:crypto');
32
+ const chalk = require('chalk').default;
33
+
34
+ const { resolveAccount } = require('../vendor/account/index.js');
35
+
36
+ const BASE62 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
37
+
38
+ /**
39
+ * Random base62 string (rejection-free modulo is fine here — ids/keys,
40
+ * not crypto material with uniformity proofs).
41
+ * @param {number} size
42
+ * @returns {string}
43
+ */
44
+ function randomBase62(size) {
45
+ const bytes = randomBytes(size);
46
+ let out = '';
47
+ for (let i = 0; i < size; i++) {
48
+ out += BASE62[bytes[i] % BASE62.length];
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** New asset doc id — matches the products' visible 14-char convention. */
54
+ function generateAssetId() {
55
+ return randomBase62(14);
56
+ }
57
+
58
+ /**
59
+ * Ensure the brand has its own user on the product: reuse by email, else
60
+ * create the auth user + the canonical users/{uid} doc.
61
+ *
62
+ * @param {object} spec
63
+ * @param {object} spec.db - FirestoreREST on the product project (operator SA)
64
+ * @param {object} spec.authAdmin - auth-admin client on the same SA
65
+ * @param {string} spec.email - the brand's product-account email
66
+ * @param {function} spec.resolvePassword - createPasswordResolver instance
67
+ * @param {function} spec.log - line logger
68
+ * @returns {Promise<{ uid: string, created: boolean }>}
69
+ */
70
+ async function ensureProductUser(spec) {
71
+ const { db, authAdmin, email, resolvePassword, log } = spec;
72
+
73
+ const existing = await authAdmin.getUserByEmail(email);
74
+ if (existing) {
75
+ log(`${chalk.green('✓')} Product user exists: ${chalk.cyan(email)} ${chalk.dim(`(${existing.uid})`)}`);
76
+ return { uid: existing.uid, created: false };
77
+ }
78
+
79
+ const { password, source } = await resolvePassword(email);
80
+ if (!password) {
81
+ throw new Error(`no password available for ${email} (channel: ${source})`);
82
+ }
83
+
84
+ const created = await authAdmin.createUser({ email, password });
85
+
86
+ // The canonical account shape (the BEM golden master) with REAL generated
87
+ // credentials — a direct write, since the product's own auth-onCreate
88
+ // trigger only fires for its backend, not for admin REST creates.
89
+ const userDoc = resolveAccount({ auth: { uid: created.uid, email } }, {
90
+ generators: {
91
+ uuid: () => randomUUID(),
92
+ randomId: () => randomBase62(8),
93
+ apiKey: () => randomBase62(43),
94
+ },
95
+ });
96
+ await db.setDoc(`users/${created.uid}`, userDoc);
97
+
98
+ log(`${chalk.green('✓')} Product user created: ${chalk.cyan(email)} ${chalk.dim(`(${created.uid}, password via ${source})`)}`);
99
+ return { uid: created.uid, created: true };
100
+ }
101
+
102
+ /**
103
+ * Mint a brand-owned product asset from a template doc.
104
+ *
105
+ * @param {object} spec
106
+ * @param {object} spec.db - FirestoreREST on the product project (operator SA)
107
+ * @param {object} spec.authAdmin - auth-admin client on the same SA
108
+ * @param {string} spec.collection - 'forms' | 'chats' | 'agents' (the product's collection)
109
+ * @param {string} spec.templateId - existing doc to shape-template from
110
+ * @param {object} spec.overrides - fields the mint sets outright (beyond owner)
111
+ * @param {string} spec.email - brand product-account email
112
+ * @param {function} spec.resolvePassword - createPasswordResolver instance
113
+ * @param {boolean} spec.dryRun
114
+ * @param {function} spec.log - line logger (indented by the caller)
115
+ * @returns {Promise<{ id: string, ownerUid: string }|null>} null on dry-run
116
+ */
117
+ async function createProductAsset(spec) {
118
+ const { db, authAdmin, collection, templateId, overrides = {}, email, resolvePassword, dryRun, log } = spec;
119
+
120
+ const template = await db.getDoc(`${collection}/${templateId}`);
121
+ if (!template) {
122
+ throw new Error(`template doc ${collection}/${templateId} not found — check the template id in omega.json5`);
123
+ }
124
+
125
+ if (dryRun) {
126
+ log(`${chalk.yellow('[DRY RUN]')} Would create ${chalk.cyan(collection)} doc from template ${chalk.dim(templateId)} + product user ${chalk.cyan(email)}`);
127
+ return null;
128
+ }
129
+
130
+ const { uid } = await ensureProductUser({ db, authAdmin, email, resolvePassword, log });
131
+
132
+ const id = generateAssetId();
133
+ // BEM docs mirror their doc id in an `id` field — a copied donor id would
134
+ // self-reference the WRONG doc. Donor usage counters copy as-is (their
135
+ // shape is product-specific; the product resets them on its own cycle).
136
+ const doc = { ...template, ...overrides, owner: uid, ...(template.id !== undefined ? { id } : {}) };
137
+ await db.setDoc(`${collection}/${id}`, doc);
138
+
139
+ log(`${chalk.green('✓')} Created ${chalk.cyan(`${collection}/${id}`)} from template ${chalk.dim(templateId)} ${chalk.dim(`(fields: ${Object.keys(doc).sort().join(', ')})`)}`);
140
+
141
+ return { id, ownerUid: uid };
142
+ }
143
+
144
+ module.exports = { createProductAsset, ensureProductUser, generateAssetId, randomBase62 };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * runCommand — spawn a child in a directory, streaming its output, and
3
+ * resolve a { success, error? } result instead of rejecting. Shared by the
4
+ * update service (npm install / npm run build) and the brand-root test
5
+ * fan-out (per-target `omega test`).
6
+ *
7
+ * Every command runs under the TARGET'S OWN Node (friction #15): the target
8
+ * root's .nvmrc picks the nvm install whose bin is prepended to PATH — one
9
+ * brand can span Node majors (web 24, backend 22) without a single nvm
10
+ * switch. A pinned-but-missing major fails fast with
11
+ * the `nvm install` to run. If a command rewrites the .nvmrc and THEN
12
+ * fails (EM setup bumps the pin when Electron's bundled Node moved), it is
13
+ * re-resolved and retried once — one run self-heals instead of two.
14
+ */
15
+ const { spawn } = require('node:child_process');
16
+ const chalk = require('chalk').default;
17
+ const { resolveTargetNode, nodeEnvFor } = require('./node-version.js');
18
+
19
+ function spawnOnce(command, args, cwd, env) {
20
+ return new Promise((resolve) => {
21
+ const child = spawn(command, args, { cwd, shell: false, stdio: 'inherit', env });
22
+
23
+ child.on('close', (code) => {
24
+ resolve(code === 0
25
+ ? { success: true, code }
26
+ : { success: false, code, error: `exit code ${code}` });
27
+ });
28
+
29
+ child.on('error', (error) => {
30
+ resolve({ success: false, error: error.message });
31
+ });
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Run a command in a directory under that directory's own Node, streaming
37
+ * output. Resolves { success, error?, code? }.
38
+ *
39
+ * @param {string} command - Executable (no shell)
40
+ * @param {string[]} args - Arguments
41
+ * @param {string} cwd - Working directory (its .nvmrc picks the Node)
42
+ * @param {object} [extraEnv] - Env this run carries on top of the caller's own
43
+ * (the test fan-out's OMEGA_TEST_FANOUT signal, #814)
44
+ * @returns {Promise<{ success: boolean, error?: string, code?: number }>}
45
+ */
46
+ async function runCommand(command, args, cwd, extraEnv) {
47
+ let resolved = resolveTargetNode(cwd);
48
+ if (resolved?.error) {
49
+ console.log(` ${chalk.red('✗')} ${resolved.error}`);
50
+ return { success: false, error: resolved.error };
51
+ }
52
+ if (resolved?.binDir) {
53
+ console.log(` ${chalk.dim(`↪ Node ${resolved.version} (.nvmrc ${resolved.spec})`)}`);
54
+ }
55
+
56
+ const specBefore = resolved?.spec || null;
57
+ const result = await spawnOnce(command, args, cwd, { ...process.env, ...nodeEnvFor(resolved), ...extraEnv });
58
+ if (result.success) {
59
+ return result;
60
+ }
61
+
62
+ // Self-heal: the command may have rewritten the .nvmrc mid-run (EM setup
63
+ // writes the pin BEFORE its own Node check). Re-resolve; a changed pin
64
+ // gets one retry under the newly pinned Node.
65
+ resolved = resolveTargetNode(cwd);
66
+ if (!resolved || resolved.error || (resolved.spec || null) === specBefore) {
67
+ return result;
68
+ }
69
+
70
+ console.log(` ${chalk.dim(`↪ .nvmrc changed to ${resolved.spec} — retrying under Node ${resolved.version || `v${resolved.major}`}`)}`);
71
+ return spawnOnce(command, args, cwd, { ...process.env, ...nodeEnvFor(resolved), ...extraEnv });
72
+ }
73
+
74
+ module.exports = { runCommand };