@omega.js/manager 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/LICENSE +98 -0
  3. package/README.md +165 -0
  4. package/bin/omega +2 -0
  5. package/bin/omg +2 -0
  6. package/claude-plugin/.claude-plugin/plugin.json +11 -0
  7. package/claude-plugin/.mcp.json +9 -0
  8. package/claude-plugin/README.md +139 -0
  9. package/claude-plugin/hooks/gate/mark.sh +58 -0
  10. package/claude-plugin/hooks/gate/run.sh +97 -0
  11. package/claude-plugin/hooks/guard/run.sh +249 -0
  12. package/claude-plugin/hooks/hooks.json +63 -0
  13. package/claude-plugin/hooks/inject/run.sh +127 -0
  14. package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
  15. package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
  16. package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
  17. package/claude-plugin/hooks/quality/run.sh +132 -0
  18. package/claude-plugin/hooks/shape/run.sh +45 -0
  19. package/claude-plugin/mcp-router-launch.js +29 -0
  20. package/claude-plugin/skills/README.md +48 -0
  21. package/claude-plugin/skills/accessibility/SKILL.md +31 -0
  22. package/claude-plugin/skills/analytics/SKILL.md +30 -0
  23. package/claude-plugin/skills/backend/SKILL.md +25 -0
  24. package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
  25. package/claude-plugin/skills/browser/SKILL.md +64 -0
  26. package/claude-plugin/skills/client/SKILL.md +24 -0
  27. package/claude-plugin/skills/desktop/SKILL.md +25 -0
  28. package/claude-plugin/skills/extension/SKILL.md +25 -0
  29. package/claude-plugin/skills/main/SKILL.md +54 -0
  30. package/claude-plugin/skills/manager/SKILL.md +25 -0
  31. package/claude-plugin/skills/seo/SKILL.md +36 -0
  32. package/claude-plugin/skills/theme/SKILL.md +33 -0
  33. package/claude-plugin/skills/web/SKILL.md +29 -0
  34. package/dist/cli-run.js +34 -0
  35. package/dist/cli.js +29 -0
  36. package/dist/commands/build.js +17 -0
  37. package/dist/commands/clean.js +16 -0
  38. package/dist/commands/company.js +46 -0
  39. package/dist/commands/deploy.js +143 -0
  40. package/dist/commands/dev.js +437 -0
  41. package/dist/commands/devlog.js +25 -0
  42. package/dist/commands/help.js +48 -0
  43. package/dist/commands/manage.js +53 -0
  44. package/dist/commands/migrate.js +76 -0
  45. package/dist/commands/onboard.js +31 -0
  46. package/dist/commands/pipeline.js +319 -0
  47. package/dist/commands/test.js +380 -0
  48. package/dist/commands/update.js +128 -0
  49. package/dist/commands/version.js +8 -0
  50. package/dist/company-init.js +167 -0
  51. package/dist/company.js +283 -0
  52. package/dist/config.js +1359 -0
  53. package/dist/devlog/index.js +226 -0
  54. package/dist/devlog/lib/collect-commits.js +136 -0
  55. package/dist/devlog/lib/generate-post.js +201 -0
  56. package/dist/devlog/lib/ghostii.js +122 -0
  57. package/dist/devlog/lib/project-map.js +98 -0
  58. package/dist/devlog/lib/publish-website.js +88 -0
  59. package/dist/index.js +45 -0
  60. package/dist/lib/agents-md.js +201 -0
  61. package/dist/lib/analytics-secret.js +22 -0
  62. package/dist/lib/argv.js +21 -0
  63. package/dist/lib/auth-admin.js +120 -0
  64. package/dist/lib/automation-client.js +210 -0
  65. package/dist/lib/backend-marketing.js +49 -0
  66. package/dist/lib/brand.js +240 -0
  67. package/dist/lib/bundle-id.js +53 -0
  68. package/dist/lib/claude-settings.js +125 -0
  69. package/dist/lib/company-scaffold.js +213 -0
  70. package/dist/lib/company.js +227 -0
  71. package/dist/lib/config-flow.js +350 -0
  72. package/dist/lib/config-write.js +41 -0
  73. package/dist/lib/custom-target.js +75 -0
  74. package/dist/lib/domain-utils.js +19 -0
  75. package/dist/lib/duration.js +39 -0
  76. package/dist/lib/env-order.js +236 -0
  77. package/dist/lib/env-secret.js +78 -0
  78. package/dist/lib/firestore-rest.js +326 -0
  79. package/dist/lib/framework-bin.js +156 -0
  80. package/dist/lib/gitignore.js +63 -0
  81. package/dist/lib/google-auth.js +495 -0
  82. package/dist/lib/google-token.js +66 -0
  83. package/dist/lib/jwt.js +31 -0
  84. package/dist/lib/legacy-oauth.js +59 -0
  85. package/dist/lib/node-version.js +127 -0
  86. package/dist/lib/owner-plan.js +78 -0
  87. package/dist/lib/package-scripts.js +152 -0
  88. package/dist/lib/preflight.js +470 -0
  89. package/dist/lib/product-create.js +144 -0
  90. package/dist/lib/run-command.js +74 -0
  91. package/dist/lib/run-gates.js +70 -0
  92. package/dist/lib/run-output.js +42 -0
  93. package/dist/lib/run-summary.js +384 -0
  94. package/dist/lib/scaffold.js +427 -0
  95. package/dist/lib/service-input.js +207 -0
  96. package/dist/lib/service-runner.js +466 -0
  97. package/dist/lib/stale.js +24 -0
  98. package/dist/lib/target-selection.js +155 -0
  99. package/dist/lib/verb-fanout.js +128 -0
  100. package/dist/lib/verify-live.js +188 -0
  101. package/dist/manage.js +311 -0
  102. package/dist/omega-bin.js +7 -0
  103. package/dist/onboard.js +532 -0
  104. package/dist/services/account/ensure/users.js +259 -0
  105. package/dist/services/account/index.js +97 -0
  106. package/dist/services/account/lib/backend-client.js +107 -0
  107. package/dist/services/account/lib/password.js +36 -0
  108. package/dist/services/account/lib/resolve-password.js +100 -0
  109. package/dist/services/advertising/ensure/sites.js +101 -0
  110. package/dist/services/advertising/index.js +101 -0
  111. package/dist/services/advertising/lib/adsense-api.js +41 -0
  112. package/dist/services/ai/ensure/keys.js +21 -0
  113. package/dist/services/ai/index.js +36 -0
  114. package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
  115. package/dist/services/analytics/ensure/google-streams.js +256 -0
  116. package/dist/services/analytics/ensure/meta-pixel.js +22 -0
  117. package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
  118. package/dist/services/analytics/index.js +134 -0
  119. package/dist/services/analytics/lib/analytics-api.js +156 -0
  120. package/dist/services/analytics/lib/meta-api.js +83 -0
  121. package/dist/services/analytics/lib/pixel-account.js +78 -0
  122. package/dist/services/analytics/lib/pixel-provision.js +152 -0
  123. package/dist/services/analytics/lib/pixel-specs.js +57 -0
  124. package/dist/services/analytics/lib/pixel-token.js +88 -0
  125. package/dist/services/analytics/lib/property-flow.js +77 -0
  126. package/dist/services/analytics/lib/tiktok-api.js +157 -0
  127. package/dist/services/analytics/lib/tiktok-auth.js +153 -0
  128. package/dist/services/assets/ensure/logo-gen.js +68 -0
  129. package/dist/services/assets/index.js +109 -0
  130. package/dist/services/assets/lib/assets-config.js +168 -0
  131. package/dist/services/assets/lib/brandmark-api.js +160 -0
  132. package/dist/services/assets/lib/derived.js +113 -0
  133. package/dist/services/assets/lib/font-loader.js +67 -0
  134. package/dist/services/assets/lib/reconcile.js +122 -0
  135. package/dist/services/assets/lib/reset.js +118 -0
  136. package/dist/services/assets/lib/svg-logo-generator.js +154 -0
  137. package/dist/services/assets/lib/svg-to-black.js +32 -0
  138. package/dist/services/assets/write/favicons.js +82 -0
  139. package/dist/services/assets/write/icons.js +64 -0
  140. package/dist/services/assets/write/process.js +96 -0
  141. package/dist/services/assets/write/reconcile.js +30 -0
  142. package/dist/services/assets/write/social-icons.js +108 -0
  143. package/dist/services/assets/write/templates.js +345 -0
  144. package/dist/services/bookmark/ensure/sync.js +255 -0
  145. package/dist/services/bookmark/index.js +17 -0
  146. package/dist/services/campaigns/ensure/contact-person.js +40 -0
  147. package/dist/services/campaigns/ensure/custom-fields.js +84 -0
  148. package/dist/services/campaigns/ensure/domain-auth.js +92 -0
  149. package/dist/services/campaigns/ensure/event-webhook.js +89 -0
  150. package/dist/services/campaigns/ensure/link-branding.js +187 -0
  151. package/dist/services/campaigns/ensure/list.js +50 -0
  152. package/dist/services/campaigns/ensure/segments.js +109 -0
  153. package/dist/services/campaigns/ensure/sender-identity.js +153 -0
  154. package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
  155. package/dist/services/campaigns/index.js +79 -0
  156. package/dist/services/campaigns/lib/dns-sync.js +81 -0
  157. package/dist/services/campaigns/lib/segment-query.js +138 -0
  158. package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
  159. package/dist/services/captcha/ensure/site-key.js +83 -0
  160. package/dist/services/captcha/index.js +78 -0
  161. package/dist/services/captcha/lib/console-url.js +45 -0
  162. package/dist/services/captcha/lib/recaptcha-api.js +36 -0
  163. package/dist/services/certificates/ensure/api-key.js +22 -0
  164. package/dist/services/certificates/ensure/bundle-ids.js +115 -0
  165. package/dist/services/certificates/ensure/certificates.js +187 -0
  166. package/dist/services/certificates/ensure/profiles.js +143 -0
  167. package/dist/services/certificates/index.js +225 -0
  168. package/dist/services/certificates/lib/apple-api.js +183 -0
  169. package/dist/services/certificates/lib/certificate-manager.js +252 -0
  170. package/dist/services/certificates/lib/identifier-manager.js +107 -0
  171. package/dist/services/certificates/lib/keychain.js +135 -0
  172. package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
  173. package/dist/services/certificates/lib/profile-manager.js +115 -0
  174. package/dist/services/chat/data/baseline-knowledge.md +96 -0
  175. package/dist/services/chat/ensure/chat.js +106 -0
  176. package/dist/services/chat/ensure/user.js +45 -0
  177. package/dist/services/chat/index.js +139 -0
  178. package/dist/services/chat/lib/baseline-knowledge.js +137 -0
  179. package/dist/services/cloud/ensure/authentication.js +267 -0
  180. package/dist/services/cloud/ensure/billing.js +120 -0
  181. package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
  182. package/dist/services/cloud/ensure/database.js +60 -0
  183. package/dist/services/cloud/ensure/firestore.js +79 -0
  184. package/dist/services/cloud/ensure/functions.js +49 -0
  185. package/dist/services/cloud/ensure/hosting.js +398 -0
  186. package/dist/services/cloud/ensure/oauth-consent.js +248 -0
  187. package/dist/services/cloud/ensure/project-settings.js +72 -0
  188. package/dist/services/cloud/ensure/sdk-config.js +95 -0
  189. package/dist/services/cloud/ensure/service-account.js +152 -0
  190. package/dist/services/cloud/ensure/services.js +103 -0
  191. package/dist/services/cloud/ensure/storage.js +47 -0
  192. package/dist/services/cloud/index.js +130 -0
  193. package/dist/services/cloud/lib/access-heal.js +130 -0
  194. package/dist/services/cloud/lib/firebase-api.js +662 -0
  195. package/dist/services/cloud/lib/project-flow.js +102 -0
  196. package/dist/services/directory/ensure/entry.js +48 -0
  197. package/dist/services/directory/index.js +64 -0
  198. package/dist/services/directory/lib/blocks.js +104 -0
  199. package/dist/services/disperse/index.js +45 -0
  200. package/dist/services/disperse/write/certs.js +117 -0
  201. package/dist/services/domain/ensure/nameservers.js +179 -0
  202. package/dist/services/domain/index.js +69 -0
  203. package/dist/services/domain/lib/namecheap-api.js +167 -0
  204. package/dist/services/domain/lib/registrars.js +46 -0
  205. package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
  206. package/dist/services/edge/ensure/cache-rules.js +105 -0
  207. package/dist/services/edge/ensure/dns-records.js +304 -0
  208. package/dist/services/edge/ensure/email-routing.js +292 -0
  209. package/dist/services/edge/ensure/rules-configuration.js +98 -0
  210. package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
  211. package/dist/services/edge/ensure/rules-redirect.js +107 -0
  212. package/dist/services/edge/ensure/rules-response-headers.js +90 -0
  213. package/dist/services/edge/ensure/rules-security.js +142 -0
  214. package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
  215. package/dist/services/edge/ensure/workers.js +219 -0
  216. package/dist/services/edge/ensure/zone-settings.js +108 -0
  217. package/dist/services/edge/ensure/zone.js +183 -0
  218. package/dist/services/edge/index.js +73 -0
  219. package/dist/services/edge/lib/cloudflare-api.js +84 -0
  220. package/dist/services/edge/lib/dns-records-helpers.js +518 -0
  221. package/dist/services/edge/lib/read-cache.js +16 -0
  222. package/dist/services/edge/lib/ruleset-helper.js +108 -0
  223. package/dist/services/edge/workers/omega-api-proxy.js +43 -0
  224. package/dist/services/email/data/baseline-filter.md +4 -0
  225. package/dist/services/email/data/baseline-knowledge.md +86 -0
  226. package/dist/services/email/ensure/agent.js +107 -0
  227. package/dist/services/email/ensure/user.js +45 -0
  228. package/dist/services/email/index.js +138 -0
  229. package/dist/services/email/lib/baseline.js +82 -0
  230. package/dist/services/email/lib/knowledge-file.js +53 -0
  231. package/dist/services/forms/ensure/form.js +46 -0
  232. package/dist/services/forms/ensure/user.js +45 -0
  233. package/dist/services/forms/index.js +138 -0
  234. package/dist/services/migrations/ensure/notifications.js +189 -0
  235. package/dist/services/migrations/ensure/orders.js +34 -0
  236. package/dist/services/migrations/ensure/payment-provider.js +132 -0
  237. package/dist/services/migrations/ensure/payments-intents.js +34 -0
  238. package/dist/services/migrations/ensure/state-retirement.js +266 -0
  239. package/dist/services/migrations/ensure/targets-rename.js +124 -0
  240. package/dist/services/migrations/ensure/users.js +1176 -0
  241. package/dist/services/migrations/index.js +101 -0
  242. package/dist/services/migrations/lib/attribution-touch.js +64 -0
  243. package/dist/services/migrations/lib/ensure-metadata.js +144 -0
  244. package/dist/services/migrations/lib/migration-runner.js +473 -0
  245. package/dist/services/migrations/lib/sanitize-strings.js +112 -0
  246. package/dist/services/migrations/lib/schema-validator.js +154 -0
  247. package/dist/services/monitoring/ensure/dsn.js +54 -0
  248. package/dist/services/monitoring/ensure/projects.js +135 -0
  249. package/dist/services/monitoring/index.js +54 -0
  250. package/dist/services/monitoring/lib/sentry-api.js +94 -0
  251. package/dist/services/newsletter/ensure/custom-fields.js +90 -0
  252. package/dist/services/newsletter/ensure/publication.js +94 -0
  253. package/dist/services/newsletter/ensure/segments.js +140 -0
  254. package/dist/services/newsletter/ensure/webhook.js +108 -0
  255. package/dist/services/newsletter/index.js +64 -0
  256. package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
  257. package/dist/services/newsletter/lib/segment-automation.js +577 -0
  258. package/dist/services/payment/ensure/chargebee-account.js +30 -0
  259. package/dist/services/payment/ensure/chargebee-products.js +359 -0
  260. package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
  261. package/dist/services/payment/ensure/paypal-account.js +36 -0
  262. package/dist/services/payment/ensure/paypal-products.js +374 -0
  263. package/dist/services/payment/ensure/paypal-webhook.js +128 -0
  264. package/dist/services/payment/ensure/stripe-account.js +93 -0
  265. package/dist/services/payment/ensure/stripe-disputes.js +56 -0
  266. package/dist/services/payment/ensure/stripe-products.js +245 -0
  267. package/dist/services/payment/ensure/stripe-radar.js +74 -0
  268. package/dist/services/payment/ensure/stripe-webhook.js +130 -0
  269. package/dist/services/payment/index.js +163 -0
  270. package/dist/services/payment/lib/chargebee-api.js +338 -0
  271. package/dist/services/payment/lib/payment-utils.js +116 -0
  272. package/dist/services/payment/lib/paypal-api.js +354 -0
  273. package/dist/services/payment/lib/provider-setup.js +134 -0
  274. package/dist/services/payment/lib/stripe-api.js +152 -0
  275. package/dist/services/repo/ensure/org.js +70 -0
  276. package/dist/services/repo/ensure/pages.js +72 -0
  277. package/dist/services/repo/ensure/repo.js +90 -0
  278. package/dist/services/repo/index.js +52 -0
  279. package/dist/services/repo/lib/github-api.js +196 -0
  280. package/dist/services/search/ensure/ga-link.js +57 -0
  281. package/dist/services/search/ensure/property.js +137 -0
  282. package/dist/services/search/ensure/sitemaps.js +70 -0
  283. package/dist/services/search/index.js +61 -0
  284. package/dist/services/search/lib/search-console-api.js +92 -0
  285. package/dist/services/seo/ensure/github-repos.js +271 -0
  286. package/dist/services/seo/index.js +55 -0
  287. package/dist/services/seo/lib/gh-api.js +161 -0
  288. package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
  289. package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
  290. package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
  291. package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
  292. package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
  293. package/dist/services/seo/templates/index.js +57 -0
  294. package/dist/services/server/ensure/brands.js +63 -0
  295. package/dist/services/server/index.js +42 -0
  296. package/dist/services/testing/ensure/target-checks.js +97 -0
  297. package/dist/services/testing/index.js +11 -0
  298. package/dist/services/testing/lib/checks.js +467 -0
  299. package/dist/services/update/index.js +20 -0
  300. package/dist/services/update/lib/cache.js +59 -0
  301. package/dist/services/update/lib/fingerprint.js +157 -0
  302. package/dist/services/update/write/targets.js +206 -0
  303. package/dist/services/workspace/ensure/agents.js +42 -0
  304. package/dist/services/workspace/ensure/claude-settings.js +34 -0
  305. package/dist/services/workspace/ensure/config.js +54 -0
  306. package/dist/services/workspace/ensure/defaults.js +60 -0
  307. package/dist/services/workspace/ensure/env-keys.js +55 -0
  308. package/dist/services/workspace/ensure/env-order.js +66 -0
  309. package/dist/services/workspace/ensure/env-rules.js +97 -0
  310. package/dist/services/workspace/ensure/gitignore.js +18 -0
  311. package/dist/services/workspace/ensure/scripts.js +126 -0
  312. package/dist/services/workspace/ensure/structure.js +97 -0
  313. package/dist/services/workspace/ensure/translation-sdk.js +148 -0
  314. package/dist/services/workspace/ensure/workflows.js +54 -0
  315. package/dist/services/workspace/index.js +8 -0
  316. package/dist/vendor/account/engine.js +182 -0
  317. package/dist/vendor/account/features.js +220 -0
  318. package/dist/vendor/account/index.js +53 -0
  319. package/dist/vendor/account/schema.js +272 -0
  320. package/dist/vendor/account/subscription.js +38 -0
  321. package/dist/vendor/config/company.js +31 -0
  322. package/dist/vendor/config/defaults.js +173 -0
  323. package/dist/vendor/config/demo.js +18 -0
  324. package/dist/vendor/config/desktop-artifacts.js +110 -0
  325. package/dist/vendor/config/edit.js +769 -0
  326. package/dist/vendor/config/env-delivery.js +145 -0
  327. package/dist/vendor/config/env-rules.js +93 -0
  328. package/dist/vendor/config/env-schema.js +1078 -0
  329. package/dist/vendor/config/env.js +445 -0
  330. package/dist/vendor/config/hooks.js +97 -0
  331. package/dist/vendor/config/index.js +237 -0
  332. package/dist/vendor/config/instances.js +208 -0
  333. package/dist/vendor/config/load.js +490 -0
  334. package/dist/vendor/config/merge.js +68 -0
  335. package/dist/vendor/config/order.js +139 -0
  336. package/dist/vendor/config/ports.js +374 -0
  337. package/dist/vendor/config/providers.js +32 -0
  338. package/dist/vendor/config/repo.js +142 -0
  339. package/dist/vendor/config/retired-keys.js +430 -0
  340. package/dist/vendor/config/schema.js +1610 -0
  341. package/dist/vendor/config/secrets.js +50 -0
  342. package/dist/vendor/config/seed.js +34 -0
  343. package/dist/vendor/config/site-global.js +205 -0
  344. package/dist/vendor/config/validate.js +554 -0
  345. package/dist/vendor/config/winback.js +61 -0
  346. package/dist/vendor/devkit/attach-log-file.js +262 -0
  347. package/dist/vendor/devkit/certs.js +199 -0
  348. package/dist/vendor/devkit/ci-workflows.js +520 -0
  349. package/dist/vendor/devkit/cli-router.js +156 -0
  350. package/dist/vendor/devkit/command-path.js +46 -0
  351. package/dist/vendor/devkit/deploy-record.js +180 -0
  352. package/dist/vendor/devkit/flows.js +317 -0
  353. package/dist/vendor/devkit/local-https.js +360 -0
  354. package/dist/vendor/devkit/local.js +1905 -0
  355. package/dist/vendor/devkit/logger.js +128 -0
  356. package/dist/vendor/devkit/omega-bin.js +345 -0
  357. package/dist/vendor/devkit/prompt.js +187 -0
  358. package/dist/vendor/devkit/safe-install.js +18 -0
  359. package/dist/vendor/devkit/stop-signals.js +28 -0
  360. package/dist/vendor/devkit/test/scope.js +162 -0
  361. package/dist/vendor/devkit/translate/cache.js +84 -0
  362. package/dist/vendor/devkit/translate/engine.js +243 -0
  363. package/dist/vendor/devkit/translate/index.js +49 -0
  364. package/dist/vendor/devkit/translate/languages.js +134 -0
  365. package/dist/vendor/devkit/translate/providers.js +188 -0
  366. package/dist/vendor/devkit/update.js +569 -0
  367. package/docs/AGENTS.md +200 -0
  368. package/docs/account.md +45 -0
  369. package/docs/advertising.md +46 -0
  370. package/docs/ai.md +34 -0
  371. package/docs/analytics.md +69 -0
  372. package/docs/assets.md +57 -0
  373. package/docs/bookmark.md +26 -0
  374. package/docs/brand.md +151 -0
  375. package/docs/campaigns.md +96 -0
  376. package/docs/captcha.md +46 -0
  377. package/docs/certificates.md +68 -0
  378. package/docs/chat.md +47 -0
  379. package/docs/cloud.md +110 -0
  380. package/docs/company.md +64 -0
  381. package/docs/directory.md +140 -0
  382. package/docs/disperse.md +46 -0
  383. package/docs/domain.md +56 -0
  384. package/docs/edge.md +234 -0
  385. package/docs/email.md +42 -0
  386. package/docs/forms.md +48 -0
  387. package/docs/index.md +109 -0
  388. package/docs/migration.md +203 -0
  389. package/docs/migrations.md +56 -0
  390. package/docs/monitoring.md +41 -0
  391. package/docs/newsletter.md +48 -0
  392. package/docs/payment.md +83 -0
  393. package/docs/repo.md +48 -0
  394. package/docs/search.md +44 -0
  395. package/docs/seo.md +34 -0
  396. package/docs/server.md +37 -0
  397. package/docs/shared/agent-docs.md +89 -0
  398. package/docs/shared/analytics.md +612 -0
  399. package/docs/shared/brands.md +51 -0
  400. package/docs/shared/breaking-changes.md +497 -0
  401. package/docs/shared/config.md +1387 -0
  402. package/docs/shared/deploys.md +215 -0
  403. package/docs/shared/icons.md +201 -0
  404. package/docs/shared/local-dev.md +147 -0
  405. package/docs/shared/logging.md +202 -0
  406. package/docs/shared/monitoring.md +153 -0
  407. package/docs/shared/publishing.md +183 -0
  408. package/docs/shared/rulings.md +34 -0
  409. package/docs/shared/testing.md +147 -0
  410. package/docs/shared/theming.md +604 -0
  411. package/docs/shared/translation.md +291 -0
  412. package/docs/shared/updates.md +61 -0
  413. package/docs/testing.md +34 -0
  414. package/docs/update.md +36 -0
  415. package/docs/workspace.md +64 -0
  416. package/package.json +88 -0
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Ensure one GA4 web data stream per enabled target.
3
+ *
4
+ * Streams are found by URI (the source of truth — each target gets a unique
5
+ * subdomain of the brand domain, virtual for app targets): create when
6
+ * missing, rename on displayName drift, enhanced measurement DIFFED before
7
+ * patching (omega-manager blind-PATCHed it every run), and a clean
8
+ * Measurement Protocol secret per stream (no dashes/underscores — regenerated
9
+ * until clean). Every value is re-resolved from GA on each run — the stream
10
+ * ids and URIs are pure derivation, so nothing about them is persisted.
11
+ *
12
+ * GA's "User Data Collection Acknowledgement" gate on secret creation has no
13
+ * API — interactive runs open the settings page (Enter-gated, house rule)
14
+ * and retry-poll until the acknowledgement lands, so ONE run finishes the
15
+ * job; non-interactive runs warn with the URL and the rerun converges.
16
+ * Acknowledging is per-property, so the first stream's prompt clears the
17
+ * gate for every stream after it.
18
+ *
19
+ * The two halves land in their two homes as they resolve. Each stream's
20
+ * measurement ID goes to CONFIG, at
21
+ * `targets.{target}.analytics.providers.google.id` — the per-surface
22
+ * override every framework reads through the merge chain, the same shape the
23
+ * monitoring service uses for its per-target DSNs (#417). Each stream's
24
+ * Measurement Protocol secret goes to the brand .env, under
25
+ * `GOOGLE_ANALYTICS_SECRET_{TARGET}` (#434) — the delivery step composes it
26
+ * into each target's runtime env as GOOGLE_ANALYTICS_SECRET, so the pair
27
+ * travels together.
28
+ */
29
+ const chalk = require('chalk').default;
30
+ const { pressEnterToOpen } = require('../../../vendor/devkit/prompt.js');
31
+ const { pollWithSpinner } = require('../../../vendor/devkit/flows.js');
32
+ const { writeBrandConfig } = require('../../../lib/config-write.js');
33
+ const { writeEnvValue } = require('../../../lib/env-secret.js');
34
+ const { streamSecretEnvName } = require('../../../lib/analytics-secret.js');
35
+ const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
36
+
37
+ // target → stream URI subdomain (null = the root domain) + display name.
38
+ // Subdomains for app targets are virtual — they exist only to give each
39
+ // target a distinct stream URI. Unlisted targets default to their own name.
40
+ const STREAM_TARGETS = {
41
+ web: { displayName: 'Website', subdomain: null },
42
+ backend: { displayName: 'Backend', subdomain: 'api' },
43
+ desktop: { displayName: 'Desktop App', subdomain: 'desktop' },
44
+ extension: { displayName: 'Browser Extension', subdomain: 'extension' },
45
+ mobile: { displayName: 'Mobile App', subdomain: 'mobile' },
46
+ };
47
+
48
+ const SECRET_NAME = 'Secret';
49
+ const MAX_SECRET_ATTEMPTS = 20;
50
+
51
+ // Secrets get embedded in .env files and URLs downstream — dashes and
52
+ // underscores have burned enough tooling that omega-manager regenerated
53
+ // until the value was clean; the port keeps that rule
54
+ function isCleanSecret(secret) {
55
+ return !secret.includes('-') && !secret.includes('_');
56
+ }
57
+
58
+ module.exports = async function ensureGoogleStreams(context) {
59
+ const { analyticsApi: api, propertyId, accountId, domain, brand, brandConfig, brandRoot, options = {} } = context;
60
+
61
+ // Config drift check: a propertyId pointing at a deleted/inaccessible
62
+ // property should say so instead of erroring cryptically per stream
63
+ const property = await api.getProperty(propertyId);
64
+ if (!property) {
65
+ console.log(` ${chalk.yellow('⚠')} GA property ${chalk.cyan(propertyId)} not found (deleted, or the authed account has no access)`);
66
+ return { status: 'warned', reason: `GA property ${propertyId} not found`, output: { streams: { error: `property ${propertyId} not found` } } };
67
+ }
68
+
69
+ const existing = await api.listDataStreams(propertyId);
70
+ console.log(` ${chalk.dim('→')} Property ${chalk.cyan(propertyId)}: ${existing.length} existing stream(s)`);
71
+
72
+ const enhancedMeasurement = brandConfig.analytics?.providers?.google?.enhancedMeasurement || {};
73
+
74
+ const streams = {};
75
+ // What warned, by name (#643) — joined into the operation's reason
76
+ const warnings = [];
77
+ const planned = [];
78
+ const configEdits = {};
79
+
80
+ for (const target of brand.enabledTargets) {
81
+ const spec = STREAM_TARGETS[target] || { displayName: target, subdomain: target };
82
+ const uri = spec.subdomain ? `https://${spec.subdomain}.${domain}` : `https://${domain}`;
83
+ const displayName = `${brandConfig.brand.name} - ${spec.displayName}`;
84
+
85
+ let stream = existing.find((s) => s.type === 'WEB_DATA_STREAM' && s.webStreamData?.defaultUri === uri);
86
+
87
+ if (!stream) {
88
+ if (options.dryRun) {
89
+ dryRunPlan(`create ${target} stream (${uri})`);
90
+ planned.push({ target, action: 'create', uri });
91
+ continue;
92
+ }
93
+ console.log(` ${chalk.dim('→')} Creating ${chalk.cyan(target)} stream (${uri})...`);
94
+ stream = await api.createWebDataStream(propertyId, { defaultUri: uri, displayName });
95
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(target)}: created`);
96
+ } else {
97
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(target)}: found by URI`);
98
+ }
99
+
100
+ const streamId = stream.name.split('/').pop();
101
+
102
+ // Rename on drift only
103
+ if (stream.displayName !== displayName) {
104
+ if (options.dryRun) {
105
+ planned.push({ target, action: 'rename', to: displayName });
106
+ } else {
107
+ await api.updateDataStream(propertyId, streamId, { displayName });
108
+ console.log(` ${chalk.green('✓')} Renamed to "${chalk.cyan(displayName)}"`);
109
+ }
110
+ }
111
+
112
+ // Enhanced measurement — diff the configured keys against the live
113
+ // settings; omega-manager PATCHed unconditionally on every run
114
+ if (Object.keys(enhancedMeasurement).length > 0) {
115
+ const current = await api.getEnhancedMeasurementSettings(propertyId, streamId);
116
+ const drifted = Object.keys(enhancedMeasurement)
117
+ .filter((key) => current?.[key] !== enhancedMeasurement[key]);
118
+ if (drifted.length > 0) {
119
+ if (options.dryRun) {
120
+ planned.push({ target, action: 'enhanced-measurement', drifted });
121
+ } else {
122
+ await api.updateEnhancedMeasurementSettings(propertyId, streamId, enhancedMeasurement);
123
+ console.log(` ${chalk.green('✓')} Enhanced measurement updated (${drifted.join(', ')})`);
124
+ }
125
+ }
126
+ }
127
+
128
+ // Measurement Protocol secret — the brand .env holds the last resolved
129
+ // value, so a failed listing below degrades to it instead of to nothing
130
+ const secretEnvName = streamSecretEnvName(target);
131
+ let apiSecret = process.env[secretEnvName] || null;
132
+ try {
133
+ const secrets = await api.listMeasurementProtocolSecrets(propertyId, streamId);
134
+ const found = secrets.find((s) => s.displayName === SECRET_NAME);
135
+
136
+ if (found && isCleanSecret(found.secretValue)) {
137
+ apiSecret = found.secretValue;
138
+ } else if (options.dryRun) {
139
+ planned.push({ target, action: found ? 'replace-unclean-secret' : 'create-secret' });
140
+ } else {
141
+ if (found) {
142
+ await api.deleteMeasurementProtocolSecret(propertyId, streamId, found.name.split('/').pop());
143
+ console.log(` ${chalk.dim('↻')} Deleted unclean secret`);
144
+ }
145
+ apiSecret = await createCleanSecret(api, propertyId, streamId);
146
+ if (apiSecret) {
147
+ console.log(` ${chalk.green('✓')} API secret created`);
148
+ } else {
149
+ console.log(` ${chalk.yellow('⚠')} Could not generate a clean secret in ${MAX_SECRET_ATTEMPTS} attempts`);
150
+ warnings.push('could not generate a clean API secret');
151
+ }
152
+ }
153
+ } catch (error) {
154
+ if (error.message.includes('User Data Collection Acknowledgement')) {
155
+ // No API for the acknowledgement — point at the stream settings page
156
+ // (the account-scoped deep link needs accountId; fall back to the app)
157
+ const settingsUrl = accountId
158
+ ? `https://analytics.google.com/analytics/web/#/a${accountId}p${propertyId}/admin/streams/table/${streamId}`
159
+ : 'https://analytics.google.com/analytics/web/';
160
+ console.log(` ${chalk.yellow('⚠')} Data collection acknowledgement required before secrets can be created`);
161
+
162
+ if (canPrompt(options)) {
163
+ await pressEnterToOpen(settingsUrl, 'the data-collection acknowledgement page');
164
+ const poll = await pollWithSpinner({
165
+ check: async () => {
166
+ try {
167
+ const secret = await createCleanSecret(api, propertyId, streamId);
168
+ return { done: true, result: secret };
169
+ } catch (retryError) {
170
+ if (retryError.message.includes('User Data Collection Acknowledgement')) {
171
+ return { done: false };
172
+ }
173
+ return { done: true, error: retryError.message };
174
+ }
175
+ },
176
+ intervalMs: 5000,
177
+ message: 'Waiting for the acknowledgement',
178
+ });
179
+
180
+ if (poll.success && poll.result) {
181
+ apiSecret = poll.result;
182
+ console.log(` ${chalk.green('✓')} API secret created`);
183
+ } else {
184
+ console.log(` ${chalk.dim('→')} Still gated${poll.error ? chalk.dim(` (${poll.error})`) : ''} — rerun converges once acknowledged`);
185
+ warnings.push('API secret still gated — acknowledge it in the GA console');
186
+ }
187
+ } else {
188
+ console.log(` ${chalk.dim('→')} Acknowledge at: ${chalk.cyan(settingsUrl)} — then rerun`);
189
+ warnings.push('API secret still gated — acknowledge it in the GA console');
190
+ }
191
+ } else {
192
+ console.log(` ${chalk.yellow('⚠')} API secret${chalk.dim(`: ${error.message}`)}`);
193
+ warnings.push('could not read or create the API secret');
194
+ }
195
+ }
196
+
197
+ const measurementId = stream.webStreamData?.measurementId || null;
198
+
199
+ streams[target] = { streamId, measurementId };
200
+
201
+ // The resolved measurement id always wins — a hand-set stale value is
202
+ // drift and gets patched (the monitoring service's DSN rule)
203
+ if (measurementId && brandConfig.targets?.[target]?.analytics?.providers?.google?.id !== measurementId) {
204
+ configEdits[`targets.${target}.analytics.providers.google.id`] = measurementId;
205
+ }
206
+
207
+ // The secret's home is the brand .env — written only on drift, so a
208
+ // converged rerun leaves the file alone
209
+ if (apiSecret && !options.dryRun && process.env[secretEnvName] !== apiSecret) {
210
+ writeEnvValue(brandRoot, secretEnvName, apiSecret);
211
+ process.env[secretEnvName] = apiSecret;
212
+ console.log(` ${chalk.green('✓')} .env ← ${chalk.cyan(secretEnvName)}`);
213
+ }
214
+ }
215
+
216
+ // Only touch omega.json5 when something actually drifted — a converged
217
+ // brand never opens the file
218
+ if (Object.keys(configEdits).length > 0) {
219
+ writeBrandConfig(context, configEdits);
220
+ }
221
+
222
+ // A Firebase web app measures through its OWN measurementId — if this GA
223
+ // property has no stream carrying it, Firebase is linked to a different
224
+ // property (expected for shared projects, a real misconfig otherwise)
225
+ const firebaseMeasurementId = brandConfig.cloud?.config?.measurementId;
226
+ if (firebaseMeasurementId && brandConfig.cloud?.shared !== true) {
227
+ const covered = existing.some((s) => s.webStreamData?.measurementId === firebaseMeasurementId)
228
+ || Object.values(streams).some((s) => s.measurementId === firebaseMeasurementId);
229
+ if (!covered) {
230
+ console.log(` ${chalk.yellow('⚠')} Firebase SDK measures via ${chalk.cyan(firebaseMeasurementId)}, but property ${chalk.cyan(propertyId)} has no stream with that ID`);
231
+ console.log(` ${chalk.dim('→')} Firebase is likely linked to a different GA property — unlink in Firebase Console and rerun`);
232
+ warnings.push('the Firebase measurement ID has no stream on this property');
233
+ }
234
+ }
235
+
236
+ const result = { output: { streams: { resolved: streams } } };
237
+ if (planned.length > 0) {
238
+ result.output.streams.planned = planned;
239
+ }
240
+ if (warnings.length > 0) {
241
+ result.status = 'warned';
242
+ result.reason = warnings.join('; ');
243
+ }
244
+ return result;
245
+ };
246
+
247
+ async function createCleanSecret(api, propertyId, streamId) {
248
+ for (let attempt = 1; attempt <= MAX_SECRET_ATTEMPTS; attempt++) {
249
+ const created = await api.createMeasurementProtocolSecret(propertyId, streamId, SECRET_NAME);
250
+ if (isCleanSecret(created.secretValue)) {
251
+ return created.secretValue;
252
+ }
253
+ await api.deleteMeasurementProtocolSecret(propertyId, streamId, created.name.split('/').pop());
254
+ }
255
+ return null;
256
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Ensure the Meta Pixel EXISTS and is configured with a Conversions API
3
+ * access token (META_ACCESS_TOKEN in the brand .env — the name
4
+ * @omega.js/backend reads, and the same system-user token the Marketing API
5
+ * create authenticates with).
6
+ *
7
+ * Near-zero input (#417, Ian 2026-08-21): one interactive pass acquires the
8
+ * token (Enter-gated open of the system-users page, then a paste-in),
9
+ * discovers the ad account that token can see (auto-selected when there's
10
+ * exactly one, landed in `analytics.providers.meta.accountId`), creates the
11
+ * pixel on it, and lands its id — so a brand configures Meta by saying Yes.
12
+ * `analytics.providers.meta: false` turns the whole thing off.
13
+ */
14
+ const { ensurePixelToken } = require('../lib/pixel-token.js');
15
+ const { provisionPixel } = require('../lib/pixel-provision.js');
16
+ const { META_PIXEL } = require('../lib/pixel-specs.js');
17
+
18
+ module.exports = async function ensureMetaPixel(context) {
19
+ const ended = await provisionPixel(context, META_PIXEL);
20
+
21
+ return ended || ensurePixelToken(context, META_PIXEL);
22
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Ensure the TikTok Pixel EXISTS (created on `analytics.providers.tiktok
3
+ * .accountId` — the advertiser account — when config has no id yet) and is
4
+ * configured with an Events API access token (TIKTOK_ACCESS_TOKEN in the
5
+ * brand .env — the name @omega.js/backend reads, and the same token the
6
+ * Business API create authenticates with).
7
+ *
8
+ * SCAFFOLD (#417, Ian 2026-08-21): the wiring is complete and the token is
9
+ * the gate, but TikTok's side is not provisioned — no brand carries the key,
10
+ * so the create path has never run against the live API and this pass always
11
+ * ends at the warned where-to-get guidance. lib/tiktok-api.js carries the
12
+ * call shapes it will use on the first real run.
13
+ */
14
+ const { ensurePixelToken } = require('../lib/pixel-token.js');
15
+ const { provisionPixel } = require('../lib/pixel-provision.js');
16
+ const { TIKTOK_PIXEL } = require('../lib/pixel-specs.js');
17
+
18
+ module.exports = async function ensureTikTokPixel(context) {
19
+ const ended = await provisionPixel(context, TIKTOK_PIXEL);
20
+
21
+ return ended || ensurePixelToken(context, TIKTOK_PIXEL);
22
+ };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Analytics service — reconciles the brand's analytics providers to
3
+ * `analytics: {}` in omega.json5: one GA4 web data stream per enabled target
4
+ * (with enhanced measurement + a Measurement Protocol secret each), the
5
+ * GA property ↔ Firebase project link, and the Meta/TikTok pixels — created
6
+ * on their ad account when config has no id yet (#417), then checked for
7
+ * their access token.
8
+ *
9
+ * Google auth: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET in the brand .env
10
+ * (OAuth2; tokens cache to .omega/auth/google-analytics-tokens.json — the
11
+ * analytics.edit scope is separate from the cloud service's tokens).
12
+ * The GA4 property itself is required config (`analytics.providers.google.
13
+ * propertyId`): interactive runs offer the account + property
14
+ * selection/creation flow (lib/property-flow.js) and land the ids in
15
+ * omega.json5. Without a propertyId (non-interactive/skipped)
16
+ * or without Google credentials the two google operations are filtered out.
17
+ *
18
+ * Pixel auth: META_ACCESS_TOKEN / TIKTOK_ACCESS_TOKEN in the brand .env (the
19
+ * names @omega.js/backend reads — one token per platform serves both the
20
+ * create and the conversions sender). A pixel operation runs whenever its
21
+ * provider has an id (token check) or an accountId to create under, and —
22
+ * Meta, whose token names its own ad accounts — whenever the token is in hand
23
+ * or the run can ask for one (lib/pixel-provision.js hasPixelWork). A missing
24
+ * token warns with guidance and never fails; `analytics.providers.{provider}:
25
+ * false` disables the provider and stops every ask.
26
+ */
27
+ const { serviceInputSpec } = require('../../config.js');
28
+ const { googleTokenStorePath } = require('../../lib/google-auth.js');
29
+ const chalk = require('chalk').default;
30
+ const { createServiceRunner } = require('../../lib/service-runner.js');
31
+ const { requestServiceInput } = require('../../lib/service-input.js');
32
+ const { GoogleAnalyticsAPI } = require('./lib/analytics-api.js');
33
+ const { MetaMarketingAPI } = require('./lib/meta-api.js');
34
+ const { TikTokBusinessAPI } = require('./lib/tiktok-api.js');
35
+ const { resolveGoogleProperty } = require('./lib/property-flow.js');
36
+ const { hasPixelWork } = require('./lib/pixel-provision.js');
37
+ const { META_PIXEL, TIKTOK_PIXEL } = require('./lib/pixel-specs.js');
38
+
39
+ // Operations that need the GA Admin API (and therefore propertyId + creds)
40
+ const GOOGLE_OPERATIONS = new Set(['google-streams', 'google-firebase-link']);
41
+
42
+ module.exports.run = createServiceRunner({
43
+ serviceDir: __dirname,
44
+ setup: async (context) => {
45
+ const analytics = context.brandConfig.analytics || {};
46
+
47
+ if (analytics.enabled === false) {
48
+ return { skip: true, reason: 'analytics.enabled = false' };
49
+ }
50
+
51
+ const domain = (context.brandConfig.brand?.url || '').replace(/^https?:\/\//, '').replace(/\/$/, '');
52
+ if (!domain) {
53
+ return { skip: true, reason: 'no brand.url configured' };
54
+ }
55
+
56
+ // The shared setup contract (#608) for the GOOGLE half only, and only
57
+ // once the brand has DECLARED it wants GA4 (a configured propertyId — the
58
+ // same gate REQUIRES.analytics.when reads): the pixel tokens are optional
59
+ // beside it and each pixel operation asks for its own in place
60
+ // (lib/pixel-token.js), so a pixel-only brand is never asked for a Google
61
+ // credential it has no use for.
62
+ if (!context.analyticsApi && analytics.providers?.google?.propertyId) {
63
+ await requestServiceInput(context, serviceInputSpec('analytics', {
64
+ names: ['GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET'],
65
+ label: 'Google Analytics',
66
+ }));
67
+ }
68
+
69
+ const haveGoogleAuth = Boolean(
70
+ context.analyticsApi
71
+ || (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET),
72
+ );
73
+
74
+ // Missing property + creds available → offer the interactive account +
75
+ // property selection/creation flow (lands both ids in omega.json5, and
76
+ // in the in-memory config re-read just below)
77
+ if (!analytics.providers?.google?.propertyId && haveGoogleAuth) {
78
+ const flowApi = context.analyticsApi || new GoogleAnalyticsAPI({
79
+ tokenStorePath: googleTokenStorePath(context.brandRoot),
80
+ });
81
+ await resolveGoogleProperty(context, flowApi);
82
+ }
83
+
84
+ const providers = context.brandConfig.analytics?.providers || {};
85
+ const google = providers.google || {};
86
+ const meta = providers.meta || {};
87
+ const tiktok = providers.tiktok || {};
88
+
89
+ // A pixel half has work when its id is configured (token check), an
90
+ // account id names where to create it, or — Meta — a token in hand (or an
91
+ // interactive run that can ask for one) can discover the account itself
92
+ const metaWork = hasPixelWork(context.brandConfig, META_PIXEL, context.options);
93
+ const tiktokWork = hasPixelWork(context.brandConfig, TIKTOK_PIXEL, context.options);
94
+ const havePixelWork = metaWork || tiktokWork;
95
+
96
+ if (!google.propertyId && !havePixelWork) {
97
+ return { skip: true, reason: 'no analytics providers configured (analytics.providers.google.propertyId / meta.{id,accountId} / tiktok.{id,accountId})' };
98
+ }
99
+
100
+ // Decide whether the google operations can run this pass
101
+ let operations = context.operations;
102
+ if (!google.propertyId) {
103
+ console.log(chalk.dim(' ⊘ google operations skipped — no analytics.providers.google.propertyId (rerun interactively to select/create the property)'));
104
+ operations = operations.filter((op) => !GOOGLE_OPERATIONS.has(op.name));
105
+ } else if (!haveGoogleAuth) {
106
+ if (!havePixelWork) {
107
+ return { skip: true, reason: 'no GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET configured (set them in the brand .env)' };
108
+ }
109
+ console.log(chalk.dim(' ⊘ google operations skipped — no GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET in the brand .env'));
110
+ operations = operations.filter((op) => !GOOGLE_OPERATIONS.has(op.name));
111
+ }
112
+
113
+ const needsGoogleApi = operations.some((op) => GOOGLE_OPERATIONS.has(op.name));
114
+
115
+ // A pixel client is built only for a provisioning pass — a brand whose
116
+ // pixel id is already config never touches the platform's API
117
+ const needsPixelApi = (work, provider) => Boolean(work && !provider.id);
118
+
119
+ // Tests inject fake clients via context.analyticsApi/metaApi/tiktokApi
120
+ return {
121
+ analyticsApi: needsGoogleApi
122
+ ? (context.analyticsApi || new GoogleAnalyticsAPI({
123
+ tokenStorePath: googleTokenStorePath(context.brandRoot),
124
+ }))
125
+ : null,
126
+ metaApi: context.metaApi || (needsPixelApi(metaWork, meta) ? new MetaMarketingAPI() : null),
127
+ tiktokApi: context.tiktokApi || (needsPixelApi(tiktokWork, tiktok) ? new TikTokBusinessAPI() : null),
128
+ domain,
129
+ propertyId: google.propertyId || null,
130
+ accountId: google.accountId || null,
131
+ operations,
132
+ };
133
+ },
134
+ });
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Google Analytics Admin API client (v1beta, plus the v1alpha enhanced-
3
+ * measurement endpoints that never made it into beta). Named-method surface
4
+ * over the shared GoogleOAuth2Client so tests can fake it method-for-method.
5
+ *
6
+ * Auth: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET in the brand .env; the ONE
7
+ * shared token store (GOOGLE_SCOPES union — one consent covers everything).
8
+ */
9
+ const { GoogleOAuth2Client, GOOGLE_SCOPES } = require('../../../lib/google-auth.js');
10
+
11
+ const API_BASE = 'https://analyticsadmin.googleapis.com/v1beta';
12
+ const API_BASE_ALPHA = 'https://analyticsadmin.googleapis.com/v1alpha';
13
+
14
+ class GoogleAnalyticsAPI {
15
+ constructor(options = {}) {
16
+ this.auth = new GoogleOAuth2Client({
17
+ clientId: options.clientId || process.env.GOOGLE_CLIENT_ID,
18
+ clientSecret: options.clientSecret || process.env.GOOGLE_CLIENT_SECRET,
19
+ tokenStorePath: options.tokenStorePath,
20
+ scopes: GOOGLE_SCOPES,
21
+ });
22
+ }
23
+
24
+ async makeRequest(endpoint, options = {}) {
25
+ return this.auth.makeRequest(`${API_BASE}${endpoint}`, options);
26
+ }
27
+
28
+ // ========== Accounts / properties ==========
29
+
30
+ async listAccounts() {
31
+ const data = await this.makeRequest('/accounts');
32
+ return data.accounts || [];
33
+ }
34
+
35
+ async listProperties(accountId) {
36
+ const data = await this.makeRequest(`/properties?filter=parent:accounts/${accountId}`);
37
+ return data.properties || [];
38
+ }
39
+
40
+ /** Create a GA4 property under an account; returns the property resource */
41
+ async createProperty(accountId, { displayName, timeZone, currencyCode }) {
42
+ return this.makeRequest('/properties', {
43
+ method: 'POST',
44
+ body: JSON.stringify({
45
+ parent: `accounts/${accountId}`,
46
+ displayName,
47
+ timeZone,
48
+ currencyCode,
49
+ }),
50
+ });
51
+ }
52
+
53
+ async getProperty(propertyId) {
54
+ try {
55
+ return await this.makeRequest(`/properties/${propertyId}`);
56
+ } catch (error) {
57
+ if (error.message.includes('404')) {
58
+ return null;
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+
64
+ // ========== Data streams ==========
65
+
66
+ async listDataStreams(propertyId) {
67
+ const data = await this.makeRequest(`/properties/${propertyId}/dataStreams`);
68
+ return data.dataStreams || [];
69
+ }
70
+
71
+ async createWebDataStream(propertyId, { defaultUri, displayName }) {
72
+ return await this.makeRequest(`/properties/${propertyId}/dataStreams`, {
73
+ method: 'POST',
74
+ body: JSON.stringify({
75
+ type: 'WEB_DATA_STREAM',
76
+ webStreamData: { defaultUri },
77
+ displayName: displayName || 'Web Stream',
78
+ }),
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Update a data stream. Nested paths (e.g. 'webStreamData.defaultUri') need
84
+ * an explicit update mask — Object.keys would produce just 'webStreamData'.
85
+ */
86
+ async updateDataStream(propertyId, dataStreamId, updates, updateMaskFields) {
87
+ const updateMask = (updateMaskFields || Object.keys(updates)).join(',');
88
+ return await this.makeRequest(
89
+ `/properties/${propertyId}/dataStreams/${dataStreamId}?updateMask=${updateMask}`,
90
+ { method: 'PATCH', body: JSON.stringify(updates) },
91
+ );
92
+ }
93
+
94
+ // ========== Enhanced measurement (v1alpha only) ==========
95
+
96
+ async getEnhancedMeasurementSettings(propertyId, dataStreamId) {
97
+ return this.auth.makeRequest(
98
+ `${API_BASE_ALPHA}/properties/${propertyId}/dataStreams/${dataStreamId}/enhancedMeasurementSettings`,
99
+ );
100
+ }
101
+
102
+ async updateEnhancedMeasurementSettings(propertyId, dataStreamId, settings) {
103
+ const updateMask = Object.keys(settings).join(',');
104
+ return this.auth.makeRequest(
105
+ `${API_BASE_ALPHA}/properties/${propertyId}/dataStreams/${dataStreamId}/enhancedMeasurementSettings?updateMask=${updateMask}`,
106
+ { method: 'PATCH', body: JSON.stringify(settings) },
107
+ );
108
+ }
109
+
110
+ // ========== Measurement Protocol secrets ==========
111
+
112
+ async listMeasurementProtocolSecrets(propertyId, dataStreamId) {
113
+ const data = await this.makeRequest(
114
+ `/properties/${propertyId}/dataStreams/${dataStreamId}/measurementProtocolSecrets`,
115
+ );
116
+ return data.measurementProtocolSecrets || [];
117
+ }
118
+
119
+ async createMeasurementProtocolSecret(propertyId, dataStreamId, displayName) {
120
+ return await this.makeRequest(
121
+ `/properties/${propertyId}/dataStreams/${dataStreamId}/measurementProtocolSecrets`,
122
+ { method: 'POST', body: JSON.stringify({ displayName }) },
123
+ );
124
+ }
125
+
126
+ async deleteMeasurementProtocolSecret(propertyId, dataStreamId, secretId) {
127
+ await this.makeRequest(
128
+ `/properties/${propertyId}/dataStreams/${dataStreamId}/measurementProtocolSecrets/${secretId}`,
129
+ { method: 'DELETE' },
130
+ );
131
+ return { deleted: true };
132
+ }
133
+
134
+ // ========== Firebase links ==========
135
+
136
+ async listFirebaseLinks(propertyId) {
137
+ const data = await this.makeRequest(`/properties/${propertyId}/firebaseLinks`);
138
+ return data.firebaseLinks || [];
139
+ }
140
+
141
+ async createFirebaseLink(propertyId, firebaseProjectId) {
142
+ return await this.makeRequest(`/properties/${propertyId}/firebaseLinks`, {
143
+ method: 'POST',
144
+ body: JSON.stringify({ project: `projects/${firebaseProjectId}` }),
145
+ });
146
+ }
147
+
148
+ async deleteFirebaseLink(propertyId, firebaseLinkId) {
149
+ await this.makeRequest(`/properties/${propertyId}/firebaseLinks/${firebaseLinkId}`, {
150
+ method: 'DELETE',
151
+ });
152
+ return { deleted: true };
153
+ }
154
+ }
155
+
156
+ module.exports = { GoogleAnalyticsAPI };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Meta Marketing API client (Graph API) — the ad account's pixels/datasets.
3
+ * Named-method surface over fetch so tests can fake it method-for-method,
4
+ * normalized to the shared pixel shape ({ id, name }) the provisioning flow
5
+ * consumes (lib/pixel-provision.js).
6
+ *
7
+ * Auth: META_ACCESS_TOKEN in the brand .env — a Business Manager SYSTEM-USER
8
+ * token with ads_management on the ad account. ONE Meta token per brand: the
9
+ * same key @omega.js/backend's Conversions API sender reads, on the same
10
+ * Graph API version.
11
+ */
12
+ const META_API_VERSION = 'v21.0'; // matches @omega.js/backend's conversions sender
13
+ const API_BASE = `https://graph.facebook.com/${META_API_VERSION}`;
14
+
15
+ /**
16
+ * Ad-account ids are configured BARE (the number Ads Manager shows); the
17
+ * Graph edge is `act_<id>`, and a value pasted with the prefix still works.
18
+ */
19
+ function adAccountRef(adAccountId) {
20
+ const id = String(adAccountId);
21
+ return id.startsWith('act_') ? id : `act_${id}`;
22
+ }
23
+
24
+ class MetaMarketingAPI {
25
+ constructor(options = {}) {
26
+ // Null unless passed explicitly: the env is read at REQUEST time, because
27
+ // an interactive run pastes META_ACCESS_TOKEN after this client was built
28
+ this.accessToken = options.accessToken || null;
29
+ }
30
+
31
+ async makeRequest(endpoint, options = {}) {
32
+ const response = await fetch(`${API_BASE}${endpoint}`, {
33
+ ...options,
34
+ headers: {
35
+ 'Authorization': `Bearer ${this.accessToken || process.env.META_ACCESS_TOKEN}`,
36
+ 'Content-Type': 'application/json',
37
+ ...options.headers,
38
+ },
39
+ });
40
+
41
+ const text = await response.text();
42
+ const data = text ? JSON.parse(text) : null;
43
+
44
+ if (!response.ok) {
45
+ // Graph errors are { error: { message, type, code } } and the message
46
+ // is the human half ("(#200) Requires ads_management permission")
47
+ throw new Error(`Meta API error (${response.status}): ${data?.error?.message || response.statusText}`);
48
+ }
49
+
50
+ return data;
51
+ }
52
+
53
+ /**
54
+ * Ad accounts this token can act on — the auto-discovery source (#417).
55
+ * `id` comes back as `act_<number>` and `account_id` as the bare number
56
+ * config carries, so the normalized id is the one a human reads in Ads
57
+ * Manager.
58
+ */
59
+ async listAdAccounts() {
60
+ const data = await this.makeRequest('/me/adaccounts?fields=id,name,account_id&limit=100');
61
+ return (data?.data || []).map((account) => ({
62
+ id: account.account_id || String(account.id).replace(/^act_/, ''),
63
+ name: account.name,
64
+ }));
65
+ }
66
+
67
+ /** Pixels on the ad account — id + name only (name is the match key). */
68
+ async listPixels(adAccountId) {
69
+ const data = await this.makeRequest(`/${adAccountRef(adAccountId)}/adspixels?fields=id,name&limit=100`);
70
+ return (data?.data || []).map((pixel) => ({ id: pixel.id, name: pixel.name }));
71
+ }
72
+
73
+ /** Create a pixel/dataset on the ad account (the create echoes id only). */
74
+ async createPixel(adAccountId, name) {
75
+ const created = await this.makeRequest(`/${adAccountRef(adAccountId)}/adspixels`, {
76
+ method: 'POST',
77
+ body: JSON.stringify({ name }),
78
+ });
79
+ return { id: created?.id, name };
80
+ }
81
+ }
82
+
83
+ module.exports = { MetaMarketingAPI };