@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.
- package/.claude-plugin/marketplace.json +14 -0
- package/LICENSE +98 -0
- package/README.md +165 -0
- package/bin/omega +2 -0
- package/bin/omg +2 -0
- package/claude-plugin/.claude-plugin/plugin.json +11 -0
- package/claude-plugin/.mcp.json +9 -0
- package/claude-plugin/README.md +139 -0
- package/claude-plugin/hooks/gate/mark.sh +58 -0
- package/claude-plugin/hooks/gate/run.sh +97 -0
- package/claude-plugin/hooks/guard/run.sh +249 -0
- package/claude-plugin/hooks/hooks.json +63 -0
- package/claude-plugin/hooks/inject/run.sh +127 -0
- package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
- package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
- package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
- package/claude-plugin/hooks/quality/run.sh +132 -0
- package/claude-plugin/hooks/shape/run.sh +45 -0
- package/claude-plugin/mcp-router-launch.js +29 -0
- package/claude-plugin/skills/README.md +48 -0
- package/claude-plugin/skills/accessibility/SKILL.md +31 -0
- package/claude-plugin/skills/analytics/SKILL.md +30 -0
- package/claude-plugin/skills/backend/SKILL.md +25 -0
- package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
- package/claude-plugin/skills/browser/SKILL.md +64 -0
- package/claude-plugin/skills/client/SKILL.md +24 -0
- package/claude-plugin/skills/desktop/SKILL.md +25 -0
- package/claude-plugin/skills/extension/SKILL.md +25 -0
- package/claude-plugin/skills/main/SKILL.md +54 -0
- package/claude-plugin/skills/manager/SKILL.md +25 -0
- package/claude-plugin/skills/seo/SKILL.md +36 -0
- package/claude-plugin/skills/theme/SKILL.md +33 -0
- package/claude-plugin/skills/web/SKILL.md +29 -0
- package/dist/cli-run.js +34 -0
- package/dist/cli.js +29 -0
- package/dist/commands/build.js +17 -0
- package/dist/commands/clean.js +16 -0
- package/dist/commands/company.js +46 -0
- package/dist/commands/deploy.js +143 -0
- package/dist/commands/dev.js +437 -0
- package/dist/commands/devlog.js +25 -0
- package/dist/commands/help.js +48 -0
- package/dist/commands/manage.js +53 -0
- package/dist/commands/migrate.js +76 -0
- package/dist/commands/onboard.js +31 -0
- package/dist/commands/pipeline.js +319 -0
- package/dist/commands/test.js +380 -0
- package/dist/commands/update.js +128 -0
- package/dist/commands/version.js +8 -0
- package/dist/company-init.js +167 -0
- package/dist/company.js +283 -0
- package/dist/config.js +1359 -0
- package/dist/devlog/index.js +226 -0
- package/dist/devlog/lib/collect-commits.js +136 -0
- package/dist/devlog/lib/generate-post.js +201 -0
- package/dist/devlog/lib/ghostii.js +122 -0
- package/dist/devlog/lib/project-map.js +98 -0
- package/dist/devlog/lib/publish-website.js +88 -0
- package/dist/index.js +45 -0
- package/dist/lib/agents-md.js +201 -0
- package/dist/lib/analytics-secret.js +22 -0
- package/dist/lib/argv.js +21 -0
- package/dist/lib/auth-admin.js +120 -0
- package/dist/lib/automation-client.js +210 -0
- package/dist/lib/backend-marketing.js +49 -0
- package/dist/lib/brand.js +240 -0
- package/dist/lib/bundle-id.js +53 -0
- package/dist/lib/claude-settings.js +125 -0
- package/dist/lib/company-scaffold.js +213 -0
- package/dist/lib/company.js +227 -0
- package/dist/lib/config-flow.js +350 -0
- package/dist/lib/config-write.js +41 -0
- package/dist/lib/custom-target.js +75 -0
- package/dist/lib/domain-utils.js +19 -0
- package/dist/lib/duration.js +39 -0
- package/dist/lib/env-order.js +236 -0
- package/dist/lib/env-secret.js +78 -0
- package/dist/lib/firestore-rest.js +326 -0
- package/dist/lib/framework-bin.js +156 -0
- package/dist/lib/gitignore.js +63 -0
- package/dist/lib/google-auth.js +495 -0
- package/dist/lib/google-token.js +66 -0
- package/dist/lib/jwt.js +31 -0
- package/dist/lib/legacy-oauth.js +59 -0
- package/dist/lib/node-version.js +127 -0
- package/dist/lib/owner-plan.js +78 -0
- package/dist/lib/package-scripts.js +152 -0
- package/dist/lib/preflight.js +470 -0
- package/dist/lib/product-create.js +144 -0
- package/dist/lib/run-command.js +74 -0
- package/dist/lib/run-gates.js +70 -0
- package/dist/lib/run-output.js +42 -0
- package/dist/lib/run-summary.js +384 -0
- package/dist/lib/scaffold.js +427 -0
- package/dist/lib/service-input.js +207 -0
- package/dist/lib/service-runner.js +466 -0
- package/dist/lib/stale.js +24 -0
- package/dist/lib/target-selection.js +155 -0
- package/dist/lib/verb-fanout.js +128 -0
- package/dist/lib/verify-live.js +188 -0
- package/dist/manage.js +311 -0
- package/dist/omega-bin.js +7 -0
- package/dist/onboard.js +532 -0
- package/dist/services/account/ensure/users.js +259 -0
- package/dist/services/account/index.js +97 -0
- package/dist/services/account/lib/backend-client.js +107 -0
- package/dist/services/account/lib/password.js +36 -0
- package/dist/services/account/lib/resolve-password.js +100 -0
- package/dist/services/advertising/ensure/sites.js +101 -0
- package/dist/services/advertising/index.js +101 -0
- package/dist/services/advertising/lib/adsense-api.js +41 -0
- package/dist/services/ai/ensure/keys.js +21 -0
- package/dist/services/ai/index.js +36 -0
- package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
- package/dist/services/analytics/ensure/google-streams.js +256 -0
- package/dist/services/analytics/ensure/meta-pixel.js +22 -0
- package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
- package/dist/services/analytics/index.js +134 -0
- package/dist/services/analytics/lib/analytics-api.js +156 -0
- package/dist/services/analytics/lib/meta-api.js +83 -0
- package/dist/services/analytics/lib/pixel-account.js +78 -0
- package/dist/services/analytics/lib/pixel-provision.js +152 -0
- package/dist/services/analytics/lib/pixel-specs.js +57 -0
- package/dist/services/analytics/lib/pixel-token.js +88 -0
- package/dist/services/analytics/lib/property-flow.js +77 -0
- package/dist/services/analytics/lib/tiktok-api.js +157 -0
- package/dist/services/analytics/lib/tiktok-auth.js +153 -0
- package/dist/services/assets/ensure/logo-gen.js +68 -0
- package/dist/services/assets/index.js +109 -0
- package/dist/services/assets/lib/assets-config.js +168 -0
- package/dist/services/assets/lib/brandmark-api.js +160 -0
- package/dist/services/assets/lib/derived.js +113 -0
- package/dist/services/assets/lib/font-loader.js +67 -0
- package/dist/services/assets/lib/reconcile.js +122 -0
- package/dist/services/assets/lib/reset.js +118 -0
- package/dist/services/assets/lib/svg-logo-generator.js +154 -0
- package/dist/services/assets/lib/svg-to-black.js +32 -0
- package/dist/services/assets/write/favicons.js +82 -0
- package/dist/services/assets/write/icons.js +64 -0
- package/dist/services/assets/write/process.js +96 -0
- package/dist/services/assets/write/reconcile.js +30 -0
- package/dist/services/assets/write/social-icons.js +108 -0
- package/dist/services/assets/write/templates.js +345 -0
- package/dist/services/bookmark/ensure/sync.js +255 -0
- package/dist/services/bookmark/index.js +17 -0
- package/dist/services/campaigns/ensure/contact-person.js +40 -0
- package/dist/services/campaigns/ensure/custom-fields.js +84 -0
- package/dist/services/campaigns/ensure/domain-auth.js +92 -0
- package/dist/services/campaigns/ensure/event-webhook.js +89 -0
- package/dist/services/campaigns/ensure/link-branding.js +187 -0
- package/dist/services/campaigns/ensure/list.js +50 -0
- package/dist/services/campaigns/ensure/segments.js +109 -0
- package/dist/services/campaigns/ensure/sender-identity.js +153 -0
- package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
- package/dist/services/campaigns/index.js +79 -0
- package/dist/services/campaigns/lib/dns-sync.js +81 -0
- package/dist/services/campaigns/lib/segment-query.js +138 -0
- package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
- package/dist/services/captcha/ensure/site-key.js +83 -0
- package/dist/services/captcha/index.js +78 -0
- package/dist/services/captcha/lib/console-url.js +45 -0
- package/dist/services/captcha/lib/recaptcha-api.js +36 -0
- package/dist/services/certificates/ensure/api-key.js +22 -0
- package/dist/services/certificates/ensure/bundle-ids.js +115 -0
- package/dist/services/certificates/ensure/certificates.js +187 -0
- package/dist/services/certificates/ensure/profiles.js +143 -0
- package/dist/services/certificates/index.js +225 -0
- package/dist/services/certificates/lib/apple-api.js +183 -0
- package/dist/services/certificates/lib/certificate-manager.js +252 -0
- package/dist/services/certificates/lib/identifier-manager.js +107 -0
- package/dist/services/certificates/lib/keychain.js +135 -0
- package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
- package/dist/services/certificates/lib/profile-manager.js +115 -0
- package/dist/services/chat/data/baseline-knowledge.md +96 -0
- package/dist/services/chat/ensure/chat.js +106 -0
- package/dist/services/chat/ensure/user.js +45 -0
- package/dist/services/chat/index.js +139 -0
- package/dist/services/chat/lib/baseline-knowledge.js +137 -0
- package/dist/services/cloud/ensure/authentication.js +267 -0
- package/dist/services/cloud/ensure/billing.js +120 -0
- package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
- package/dist/services/cloud/ensure/database.js +60 -0
- package/dist/services/cloud/ensure/firestore.js +79 -0
- package/dist/services/cloud/ensure/functions.js +49 -0
- package/dist/services/cloud/ensure/hosting.js +398 -0
- package/dist/services/cloud/ensure/oauth-consent.js +248 -0
- package/dist/services/cloud/ensure/project-settings.js +72 -0
- package/dist/services/cloud/ensure/sdk-config.js +95 -0
- package/dist/services/cloud/ensure/service-account.js +152 -0
- package/dist/services/cloud/ensure/services.js +103 -0
- package/dist/services/cloud/ensure/storage.js +47 -0
- package/dist/services/cloud/index.js +130 -0
- package/dist/services/cloud/lib/access-heal.js +130 -0
- package/dist/services/cloud/lib/firebase-api.js +662 -0
- package/dist/services/cloud/lib/project-flow.js +102 -0
- package/dist/services/directory/ensure/entry.js +48 -0
- package/dist/services/directory/index.js +64 -0
- package/dist/services/directory/lib/blocks.js +104 -0
- package/dist/services/disperse/index.js +45 -0
- package/dist/services/disperse/write/certs.js +117 -0
- package/dist/services/domain/ensure/nameservers.js +179 -0
- package/dist/services/domain/index.js +69 -0
- package/dist/services/domain/lib/namecheap-api.js +167 -0
- package/dist/services/domain/lib/registrars.js +46 -0
- package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
- package/dist/services/edge/ensure/cache-rules.js +105 -0
- package/dist/services/edge/ensure/dns-records.js +304 -0
- package/dist/services/edge/ensure/email-routing.js +292 -0
- package/dist/services/edge/ensure/rules-configuration.js +98 -0
- package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
- package/dist/services/edge/ensure/rules-redirect.js +107 -0
- package/dist/services/edge/ensure/rules-response-headers.js +90 -0
- package/dist/services/edge/ensure/rules-security.js +142 -0
- package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
- package/dist/services/edge/ensure/workers.js +219 -0
- package/dist/services/edge/ensure/zone-settings.js +108 -0
- package/dist/services/edge/ensure/zone.js +183 -0
- package/dist/services/edge/index.js +73 -0
- package/dist/services/edge/lib/cloudflare-api.js +84 -0
- package/dist/services/edge/lib/dns-records-helpers.js +518 -0
- package/dist/services/edge/lib/read-cache.js +16 -0
- package/dist/services/edge/lib/ruleset-helper.js +108 -0
- package/dist/services/edge/workers/omega-api-proxy.js +43 -0
- package/dist/services/email/data/baseline-filter.md +4 -0
- package/dist/services/email/data/baseline-knowledge.md +86 -0
- package/dist/services/email/ensure/agent.js +107 -0
- package/dist/services/email/ensure/user.js +45 -0
- package/dist/services/email/index.js +138 -0
- package/dist/services/email/lib/baseline.js +82 -0
- package/dist/services/email/lib/knowledge-file.js +53 -0
- package/dist/services/forms/ensure/form.js +46 -0
- package/dist/services/forms/ensure/user.js +45 -0
- package/dist/services/forms/index.js +138 -0
- package/dist/services/migrations/ensure/notifications.js +189 -0
- package/dist/services/migrations/ensure/orders.js +34 -0
- package/dist/services/migrations/ensure/payment-provider.js +132 -0
- package/dist/services/migrations/ensure/payments-intents.js +34 -0
- package/dist/services/migrations/ensure/state-retirement.js +266 -0
- package/dist/services/migrations/ensure/targets-rename.js +124 -0
- package/dist/services/migrations/ensure/users.js +1176 -0
- package/dist/services/migrations/index.js +101 -0
- package/dist/services/migrations/lib/attribution-touch.js +64 -0
- package/dist/services/migrations/lib/ensure-metadata.js +144 -0
- package/dist/services/migrations/lib/migration-runner.js +473 -0
- package/dist/services/migrations/lib/sanitize-strings.js +112 -0
- package/dist/services/migrations/lib/schema-validator.js +154 -0
- package/dist/services/monitoring/ensure/dsn.js +54 -0
- package/dist/services/monitoring/ensure/projects.js +135 -0
- package/dist/services/monitoring/index.js +54 -0
- package/dist/services/monitoring/lib/sentry-api.js +94 -0
- package/dist/services/newsletter/ensure/custom-fields.js +90 -0
- package/dist/services/newsletter/ensure/publication.js +94 -0
- package/dist/services/newsletter/ensure/segments.js +140 -0
- package/dist/services/newsletter/ensure/webhook.js +108 -0
- package/dist/services/newsletter/index.js +64 -0
- package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
- package/dist/services/newsletter/lib/segment-automation.js +577 -0
- package/dist/services/payment/ensure/chargebee-account.js +30 -0
- package/dist/services/payment/ensure/chargebee-products.js +359 -0
- package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
- package/dist/services/payment/ensure/paypal-account.js +36 -0
- package/dist/services/payment/ensure/paypal-products.js +374 -0
- package/dist/services/payment/ensure/paypal-webhook.js +128 -0
- package/dist/services/payment/ensure/stripe-account.js +93 -0
- package/dist/services/payment/ensure/stripe-disputes.js +56 -0
- package/dist/services/payment/ensure/stripe-products.js +245 -0
- package/dist/services/payment/ensure/stripe-radar.js +74 -0
- package/dist/services/payment/ensure/stripe-webhook.js +130 -0
- package/dist/services/payment/index.js +163 -0
- package/dist/services/payment/lib/chargebee-api.js +338 -0
- package/dist/services/payment/lib/payment-utils.js +116 -0
- package/dist/services/payment/lib/paypal-api.js +354 -0
- package/dist/services/payment/lib/provider-setup.js +134 -0
- package/dist/services/payment/lib/stripe-api.js +152 -0
- package/dist/services/repo/ensure/org.js +70 -0
- package/dist/services/repo/ensure/pages.js +72 -0
- package/dist/services/repo/ensure/repo.js +90 -0
- package/dist/services/repo/index.js +52 -0
- package/dist/services/repo/lib/github-api.js +196 -0
- package/dist/services/search/ensure/ga-link.js +57 -0
- package/dist/services/search/ensure/property.js +137 -0
- package/dist/services/search/ensure/sitemaps.js +70 -0
- package/dist/services/search/index.js +61 -0
- package/dist/services/search/lib/search-console-api.js +92 -0
- package/dist/services/seo/ensure/github-repos.js +271 -0
- package/dist/services/seo/index.js +55 -0
- package/dist/services/seo/lib/gh-api.js +161 -0
- package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
- package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
- package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
- package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
- package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
- package/dist/services/seo/templates/index.js +57 -0
- package/dist/services/server/ensure/brands.js +63 -0
- package/dist/services/server/index.js +42 -0
- package/dist/services/testing/ensure/target-checks.js +97 -0
- package/dist/services/testing/index.js +11 -0
- package/dist/services/testing/lib/checks.js +467 -0
- package/dist/services/update/index.js +20 -0
- package/dist/services/update/lib/cache.js +59 -0
- package/dist/services/update/lib/fingerprint.js +157 -0
- package/dist/services/update/write/targets.js +206 -0
- package/dist/services/workspace/ensure/agents.js +42 -0
- package/dist/services/workspace/ensure/claude-settings.js +34 -0
- package/dist/services/workspace/ensure/config.js +54 -0
- package/dist/services/workspace/ensure/defaults.js +60 -0
- package/dist/services/workspace/ensure/env-keys.js +55 -0
- package/dist/services/workspace/ensure/env-order.js +66 -0
- package/dist/services/workspace/ensure/env-rules.js +97 -0
- package/dist/services/workspace/ensure/gitignore.js +18 -0
- package/dist/services/workspace/ensure/scripts.js +126 -0
- package/dist/services/workspace/ensure/structure.js +97 -0
- package/dist/services/workspace/ensure/translation-sdk.js +148 -0
- package/dist/services/workspace/ensure/workflows.js +54 -0
- package/dist/services/workspace/index.js +8 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/certs.js +199 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/flows.js +317 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/prompt.js +187 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/docs/AGENTS.md +200 -0
- package/docs/account.md +45 -0
- package/docs/advertising.md +46 -0
- package/docs/ai.md +34 -0
- package/docs/analytics.md +69 -0
- package/docs/assets.md +57 -0
- package/docs/bookmark.md +26 -0
- package/docs/brand.md +151 -0
- package/docs/campaigns.md +96 -0
- package/docs/captcha.md +46 -0
- package/docs/certificates.md +68 -0
- package/docs/chat.md +47 -0
- package/docs/cloud.md +110 -0
- package/docs/company.md +64 -0
- package/docs/directory.md +140 -0
- package/docs/disperse.md +46 -0
- package/docs/domain.md +56 -0
- package/docs/edge.md +234 -0
- package/docs/email.md +42 -0
- package/docs/forms.md +48 -0
- package/docs/index.md +109 -0
- package/docs/migration.md +203 -0
- package/docs/migrations.md +56 -0
- package/docs/monitoring.md +41 -0
- package/docs/newsletter.md +48 -0
- package/docs/payment.md +83 -0
- package/docs/repo.md +48 -0
- package/docs/search.md +44 -0
- package/docs/seo.md +34 -0
- package/docs/server.md +37 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/testing.md +34 -0
- package/docs/update.md +36 -0
- package/docs/workspace.md +64 -0
- package/package.json +88 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ad-account discovery for the pixel provisioning path (#417, Ian
|
|
3
|
+
* 2026-08-21: "auto-set accountId") — the near-zero-input half of the GA4
|
|
4
|
+
* standard, applied to the pixels.
|
|
5
|
+
*
|
|
6
|
+
* The token already names the accounts it can act on, so a brand should
|
|
7
|
+
* never have to look one up: with `analytics.providers.{provider}.accountId`
|
|
8
|
+
* missing, ask the platform which accounts this token sees. Exactly one is
|
|
9
|
+
* the answer (auto-selected, landed in omega.json5 — nothing to ask);
|
|
10
|
+
* several is a real choice (the standard selection flow, with the tri-state
|
|
11
|
+
* opt-out in the list); non-interactive runs print the candidates so the
|
|
12
|
+
* value can be pasted into config, and zero means the token has no ad-account
|
|
13
|
+
* access at all — guidance, never a failure.
|
|
14
|
+
*
|
|
15
|
+
* Only providers whose client can enumerate accounts take this path
|
|
16
|
+
* (spec.discoverAccounts); the others keep the configured-accountId gate.
|
|
17
|
+
*/
|
|
18
|
+
const chalk = require('chalk').default;
|
|
19
|
+
|
|
20
|
+
const { landValue, resolveConfigValue } = require('../../../lib/config-flow.js');
|
|
21
|
+
const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the ad account the pixel gets created on, landing it in config.
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} context - Handler context (carries the client at spec.apiKey)
|
|
27
|
+
* @param {Object} spec - { key, label, envVar, apiKey, accountLabel, accountGuidance }
|
|
28
|
+
* @returns {Promise<string|null>} - The account id, or null when unresolved
|
|
29
|
+
*/
|
|
30
|
+
async function resolvePixelAccount(context, spec) {
|
|
31
|
+
const { options = {} } = context;
|
|
32
|
+
const path = `analytics.providers.${spec.key}.accountId`;
|
|
33
|
+
const accounts = await context[spec.apiKey].listAdAccounts();
|
|
34
|
+
|
|
35
|
+
if (accounts.length === 0) {
|
|
36
|
+
console.log(` ${chalk.yellow('⚠')} No ${spec.accountLabel}s are visible to ${chalk.cyan(spec.envVar)}`);
|
|
37
|
+
console.log(` ${chalk.dim('→')} ${spec.accountGuidance}`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (accounts.length === 1) {
|
|
42
|
+
const [only] = accounts;
|
|
43
|
+
const display = `${only.name} (${only.id})`;
|
|
44
|
+
|
|
45
|
+
if (options.dryRun) {
|
|
46
|
+
dryRunPlan(`set ${path} to ${only.id} (the only ${spec.accountLabel} ${spec.envVar} can see)`);
|
|
47
|
+
return only.id;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(` ${chalk.green('✓')} ${spec.label} ${spec.accountLabel}: ${chalk.cyan(display)} ${chalk.dim(`— the only one ${spec.envVar} can see`)}`);
|
|
51
|
+
landValue(context, path, only.id);
|
|
52
|
+
return only.id;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!canPrompt(options)) {
|
|
56
|
+
console.log(` ${chalk.yellow('⚠')} ${accounts.length} ${spec.accountLabel}s visible — set ${chalk.cyan(path)} in omega.json5 (or rerun interactively to pick):`);
|
|
57
|
+
for (const account of accounts) {
|
|
58
|
+
console.log(` ${chalk.dim('·')} ${account.name} ${chalk.dim(`(${account.id})`)}`);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Several: the standard selection flow — brand matches sort first, and the
|
|
64
|
+
// inline opt-out lands `analytics.providers.{provider}: false`
|
|
65
|
+
return resolveConfigValue(context, {
|
|
66
|
+
path,
|
|
67
|
+
label: `${spec.label} ${spec.accountLabel}`,
|
|
68
|
+
message: `Select the ${spec.accountLabel} for ${spec.label}:`,
|
|
69
|
+
gate: false, // the provisioning flow already asked
|
|
70
|
+
choices: () => accounts,
|
|
71
|
+
getName: (account) => `${account.name} (${account.id})`,
|
|
72
|
+
getValue: (account) => account.id,
|
|
73
|
+
optOut: { label: `No ${spec.label} — don't ask again` },
|
|
74
|
+
disablePath: `analytics.providers.${spec.key}`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { resolvePixelAccount };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared pixel PROVISIONING for the meta-pixel/tiktok-pixel operations — the
|
|
3
|
+
* create half of the pair whose check half is pixel-token.js, and the same
|
|
4
|
+
* reconcile shape the GA4 side uses (#417).
|
|
5
|
+
*
|
|
6
|
+
* The pixel id is public config, so a configured
|
|
7
|
+
* `analytics.providers.{provider}.id` is converged proof and nothing is
|
|
8
|
+
* called. A missing id reconciles idempotently in ONE interactive pass
|
|
9
|
+
* (Ian 2026-08-21, the GA4 near-zero-input standard): acquire the access
|
|
10
|
+
* token (pixel-token.js — guidance, the Enter-gated open, paste-in), resolve
|
|
11
|
+
* the ad account (pixel-account.js — auto-selected when the token sees
|
|
12
|
+
* exactly one, `meta.accountId` = the Meta AD ACCOUNT, `tiktok.accountId` =
|
|
13
|
+
* the TikTok ADVERTISER; the non-secret ids live in config beside
|
|
14
|
+
* `google.accountId`/`propertyId`, never in .env), find the brand's pixel BY
|
|
15
|
+
* NAME, create it when absent, then land the id in omega.json5
|
|
16
|
+
* (comment-preserving) and in the in-memory config, so the token check in the
|
|
17
|
+
* same pass reports the pixel it just made.
|
|
18
|
+
*
|
|
19
|
+
* The access token is still the gate. No token — nothing pasted, or a
|
|
20
|
+
* non-interactive run — means no provisioning this pass: warned with the same
|
|
21
|
+
* where-to-get guidance the token check prints, never a failure (Ian
|
|
22
|
+
* 2026-08-21: a manage walk must not break for a key that isn't filled in).
|
|
23
|
+
*
|
|
24
|
+
* Tri-state (#33): `analytics.providers.{provider}: false` disables the
|
|
25
|
+
* provider outright — no calls, no prompts, no warns — and every prompt this
|
|
26
|
+
* flow opens offers the choice that writes it.
|
|
27
|
+
*
|
|
28
|
+
* Clients hand back one normalized shape: `listPixels(accountId)` →
|
|
29
|
+
* `[{ id, name }]`, `createPixel(accountId, name)` → `{ id, name }`, and
|
|
30
|
+
* (when the spec declares discoverAccounts) `listAdAccounts()` →
|
|
31
|
+
* `[{ id, name }]`.
|
|
32
|
+
*/
|
|
33
|
+
const chalk = require('chalk').default;
|
|
34
|
+
|
|
35
|
+
const { confirmSetup, landValue, readTriState } = require('../../../lib/config-flow.js');
|
|
36
|
+
const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
|
|
37
|
+
const { acquirePixelToken } = require('./pixel-token.js');
|
|
38
|
+
const { resolvePixelAccount } = require('./pixel-account.js');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether a provider has anything for the service to do: a configured id to
|
|
42
|
+
* check, an account to create under, or — for a provider that can discover
|
|
43
|
+
* its own account — a token in hand or an interactive run that can ask for
|
|
44
|
+
* one. A disabled provider never has work.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} brandConfig - The merged brand config
|
|
47
|
+
* @param {Object} spec - The provider spec
|
|
48
|
+
* @param {Object} [options] - Run options (dryRun gates the prompt path)
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
function hasPixelWork(brandConfig, spec, options) {
|
|
52
|
+
const { optedOut, value } = readTriState(brandConfig, `analytics.providers.${spec.key}`);
|
|
53
|
+
if (optedOut) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const provider = value || {};
|
|
58
|
+
if (provider.id || provider.accountId) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Boolean(spec.discoverAccounts && (process.env[spec.envVar] || canPrompt(options)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create the provider's pixel when config has no id yet.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} context - Handler context (carries the client at spec.apiKey)
|
|
69
|
+
* @param {Object} spec - { key, label, envVar, tokenSource, tokenUrl, apiKey,
|
|
70
|
+
* accountLabel, accountGuidance, discoverAccounts, instructions }
|
|
71
|
+
* @returns {Promise<Object|null>} A handler return when the pass ENDS here (the
|
|
72
|
+
* provider is disabled, no token to create with, no account to create on, or
|
|
73
|
+
* a dry-run plan), or null when the id is in hand and the token check should
|
|
74
|
+
* run.
|
|
75
|
+
*/
|
|
76
|
+
async function provisionPixel(context, spec) {
|
|
77
|
+
const { brandConfig, options = {} } = context;
|
|
78
|
+
const { optedOut, value } = readTriState(brandConfig, `analytics.providers.${spec.key}`);
|
|
79
|
+
|
|
80
|
+
if (optedOut) {
|
|
81
|
+
console.log(chalk.dim(` ⊘ ${spec.label} disabled (analytics.providers.${spec.key}: false)`));
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const provider = value || {};
|
|
86
|
+
|
|
87
|
+
// Configured id = converged; nothing configured and nothing to discover
|
|
88
|
+
// with = the token check keeps its clean "not configured" skip
|
|
89
|
+
if (provider.id || !hasPixelWork(brandConfig, spec, options)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Nothing configured at all: ask permission before asking for a secret —
|
|
94
|
+
// the uniform gate, whose Disable lands `providers.{provider}: false`
|
|
95
|
+
let gated = false;
|
|
96
|
+
if (!provider.accountId && canPrompt(options)) {
|
|
97
|
+
const action = await confirmSetup(context, {
|
|
98
|
+
label: spec.label,
|
|
99
|
+
instructions: spec.instructions,
|
|
100
|
+
disablePath: `analytics.providers.${spec.key}`,
|
|
101
|
+
});
|
|
102
|
+
if (action !== 'yes') {
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
gated = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// When the gate above ran, the acquire must not open the same question
|
|
109
|
+
// twice; when it didn't (a configured accountId), the acquire owns it
|
|
110
|
+
if (!await acquirePixelToken(context, spec, gated ? { gate: false } : {})) {
|
|
111
|
+
console.log(` ${chalk.dim('→')} ${spec.label} not created — rerun with ${chalk.cyan(spec.envVar)} set (or set ${chalk.cyan(`analytics.providers.${spec.key}: false`)} to stop asking)`);
|
|
112
|
+
return { status: 'warned', reason: `${spec.label} not created — no ${spec.envVar}`, output: { [spec.key]: { pixelId: null, tokenConfigured: false } } };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const accountId = provider.accountId || await resolvePixelAccount(context, spec);
|
|
116
|
+
if (!accountId) {
|
|
117
|
+
return { status: 'warned', reason: `${spec.label} not created — no account resolved`, output: { [spec.key]: { pixelId: null, tokenConfigured: true } } };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const api = context[spec.apiKey];
|
|
121
|
+
const name = brandConfig.brand.name;
|
|
122
|
+
const configPath = `analytics.providers.${spec.key}.id`;
|
|
123
|
+
|
|
124
|
+
const existing = await api.listPixels(accountId);
|
|
125
|
+
const found = existing.find((pixel) => pixel.name === name);
|
|
126
|
+
|
|
127
|
+
if (found) {
|
|
128
|
+
console.log(` ${chalk.green('✓')} Matched ${spec.label} ${chalk.cyan(`"${name}"`)} ${chalk.dim(`(${found.id})`)}`);
|
|
129
|
+
landValue(context, configPath, found.id);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (options.dryRun) {
|
|
134
|
+
return dryRunPlan(
|
|
135
|
+
`create ${spec.label} "${name}" on ${spec.accountLabel} ${accountId}`,
|
|
136
|
+
{ output: { [spec.key]: { planned: 'create', name } } },
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const created = await api.createPixel(accountId, name);
|
|
141
|
+
if (!created?.id) {
|
|
142
|
+
// The create answered 200 without an id: the response shape moved and
|
|
143
|
+
// landing `undefined` would poison the config — fail at the break
|
|
144
|
+
throw new Error(`${spec.label} create returned no id (${JSON.stringify(created)})`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(` ${chalk.green('✓')} Created ${spec.label} ${chalk.cyan(`"${name}"`)} ${chalk.dim(`(${created.id})`)}`);
|
|
148
|
+
landValue(context, configPath, created.id);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { hasPixelWork, provisionPixel };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two pixel providers, declared once — the SSOT the ensure handlers
|
|
3
|
+
* (ensure/meta-pixel.js, ensure/tiktok-pixel.js) run on and the service setup
|
|
4
|
+
* reads to decide whether a provider has work and needs a client at all.
|
|
5
|
+
*
|
|
6
|
+
* Each spec names: the config key under `analytics.providers`, the .env key
|
|
7
|
+
* @omega.js/backend reads, the context key its API client rides on, and what
|
|
8
|
+
* the platform calls the account the pixel lives on. `discoverAccounts` marks
|
|
9
|
+
* a provider whose client can enumerate the accounts its token sees
|
|
10
|
+
* (lib/pixel-account.js) — the others stay gated on a configured accountId.
|
|
11
|
+
*
|
|
12
|
+
* WHERE a human gets the token is NOT here: the REQUIRES registry
|
|
13
|
+
* (src/config.js) owns every env descriptor — label, mint URL, hint — so the
|
|
14
|
+
* shared setup contract (#608) and the preflight walkthrough read one home.
|
|
15
|
+
* `acquire` names the provider whose token is MINTED rather than pasted
|
|
16
|
+
* (TikTok's portal exchange, #448); without it the generic paste lane runs.
|
|
17
|
+
*/
|
|
18
|
+
const chalk = require('chalk').default;
|
|
19
|
+
|
|
20
|
+
const { acquireTikTokToken } = require('./tiktok-auth.js');
|
|
21
|
+
|
|
22
|
+
const META_PIXEL = {
|
|
23
|
+
key: 'meta',
|
|
24
|
+
label: 'Meta Pixel',
|
|
25
|
+
idLabel: 'Meta Pixel ID',
|
|
26
|
+
envVar: 'META_ACCESS_TOKEN',
|
|
27
|
+
apiKey: 'metaApi',
|
|
28
|
+
accountLabel: 'ad account',
|
|
29
|
+
discoverAccounts: true,
|
|
30
|
+
accountGuidance: 'Assign an ad account to the system user in Business Settings → Users → System users → Add assets',
|
|
31
|
+
instructions: [
|
|
32
|
+
`1. Mint a Business Manager SYSTEM USER token with ${chalk.cyan('ads_management')} (the next step opens the page)`,
|
|
33
|
+
'2. The ad account that token can see is selected for you, and the pixel is created on it',
|
|
34
|
+
`3. The same token signs the Conversions API events ${chalk.cyan('@omega.js/backend')} sends`,
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// SCAFFOLD (#417): no discoverAccounts until TikTok's app is provisioned and
|
|
39
|
+
// the shapes are proven — the advertiser id stays required config, and the
|
|
40
|
+
// create path waits for a configured accountId. The token, though, is no
|
|
41
|
+
// longer a standing key: `acquire` mints it through the portal exchange (#448).
|
|
42
|
+
const TIKTOK_PIXEL = {
|
|
43
|
+
key: 'tiktok',
|
|
44
|
+
label: 'TikTok Pixel',
|
|
45
|
+
idLabel: 'TikTok Pixel Code',
|
|
46
|
+
envVar: 'TIKTOK_ACCESS_TOKEN',
|
|
47
|
+
apiKey: 'tiktokApi',
|
|
48
|
+
accountLabel: 'advertiser',
|
|
49
|
+
acquire: acquireTikTokToken,
|
|
50
|
+
instructions: [
|
|
51
|
+
`1. Set the developer app's id at ${chalk.cyan('analytics.providers.tiktok.appId')} in omega.json5`,
|
|
52
|
+
'2. Paste its app secret once — it authorizes the portal and is never saved',
|
|
53
|
+
`3. The exchange runs here and saves only ${chalk.cyan('TIKTOK_ACCESS_TOKEN')}`,
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
module.exports = { META_PIXEL, TIKTOK_PIXEL };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared pixel-token machinery for the meta-pixel/tiktok-pixel operations —
|
|
3
|
+
* ONE acquisition flow, used by both halves of the pair: the CREATE path
|
|
4
|
+
* needs the token before it can provision (lib/pixel-provision.js) and the
|
|
5
|
+
* CHECK path needs it before the frontend can send conversions.
|
|
6
|
+
*
|
|
7
|
+
* The pixel ID is public config (`analytics.providers.{provider}.id` — it
|
|
8
|
+
* ships in the frontend); the conversions/events access token is a secret
|
|
9
|
+
* and lives in the brand .env under the exact name @omega.js/backend reads.
|
|
10
|
+
* There's no practical validation API for either token, so this is a
|
|
11
|
+
* presence check plus an ask — and the ask is the SHARED setup contract
|
|
12
|
+
* (#608, lib/service-input.js): provide (the Enter-gated open of the exact
|
|
13
|
+
* page that mints one, then a masked paste saved to the brand .env), skip
|
|
14
|
+
* this run, or disable the provider for good (`analytics.providers.
|
|
15
|
+
* {provider}: false`). Non-interactive runs never prompt and keep the warned
|
|
16
|
+
* guidance. The backend's stage step composes the saved token out of the
|
|
17
|
+
* brand .env into its dist/.env.
|
|
18
|
+
*
|
|
19
|
+
* A provider whose token is MINTED rather than pasted names its own acquire
|
|
20
|
+
* on the spec (TikTok's portal exchange, #448) — the outcomes are identical.
|
|
21
|
+
*/
|
|
22
|
+
const chalk = require('chalk').default;
|
|
23
|
+
|
|
24
|
+
const { serviceInputSpec } = require('../../../config.js');
|
|
25
|
+
const { requestServiceInput } = require('../../../lib/service-input.js');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Make sure the provider's access token is in the environment, asking for
|
|
29
|
+
* it when the run can prompt. Silent when the token is already set — the
|
|
30
|
+
* caller reports it (the create path stays quiet, the check path prints its
|
|
31
|
+
* ✓ line).
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} context - Handler context
|
|
34
|
+
* @param {Object} spec - A pixel spec ({ key, label, envVar, acquire?, instructions? })
|
|
35
|
+
* @param {Object} [options] - { gate: false } when the caller already ran the
|
|
36
|
+
* Provide / Skip / Disable gate for this provider.
|
|
37
|
+
* @returns {Promise<boolean>} - Whether the token is now in process.env
|
|
38
|
+
*/
|
|
39
|
+
async function acquirePixelToken(context, spec, options = {}) {
|
|
40
|
+
if (process.env[spec.envVar]) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (spec.acquire) {
|
|
45
|
+
return spec.acquire(context, spec, options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const gate = await requestServiceInput(context, serviceInputSpec('analytics', {
|
|
49
|
+
names: [spec.envVar],
|
|
50
|
+
label: spec.label,
|
|
51
|
+
instructions: spec.instructions,
|
|
52
|
+
gate: options.gate,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
return gate === null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check one provider's pixel config + access token.
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} context - Handler context
|
|
62
|
+
* @param {Object} spec - { key, label, idLabel, envVar }
|
|
63
|
+
* @returns {Object} - Handler return ({ output } / warned)
|
|
64
|
+
*/
|
|
65
|
+
async function ensurePixelToken(context, spec) {
|
|
66
|
+
const { brandConfig } = context;
|
|
67
|
+
const pixelId = brandConfig.analytics?.providers?.[spec.key]?.id;
|
|
68
|
+
|
|
69
|
+
if (!pixelId) {
|
|
70
|
+
console.log(chalk.dim(` ⊘ ${spec.label} not configured (analytics.providers.${spec.key}.id)`));
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(` ${chalk.green('✓')} ${spec.idLabel}: ${chalk.cyan(pixelId)}`);
|
|
75
|
+
|
|
76
|
+
const alreadySet = Boolean(process.env[spec.envVar]);
|
|
77
|
+
if (!await acquirePixelToken(context, spec)) {
|
|
78
|
+
return { status: 'warned', reason: `no ${spec.envVar} — the access token is not configured`, output: { [spec.key]: { pixelId, tokenConfigured: false } } };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (alreadySet) {
|
|
82
|
+
console.log(` ${chalk.green('✓')} Access token configured (${chalk.cyan(spec.envVar)})`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { output: { [spec.key]: { pixelId, tokenConfigured: true } } };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { acquirePixelToken, ensurePixelToken };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive GA account + property selection/creation (config-landing
|
|
3
|
+
* flow): when analytics.providers.google.propertyId is missing in an
|
|
4
|
+
* interactive run, pick the GA account (create-new opens the provision
|
|
5
|
+
* page), then pick or create the GA4 property via the Admin API — both land
|
|
6
|
+
* in omega.json5 (comment-preserving writeback). An accountId already in
|
|
7
|
+
* config is used as-is without prompting — and when it arrived through the
|
|
8
|
+
* COMPANY layer (company omega.json5 `analytics.providers.google.accountId`,
|
|
9
|
+
* merged under the brand file so an explicit brand value always wins), the
|
|
10
|
+
* flow says so with a dim note instead of silently proceeding. The
|
|
11
|
+
* interactive picker stays the fallback when nothing is configured anywhere.
|
|
12
|
+
*/
|
|
13
|
+
const chalk = require('chalk').default;
|
|
14
|
+
const { resolveConfigValue } = require('../../../lib/config-flow.js');
|
|
15
|
+
|
|
16
|
+
const CREATE_ACCOUNT_URL = 'https://analytics.google.com/analytics/web/#/provision/create';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve (or interactively land) the GA4 property id.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} context - Service context
|
|
22
|
+
* @param {Object} api - GoogleAnalyticsAPI client
|
|
23
|
+
* @returns {Promise<string|null>} - propertyId, or null when skipped
|
|
24
|
+
*/
|
|
25
|
+
async function resolveGoogleProperty(context, api) {
|
|
26
|
+
// When the account flow just ran (and the user said Yes), the property
|
|
27
|
+
// selection rides that gate instead of asking "Set up now?" twice
|
|
28
|
+
const configuredAccountId = context.brandConfig.analytics?.providers?.google?.accountId;
|
|
29
|
+
const accountPreConfigured = Boolean(configuredAccountId);
|
|
30
|
+
|
|
31
|
+
// Company-default GA account (Ian 2026-07-21): a company-managed brand
|
|
32
|
+
// inherits the company's accountId through the merge chain — note the
|
|
33
|
+
// provenance instead of prompting (an explicit brand-level accountId
|
|
34
|
+
// wins in the merge before this ever runs)
|
|
35
|
+
const companyAccountId = context.companyConfig?.analytics?.providers?.google?.accountId;
|
|
36
|
+
if (configuredAccountId && companyAccountId && configuredAccountId === companyAccountId) {
|
|
37
|
+
console.log(` ${chalk.dim(`→ Google Analytics account ${configuredAccountId} — defaulting to company account`)}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const accountId = await resolveConfigValue(context, {
|
|
41
|
+
path: 'analytics.providers.google.accountId',
|
|
42
|
+
label: 'Google Analytics account',
|
|
43
|
+
choices: () => api.listAccounts(),
|
|
44
|
+
getName: (account) => `${account.displayName} (${account.name.replace('accounts/', '')})`,
|
|
45
|
+
getValue: (account) => account.name.replace('accounts/', ''),
|
|
46
|
+
createNew: { label: 'account', url: CREATE_ACCOUNT_URL, refreshChoices: true },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!accountId) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return resolveConfigValue(context, {
|
|
54
|
+
path: 'analytics.providers.google.propertyId',
|
|
55
|
+
label: 'GA4 property',
|
|
56
|
+
gate: accountPreConfigured,
|
|
57
|
+
choices: () => api.listProperties(accountId),
|
|
58
|
+
getName: (property) => `${property.displayName} (${property.name.replace('properties/', '')})`,
|
|
59
|
+
getValue: (property) => property.name.replace('properties/', ''),
|
|
60
|
+
createNew: {
|
|
61
|
+
label: 'property',
|
|
62
|
+
handler: async () => {
|
|
63
|
+
const { brand } = context.brandConfig;
|
|
64
|
+
const google = context.brandConfig.analytics.providers.google;
|
|
65
|
+
const domain = brand.url.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
66
|
+
const property = await api.createProperty(accountId, {
|
|
67
|
+
displayName: `${brand.name} (${domain})`,
|
|
68
|
+
timeZone: google.timeZone,
|
|
69
|
+
currencyCode: google.currency,
|
|
70
|
+
});
|
|
71
|
+
return property.name.replace('properties/', '');
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { resolveGoogleProperty };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TikTok Business API client (v1.3) — the advertiser account's pixels.
|
|
3
|
+
* Named-method surface over fetch, normalized to the same { id, name } shape
|
|
4
|
+
* as the Meta client so one provisioning flow drives both
|
|
5
|
+
* (lib/pixel-provision.js). TikTok's "pixel code" IS the public pixel id the
|
|
6
|
+
* frontends embed, so it fills the `id` slot.
|
|
7
|
+
*
|
|
8
|
+
* Auth: TIKTOK_ACCESS_TOKEN in the brand .env (the `Access-Token` header) —
|
|
9
|
+
* the long-lived token the portal authorization mints (#448, lib/tiktok-auth.js),
|
|
10
|
+
* the same key @omega.js/backend's Events API sender reads. The exchange that
|
|
11
|
+
* MINTS it lives here too, as a free function: it is the one call that runs
|
|
12
|
+
* without a token, so it can never be a method on the authenticated client.
|
|
13
|
+
*
|
|
14
|
+
* SCAFFOLD (#417, Ian 2026-08-21): the endpoints below are the documented
|
|
15
|
+
* v1.3 pixel shapes, never yet exercised against the live API — Ian's TikTok
|
|
16
|
+
* app is not provisioned, so no brand carries the token and the ensure step
|
|
17
|
+
* cannot reach this client. Treat a first live run as the proving run.
|
|
18
|
+
*/
|
|
19
|
+
const API_BASE = 'https://business-api.tiktok.com/open_api/v1.3';
|
|
20
|
+
// Where a developer app (and so its app id) is created — the page the setup
|
|
21
|
+
// walks to when a brand has no `analytics.providers.tiktok.appId` yet (#635).
|
|
22
|
+
const TIKTOK_APPS_URL = 'https://business-api.tiktok.com/portal/apps';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The app's own page in the developer portal. Its Basic Information shows the
|
|
26
|
+
* "Advertiser authorization URL" TikTok built for the app (app id, state, and
|
|
27
|
+
* the redirect URI configured ON the app), so the setup opens THIS page and the
|
|
28
|
+
* human clicks that link there (Ian 2026-08-27): the redirect URI is the app's
|
|
29
|
+
* setting, never brand config, and manage never asks for it.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} appId - The TikTok developer app id (public config).
|
|
32
|
+
* @returns {string} The app page URL.
|
|
33
|
+
*/
|
|
34
|
+
// The portal's authorization endpoint — what the app page's "Advertiser
|
|
35
|
+
// authorization URL" starts with. Only used to check a pasted URL is that link.
|
|
36
|
+
const TIKTOK_AUTH_BASE = 'https://business-api.tiktok.com/portal/auth';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether a pasted URL is the app's own advertiser authorization link.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} url - The pasted URL.
|
|
42
|
+
* @param {string} appId - The app id it must carry.
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
function isAuthUrlFor(url, appId) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = new URL(url);
|
|
48
|
+
return `${parsed.origin}${parsed.pathname}` === TIKTOK_AUTH_BASE && parsed.searchParams.get('app_id') === String(appId);
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The auth_code out of a pasted redirect URL, or the bare code when the human
|
|
56
|
+
* pasted just that.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} pasted - A redirect URL carrying `auth_code`, or the code.
|
|
59
|
+
* @returns {string} The code, or '' when neither.
|
|
60
|
+
*/
|
|
61
|
+
function parseAuthCode(pasted) {
|
|
62
|
+
if (!pasted) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return new URL(pasted).searchParams.get('auth_code') || '';
|
|
67
|
+
} catch {
|
|
68
|
+
// Not a URL: the bare code
|
|
69
|
+
return pasted;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function TIKTOK_APP_URL(appId) {
|
|
74
|
+
return `${TIKTOK_APPS_URL}/${encodeURIComponent(appId)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Exchange a portal `auth_code` for the long-lived access token (#448). The
|
|
79
|
+
* app secret is a MINT-TIME credential — it is passed here and never stored.
|
|
80
|
+
*
|
|
81
|
+
* @param {object} params - { appId, secret, authCode }.
|
|
82
|
+
* @param {Function} [fetchImpl] - Test seam; defaults to global fetch.
|
|
83
|
+
* @returns {Promise<string>} The long-lived access token.
|
|
84
|
+
*/
|
|
85
|
+
async function exchangeAuthCode({ appId, secret, authCode }, fetchImpl = fetch) {
|
|
86
|
+
const response = await fetchImpl(`${API_BASE}/oauth2/access_token/`, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({ app_id: appId, secret, auth_code: authCode }),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const text = await response.text();
|
|
93
|
+
const body = text ? JSON.parse(text) : null;
|
|
94
|
+
|
|
95
|
+
// Same rule as the client: TikTok answers 200 with a non-zero `code`
|
|
96
|
+
if (!response.ok || (body && body.code !== 0)) {
|
|
97
|
+
throw new Error(`TikTok API error (${body?.code ?? response.status}): ${body?.message || response.statusText}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const token = body?.data?.access_token;
|
|
101
|
+
if (!token) {
|
|
102
|
+
// A 200 with code 0 and no token means the response shape moved —
|
|
103
|
+
// persisting `undefined` would poison the brand .env
|
|
104
|
+
throw new Error(`TikTok token exchange returned no access_token (${JSON.stringify(body?.data)})`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return token;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class TikTokBusinessAPI {
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
this.accessToken = options.accessToken || process.env.TIKTOK_ACCESS_TOKEN;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async makeRequest(endpoint, options = {}) {
|
|
116
|
+
const response = await fetch(`${API_BASE}${endpoint}`, {
|
|
117
|
+
...options,
|
|
118
|
+
headers: {
|
|
119
|
+
'Access-Token': this.accessToken,
|
|
120
|
+
'Content-Type': 'application/json',
|
|
121
|
+
...options.headers,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const text = await response.text();
|
|
126
|
+
const body = text ? JSON.parse(text) : null;
|
|
127
|
+
|
|
128
|
+
// TikTok answers HTTP 200 with a non-zero `code` for real failures, so
|
|
129
|
+
// the status alone never proves success
|
|
130
|
+
if (!response.ok || (body && body.code !== 0)) {
|
|
131
|
+
throw new Error(`TikTok API error (${body?.code ?? response.status}): ${body?.message || response.statusText}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return body?.data || null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Pixels on the advertiser account, normalized to { id, name }. */
|
|
138
|
+
async listPixels(advertiserId) {
|
|
139
|
+
const data = await this.makeRequest(`/pixel/list/?advertiser_id=${encodeURIComponent(advertiserId)}`);
|
|
140
|
+
return (data?.pixels || []).map((pixel) => ({ id: pixel.pixel_code, name: pixel.pixel_name }));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Create a pixel on the advertiser account. */
|
|
144
|
+
async createPixel(advertiserId, name) {
|
|
145
|
+
const created = await this.makeRequest('/pixel/create/', {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
advertiser_id: String(advertiserId),
|
|
149
|
+
pixel_name: name,
|
|
150
|
+
pixel_mode: 'STANDARD_MODE',
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
153
|
+
return { id: created?.pixel_code, name: created?.pixel_name || name };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { TikTokBusinessAPI, exchangeAuthCode, TIKTOK_APP_URL, TIKTOK_APPS_URL, TIKTOK_AUTH_BASE, isAuthUrlFor, parseAuthCode };
|