@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
package/dist/config.js ADDED
@@ -0,0 +1,1359 @@
1
+ /**
2
+ * Manager registry — SERVICE_ORDER, per-service OPERATIONS, and the manager
3
+ * defaults layer. This is omega-manager's config.js reborn for the brand-
4
+ * monorepo world: the brand's config/omega.json5 is the single home of every
5
+ * provisioned fact (no .brands/ mirror), secrets live in the brand .env, and
6
+ * per-run transients in .omega/runs/{ts}.json.
7
+ *
8
+ * omega-manager's full provisioning order is ported — every service in
9
+ *
10
+ * github → cloudflare → domain → firebase → recaptcha → analytics →
11
+ * search-console → adsense → sendgrid → beehiiv → payment → slapform →
12
+ * chatsy → replyify → server → assets → certificates → seo → disperse →
13
+ * update → account → migrations → bookmark → testing
14
+ *
15
+ * now runs here, plus the workspace service (brand structure/config
16
+ * health) the monorepo world added. disperse is the remnant of its old
17
+ * self (signing artifacts — the config dispersal dissolved into the
18
+ * omega.json5 hierarchy, the .env composition into the delivery step every
19
+ * verb runs, #678), and bookmark +
20
+ * the beehiiv segment automation talk to the companion Chrome extension
21
+ * in extension/ (the last piece, ported with it). Onboarding is the
22
+ * `onboard` wizard and company mode rides runCompany.
23
+ *
24
+ * Provider-named services renamed to their config ROLE key (cp134, Ian:
25
+ * "rename services so they match the config key"): firebase→cloud,
26
+ * sendgrid→campaigns, beehiiv→newsletter, sentry→monitoring; the last eight
27
+ * followed in #418: github→repo, cloudflare→edge, recaptcha→captcha,
28
+ * search-console→search, adsense→advertising, slapform→forms, chatsy→chat,
29
+ * replyify→email. Provider STRINGS and provider KEYS in config
30
+ * (cloud.provider: 'firebase', edge.providers.cloudflare,
31
+ * inbound.chat.providers.chatsy, …) are unchanged — the service is the role,
32
+ * the provider is a value.
33
+ */
34
+
35
+ // =============================================================================
36
+ // TARGET DIRECTORY CONVENTIONS
37
+ // =============================================================================
38
+ // targets/<dir> → target mapping when the target dir doesn't declare its target
39
+ // in its
40
+ // own omega.json5. Exact match or `<name>-<id>` suffix (targets/website-admin →
41
+ // web, instance admin). The mapping's SSOT moved to @omega.js/config with the
42
+ // multi-instance work (the config loader walks the same dirs) — re-exported
43
+ // here so every existing manager import keeps working.
44
+ const { DIR_TARGETS, TARGET_DIRS, isDemoProject, chosenProvider, schemaDefaults, deepMerge } = require('./vendor/config/index.js');
45
+ const { resolveRegistrar } = require('./services/domain/lib/registrars.js');
46
+ const { API_ACCESS_URL: NAMECHEAP_API_ACCESS_URL } = require('./services/domain/lib/namecheap-api.js');
47
+
48
+ // Framework package per target — used by the testing service to compare each
49
+ // target's installed framework against the npm latest. Names flip to their
50
+ // @omega.js/* successors at each rename cutover; mobile is reserved (MAM
51
+ // parked, no framework to check).
52
+ const TARGET_FRAMEWORKS = {
53
+ web: '@omega.js/web',
54
+ backend: '@omega.js/backend',
55
+ extension: '@omega.js/extension',
56
+ desktop: '@omega.js/desktop',
57
+ };
58
+
59
+ // =============================================================================
60
+ // DEFAULT SETTINGS - The manager defaults layer under every brand config
61
+ // =============================================================================
62
+ // Deliberately minimal: omega-manager's giant DEFAULTS block is service-owned
63
+ // data — each section moves here WITH its service port (cloudflare settings
64
+ // arrive with the cloudflare service, etc.). Never park defaults for services
65
+ // that don't exist here yet.
66
+ //
67
+ // SERVICE-OWNED data ONLY (#478): a default for a key @omega.js/config's schema
68
+ // declares lives THERE, as that entry's `default:` — the schema is the one home,
69
+ // and the manage walk materializes those blocks into the brand's own
70
+ // omega.json5. What stays here is the engine data the schema does not declare
71
+ // (Cloudflare zone settings, Apple cert types, Stripe Radar rules, the GA4
72
+ // property fields) plus the placeholder keys a service writes back into. The
73
+ // exported DEFAULTS is the two composed, schema first.
74
+ const MANAGER_DEFAULTS = {
75
+ // Whether the brand is active (disabled brands are skipped)
76
+ enabled: true,
77
+
78
+ // Parent brand URL — the central backend that children fan webhook events
79
+ // to (sendgrid event-webhook, beehiiv webhook) and newsletter generators
80
+ // fetch sources from. The parent brand itself uses 'self'. omega-manager
81
+ // defaulted this to the company's parent URL — it's config now, no default.
82
+ parent: null,
83
+
84
+ // Domain registrar + email — two roles, one providers block each (#425).
85
+ // The domain service reconciles registrar nameservers from the KEY under
86
+ // `providers`; the cloudflare dns/email-routing operations read the key
87
+ // under `email.providers` + `email.forwarding`. No entry = not chosen yet,
88
+ // and the service skips (what a null provider meant).
89
+ domain: {
90
+ providers: {}, // { namecheap: {} } | { squarespace: {} } — one registrar
91
+ email: {
92
+ providers: {}, // { cloudflare: {} } | { squarespace: {} } | { privateemail: {} }
93
+ forwarding: [], // [{ from: 'support' | '*', to: 'inbox@example.com' }] — role-level, provider-agnostic
94
+ },
95
+ },
96
+
97
+ // Cloud settings — ONE home (#23): the provisioning fields the manager owns
98
+ // sit beside `cloud.provider`/`cloud.config` (the target config @omega.js/config
99
+ // declares), including the platform-level org + billing account the project
100
+ // ensures consume. Auth: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET in the
101
+ // brand .env. projectId has no default and lives ONLY at cloud.config.projectId
102
+ // — interactive runs offer the project selection/creation flow and land it
103
+ // there; non-interactive runs skip until it's set. Company values
104
+ // omega-manager hardcoded (googlegroup support email, the company org and
105
+ // billing account) are config now — they land in company/brand config.
106
+ cloud: {
107
+ supportEmail: null, // OAuth consent screen support email (defaults to the AUTHORIZING user's email — Google rejects any address the caller doesn't own; set only for an owned Google Group)
108
+ organizationId: null, // GCloud org ID — tri-state (#33): null = ask at project create, false = no org (standalone), value = create inside it (proper default permissions)
109
+ billingAccount: null, // 'billingAccounts/XXXXXX-XXXXXX-XXXXXX' — tri-state: null = ask, false = stay on Spark, value = auto-upgrade to Blaze
110
+ },
111
+
112
+ // Analytics providers. Google auth: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET
113
+ // in the brand .env (analytics.edit scope, tokens cached separately from
114
+ // firebase's). propertyId is required config — interactive runs offer the
115
+ // account + property selection/creation flow and land both ids here
116
+ // (comment-preserving writeback). Meta/TikTok pixel IDs are public config
117
+ // too — the service CREATES the pixel on the platform's accountId and lands
118
+ // its id here (#417); Meta needs neither filled in, since an interactive run
119
+ // pastes the token in and discovers the ad account from it (a provider set
120
+ // to `false` opts out of all of it). Their access tokens live in the brand
121
+ // .env (META_ACCESS_TOKEN / TIKTOK_ACCESS_TOKEN — the names @omega.js/backend
122
+ // reads, and the credentials the creates authenticate with). Per-target
123
+ // GA4 measurement ids land under targets.{target}.analytics.providers
124
+ // .google.id — one stream per surface, so one id per surface.
125
+ analytics: {
126
+ providers: {
127
+ google: {
128
+ accountId: null, // GA account number — console deep-links; the selection flow lands it
129
+ propertyId: null, // GA4 property number — google operations skip until set (the selection/create flow lands it)
130
+ timeZone: 'America/Los_Angeles', // GA4 property reporting time zone (used when the flow creates the property)
131
+ currency: 'USD', // GA4 property reporting currency (used when the flow creates the property)
132
+ enhancedMeasurement: {
133
+ streamEnabled: true,
134
+ scrollsEnabled: true,
135
+ outboundClicksEnabled: true,
136
+ siteSearchEnabled: true,
137
+ videoEngagementEnabled: true,
138
+ fileDownloadsEnabled: true,
139
+ pageChangesEnabled: true,
140
+ formInteractionsEnabled: true,
141
+ },
142
+ },
143
+ meta: {
144
+ id: null, // Meta Pixel ID — the create lands it (public config: it ships in the frontend)
145
+ accountId: null, // Meta AD ACCOUNT number (bare, no act_ prefix) the pixel is created on — discovered from META_ACCESS_TOKEN when unset
146
+ },
147
+ tiktok: {
148
+ id: null, // TikTok Pixel Code — the create lands it
149
+ accountId: null, // TikTok ADVERTISER id the pixel is created on — no account, no create
150
+ },
151
+ },
152
+ },
153
+
154
+ // Devlog — auto-generated commit-digest blog posts (standalone
155
+ // `omega-manager devlog`). Stateless: each run fetches commits from the
156
+ // last `lookbackDays` days, has the provider (Ghostii) write the article,
157
+ // and publishes to `destinations`. Not a service — never runs during manage.
158
+ devlog: {
159
+ enabled: false, // Role-level: the whole pipeline's switch
160
+ providers: {
161
+ ghostii: { // Article writer — presence picks it (only ghostii exists)
162
+ lookbackDays: 5,
163
+ orgs: [], // GitHub orgs/users to fully scan — for NON-brand repos (frameworks, tooling); brand repos are always scanned via the brand configs
164
+ excludeRepos: [], // Repo names never fetched or mentioned
165
+ excludeCommits: [], // Regex patterns (case-insensitive) — matching commit messages never reach the digest
166
+ excludeTopics: [], // Topics the writer must never discuss
167
+ includePrivate: true, // Scan private repos too — exclude rules govern what gets WRITTEN, not what gets read
168
+ postPath: 'devlog', // Sub-folder under src/_posts/{year}/ in the website target
169
+ destinations: ['website'], // website | devto | hashnode | medium (planned)
170
+ overrides: { // Ghostii API overrides
171
+ length: 'long',
172
+ research: false, // The digest is the source — no web research
173
+ insertImages: false, // Local publisher doesn't mirror images into the repo (yet)
174
+ headerImageUrl: 'disabled',
175
+ maxLinks: 10, // Backlinks are the point — allow more than Ghostii's default 6
176
+ },
177
+ },
178
+ },
179
+ },
180
+
181
+ // Search Console (submitSitemap + sitemapPaths: schema defaults) needs no
182
+ // manager-owned data. The domain property (sc-domain:) covers every
183
+ // subdomain; DNS TXT verification writes through Cloudflare. Auth: the same
184
+ // GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET as firebase/analytics (own token
185
+ // cache — webmasters + siteverification scopes). omega-manager also carried
186
+ // a `subdomains: []` key here that nothing ever read — dropped.
187
+
188
+ // AdSense: deliberately NO defaults entry (wave-5 F10). The service gates on
189
+ // the PRESENCE of `advertising.providers.adsense` in the merged
190
+ // config — seeding it here would defeat that gate (defaults merge under
191
+ // every brand), and the account-selection flow would land a shared account
192
+ // into brands that never opted in. A brand (or the company layer) authors
193
+ // the provider entry — even empty — to opt in; `client` (ca-pub-…) comes
194
+ // from config or the interactive selection flow.
195
+
196
+ // Marketing — TWO roles, one providers block each (#425). campaigns = email
197
+ // marketing (the campaigns service); newsletter = the newsletter (the
198
+ // newsletter service). The vendor is a KEY under `providers`; `enabled`
199
+ // stays role-level, and so does `newsletter.content` (it configures
200
+ // @omega.js/backend's generator, not Beehiiv). listId/publicationId are
201
+ // resolved by the services and written back under their provider
202
+ // (comment-preserving writeback), with a state mirror as the resolution
203
+ // cache. Auth: SENDGRID_API_KEY / BEEHIIV_API_KEY in the brand .env
204
+ // (+ OMEGA_WEBHOOK_KEY for the webhook operations).
205
+ // omega-manager also carried a newsletter.content generator blob here —
206
+ // it's @omega.js/backend newsletter-generator data, not service config; it rides the
207
+ // config-hierarchy dispersal story.
208
+ marketing: {
209
+ campaigns: {
210
+ providers: {
211
+ sendgrid: {
212
+ listId: null,
213
+ },
214
+ },
215
+ },
216
+ newsletter: {
217
+ providers: {
218
+ beehiiv: {
219
+ publicationId: null,
220
+ },
221
+ },
222
+ },
223
+ },
224
+
225
+ // Payment providers + products. Public halves live here (publishableKey,
226
+ // clientId, site); secrets come from the brand .env (STRIPE_SECRET_KEY,
227
+ // PAYPAL_CLIENT_SECRET, CHARGEBEE_API_KEY). Interactive runs offer the
228
+ // key-collection flow when an enabled provider is missing credentials
229
+ // (public keys land here, secrets in the .env). Set a provider to
230
+ // `false` to disable it — the flow's Disable answer writes that.
231
+ // Product IDs are written back here by the services (state keeps a mirror).
232
+ // omega-manager's DEFAULTS also carried the company's Stripe organizationId
233
+ // (dashboard deep-links use the account ID from state now) and hardcoded
234
+ // the company CDN for product images (brand.images.brandmark now).
235
+ payment: {
236
+ enabled: true,
237
+ providers: {
238
+ stripe: {
239
+ publishableKey: null,
240
+ updateAccountInfo: true, // false = leave the account's business profile alone
241
+ // Radar fraud-prevention rules — Dashboard-only (no API); the
242
+ // stripe-radar operation prints these as guidance until confirmed.
243
+ // Actions: 'block', 'allow', 'review', 'request_three_d_secure'.
244
+ // Predicate syntax: https://docs.stripe.com/radar/rules/reference
245
+ radar: [
246
+ // --- Card type restrictions ---
247
+ { action: 'block', predicate: ":card_funding: = 'prepaid'", description: 'Block prepaid cards' },
248
+ { action: 'block', predicate: ":card_brand: = 'mastercard' AND :card_funding: = 'debit'", description: 'Block Mastercard debit' },
249
+
250
+ // --- Risk-based rules ---
251
+ { action: 'block', predicate: ":risk_level: = 'highest'", description: 'Block highest risk payments' },
252
+ { action: 'review', predicate: ":risk_level: = 'elevated'", description: 'Review elevated risk payments' },
253
+
254
+ // --- Address/CVC verification ---
255
+ { action: 'block', predicate: ":cvc_check: = 'fail'", description: 'Block failed CVC checks' },
256
+ { action: 'block', predicate: ":address_zip_check: = 'fail'", description: 'Block failed ZIP checks' },
257
+
258
+ // --- Velocity / abuse ---
259
+ { action: 'block', predicate: ':total_charges_per_card_number_daily: > 5', description: 'Block >5 charges per card per day' },
260
+ { action: 'block', predicate: ':total_charges_per_ip_address_daily: > 10', description: 'Block >10 charges per IP per day' },
261
+
262
+ // --- 3D Secure for risky but not blocked ---
263
+ { action: 'request_three_d_secure', predicate: ":risk_level: = 'elevated'", description: 'Require 3DS for elevated risk' },
264
+ ],
265
+ },
266
+ paypal: {
267
+ clientId: null,
268
+ },
269
+ chargebee: {
270
+ site: null,
271
+ },
272
+ },
273
+ products: [], // [{ id, name, type: 'subscription'|'one-time', prices: { monthly, annually, once }, trial: { days }, ... }]
274
+ },
275
+
276
+ // Slapform contact form (slapform.com) — Slapform-operator integration:
277
+ // needs SLAPFORM_SERVICE_ACCOUNT in the brand .env (path to the Slapform
278
+ // Firebase project's service-account JSON). formId comes from the Slapform
279
+ // dashboard (interactive runs offer the paste-back flow and land it
280
+ // here); plan = the tier granted to the form-owner account (Slapform's
281
+ // top tier by default — omega-manager resolved it from the slapform brand's
282
+ // own config in `.brands/`, a company-mode read).
283
+ forms: {
284
+ providers: {
285
+ slapform: {
286
+ formId: null,
287
+ },
288
+ },
289
+ },
290
+
291
+ // Inbound conversations — one home per CHANNEL, provider-discriminated
292
+ // inside it (#23).
293
+ inbound: {
294
+ // Chat: Chatsy support chat (chatsy.ai) — Chatsy-operator integration:
295
+ // needs CHATSY_SERVICE_ACCOUNT in the brand .env (path to the Chatsy
296
+ // Firebase project's service-account JSON). agentId comes from the Chatsy
297
+ // dashboard (interactive runs offer the paste-back flow and land it here);
298
+ // plan = the tier granted to the agent-owner account (Chatsy's top tier by
299
+ // default — omega-manager resolved it from the chatsy brand's own config in
300
+ // `.brands/`, a company-mode read). updateAgentInfo: false = the agent is
301
+ // shared and managed by another brand. sponsorshipsUrl fills the baseline
302
+ // knowledge's sponsorship line (omega-manager hardcoded the company page;
303
+ // null → {website}/contact). The agent image comes from
304
+ // brand.images.brandmark (omega-manager hardcoded the company CDN).
305
+ // `settings` is the widget presentation block @omega.js/client passes to
306
+ // the Chatsy SDK — same home as the provisioning fields, no second copy.
307
+ chat: {
308
+ providers: {
309
+ chatsy: {
310
+ agentId: null,
311
+ sponsorshipsUrl: null,
312
+ },
313
+ },
314
+ },
315
+
316
+ // Email: Replyify customer-service email agent (replyify.app) —
317
+ // Replyify-operator integration: needs REPLYIFY_SERVICE_ACCOUNT in the
318
+ // brand .env (path to the Replyify Firebase project's service-account
319
+ // JSON). agentId comes from the Replyify dashboard (interactive runs offer
320
+ // the paste-back flow and land it here); plan = the tier granted to the
321
+ // agent-owner account (Replyify's top tier by default — omega-manager
322
+ // resolved it from the replyify brand's own config in `.brands/`, a
323
+ // company-mode read). updateAgentInfo: false = the agent is shared and
324
+ // managed by another brand. discount renders the baseline's discount
325
+ // section only when set ({ code, label } — omega-manager hardcoded the
326
+ // company's code for every brand); the company sponsorship block left the
327
+ // packaged baseline entirely — that prose belongs in config/replyify.md.
328
+ email: {
329
+ providers: {
330
+ replyify: {
331
+ agentId: null,
332
+ discount: null,
333
+ },
334
+ },
335
+ },
336
+ },
337
+
338
+ // Company-server brand registry — publishes the brand's registry entry
339
+ // (brand identity, github, sponsorships) to the company server's Firestore
340
+ // at brands/{brand.id}, where the parent backend reads it (webhook fan-out,
341
+ // cross-brand features). Needs SERVER_SERVICE_ACCOUNT in the brand .env
342
+ // (path to the company server's Firebase service-account JSON — omega-
343
+ // manager hardcoded the company project and read company-instance secrets).
344
+ server: {
345
+ enabled: true,
346
+ },
347
+
348
+ // Parent-project brand directory (#246) — pushes this brand's own entry
349
+ // into the PARENT project's brands collection, so whatever the parent runs
350
+ // on top of it (ITW's guest-post sponsorship marketplace is the first) reads
351
+ // a current directory. Its opt-in switch (`directory.enabled`, off by
352
+ // default because the entry is world-readable by design) is a schema
353
+ // default; the service also needs `parent` to name the relationship and
354
+ // DIRECTORY_SERVICE_ACCOUNT in the brand .env.
355
+
356
+ // Derived visual collateral generated locally from the brand's logo
357
+ // sources (assets/logo/*.svg in the brand repo → .omega/assets/):
358
+ // wordmark/combomark from brand.font (omega-manager packaged the
359
+ // company's commercial fonts and defaulted every brand to CromaSans —
360
+ // the port has no packaged fonts, the brand owns its font choice), logo
361
+ // variants + PNG ladders, app icons, social icons, favicons. Every
362
+ // operation is mtime-diffed; no brandmark → the AI generation flow when
363
+ // MrLogo credentials are in the brand .env (MRLOGO_SERVICE_ACCOUNT /
364
+ // MRLOGO_API_KEY / LOGO_API_ID_TOKEN — zero config options), else a
365
+ // clean skip.
366
+ assets: {
367
+ enabled: true,
368
+ },
369
+
370
+ // Apple signing certificates, bundle IDs, and provisioning profiles for
371
+ // brands with desktop/mobile targets, reconciled via the App Store
372
+ // Connect API. Credentials live in the brand .env (APPLE_API_ISSUER,
373
+ // APPLE_API_KEY_ID, APPLE_TEAM_ID) plus an AuthKey_*.p8 placed in
374
+ // .omega/certificates/apple/. bundleIdPrefix MUST be set by the brand
375
+ // (reverse-DNS of the company/brand domain, e.g. 'com.mycompany' — the
376
+ // onboard wizard derives + seeds it) — omega-manager hardcoded the
377
+ // company prefix and kept one shared cert set in its company-instance
378
+ // .output/_shared/; the port keeps everything brand-local.
379
+ certificates: {
380
+ enabled: true,
381
+ providers: {
382
+ apple: {
383
+ // Full bundle ID = composeBundleId(prefix, brand.id) — the brand id's
384
+ // dashes become dots (Android-safe segments), e.g.
385
+ // com.itwcreativeworks + omega-playground → com.itwcreativeworks.omega.playground
386
+ bundleIdPrefix: null,
387
+ // Capabilities enabled on the brand's bundle ID
388
+ capabilities: ['APPLE_ID_AUTH'],
389
+ // Cert types that get a provisioning profile per applicable platform
390
+ profiles: ['IOS_DISTRIBUTION', 'MAC_APP_DISTRIBUTION', 'DEVELOPER_ID_APPLICATION_G2'],
391
+ // Certificate types to manage (one set per Apple Developer account).
392
+ // manual: Apple's API cannot create these — the Account Holder
393
+ // downloads the .cer from the developer portal.
394
+ certificates: [
395
+ { type: 'DEVELOPMENT' },
396
+ { type: 'IOS_DISTRIBUTION' },
397
+ { type: 'MAC_INSTALLER_DISTRIBUTION' },
398
+ { type: 'MAC_APP_DISTRIBUTION' },
399
+ { type: 'DEVELOPER_ID_APPLICATION_G2', manual: true },
400
+ { type: 'DEVELOPER_ID_INSTALLER_G2', manual: true },
401
+ ],
402
+ },
403
+ },
404
+ },
405
+
406
+ // Parasite SEO content — programmatically created GitHub repos with
407
+ // templated READMEs. Content definitions live in seo.github.content
408
+ // (config/omega.json5) or the config/seo.json5 sidecar (omega-manager
409
+ // kept them in .brands/{id}/seo.json and auto-created a default entry
410
+ // for every brand; the port never writes config — no content = skip).
411
+ seo: {
412
+ enabled: true,
413
+ },
414
+
415
+ // Required accounts in the brand's Firebase Auth — created/converged with
416
+ // deterministic passwords derived from ACCOUNT_PASSWORD_SEED (brand .env,
417
+ // auto-generated on the first real run). `{domain}` in emails resolves to
418
+ // the brand's domain. Per entry: account: true = ensure the auth user
419
+ // exists with the derived password + roles.admin + the highest plan;
420
+ // marketing: true = push the contact to the marketing providers via the
421
+ // brand backend. Any OTHER user holding roles.admin fails the service.
422
+ // (omega-manager hardcoded the company's personal emails as ADMIN_EMAILS
423
+ // and derived passwords from a company-specific formula in code.)
424
+ account: {
425
+ enabled: true,
426
+ admins: [
427
+ { email: 'support@{domain}', account: true, marketing: true },
428
+ ],
429
+ },
430
+
431
+ // Classic reCAPTCHA — the brand's OWN keys (de-ITW: never a company-shared
432
+ // key), read from the brand .env (RECAPTCHA_SITE_KEY + RECAPTCHA_SECRET_KEY;
433
+ // missing keys → the service asks interactively, else skips). `project` =
434
+ // the brand's GCP project hosting the key, used only for the console
435
+ // deep-link in guidance (omega-manager hardcoded the company project here).
436
+ captcha: {
437
+ providers: {
438
+ recaptcha: {
439
+ project: null,
440
+ },
441
+ },
442
+ },
443
+
444
+ // Cloudflare settings — the engine defaults are PLATFORM defaults only.
445
+ // Company-specific records (DMARC report addresses, BIMI logo, verification
446
+ // TXTs, extra CSP hosts) belong in company/brand config, NOT here —
447
+ // omega-manager hardcoded them; the port moved them to config:
448
+ // dns.dmarcReports { rua, ruf }, dns.bimiLogo, dns.records [...], and the
449
+ // responseHeaders rules. The SendGrid domain-auth CNAMEs are NOT config in
450
+ // any layer: they are SendGrid's own observed facts, read live per run
451
+ // ([#692](https://github.com/Omega-JS-Stack/omega/issues/692)).
452
+ edge: {
453
+ providers: {
454
+ cloudflare: {
455
+ dns: {
456
+ spf: 'strict', // 'strict' (-all) | 'soft' (~all)
457
+ dmarcPolicy: 'quarantine', // 'none' | 'quarantine' | 'reject'
458
+ spfIncludes: ['_spf.google.com', 'sendgrid.net'], // provider include appended automatically
459
+ },
460
+ // Zone settings — flat map matching the Cloudflare API setting IDs exactly.
461
+ // Managed by the generic `zone-settings` operation which diffs all keys in
462
+ // one pass. Includes both bulk settings (from /zones/{id}/settings) and
463
+ // addon settings (speed_brain, fonts) fetched individually.
464
+ // Any brand can override with `edge.providers.cloudflare.settings.{setting_id}`.
465
+ settings: {
466
+ // SSL / TLS
467
+ ssl: 'full',
468
+ min_tls_version: '1.2',
469
+ always_use_https: 'on',
470
+ automatic_https_rewrites: 'on',
471
+ opportunistic_encryption: 'on',
472
+ opportunistic_onion: 'on',
473
+ tls_1_3: 'zrt',
474
+ tls_1_2_only: 'off',
475
+ tls_client_auth: 'off',
476
+ ech: 'on', // Encrypted Client Hello
477
+ pq_keyex: 'on', // Post-quantum key exchange
478
+ replace_insecure_js: 'on', // Block mixed content by rewriting http:// → https://
479
+
480
+ // Scrape Shield
481
+ email_obfuscation: 'off', // Injects cloudflare-static/email-decode.min.js — breaks clean HTML
482
+ server_side_exclude: 'on',
483
+ hotlink_protection: 'off', // Breaks legit embeds (Slack unfurls, etc.)
484
+
485
+ // Speed
486
+ brotli: 'on',
487
+ early_hints: 'on',
488
+ rocket_loader: 'off', // Rewrites <script> tags — causes issues with modern frameworks
489
+ speed_brain: 'on', // Speculation Rules API — addon setting (not in bulk endpoint)
490
+ fonts: 'on', // Cloudflare Fonts — addon setting (not in bulk endpoint)
491
+
492
+ // Network
493
+ http3: 'on',
494
+ '0rtt': 'on',
495
+ ipv6: 'on',
496
+ websockets: 'on',
497
+ ip_geolocation: 'on',
498
+ pseudo_ipv4: 'off',
499
+ orange_to_orange: 'off',
500
+ visitor_ip: 'on',
501
+
502
+ // Caching
503
+ cache_level: 'aggressive',
504
+ browser_cache_ttl: 432000, // 5 days
505
+ always_online: 'on',
506
+ development_mode: 'off',
507
+ edge_cache_ttl: 7200, // 2 hours — only applies without cache rules
508
+
509
+ // Security
510
+ security_level: 'low',
511
+ browser_check: 'on',
512
+ challenge_ttl: 1800,
513
+ privacy_pass: 'on',
514
+ waf: 'off', // Legacy WAF — Cloudflare replaced with rulesets
515
+ max_upload: 100,
516
+ security_header: {
517
+ strict_transport_security: {
518
+ enabled: true,
519
+ max_age: 0,
520
+ include_subdomains: true,
521
+ preload: true,
522
+ nosniff: true,
523
+ },
524
+ },
525
+
526
+ // Logging
527
+ log_to_cloudflare: 'on',
528
+ filter_logs_to_cloudflare: 'off',
529
+ },
530
+ // Speed scheduled tests — custom API endpoint, kept as separate operation
531
+ speedTest: {
532
+ frequency: 'WEEKLY',
533
+ region: 'us-central1',
534
+ },
535
+ // Cache rules — complex ruleset, kept as separate operation.
536
+ // The web build content-hashes the bundles it emits under /assets
537
+ // (`main-<hash>.js`, `theme-<hash>.css`), so those bytes are immutable
538
+ // and cacheable forever. HTML is the opposite: its URL never changes,
539
+ // so whatever a browser holds is what a returning visitor sees until it
540
+ // expires — a minute here, and the edge copy a deploy purges (#751).
541
+ // One path, one lifetime: the HTML rule excludes /assets so the two
542
+ // never both match a request.
543
+ cacheRules: [
544
+ {
545
+ name: 'Assets: Cache for 1 Year',
546
+ expression: '(http.request.uri.path wildcard r"/assets/*") or (http.request.uri.path eq "/__/auth/iframe.js")',
547
+ edgeTtl: 31536000,
548
+ browserTtl: 31536000,
549
+ enabled: true,
550
+ priority: 100,
551
+ },
552
+ {
553
+ name: 'HTML: Short Browser Cache',
554
+ // A cache rule is ZONE-scoped, and the zone also serves the api
555
+ // host — whose Firebase rewrites answer EXTENSIONLESS user-scoped
556
+ // GETs (/authorize, /token, /omega/**, /mcp/**). Caching one of
557
+ // those at the edge would hand one user's answer to the next, so
558
+ // the rule is guarded by host: every SITE host on the zone (apex,
559
+ // www, a subdomain project under the parent zone) matches, and the
560
+ // two non-site hosts the stack creates never do: api.<domain>,
561
+ // `api.` + the target's own domain at every shape
562
+ // (cloud/ensure/hosting.js), and emailurl.<domain>, the proxied
563
+ // SendGrid link-tracking CNAME (edge/lib/dns-records-helpers.js)
564
+ // whose extensionless click and open URLs must reach SendGrid
565
+ // every time or campaign counts undercount.
566
+ // Pages are extensionless (the trailing-slash redirect below) or
567
+ // end in .html; `not … contains "."` is the free-plan way to say
568
+ // "no file extension" (`matches` needs Business).
569
+ expression: '(not starts_with(http.host, "api.") and not starts_with(http.host, "emailurl.") and not starts_with(http.request.uri.path, "/assets/") and (not (http.request.uri.path contains ".") or ends_with(http.request.uri.path, ".html")))',
570
+ // The edge copy is what a deploy purges; only the browser copy,
571
+ // which no purge can reach, has to expire on its own.
572
+ edgeTtl: 7200,
573
+ browserTtl: 60,
574
+ enabled: true,
575
+ priority: 200,
576
+ },
577
+ ],
578
+ rules: {
579
+ managedTransforms: {
580
+ request: {
581
+ addClientCertificateHeaders: false,
582
+ addVisitorLocationHeaders: true,
583
+ removeVisitorIpHeaders: false,
584
+ addWafCredentialCheckStatusHeader: false,
585
+ },
586
+ response: {
587
+ removeXPoweredByHeader: true,
588
+ addSecurityHeaders: false,
589
+ },
590
+ },
591
+ responseHeaders: [
592
+ {
593
+ name: 'CSP: Allow iframe from self',
594
+ expression: 'true',
595
+ headers: {
596
+ // Brand/company config overrides this rule to append extra hosts
597
+ 'Content-Security-Policy': "frame-ancestors 'self' https://localhost:* https://{ domain } https://*.{ domain }",
598
+ },
599
+ enabled: true,
600
+ priority: 100,
601
+ },
602
+ ],
603
+ redirect: [
604
+ {
605
+ name: 'Redirect: Remove Trailing Slash',
606
+ expression: '(ends_with(http.request.uri.path, "/") and http.request.uri.path ne "/")',
607
+ statusCode: 301,
608
+ preserveQueryString: true,
609
+ targetUrl: {
610
+ expression: 'concat("https://", http.host, substring(http.request.uri.path, 0, -1))',
611
+ },
612
+ enabled: true,
613
+ priority: 100,
614
+ },
615
+ ],
616
+ security: [
617
+ {
618
+ name: 'API: Minimal Security',
619
+ action: 'skip',
620
+ expression: '(http.host eq "api.{ domain }")',
621
+ skipProducts: ['uaBlock', 'bic', 'hot', 'securityLevel', 'rateLimit', 'zoneLockdown', 'waf'],
622
+ skipPhases: ['http_ratelimit', 'http_request_firewall_managed', 'http_request_sbfm'],
623
+ skipRuleset: 'current',
624
+ logging: true,
625
+ enabled: true,
626
+ priority: 100,
627
+ },
628
+ ],
629
+ },
630
+ },
631
+ },
632
+ },
633
+ };
634
+
635
+ // The manager's defaults layer: the schema's own answers first, this file's
636
+ // service-owned data on top (#478). Every existing read site — the walk's
637
+ // loadConfig defaults, `DEFAULTS.account.admins`, the zone-settings diff —
638
+ // sees one composed object, and no default has two homes.
639
+ const DEFAULTS = deepMerge(schemaDefaults(), MANAGER_DEFAULTS);
640
+
641
+ // =============================================================================
642
+ // SERVICE ORDER - Services run in this order due to dependencies
643
+ // =============================================================================
644
+ const SERVICE_ORDER = [
645
+ 'workspace', // brand monorepo structure + config health — everything depends on a sane workspace
646
+ 'repo', // the brand repo must exist before services that write to it
647
+ 'edge', // zone must exist before DNS-dependent services
648
+ 'domain', // registrar nameservers point at the zone the edge service just created/verified
649
+ 'cloud', // cloud project (Firebase provider) must exist before analytics/backend-dependent services
650
+ 'captcha', // validates the shared keys the frontend/backend consume from .env
651
+ 'analytics', // GA4 streams need the firebase link; search links to analytics next
652
+ 'search', // needs the edge zone (DNS verification) + the GA property (association)
653
+ 'advertising', // domain present in the AdSense account + approval state (read-only API)
654
+ 'monitoring', // error-monitoring project per target + DSN writeback (Sentry provider; own API, no cross-service deps)
655
+ 'campaigns', // email marketing (SendGrid provider): domain auth (DNS via the edge zone), sender, list, unsubscribe groups, fields, segments, webhook
656
+ 'newsletter', // newsletter publication (Beehiiv provider): access, fields, segments (verify-only), webhook
657
+ 'payment', // Stripe/PayPal/Chargebee products + prices + webhooks reconciled to payment.products
658
+ 'forms', // brand's Slapform contact form settings + owner-account plan (Slapform operator only)
659
+ 'chat', // brand's Chatsy chat agent settings + knowledge + owner-account plan (Chatsy operator only)
660
+ 'email', // brand's Replyify email agent filter + knowledge + owner-account plan (Replyify operator only)
661
+ 'server', // brand registry entry on the company server's Firestore (company-server operators only)
662
+ 'directory', // brand's own entry pushed into the PARENT project's brands collection (opt-in; no cross-service deps)
663
+ 'assets', // derived logo variants, app icons, social icons, favicons (local, mtime-diffed)
664
+ 'certificates', // Apple certs, bundle IDs, provisioning profiles (desktop/mobile targets only)
665
+ 'ai', // AI provider keys asked into the brand .env — the ONE file every target's runtime env composes from
666
+ 'disperse', // signing artifacts land in the targets (after certificates, before update builds)
667
+ 'seo', // parasite SEO GitHub repos — low priority, no downstream deps
668
+ 'update', // installs deps + builds every target
669
+ 'account', // required Firebase Auth accounts + admin roles (after deploy — signup calls hit the live backend)
670
+ 'migrations', // Firestore data migrations — only with --migration (audit unless --execute), after the deployed backend is current
671
+ 'bookmark', // brand bookmarks → the companion Chrome extension (interactive sessions only)
672
+ 'testing', // health checks after everything else ran
673
+ ];
674
+
675
+ // The BOOT lane (#228) — what the dev legs consume every boot: local
676
+ // redistribution only (file work, no network, no rebuilds), so `omega dev`
677
+ // starts in about a second instead of waiting on the cloud services.
678
+ // NOT in this list = manage lane by construction: a new service can never
679
+ // slow the boot by default, and promoting one is a deliberate edit here.
680
+ // It is the DELIVERY lane too (#678): brand-root `omega deploy` runs this
681
+ // same list before it fans out, so a publish never ships inputs a manage run
682
+ // happened to be current on. One constant, two triggers.
683
+ const BOOT_SERVICES = [
684
+ 'workspace', // brand structure + config health — a broken brand must not serve
685
+ 'assets', // derived logo/icon variants the targets read from their own dirs
686
+ 'disperse', // signing artifacts land in the targets
687
+ ];
688
+
689
+ // =============================================================================
690
+ // OPERATIONS CONFIG - What operations to run for each service
691
+ // =============================================================================
692
+ const OPERATIONS = {
693
+ workspace: [
694
+ { name: 'structure', ensure: true }, // Root workspaces + a dir per enabled target
695
+ { name: 'config', ensure: true }, // omega.json5 loads + validates (brand and per-target)
696
+ { name: 'defaults', ensure: true }, // Schema-defaulted blocks the brand file lacks are materialized (#478)
697
+ { name: 'gitignore', ensure: true }, // .omega/ is gitignored (state never gets committed)
698
+ { name: 'scripts', ensure: true }, // Root scripts say `omega` + deploy exists; target scripts fill from framework projectScripts (#675)
699
+ { name: 'agents', ensure: true }, // AGENTS.md framework-guide import + CLAUDE.md pointer
700
+ { name: 'claude-settings', ensure: true }, // .claude/settings.json enables the omega plugin from the installed manager (published installs)
701
+ { name: 'workflows', ensure: true }, // Composed .github/workflows/<target>-*.yml for targets the config no longer enables are removed (#636)
702
+ { name: 'env-keys', ensure: true }, // Brand-generated keys (the OMEGA_* trio + UNSUBSCRIBE_HMAC_KEY) minted into the brand .env when the cascade has none (#569)
703
+ { name: 'env-order', ensure: true }, // Brand/company .env in the canonical group order (cp137)
704
+ { name: 'env-rules', ensure: true }, // Keys the brand's own config makes mandatory (the schema's requiredWhen) — WARNS, never fails (#626)
705
+ { name: 'translation-sdk', ensure: true }, // Translating web targets declare + install @anthropic-ai/claude-agent-sdk (#168)
706
+ ],
707
+
708
+ repo: [
709
+ { name: 'org', ensure: true }, // Org profile matches the brand (skipped for shared orgs)
710
+ { name: 'repo', ensure: true }, // The brand-monorepo repo exists with the right settings
711
+ { name: 'pages', ensure: true }, // GitHub Pages on gh-pages + custom domain (web target)
712
+ ],
713
+
714
+ edge: [
715
+ { name: 'zone', ensure: true }, // Zone exists (created when missing; nameservers reported when pending)
716
+ { name: 'dns-records', ensure: true }, // Required + custom records diff-synced
717
+ { name: 'email-routing', ensure: true }, // Cloudflare Email Routing (only when domain.email.providers names cloudflare)
718
+ { name: 'zone-settings', ensure: true }, // Generic — diffs all /zones/{id}/settings values + addons
719
+ { name: 'cache-rules', ensure: true },
720
+ { name: 'rules-managed-transforms', ensure: true },
721
+ { name: 'rules-redirect', ensure: true },
722
+ { name: 'rules-configuration', ensure: true },
723
+ { name: 'rules-response-headers', ensure: true },
724
+ { name: 'rules-security', ensure: true },
725
+ { name: 'speed-scheduled-tests', ensure: true }, // Custom Speed API endpoint
726
+ { name: 'workers', ensure: true }, // Worker scripts + routes (only when edge.providers.cloudflare.workers configured)
727
+ ],
728
+
729
+ domain: [
730
+ { name: 'nameservers', ensure: true }, // Registrar nameservers → Cloudflare (namecheap via API, manual registrars get instructions)
731
+ ],
732
+
733
+ cloud: [
734
+ { name: 'billing', ensure: true }, // Blaze plan (links cloud.billingAccount when configured)
735
+ { name: 'services', ensure: true }, // Required Google Cloud APIs + compute deploy roles
736
+ { name: 'project-settings', ensure: true }, // GCP display name + the 'Web App' web app
737
+ { name: 'oauth-consent', ensure: true }, // OAuth consent screen (support email)
738
+ { name: 'service-account', ensure: true }, // Admin SDK service account + key → .omega/secrets (the ONE home; omega build stages it)
739
+ { name: 'hosting', ensure: true }, // Hosting site + api.{domain} custom domains (DNS via Cloudflare)
740
+ { name: 'firestore', ensure: true }, // Firestore database + PITR
741
+ { name: 'database', ensure: true }, // Realtime Database
742
+ { name: 'authentication', ensure: true }, // Identity Platform + sign-in methods + authorized domains
743
+ { name: 'storage', ensure: true }, // Default storage bucket
744
+ { name: 'functions', ensure: true }, // Cloud Functions readiness check (read-only)
745
+ { name: 'cloud-messaging', ensure: true }, // FCM API + VAPID key pair (from state)
746
+ { name: 'sdk-config', ensure: true }, // Web SDK config → state + omega.json5 drift check
747
+ ],
748
+
749
+ captcha: [
750
+ { name: 'site-key', ensure: true }, // Secret key proven valid via siteverify; domain list is manual guidance (no classic API)
751
+ ],
752
+
753
+ analytics: [
754
+ { name: 'google-streams', ensure: true }, // One GA4 web stream per target (+ enhanced measurement + MP secret + the per-target measurement id in config)
755
+ { name: 'google-firebase-link', ensure: true }, // GA property ↔ Firebase project link (+ auto-stream normalization)
756
+ { name: 'meta-pixel', ensure: true }, // Pixel created on meta.accountId when missing + META_ACCESS_TOKEN presence
757
+ { name: 'tiktok-pixel', ensure: true }, // Pixel created on tiktok.accountId when missing + TIKTOK_ACCESS_TOKEN presence
758
+ ],
759
+
760
+ search: [
761
+ { name: 'property', ensure: true }, // sc-domain property exists (DNS TXT verification via Cloudflare, one-pass)
762
+ { name: 'ga-link', ensure: true }, // GA association — no API exists; warned + URL until confirmed
763
+ { name: 'sitemaps', ensure: true }, // Missing sitemaps submitted (existing ones are converged, not resubmitted)
764
+ ],
765
+
766
+ advertising: [
767
+ { name: 'sites', ensure: true }, // Domain present in AdSense + approval state (read-only API — adding is manual)
768
+ ],
769
+
770
+ monitoring: [
771
+ { name: 'projects', ensure: true }, // Org/team resolution + one Sentry project per enabled target (monitoring.providers.sentry.org written back)
772
+ { name: 'dsn', ensure: true }, // Client-key DSNs → targets.<type>.monitoring.providers.sentry.dsn (comment-preserving writeback)
773
+ ],
774
+
775
+ campaigns: [
776
+ { name: 'domain-auth', ensure: true }, // Domain authentication (DKIM CNAMEs via Cloudflare, one-pass validate)
777
+ { name: 'link-branding', ensure: true }, // Link branding for emailurl.<domain> (create + validate, then the CNAME flips proxied)
778
+ { name: 'sender-identity', ensure: true }, // Verified sender for Single Sends (offers@{contact domain})
779
+ { name: 'list', ensure: true }, // The brand's marketing list (id written back to omega.json5)
780
+ { name: 'unsubscribe-groups', ensure: true }, // The account's ASM groups, matched by name (ids written back to omega.json5)
781
+ { name: 'custom-fields', ensure: true }, // @omega.js/backend custom fields (@omega.js/backend's marketing SSOT)
782
+ { name: 'segments', ensure: true }, // @omega.js/backend segments (query_dsl diffed; __temp_ orphans swept)
783
+ { name: 'event-webhook', ensure: true }, // Account-global Event Webhook → parent @omega.js/backend forwarder (min-diff PATCH)
784
+ { name: 'contact-person', ensure: true }, // brand.contact.person.name — @omega.js/backend's personal sends fail at runtime without it (#694)
785
+ ],
786
+
787
+ newsletter: [
788
+ { name: 'publication', ensure: true }, // Publication access (config/state id, auto-match by name; creation is manual)
789
+ { name: 'custom-fields', ensure: true }, // @omega.js/backend custom fields (@omega.js/backend's marketing SSOT, diffed by display)
790
+ { name: 'segments', ensure: true }, // @omega.js/backend segments verified (no create API — instructions when missing)
791
+ { name: 'webhook', ensure: true }, // Publication webhook → parent @omega.js/backend forwarder (min-diff PATCH)
792
+ ],
793
+
794
+ payment: [
795
+ { name: 'paypal-account', ensure: true }, // Auth probe (live/sandbox detection) + app info
796
+ { name: 'paypal-webhook', ensure: true }, // Webhook endpoint → brand backend (event_types diffed)
797
+ { name: 'paypal-products', ensure: true }, // Catalog products + billing plans (config → state → name match → create)
798
+ { name: 'stripe-account', ensure: true }, // Business profile diffed to brand config
799
+ { name: 'stripe-radar', ensure: true }, // Radar rules guidance (no API — warned until confirmed)
800
+ { name: 'stripe-disputes', ensure: true }, // Enhanced Dispute Protection guidance (no API — warned until confirmed)
801
+ { name: 'stripe-webhook', ensure: true }, // Webhook endpoint → brand backend (re-enable + enabled_events diffed)
802
+ { name: 'stripe-products', ensure: true }, // Products + prices (config → state → metadata match → create; stale prices archived)
803
+ { name: 'chargebee-account', ensure: true }, // API access probe + site info
804
+ { name: 'chargebee-webhook', ensure: true }, // Webhook endpoint → brand backend (&brand= URL; events set on create only)
805
+ { name: 'chargebee-products', ensure: true }, // Item family → items → item prices (deterministic IDs; legacy plans reported)
806
+ ],
807
+
808
+ forms: [
809
+ { name: 'form', ensure: true }, // Form name + enabled diffed against Slapform Firestore
810
+ { name: 'user', ensure: true }, // Form-owner account set to forms.providers.slapform.plan (internal comp)
811
+ ],
812
+
813
+ chat: [
814
+ { name: 'chat', ensure: true }, // Agent settings + knowledge diffed against Chatsy Firestore
815
+ { name: 'user', ensure: true }, // Agent-owner account set to inbound.chat.providers.chatsy.plan (internal comp)
816
+ ],
817
+
818
+ email: [
819
+ { name: 'agent', ensure: true }, // Agent filter + knowledge diffed against Replyify Firestore
820
+ { name: 'user', ensure: true }, // Agent-owner account set to inbound.email.providers.replyify.plan (internal comp)
821
+ ],
822
+
823
+ server: [
824
+ { name: 'brands', ensure: true }, // Registry entry replace-synced against the company server's Firestore
825
+ ],
826
+
827
+ directory: [
828
+ { name: 'entry', ensure: true }, // Directory entry (identity + declared blocks) merge-synced into the parent project's brands collection
829
+ ],
830
+
831
+ assets: [
832
+ { name: 'logo-gen', ensure: true }, // Wordmark + combomark from brandmark + brand.font (missing-only)
833
+ { name: 'process', write: true }, // Color/black SVG variants + PNG size ladders per logo source
834
+ { name: 'templates', write: true }, // Brand PSDs seeded from the company + logo/text layers refreshed + PNG exports
835
+ { name: 'icons', write: true }, // macOS .icns + Windows .ico app icons (composited icon.png when the templates op made one)
836
+ { name: 'social-icons', write: true }, // Brandmark-on-white social profile icons
837
+ { name: 'favicons', write: true }, // Web favicon set + site.webmanifest
838
+ { name: 'reconcile', write: true }, // Derived files the brand's current sources no longer name are deleted (#636)
839
+ ],
840
+
841
+ certificates: [
842
+ { name: 'api-key', ensure: true }, // App Store Connect creds resolved + client ready
843
+ { name: 'certificates', ensure: true }, // Signing certs — download or create via CSR, export .p12, keychain import
844
+ { name: 'bundle-ids', ensure: true }, // Brand bundle ID exists with the required capabilities
845
+ { name: 'profiles', ensure: true }, // Provisioning profiles per platform × cert type
846
+ ],
847
+
848
+ ai: [
849
+ { name: 'keys', ensure: true }, // The provider API keys, asked once through the shared setup contract (#639)
850
+ ],
851
+
852
+ disperse: [
853
+ { name: 'certs', write: true }, // Signing artifacts copied into desktop/mobile targets' certs dirs
854
+ ],
855
+
856
+ seo: [
857
+ { name: 'github-repos', ensure: true }, // Parasite SEO repos exist + match their template
858
+ ],
859
+
860
+ update: [
861
+ { name: 'targets', write: true }, // Installs deps, builds every target
862
+ ],
863
+
864
+ account: [
865
+ { name: 'users', ensure: true }, // Auth accounts exist + passwords/admin roles converged + admin audit
866
+ ],
867
+
868
+ migrations: [
869
+ { name: 'targets-rename', ensure: true, local: true }, // #443: the brand's apps/ → targets/, workspaces glob following (runs ALONE — see manage.js)
870
+ { name: 'notifications', ensure: true }, // uid→owner + metadata/context/attribution + validate schema
871
+ { name: 'users', ensure: true }, // plan→subscription + @omega.js/backend-schema backfill + orphan cleanup + validate
872
+ { name: 'orders', ensure: true }, // payments-orders: legacy attribution.utm blob → first/last touches
873
+ { name: 'payments-intents', ensure: true }, // payments-intents: legacy attribution.utm blob → first/last touches
874
+ { name: 'payment-provider', ensure: true }, // #428 word rename: the stored `processor` field → `provider`, across all five payment collections
875
+ { name: 'state-retirement', ensure: true, local: true }, // #434: the retired .omega/state.json content → config/omega.json5 + .env; the file's machine records stay (#479)
876
+ ],
877
+
878
+ bookmark: [
879
+ { name: 'sync', ensure: true }, // Push brand console/dashboard bookmarks to the companion Chrome extension
880
+ ],
881
+
882
+ testing: [
883
+ { name: 'target-checks', ensure: true }, // Per-target local checks (build output, backend files, framework version) + live checks (homepage, API health, GitHub Actions)
884
+ ],
885
+ };
886
+
887
+ // =============================================================================
888
+ // REQUIRES - Per-service inputs (env var names + Google scopes)
889
+ // =============================================================================
890
+ // The declarative half of the setup contract (#608, cp114 before it): every
891
+ // service that needs a credential or a one-time authorization declares it
892
+ // HERE, next to its operations — ONE home, read by both halves:
893
+ // - lib/preflight.js checks the whole enabled set before any service runs and
894
+ // prints one consolidated fix walkthrough (cp236's 403-diagnostics tone)
895
+ // instead of N mid-run skips;
896
+ // - lib/service-input.js asks for what is still missing when the service
897
+ // actually runs, through the uniform Provide / Skip / Disable gate.
898
+ // Every env NAME therefore lives exactly once. WHICH keys OMEGA mints for
899
+ // itself and which a human acquires is the env schema's to say
900
+ // (@omega.js/config, #581) — the sweep test (test/service-input.test.js) holds
901
+ // this registry to it: every acquired key the manager actually reads must be
902
+ // declared here.
903
+ //
904
+ // Shape per service — requires: { env, scopes } plus the gates around them:
905
+ // why - one line: what the requirement buys (the walkthrough's "needed")
906
+ // label - the human name the setup gate opens with ("Cloudflare")
907
+ // disablePath - where "Disable permanently" writes `false` (the tri-state
908
+ // opt-out, #33). Mirrors the key the service's own setup gate reads.
909
+ // when - (brandConfig) => bool: whether the service would run at all for
910
+ // this brand. Mirrors ONLY the service setup's own config gate —
911
+ // keep the two in lockstep. Absent = always applies.
912
+ // env - [{ name, label?, url?, hint?, prompted?, when?, gates?, disablePath? }]
913
+ // — the descriptor shape lib/service-input.js takes. prompted: true =
914
+ // an interactive run collects the value mid-run (the paste flow), so
915
+ // preflight lets the service run on a TTY. Entry-level `when` = the
916
+ // entry only applies for some configs (registrar-specific creds).
917
+ // `gates: false` = OPTIONAL input: the service runs without it (a
918
+ // second payment provider, an operator-tier service account, one of
919
+ // two pixel platforms), so preflight never gates on it and the
920
+ // operation that needs it asks in place. Entry-level `disablePath`
921
+ // narrows the opt-out to the provider that owns the key.
922
+ // Values are NEVER read here — names only.
923
+ // scopes - the Google OAuth scopes this service's API calls actually hit
924
+ // (each a member of google-auth's GOOGLE_SCOPES union — every
925
+ // consent grants the union, so these only ever miss against a
926
+ // pre-union or stale token store). Checked against the token
927
+ // store's granted-scopes record — what IS knowable before a
928
+ // call; the live grant is proven at call time (the google-auth
929
+ // 403 diagnostics are the backstop).
930
+ // Services NOT listed need no credential of their own (repo authorizes through
931
+ // `gh auth login`; the local services touch nothing external).
932
+
933
+ // The shared Google OAuth app credentials — the ONE identity every Google
934
+ // service authorizes (google-auth.js). Declared once, referenced per service.
935
+ const GOOGLE_ENV = [
936
+ { name: 'GOOGLE_CLIENT_ID', label: 'Google OAuth client ID', url: 'https://console.cloud.google.com/apis/credentials', hint: 'A Desktop-app OAuth client — the one Google identity every service shares' },
937
+ { name: 'GOOGLE_CLIENT_SECRET', label: 'Google OAuth client secret', url: 'https://console.cloud.google.com/apis/credentials' },
938
+ ];
939
+
940
+ // The key every webhook route compares — OMEGA's OWN (`generated:` in the env
941
+ // schema), so the setup contract MINTS it rather than asking (#635). Declared
942
+ // by each service whose webhook operations build a forwarder URL from it, so
943
+ // one that runs before the workspace service did still has it. `gates: false`:
944
+ // nothing to acquire means nothing for preflight to gate on.
945
+ const WEBHOOK_KEY_ENV = { name: 'OMEGA_WEBHOOK_KEY', label: 'Omega webhook key', gates: false };
946
+
947
+ const REQUIRES = {
948
+ edge: {
949
+ why: 'reconciles the zone, DNS records, rulesets, and settings via the Cloudflare API',
950
+ label: 'Cloudflare',
951
+ disablePath: 'edge.providers.cloudflare.enabled',
952
+ when: (config) => config.edge?.providers?.cloudflare?.enabled !== false,
953
+ env: [
954
+ { name: 'CLOUDFLARE_TOKEN', label: 'Cloudflare API token', url: 'https://dash.cloudflare.com/profile/api-tokens', prompted: true },
955
+ ],
956
+ scopes: [],
957
+ },
958
+
959
+ domain: {
960
+ why: 'points the registrar nameservers at the Cloudflare zone',
961
+ label: 'Domain registrar',
962
+ disablePath: 'domain.enabled',
963
+ when: (config) => config.domain?.enabled !== false && Boolean(resolveRegistrar(config)),
964
+ env: [
965
+ { name: 'CLOUDFLARE_TOKEN', label: 'Cloudflare API token (reads the zone nameservers)', url: 'https://dash.cloudflare.com/profile/api-tokens', prompted: true },
966
+ { name: 'NAMECHEAP_USERNAME', label: 'Namecheap account username', prompted: true, when: (config) => resolveRegistrar(config) === 'namecheap' },
967
+ { name: 'NAMECHEAP_API_KEY', label: 'Namecheap API key', url: NAMECHEAP_API_ACCESS_URL, prompted: true, when: (config) => resolveRegistrar(config) === 'namecheap' },
968
+ ],
969
+ scopes: [],
970
+ },
971
+
972
+ cloud: {
973
+ why: 'reconciles the Firebase/GCP project (billing, APIs, hosting, auth, data stores) via Google APIs',
974
+ label: 'Google Cloud',
975
+ disablePath: 'cloud.enabled',
976
+ when: (config) => {
977
+ if (config.cloud?.enabled === false) return false;
978
+ // No projectId yet → the interactive selection flow is the fix, not a
979
+ // secret; demo-* projects have no real cloud to reconcile. ONE home
980
+ // (#23): cloud.config.projectId, never a second key.
981
+ const projectId = config.cloud?.config?.projectId;
982
+ return Boolean(projectId) && !isDemoProject(projectId);
983
+ },
984
+ env: GOOGLE_ENV,
985
+ scopes: [
986
+ 'https://www.googleapis.com/auth/firebase',
987
+ 'https://www.googleapis.com/auth/cloud-platform',
988
+ 'https://www.googleapis.com/auth/cloud-billing',
989
+ 'https://www.googleapis.com/auth/userinfo.email',
990
+ ],
991
+ },
992
+
993
+ captcha: {
994
+ why: "proves the brand's own classic reCAPTCHA keys are valid (siteverify)",
995
+ label: 'reCAPTCHA',
996
+ disablePath: 'captcha.providers.recaptcha.enabled',
997
+ when: (config) => config.captcha?.providers?.recaptcha?.enabled !== false,
998
+ env: [
999
+ // De-ITW (Ian 2026-07-21): the key is the brand's OWN, minted in the
1000
+ // brand's own GCP project — the walkthrough points at the GCP reCAPTCHA
1001
+ // console, never a company-shared key
1002
+ { name: 'RECAPTCHA_SITE_KEY', label: "reCAPTCHA site key (the brand's own key)", url: 'https://console.cloud.google.com/security/recaptcha', hint: "Create a classic key in the brand's OWN GCP project — never a shared company key", prompted: true },
1003
+ { name: 'RECAPTCHA_SECRET_KEY', label: 'reCAPTCHA secret key', url: 'https://console.cloud.google.com/security/recaptcha', prompted: true },
1004
+ ],
1005
+ scopes: [],
1006
+ },
1007
+
1008
+ analytics: {
1009
+ why: 'reconciles GA4 streams and the Firebase link via the GA Admin API',
1010
+ label: 'Google Analytics',
1011
+ disablePath: 'analytics.enabled',
1012
+ when: (config) => config.analytics?.enabled !== false && Boolean(config.analytics?.providers?.google?.propertyId),
1013
+ // The pixel platforms are OPTIONAL beside GA4 (`gates: false`): a brand may
1014
+ // run one, both, or neither, and their absence must never gate the GA4 half
1015
+ // — the pixel operations ask for them in place (services/analytics/lib/
1016
+ // pixel-token.js), each disabling only its own provider.
1017
+ env: [
1018
+ ...GOOGLE_ENV,
1019
+ {
1020
+ name: 'META_ACCESS_TOKEN',
1021
+ label: 'Meta Pixel access token',
1022
+ url: 'https://business.facebook.com/settings/system-users',
1023
+ hint: 'A Business Manager SYSTEM USER token with ads_management — it creates the pixel and signs the conversions',
1024
+ prompted: true,
1025
+ gates: false,
1026
+ disablePath: 'analytics.providers.meta',
1027
+ when: (config) => config.analytics?.providers?.meta !== false,
1028
+ },
1029
+ {
1030
+ name: 'TIKTOK_ACCESS_TOKEN',
1031
+ label: 'TikTok Events API access token',
1032
+ hint: 'Minted by the portal authorization the setup walks (#448) — the app secret is only needed at mint time',
1033
+ prompted: true,
1034
+ gates: false,
1035
+ disablePath: 'analytics.providers.tiktok',
1036
+ when: (config) => config.analytics?.providers?.tiktok !== false,
1037
+ },
1038
+ ],
1039
+ scopes: ['https://www.googleapis.com/auth/analytics.edit'],
1040
+ },
1041
+
1042
+ search: {
1043
+ why: 'creates/verifies the sc-domain property and submits sitemaps via the Search Console API',
1044
+ label: 'Search Console',
1045
+ disablePath: 'search.providers.searchConsole.enabled',
1046
+ when: (config) => config.search?.providers?.searchConsole?.enabled !== false,
1047
+ env: GOOGLE_ENV,
1048
+ scopes: [
1049
+ 'https://www.googleapis.com/auth/webmasters',
1050
+ 'https://www.googleapis.com/auth/siteverification',
1051
+ ],
1052
+ },
1053
+
1054
+ advertising: {
1055
+ why: 'verifies the domain is present + approved in the AdSense account (read-only API)',
1056
+ label: 'AdSense',
1057
+ // Disable must NOT land `client: false` — client is schema-typed as a
1058
+ // string. It opts the PROVIDER out instead, and since #527 there is no
1059
+ // second `enabled` switch to land it on.
1060
+ disablePath: 'advertising.providers.adsense',
1061
+ // The client id is the ONE adsense switch (#527) — no second gate.
1062
+ when: (config) => Boolean(config.advertising?.providers?.adsense?.client),
1063
+ env: GOOGLE_ENV,
1064
+ scopes: ['https://www.googleapis.com/auth/adsense.readonly'],
1065
+ },
1066
+
1067
+ monitoring: {
1068
+ why: 'creates one Sentry project per enabled target and lands the DSNs',
1069
+ label: 'Sentry',
1070
+ disablePath: 'monitoring.enabled',
1071
+ when: (config) => config.monitoring?.enabled !== false
1072
+ && chosenProvider(config.monitoring?.providers) === 'sentry',
1073
+ env: [
1074
+ {
1075
+ name: 'SENTRY_AUTH_TOKEN',
1076
+ label: 'Sentry personal auth token',
1077
+ url: 'https://sentry.io/settings/account/api/auth-tokens/',
1078
+ hint: 'Create a personal token with scopes: org:read, project:read, project:write, team:read, team:write — organization tokens cannot create projects',
1079
+ prompted: true,
1080
+ },
1081
+ ],
1082
+ scopes: [],
1083
+ },
1084
+
1085
+ campaigns: {
1086
+ why: 'reconciles domain auth, link branding, the sender, the list, unsubscribe groups, fields, segments, and the event webhook via the SendGrid API',
1087
+ label: 'SendGrid',
1088
+ disablePath: 'marketing.campaigns.enabled',
1089
+ when: (config) => config.marketing?.campaigns?.enabled !== false
1090
+ && chosenProvider(config.marketing?.campaigns?.providers) === 'sendgrid',
1091
+ env: [
1092
+ { name: 'SENDGRID_API_KEY', label: 'SendGrid API key', url: 'https://app.sendgrid.com/settings/api_keys', prompted: true },
1093
+ WEBHOOK_KEY_ENV,
1094
+ ],
1095
+ scopes: [],
1096
+ },
1097
+
1098
+ newsletter: {
1099
+ why: 'verifies publication access, fields, segments, and the webhook via the Beehiiv API',
1100
+ label: 'Beehiiv',
1101
+ disablePath: 'marketing.newsletter.enabled',
1102
+ when: (config) => config.marketing?.newsletter?.enabled !== false
1103
+ && chosenProvider(config.marketing?.newsletter?.providers) === 'beehiiv',
1104
+ env: [
1105
+ { name: 'BEEHIIV_API_KEY', label: 'Beehiiv API key', url: 'https://app.beehiiv.com/settings/workspace/api', prompted: true },
1106
+ WEBHOOK_KEY_ENV,
1107
+ ],
1108
+ scopes: [],
1109
+ },
1110
+
1111
+ certificates: {
1112
+ why: 'reconciles Apple signing certs, bundle IDs, and provisioning profiles via App Store Connect',
1113
+ label: 'Apple signing',
1114
+ disablePath: 'certificates.enabled',
1115
+ when: (config) => config.certificates?.enabled !== false
1116
+ && Boolean(config.targets?.desktop || config.targets?.mobile),
1117
+ env: [
1118
+ { name: 'APPLE_API_ISSUER', label: 'App Store Connect issuer ID', url: 'https://appstoreconnect.apple.com/access/api', prompted: true },
1119
+ { name: 'APPLE_API_KEY_ID', label: 'App Store Connect API key ID', url: 'https://appstoreconnect.apple.com/access/api', prompted: true },
1120
+ { name: 'APPLE_TEAM_ID', label: 'Apple Developer team ID', prompted: true },
1121
+ ],
1122
+ scopes: [],
1123
+ },
1124
+
1125
+ // Per-provider credentials, every one OPTIONAL (`gates: false`): the payment
1126
+ // service runs on whichever provider IS configured, so a missing Stripe key
1127
+ // must never gate a PayPal brand. Each entry disables only its own provider.
1128
+ payment: {
1129
+ why: 'reconciles products, prices, and webhooks on the brand payment providers',
1130
+ label: 'Payments',
1131
+ disablePath: 'payment.enabled',
1132
+ when: (config) => config.payment?.enabled !== false,
1133
+ env: [
1134
+ {
1135
+ name: 'STRIPE_SECRET_KEY',
1136
+ label: 'Stripe secret key',
1137
+ url: 'https://dashboard.stripe.com/apikeys',
1138
+ hint: 'Developers → API keys, on the BRAND\'s Stripe account (sk_live_… or sk_test_…)',
1139
+ prompted: true,
1140
+ gates: false,
1141
+ disablePath: 'payment.providers.stripe',
1142
+ when: (config) => config.payment?.providers?.stripe !== false,
1143
+ },
1144
+ {
1145
+ name: 'PAYPAL_CLIENT_SECRET',
1146
+ label: 'PayPal client secret',
1147
+ url: 'https://developer.paypal.com/dashboard/applications',
1148
+ hint: 'The secret half of the brand\'s REST API app (its client id is public and lives in omega.json5)',
1149
+ prompted: true,
1150
+ gates: false,
1151
+ disablePath: 'payment.providers.paypal',
1152
+ when: (config) => config.payment?.providers?.paypal !== false,
1153
+ },
1154
+ {
1155
+ name: 'CHARGEBEE_API_KEY',
1156
+ label: 'Chargebee API key',
1157
+ url: 'https://app.chargebee.com/',
1158
+ hint: 'Settings → API keys, on the brand\'s Chargebee site (the site name is public and lives in omega.json5)',
1159
+ prompted: true,
1160
+ gates: false,
1161
+ disablePath: 'payment.providers.chargebee',
1162
+ when: (config) => config.payment?.providers?.chargebee !== false,
1163
+ },
1164
+ {
1165
+ // The crypto provider ([#642](https://github.com/Omega-JS-Stack/omega/issues/642)).
1166
+ // Two things make it the odd one out, both for the same reason — the API
1167
+ // key is its ENTIRE credential, with no public half to live in
1168
+ // omega.json5:
1169
+ // - `when` demands an explicit ON, where the siblings ask unless
1170
+ // switched off. `payment.providers.coinbase.enabled` is the whole
1171
+ // switch, so an absent one means the brand has no use for the key.
1172
+ // - `disablePath` is that same `enabled`, not the provider block: the
1173
+ // schema declares `enabled`, and the checkout reads it.
1174
+ name: 'COINBASE_COMMERCE_API_KEY',
1175
+ label: 'Coinbase Commerce API key',
1176
+ url: 'https://commerce.coinbase.com/settings/security',
1177
+ hint: 'Settings → Security → API keys, on the brand\'s Coinbase Commerce account (this key is the whole credential — there is no public half)',
1178
+ prompted: true,
1179
+ gates: false,
1180
+ disablePath: 'payment.providers.coinbase.enabled',
1181
+ when: (config) => config.payment?.providers?.coinbase?.enabled === true,
1182
+ },
1183
+ WEBHOOK_KEY_ENV,
1184
+ ],
1185
+ scopes: [],
1186
+ },
1187
+
1188
+ // Operator-tier credentials (`gates: false`): the product lives in ITS OWN
1189
+ // Firebase project, so only that product's operator holds the service
1190
+ // account. Every other brand's clean skip is the sanctioned outcome, which
1191
+ // is why these never gate a run — the service asks the operator in place.
1192
+ forms: {
1193
+ why: "manages the brand's Slapform contact form and its owner account (Slapform operator only)",
1194
+ label: 'Slapform operator access',
1195
+ disablePath: 'forms.providers.slapform.enabled',
1196
+ when: (config) => config.forms?.providers?.slapform !== false
1197
+ && config.forms?.providers?.slapform?.enabled !== false,
1198
+ env: [
1199
+ { name: 'SLAPFORM_SERVICE_ACCOUNT', label: "Slapform's service-account JSON path", hint: 'Absolute, or relative to the brand root', prompted: true, gates: false },
1200
+ ],
1201
+ scopes: [],
1202
+ },
1203
+
1204
+ chat: {
1205
+ why: "manages the brand's Chatsy agent, knowledge, and owner account (Chatsy operator only)",
1206
+ label: 'Chatsy operator access',
1207
+ disablePath: 'inbound.chat.providers.chatsy.enabled',
1208
+ when: (config) => config.inbound?.chat?.providers?.chatsy !== false
1209
+ && config.inbound?.chat?.providers?.chatsy?.enabled !== false,
1210
+ env: [
1211
+ { name: 'CHATSY_SERVICE_ACCOUNT', label: "Chatsy's service-account JSON path", hint: 'Absolute, or relative to the brand root', prompted: true, gates: false },
1212
+ ],
1213
+ scopes: [],
1214
+ },
1215
+
1216
+ email: {
1217
+ why: "manages the brand's Replyify agent, filter, and owner account (Replyify operator only)",
1218
+ label: 'Replyify operator access',
1219
+ disablePath: 'inbound.email.providers.replyify.enabled',
1220
+ when: (config) => config.inbound?.email?.providers?.replyify !== false
1221
+ && config.inbound?.email?.providers?.replyify?.enabled !== false,
1222
+ env: [
1223
+ { name: 'REPLYIFY_SERVICE_ACCOUNT', label: "Replyify's service-account JSON path", hint: 'Absolute, or relative to the brand root', prompted: true, gates: false },
1224
+ ],
1225
+ scopes: [],
1226
+ },
1227
+
1228
+ // ONE key per provider (#639): the OMEGA_-prefixed twins are gone, so the
1229
+ // bare name is the only home and a company-wide key is simply the COMPANY
1230
+ // layer of the .env cascade. Both are OPTIONAL (`gates: false`) — a brand
1231
+ // that calls neither provider must never be gated on a key it will not use,
1232
+ // and the ai service asks for them in place.
1233
+ ai: {
1234
+ why: 'lets the backend call OpenAI/Anthropic (contact inference, content + newsletter generation)',
1235
+ label: 'AI providers',
1236
+ disablePath: 'ai.enabled',
1237
+ when: (config) => config.ai?.enabled !== false,
1238
+ env: [
1239
+ {
1240
+ name: 'OPENAI_API_KEY',
1241
+ label: 'OpenAI API key',
1242
+ url: 'https://platform.openai.com/api-keys',
1243
+ hint: 'A secret key on the account that should be billed for the brand\'s OpenAI calls',
1244
+ prompted: true,
1245
+ gates: false,
1246
+ },
1247
+ {
1248
+ name: 'ANTHROPIC_API_KEY',
1249
+ label: 'Anthropic API key',
1250
+ url: 'https://console.anthropic.com/settings/keys',
1251
+ hint: 'A workspace API key — the Claude Code subscription login is a separate thing and needs no key',
1252
+ prompted: true,
1253
+ gates: false,
1254
+ },
1255
+ ],
1256
+ scopes: [],
1257
+ },
1258
+
1259
+ server: {
1260
+ why: "keeps the brand's registry entry on the company server's Firestore (company-server operators only)",
1261
+ label: 'Company server access',
1262
+ disablePath: 'server.enabled',
1263
+ when: (config) => config.server !== false && config.server?.enabled !== false,
1264
+ env: [
1265
+ { name: 'SERVER_SERVICE_ACCOUNT', label: "the company server's service-account JSON path", hint: 'Absolute, or relative to the brand root', prompted: true, gates: false },
1266
+ ],
1267
+ scopes: [],
1268
+ },
1269
+ };
1270
+
1271
+ /**
1272
+ * Build the setup-contract spec for a service (#608) — what
1273
+ * lib/service-input.js asks for. The registry is the SSOT; this is its
1274
+ * accessor, so no call site re-spells an env name, a mint URL, or an opt-out
1275
+ * path.
1276
+ *
1277
+ * @param {string} service - Service name (a REQUIRES key).
1278
+ * @param {object} [options]
1279
+ * @param {string[]} [options.names] - Narrow to these input names (a
1280
+ * per-provider ask: just the Stripe key, just the Meta token).
1281
+ * @param {string} [options.label] - Override the gate's human name (the
1282
+ * PROVIDER's name, when the ask is per-provider).
1283
+ * @param {string} [options.disablePath] - Override where Disable lands `false`
1284
+ * (defaults to the narrowed entry's own path, then the service's).
1285
+ * @param {string[]} [options.instructions] - Guidance lines shown before the gate.
1286
+ * @param {boolean} [options.gate] - false = the caller already ran the gate.
1287
+ * @returns {object} The spec requestServiceInput takes.
1288
+ */
1289
+ function serviceInputSpec(service, options = {}) {
1290
+ const declaration = REQUIRES[service];
1291
+ if (!declaration) {
1292
+ throw new Error(`No REQUIRES entry for service "${service}" — declare its inputs in src/config.js`);
1293
+ }
1294
+
1295
+ const inputs = options.names
1296
+ ? declaration.env.filter((entry) => options.names.includes(entry.name))
1297
+ : declaration.env;
1298
+
1299
+ // A single narrowed input carrying its own opt-out path owns the gate: the
1300
+ // ask is about THAT provider, so Disable must not switch off the service.
1301
+ const ownPath = inputs.length === 1 ? inputs[0].disablePath : null;
1302
+
1303
+ return {
1304
+ service,
1305
+ label: options.label || declaration.label,
1306
+ disablePath: options.disablePath || ownPath || declaration.disablePath,
1307
+ instructions: options.instructions,
1308
+ ...(options.gate === false ? { gate: false } : {}),
1309
+ inputs,
1310
+ };
1311
+ }
1312
+
1313
+ // =============================================================================
1314
+ // HELPER FUNCTIONS
1315
+ // =============================================================================
1316
+
1317
+ /**
1318
+ * Replace `{ key }` placeholders (spaces allowed) in every string of an
1319
+ * object tree — omega-manager's templateObject, minus the node-powertools
1320
+ * dependency. Unknown keys are left untouched.
1321
+ *
1322
+ * @param {*} obj - Any value; strings are templated, objects/arrays recursed
1323
+ * @param {Object} data - Placeholder values (e.g. { domain: 'somiibo.com' })
1324
+ * @returns {*} - Same shape with placeholders substituted
1325
+ */
1326
+ function templateObject(obj, data) {
1327
+ if (typeof obj === 'string') {
1328
+ return obj.replace(/\{\s*([\w.]+)\s*\}/g, (match, key) => {
1329
+ return key in data ? data[key] : match;
1330
+ });
1331
+ }
1332
+
1333
+ if (Array.isArray(obj)) {
1334
+ return obj.map((item) => templateObject(item, data));
1335
+ }
1336
+
1337
+ if (obj !== null && typeof obj === 'object') {
1338
+ const result = {};
1339
+ for (const [key, value] of Object.entries(obj)) {
1340
+ result[key] = templateObject(value, data);
1341
+ }
1342
+ return result;
1343
+ }
1344
+
1345
+ return obj;
1346
+ }
1347
+
1348
+ module.exports = {
1349
+ DIR_TARGETS,
1350
+ TARGET_DIRS,
1351
+ TARGET_FRAMEWORKS,
1352
+ DEFAULTS,
1353
+ SERVICE_ORDER,
1354
+ BOOT_SERVICES,
1355
+ OPERATIONS,
1356
+ REQUIRES,
1357
+ serviceInputSpec,
1358
+ templateObject,
1359
+ };