thevoidforge 21.0.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/dist/scripts/vault-read.d.ts +11 -0
- package/dist/scripts/vault-read.js +89 -0
- package/dist/scripts/voidforge.d.ts +20 -0
- package/dist/scripts/voidforge.js +404 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/wizard/api/auth.d.ts +5 -0
- package/dist/wizard/api/auth.js +133 -0
- package/dist/wizard/api/blueprint.d.ts +45 -0
- package/dist/wizard/api/blueprint.js +184 -0
- package/dist/wizard/api/cloud-providers.d.ts +16 -0
- package/dist/wizard/api/cloud-providers.js +363 -0
- package/dist/wizard/api/credentials.d.ts +1 -0
- package/dist/wizard/api/credentials.js +258 -0
- package/dist/wizard/api/danger-room.d.ts +18 -0
- package/dist/wizard/api/danger-room.js +401 -0
- package/dist/wizard/api/deploy.d.ts +4 -0
- package/dist/wizard/api/deploy.js +164 -0
- package/dist/wizard/api/prd.d.ts +1 -0
- package/dist/wizard/api/prd.js +363 -0
- package/dist/wizard/api/project.d.ts +1 -0
- package/dist/wizard/api/project.js +239 -0
- package/dist/wizard/api/projects.d.ts +6 -0
- package/dist/wizard/api/projects.js +648 -0
- package/dist/wizard/api/provision.d.ts +4 -0
- package/dist/wizard/api/provision.js +535 -0
- package/dist/wizard/api/terminal.d.ts +25 -0
- package/dist/wizard/api/terminal.js +241 -0
- package/dist/wizard/api/users.d.ts +6 -0
- package/dist/wizard/api/users.js +244 -0
- package/dist/wizard/api/war-room.d.ts +14 -0
- package/dist/wizard/api/war-room.js +45 -0
- package/dist/wizard/lib/ad-platform-core.d.ts +6 -0
- package/dist/wizard/lib/ad-platform-core.js +1 -0
- package/dist/wizard/lib/adapters/index.d.ts +52 -0
- package/dist/wizard/lib/adapters/index.js +38 -0
- package/dist/wizard/lib/adapters/sandbox-bank.d.ts +17 -0
- package/dist/wizard/lib/adapters/sandbox-bank.js +77 -0
- package/dist/wizard/lib/adapters/sandbox.d.ts +39 -0
- package/dist/wizard/lib/adapters/sandbox.js +174 -0
- package/dist/wizard/lib/adapters/stripe.d.ts +19 -0
- package/dist/wizard/lib/adapters/stripe.js +143 -0
- package/dist/wizard/lib/adapters/types.d.ts +9 -0
- package/dist/wizard/lib/adapters/types.js +10 -0
- package/dist/wizard/lib/agent-memory.d.ts +36 -0
- package/dist/wizard/lib/agent-memory.js +114 -0
- package/dist/wizard/lib/anomaly-detection.d.ts +59 -0
- package/dist/wizard/lib/anomaly-detection.js +122 -0
- package/dist/wizard/lib/anthropic.d.ts +21 -0
- package/dist/wizard/lib/anthropic.js +105 -0
- package/dist/wizard/lib/asset-scanner.d.ts +23 -0
- package/dist/wizard/lib/asset-scanner.js +107 -0
- package/dist/wizard/lib/audit-log.d.ts +23 -0
- package/dist/wizard/lib/audit-log.js +70 -0
- package/dist/wizard/lib/autonomy-controller.d.ts +76 -0
- package/dist/wizard/lib/autonomy-controller.js +183 -0
- package/dist/wizard/lib/body-parser.d.ts +2 -0
- package/dist/wizard/lib/body-parser.js +36 -0
- package/dist/wizard/lib/build-analytics.d.ts +39 -0
- package/dist/wizard/lib/build-analytics.js +91 -0
- package/dist/wizard/lib/build-step.d.ts +21 -0
- package/dist/wizard/lib/build-step.js +104 -0
- package/dist/wizard/lib/campaign-proposer.d.ts +39 -0
- package/dist/wizard/lib/campaign-proposer.js +180 -0
- package/dist/wizard/lib/campaign-state-machine.d.ts +63 -0
- package/dist/wizard/lib/campaign-state-machine.js +114 -0
- package/dist/wizard/lib/ci-generator.d.ts +14 -0
- package/dist/wizard/lib/ci-generator.js +187 -0
- package/dist/wizard/lib/claude-merge.d.ts +38 -0
- package/dist/wizard/lib/claude-merge.js +115 -0
- package/dist/wizard/lib/codegen/erd-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/erd-gen.js +98 -0
- package/dist/wizard/lib/codegen/integrations.d.ts +18 -0
- package/dist/wizard/lib/codegen/integrations.js +189 -0
- package/dist/wizard/lib/codegen/openapi-gen.d.ts +15 -0
- package/dist/wizard/lib/codegen/openapi-gen.js +79 -0
- package/dist/wizard/lib/codegen/prisma-types.d.ts +15 -0
- package/dist/wizard/lib/codegen/prisma-types.js +44 -0
- package/dist/wizard/lib/codegen/seed-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/seed-gen.js +128 -0
- package/dist/wizard/lib/compliance.d.ts +51 -0
- package/dist/wizard/lib/compliance.js +112 -0
- package/dist/wizard/lib/correlation-engine.d.ts +59 -0
- package/dist/wizard/lib/correlation-engine.js +151 -0
- package/dist/wizard/lib/cost-estimator.d.ts +22 -0
- package/dist/wizard/lib/cost-estimator.js +72 -0
- package/dist/wizard/lib/cost-tracker.d.ts +27 -0
- package/dist/wizard/lib/cost-tracker.js +37 -0
- package/dist/wizard/lib/daemon-aggregator.d.ts +71 -0
- package/dist/wizard/lib/daemon-aggregator.js +204 -0
- package/dist/wizard/lib/daemon-core.d.ts +6 -0
- package/dist/wizard/lib/daemon-core.js +5 -0
- package/dist/wizard/lib/dashboard-data.d.ts +132 -0
- package/dist/wizard/lib/dashboard-data.js +336 -0
- package/dist/wizard/lib/dashboard-ws.d.ts +25 -0
- package/dist/wizard/lib/dashboard-ws.js +91 -0
- package/dist/wizard/lib/deep-current.d.ts +77 -0
- package/dist/wizard/lib/deep-current.js +234 -0
- package/dist/wizard/lib/deploy-coordinator.d.ts +40 -0
- package/dist/wizard/lib/deploy-coordinator.js +86 -0
- package/dist/wizard/lib/deploy-log.d.ts +28 -0
- package/dist/wizard/lib/deploy-log.js +52 -0
- package/dist/wizard/lib/desktop-notify.d.ts +27 -0
- package/dist/wizard/lib/desktop-notify.js +98 -0
- package/dist/wizard/lib/dns/cloudflare-dns.d.ts +35 -0
- package/dist/wizard/lib/dns/cloudflare-dns.js +216 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.d.ts +31 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.js +148 -0
- package/dist/wizard/lib/dns/types.d.ts +22 -0
- package/dist/wizard/lib/dns/types.js +4 -0
- package/dist/wizard/lib/document-discovery.d.ts +33 -0
- package/dist/wizard/lib/document-discovery.js +145 -0
- package/dist/wizard/lib/env-validator.d.ts +14 -0
- package/dist/wizard/lib/env-validator.js +205 -0
- package/dist/wizard/lib/env-writer.d.ts +13 -0
- package/dist/wizard/lib/env-writer.js +26 -0
- package/dist/wizard/lib/exec.d.ts +30 -0
- package/dist/wizard/lib/exec.js +52 -0
- package/dist/wizard/lib/experiment.d.ts +70 -0
- package/dist/wizard/lib/experiment.js +169 -0
- package/dist/wizard/lib/extensions.d.ts +20 -0
- package/dist/wizard/lib/extensions.js +183 -0
- package/dist/wizard/lib/financial/adapter-factory.d.ts +47 -0
- package/dist/wizard/lib/financial/adapter-factory.js +225 -0
- package/dist/wizard/lib/financial/billing/base.d.ts +6 -0
- package/dist/wizard/lib/financial/billing/base.js +1 -0
- package/dist/wizard/lib/financial/billing/google-billing.d.ts +56 -0
- package/dist/wizard/lib/financial/billing/google-billing.js +298 -0
- package/dist/wizard/lib/financial/billing/meta-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/meta-billing.js +243 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.js +260 -0
- package/dist/wizard/lib/financial/campaign/base.d.ts +13 -0
- package/dist/wizard/lib/financial/campaign/base.js +1 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.d.ts +42 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.js +388 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.d.ts +41 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.js +311 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.d.ts +45 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.js +261 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.d.ts +40 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.js +350 -0
- package/dist/wizard/lib/financial/funding-auto.d.ts +44 -0
- package/dist/wizard/lib/financial/funding-auto.js +52 -0
- package/dist/wizard/lib/financial/funding-policy.d.ts +60 -0
- package/dist/wizard/lib/financial/funding-policy.js +179 -0
- package/dist/wizard/lib/financial/platform-planner.d.ts +47 -0
- package/dist/wizard/lib/financial/platform-planner.js +134 -0
- package/dist/wizard/lib/financial/reconciliation-engine.d.ts +78 -0
- package/dist/wizard/lib/financial/reconciliation-engine.js +193 -0
- package/dist/wizard/lib/financial/registry.d.ts +22 -0
- package/dist/wizard/lib/financial/registry.js +26 -0
- package/dist/wizard/lib/financial/reporting.d.ts +96 -0
- package/dist/wizard/lib/financial/reporting.js +198 -0
- package/dist/wizard/lib/financial/stablecoin/base.d.ts +6 -0
- package/dist/wizard/lib/financial/stablecoin/base.js +1 -0
- package/dist/wizard/lib/financial/stablecoin/circle.d.ts +54 -0
- package/dist/wizard/lib/financial/stablecoin/circle.js +367 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.d.ts +24 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.js +171 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.d.ts +47 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.js +202 -0
- package/dist/wizard/lib/financial/treasury-planner.d.ts +52 -0
- package/dist/wizard/lib/financial/treasury-planner.js +128 -0
- package/dist/wizard/lib/financial-core.d.ts +6 -0
- package/dist/wizard/lib/financial-core.js +5 -0
- package/dist/wizard/lib/financial-vault.d.ts +34 -0
- package/dist/wizard/lib/financial-vault.js +199 -0
- package/dist/wizard/lib/frontmatter.d.ts +30 -0
- package/dist/wizard/lib/frontmatter.js +96 -0
- package/dist/wizard/lib/gap-analysis.d.ts +37 -0
- package/dist/wizard/lib/gap-analysis.js +218 -0
- package/dist/wizard/lib/github.d.ts +22 -0
- package/dist/wizard/lib/github.js +261 -0
- package/dist/wizard/lib/headless-deploy.d.ts +14 -0
- package/dist/wizard/lib/headless-deploy.js +452 -0
- package/dist/wizard/lib/health-monitor.d.ts +15 -0
- package/dist/wizard/lib/health-monitor.js +91 -0
- package/dist/wizard/lib/health-poller.d.ts +9 -0
- package/dist/wizard/lib/health-poller.js +123 -0
- package/dist/wizard/lib/heartbeat.d.ts +15 -0
- package/dist/wizard/lib/heartbeat.js +827 -0
- package/dist/wizard/lib/http-helpers.d.ts +9 -0
- package/dist/wizard/lib/http-helpers.js +24 -0
- package/dist/wizard/lib/image-gen.d.ts +56 -0
- package/dist/wizard/lib/image-gen.js +159 -0
- package/dist/wizard/lib/instance-sizing.d.ts +26 -0
- package/dist/wizard/lib/instance-sizing.js +51 -0
- package/dist/wizard/lib/kongo/analytics.d.ts +29 -0
- package/dist/wizard/lib/kongo/analytics.js +179 -0
- package/dist/wizard/lib/kongo/campaigns.d.ts +52 -0
- package/dist/wizard/lib/kongo/campaigns.js +91 -0
- package/dist/wizard/lib/kongo/client.d.ts +58 -0
- package/dist/wizard/lib/kongo/client.js +221 -0
- package/dist/wizard/lib/kongo/jobs.d.ts +57 -0
- package/dist/wizard/lib/kongo/jobs.js +122 -0
- package/dist/wizard/lib/kongo/pages.d.ts +60 -0
- package/dist/wizard/lib/kongo/pages.js +150 -0
- package/dist/wizard/lib/kongo/provisioner.d.ts +64 -0
- package/dist/wizard/lib/kongo/provisioner.js +116 -0
- package/dist/wizard/lib/kongo/seed.d.ts +49 -0
- package/dist/wizard/lib/kongo/seed.js +237 -0
- package/dist/wizard/lib/kongo/types.d.ts +323 -0
- package/dist/wizard/lib/kongo/types.js +11 -0
- package/dist/wizard/lib/kongo/variants.d.ts +57 -0
- package/dist/wizard/lib/kongo/variants.js +88 -0
- package/dist/wizard/lib/kongo/webhooks.d.ts +41 -0
- package/dist/wizard/lib/kongo/webhooks.js +112 -0
- package/dist/wizard/lib/marker.d.ts +28 -0
- package/dist/wizard/lib/marker.js +79 -0
- package/dist/wizard/lib/migrator.d.ts +35 -0
- package/dist/wizard/lib/migrator.js +190 -0
- package/dist/wizard/lib/natural-language-deploy.d.ts +30 -0
- package/dist/wizard/lib/natural-language-deploy.js +186 -0
- package/dist/wizard/lib/network.d.ts +22 -0
- package/dist/wizard/lib/network.js +72 -0
- package/dist/wizard/lib/oauth-core.d.ts +6 -0
- package/dist/wizard/lib/oauth-core.js +5 -0
- package/dist/wizard/lib/open-browser.d.ts +1 -0
- package/dist/wizard/lib/open-browser.js +26 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.d.ts +209 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.js +269 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.d.ts +200 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.js +212 -0
- package/dist/wizard/lib/patterns/daemon-process.d.ts +88 -0
- package/dist/wizard/lib/patterns/daemon-process.js +271 -0
- package/dist/wizard/lib/patterns/financial-transaction.d.ts +161 -0
- package/dist/wizard/lib/patterns/financial-transaction.js +132 -0
- package/dist/wizard/lib/patterns/funding-plan.d.ts +136 -0
- package/dist/wizard/lib/patterns/funding-plan.js +200 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +94 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +139 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.d.ts +67 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.js +216 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.d.ts +96 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.js +182 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.d.ts +218 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.js +264 -0
- package/dist/wizard/lib/prd-validator.d.ts +39 -0
- package/dist/wizard/lib/prd-validator.js +137 -0
- package/dist/wizard/lib/project-init.d.ts +24 -0
- package/dist/wizard/lib/project-init.js +193 -0
- package/dist/wizard/lib/project-registry.d.ts +86 -0
- package/dist/wizard/lib/project-registry.js +359 -0
- package/dist/wizard/lib/provision-manifest.d.ts +44 -0
- package/dist/wizard/lib/provision-manifest.js +164 -0
- package/dist/wizard/lib/provisioner-registry.d.ts +15 -0
- package/dist/wizard/lib/provisioner-registry.js +34 -0
- package/dist/wizard/lib/provisioners/aws-vps.d.ts +6 -0
- package/dist/wizard/lib/provisioners/aws-vps.js +643 -0
- package/dist/wizard/lib/provisioners/cloudflare.d.ts +6 -0
- package/dist/wizard/lib/provisioners/cloudflare.js +300 -0
- package/dist/wizard/lib/provisioners/docker.d.ts +6 -0
- package/dist/wizard/lib/provisioners/docker.js +75 -0
- package/dist/wizard/lib/provisioners/http-client.d.ts +20 -0
- package/dist/wizard/lib/provisioners/http-client.js +79 -0
- package/dist/wizard/lib/provisioners/railway.d.ts +6 -0
- package/dist/wizard/lib/provisioners/railway.js +413 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.js +54 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.js +112 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.d.ts +11 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.js +91 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.d.ts +5 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.js +185 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.js +36 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.d.ts +14 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.js +202 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.js +67 -0
- package/dist/wizard/lib/provisioners/self-deploy.d.ts +41 -0
- package/dist/wizard/lib/provisioners/self-deploy.js +185 -0
- package/dist/wizard/lib/provisioners/static-s3.d.ts +6 -0
- package/dist/wizard/lib/provisioners/static-s3.js +235 -0
- package/dist/wizard/lib/provisioners/types.d.ts +40 -0
- package/dist/wizard/lib/provisioners/types.js +4 -0
- package/dist/wizard/lib/provisioners/vercel.d.ts +6 -0
- package/dist/wizard/lib/provisioners/vercel.js +287 -0
- package/dist/wizard/lib/pty-manager.d.ts +42 -0
- package/dist/wizard/lib/pty-manager.js +231 -0
- package/dist/wizard/lib/rate-limiter-core.d.ts +5 -0
- package/dist/wizard/lib/rate-limiter-core.js +5 -0
- package/dist/wizard/lib/reconciliation.d.ts +43 -0
- package/dist/wizard/lib/reconciliation.js +173 -0
- package/dist/wizard/lib/revenue-types.d.ts +5 -0
- package/dist/wizard/lib/revenue-types.js +1 -0
- package/dist/wizard/lib/route-optimizer.d.ts +28 -0
- package/dist/wizard/lib/route-optimizer.js +93 -0
- package/dist/wizard/lib/s3-deploy.d.ts +19 -0
- package/dist/wizard/lib/s3-deploy.js +156 -0
- package/dist/wizard/lib/safety-tiers.d.ts +76 -0
- package/dist/wizard/lib/safety-tiers.js +134 -0
- package/dist/wizard/lib/sentry-generator.d.ts +15 -0
- package/dist/wizard/lib/sentry-generator.js +116 -0
- package/dist/wizard/lib/server-config.d.ts +13 -0
- package/dist/wizard/lib/server-config.js +23 -0
- package/dist/wizard/lib/service-install.d.ts +18 -0
- package/dist/wizard/lib/service-install.js +182 -0
- package/dist/wizard/lib/site-scanner.d.ts +80 -0
- package/dist/wizard/lib/site-scanner.js +262 -0
- package/dist/wizard/lib/ssh-deploy.d.ts +25 -0
- package/dist/wizard/lib/ssh-deploy.js +225 -0
- package/dist/wizard/lib/templates.d.ts +24 -0
- package/dist/wizard/lib/templates.js +219 -0
- package/dist/wizard/lib/totp.d.ts +35 -0
- package/dist/wizard/lib/totp.js +276 -0
- package/dist/wizard/lib/tower-auth.d.ts +43 -0
- package/dist/wizard/lib/tower-auth.js +352 -0
- package/dist/wizard/lib/tower-rate-limit.d.ts +14 -0
- package/dist/wizard/lib/tower-rate-limit.js +61 -0
- package/dist/wizard/lib/tower-session.d.ts +28 -0
- package/dist/wizard/lib/tower-session.js +119 -0
- package/dist/wizard/lib/treasury-backup.d.ts +23 -0
- package/dist/wizard/lib/treasury-backup.js +126 -0
- package/dist/wizard/lib/treasury-heartbeat.d.ts +82 -0
- package/dist/wizard/lib/treasury-heartbeat.js +1104 -0
- package/dist/wizard/lib/updater.d.ts +29 -0
- package/dist/wizard/lib/updater.js +190 -0
- package/dist/wizard/lib/user-manager.d.ts +39 -0
- package/dist/wizard/lib/user-manager.js +182 -0
- package/dist/wizard/lib/vault.d.ts +26 -0
- package/dist/wizard/lib/vault.js +161 -0
- package/dist/wizard/router.d.ts +5 -0
- package/dist/wizard/router.js +15 -0
- package/dist/wizard/server.d.ts +18 -0
- package/dist/wizard/server.js +436 -0
- package/package.json +59 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP helpers — used across all API modules.
|
|
3
|
+
* Extracted to eliminate 13 duplicate sendJson() implementations.
|
|
4
|
+
*/
|
|
5
|
+
import type { ServerResponse } from 'node:http';
|
|
6
|
+
/** Send a JSON response with the given status code. */
|
|
7
|
+
export declare function sendJson(res: ServerResponse, status: number, data: unknown, noCache?: boolean): void;
|
|
8
|
+
/** Read a file, returning null if it doesn't exist or fails. */
|
|
9
|
+
export declare function readFileOrNull(path: string): Promise<string | null>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP helpers — used across all API modules.
|
|
3
|
+
* Extracted to eliminate 13 duplicate sendJson() implementations.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
/** Send a JSON response with the given status code. */
|
|
7
|
+
export function sendJson(res, status, data, noCache = false) {
|
|
8
|
+
const headers = { 'Content-Type': 'application/json; charset=utf-8' };
|
|
9
|
+
if (noCache) {
|
|
10
|
+
headers['Cache-Control'] = 'no-store';
|
|
11
|
+
headers['Pragma'] = 'no-cache';
|
|
12
|
+
}
|
|
13
|
+
res.writeHead(status, headers);
|
|
14
|
+
res.end(JSON.stringify(data));
|
|
15
|
+
}
|
|
16
|
+
/** Read a file, returning null if it doesn't exist or fails. */
|
|
17
|
+
export async function readFileOrNull(path) {
|
|
18
|
+
try {
|
|
19
|
+
return await readFile(path, 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image generation provider abstraction — Celebrimbor's forge tools.
|
|
3
|
+
* Default: OpenAI (gpt-image-1). Extensible to other providers.
|
|
4
|
+
* Uses the same vault system as other VoidForge credentials.
|
|
5
|
+
*/
|
|
6
|
+
import type { ProvisionEmitter } from './provisioners/types.js';
|
|
7
|
+
export interface ImageGenerationOptions {
|
|
8
|
+
prompt: string;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
model?: string;
|
|
12
|
+
quality?: 'low' | 'medium' | 'high';
|
|
13
|
+
}
|
|
14
|
+
export interface GeneratedAsset {
|
|
15
|
+
name: string;
|
|
16
|
+
filename: string;
|
|
17
|
+
prompt: string;
|
|
18
|
+
size: string;
|
|
19
|
+
generatedAt: string;
|
|
20
|
+
hash: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AssetManifest {
|
|
23
|
+
generated: string;
|
|
24
|
+
model: string;
|
|
25
|
+
style: string;
|
|
26
|
+
assets: GeneratedAsset[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generate an image via OpenAI's API.
|
|
30
|
+
* Returns the raw image bytes as a Buffer.
|
|
31
|
+
*/
|
|
32
|
+
export declare function generateImage(apiKey: string, options: ImageGenerationOptions, emit: ProvisionEmitter): Promise<Buffer | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Validate an OpenAI API key by making a lightweight models list request.
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateOpenAIKey(apiKey: string): Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Estimate the cost of generating N images.
|
|
39
|
+
*/
|
|
40
|
+
export declare function estimateImageCost(count: number, model?: string): number;
|
|
41
|
+
/**
|
|
42
|
+
* Read the asset manifest from disk.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readManifest(imagesDir: string): Promise<AssetManifest | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Write the asset manifest to disk.
|
|
47
|
+
*/
|
|
48
|
+
export declare function writeManifest(imagesDir: string, manifest: AssetManifest): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Save a generated image to disk and update the manifest.
|
|
51
|
+
*/
|
|
52
|
+
export declare function saveGeneratedImage(imagesDir: string, category: string, name: string, imageBuffer: Buffer, prompt: string, size: string, manifest: AssetManifest): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Check if an asset already exists on disk.
|
|
55
|
+
*/
|
|
56
|
+
export declare function assetExists(imagesDir: string, category: string, name: string): boolean;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image generation provider abstraction — Celebrimbor's forge tools.
|
|
3
|
+
* Default: OpenAI (gpt-image-1). Extensible to other providers.
|
|
4
|
+
* Uses the same vault system as other VoidForge credentials.
|
|
5
|
+
*/
|
|
6
|
+
import { writeFile, readFile, mkdir } from 'node:fs/promises';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { httpsPost, httpsGet, safeJsonParse } from './provisioners/http-client.js';
|
|
11
|
+
// ── OpenAI Provider ──────────────────────────────────────
|
|
12
|
+
const OPENAI_API = 'api.openai.com';
|
|
13
|
+
/**
|
|
14
|
+
* Generate an image via OpenAI's API.
|
|
15
|
+
* Returns the raw image bytes as a Buffer.
|
|
16
|
+
*/
|
|
17
|
+
export async function generateImage(apiKey, options, emit) {
|
|
18
|
+
const model = options.model || 'gpt-image-1';
|
|
19
|
+
const size = `${options.width}x${options.height}`;
|
|
20
|
+
// OpenAI only supports specific sizes — map to nearest and warn
|
|
21
|
+
const validSizes = ['1024x1024', '1792x1024', '1024x1792'];
|
|
22
|
+
const actualSize = validSizes.includes(size) ? size : '1024x1024';
|
|
23
|
+
if (actualSize !== size) {
|
|
24
|
+
emit({ step: 'image-gen', status: 'started', message: `Requested ${size} → using ${actualSize} (API constraint)` });
|
|
25
|
+
}
|
|
26
|
+
const body = JSON.stringify({
|
|
27
|
+
model,
|
|
28
|
+
prompt: options.prompt,
|
|
29
|
+
n: 1,
|
|
30
|
+
size: actualSize,
|
|
31
|
+
quality: options.quality || 'medium',
|
|
32
|
+
response_format: 'b64_json',
|
|
33
|
+
});
|
|
34
|
+
// Retry logic: 3 attempts with exponential backoff (1s, 3s, 9s)
|
|
35
|
+
// DALL-E 3 returns 500 errors on ~15% of requests (field report #1)
|
|
36
|
+
const MAX_RETRIES = 3;
|
|
37
|
+
const BACKOFF_BASE_MS = 1000;
|
|
38
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
39
|
+
try {
|
|
40
|
+
const res = await httpsPost(OPENAI_API, '/v1/images/generations', {
|
|
41
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
}, body, 120_000); // 2 min timeout for image generation
|
|
44
|
+
if (res.status === 500 || res.status === 502 || res.status === 503) {
|
|
45
|
+
// Server error — retry with backoff
|
|
46
|
+
if (attempt < MAX_RETRIES) {
|
|
47
|
+
const delay = BACKOFF_BASE_MS * Math.pow(3, attempt - 1);
|
|
48
|
+
emit({ step: 'image-gen', status: 'started', message: `Server error (${res.status}), retrying in ${delay / 1000}s (attempt ${attempt}/${MAX_RETRIES})` });
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
emit({ step: 'image-gen', status: 'error', message: `Server error (${res.status}) after ${MAX_RETRIES} attempts` });
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (res.status !== 200) {
|
|
56
|
+
const errData = safeJsonParse(res.body);
|
|
57
|
+
const errMsg = errData?.error?.message || `API returned ${res.status}`;
|
|
58
|
+
emit({ step: 'image-gen', status: 'error', message: `Generation failed: ${errMsg}` });
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const data = safeJsonParse(res.body);
|
|
62
|
+
const b64 = data?.data?.[0]?.b64_json;
|
|
63
|
+
if (!b64) {
|
|
64
|
+
emit({ step: 'image-gen', status: 'error', message: 'No image data in API response' });
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (attempt > 1) {
|
|
68
|
+
emit({ step: 'image-gen', status: 'done', message: `Succeeded on attempt ${attempt}` });
|
|
69
|
+
}
|
|
70
|
+
return Buffer.from(b64, 'base64');
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (attempt < MAX_RETRIES) {
|
|
74
|
+
const delay = BACKOFF_BASE_MS * Math.pow(3, attempt - 1);
|
|
75
|
+
emit({ step: 'image-gen', status: 'started', message: `Request failed, retrying in ${delay / 1000}s (attempt ${attempt}/${MAX_RETRIES})`, detail: err.message });
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
emit({ step: 'image-gen', status: 'error', message: `Image generation failed after ${MAX_RETRIES} attempts`, detail: err.message });
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validate an OpenAI API key by making a lightweight models list request.
|
|
87
|
+
*/
|
|
88
|
+
export async function validateOpenAIKey(apiKey) {
|
|
89
|
+
try {
|
|
90
|
+
const res = await httpsGet(OPENAI_API, '/v1/models', {
|
|
91
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
92
|
+
}, 10_000);
|
|
93
|
+
return res.status === 200;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Estimate the cost of generating N images.
|
|
101
|
+
*/
|
|
102
|
+
export function estimateImageCost(count, model = 'gpt-image-1') {
|
|
103
|
+
const costPerImage = {
|
|
104
|
+
'gpt-image-1': 0.04,
|
|
105
|
+
'dall-e-3': 0.08,
|
|
106
|
+
};
|
|
107
|
+
return count * (costPerImage[model] || 0.04);
|
|
108
|
+
}
|
|
109
|
+
// ── Asset Manifest ──────────────────────────────────────
|
|
110
|
+
const MANIFEST_FILENAME = 'manifest.json';
|
|
111
|
+
/**
|
|
112
|
+
* Read the asset manifest from disk.
|
|
113
|
+
*/
|
|
114
|
+
export async function readManifest(imagesDir) {
|
|
115
|
+
const manifestPath = join(imagesDir, MANIFEST_FILENAME);
|
|
116
|
+
try {
|
|
117
|
+
const content = await readFile(manifestPath, 'utf-8');
|
|
118
|
+
return JSON.parse(content);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Write the asset manifest to disk.
|
|
126
|
+
*/
|
|
127
|
+
export async function writeManifest(imagesDir, manifest) {
|
|
128
|
+
await mkdir(imagesDir, { recursive: true });
|
|
129
|
+
await writeFile(join(imagesDir, MANIFEST_FILENAME), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Save a generated image to disk and update the manifest.
|
|
133
|
+
*/
|
|
134
|
+
export async function saveGeneratedImage(imagesDir, category, name, imageBuffer, prompt, size, manifest) {
|
|
135
|
+
const categoryDir = join(imagesDir, category);
|
|
136
|
+
await mkdir(categoryDir, { recursive: true });
|
|
137
|
+
const filename = `${category}/${name}.png`;
|
|
138
|
+
const filepath = join(imagesDir, filename);
|
|
139
|
+
await writeFile(filepath, imageBuffer);
|
|
140
|
+
const hash = createHash('sha256').update(imageBuffer).digest('hex');
|
|
141
|
+
// Remove any existing entry with the same filename (dedup for --regen)
|
|
142
|
+
manifest.assets = manifest.assets.filter(a => a.filename !== filename);
|
|
143
|
+
manifest.assets.push({
|
|
144
|
+
name,
|
|
145
|
+
filename,
|
|
146
|
+
prompt,
|
|
147
|
+
size,
|
|
148
|
+
generatedAt: new Date().toISOString(),
|
|
149
|
+
hash: `sha256:${hash}`,
|
|
150
|
+
});
|
|
151
|
+
await writeManifest(imagesDir, manifest);
|
|
152
|
+
return filepath;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check if an asset already exists on disk.
|
|
156
|
+
*/
|
|
157
|
+
export function assetExists(imagesDir, category, name) {
|
|
158
|
+
return existsSync(join(imagesDir, category, `${name}.png`));
|
|
159
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EC2 instance type recommendation based on PRD scope signals.
|
|
3
|
+
* Pure function — no side effects, no AWS calls.
|
|
4
|
+
*/
|
|
5
|
+
export declare const VALID_INSTANCE_TYPES: readonly ["t3.micro", "t3.small", "t3.medium", "t3.large"];
|
|
6
|
+
export type InstanceType = typeof VALID_INSTANCE_TYPES[number];
|
|
7
|
+
export interface SizingInput {
|
|
8
|
+
type?: string;
|
|
9
|
+
framework?: string;
|
|
10
|
+
database?: string;
|
|
11
|
+
cache?: string;
|
|
12
|
+
workers?: string;
|
|
13
|
+
payments?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Recommend an EC2 instance type based on PRD signals.
|
|
17
|
+
* Never auto-recommends above t3.medium (cost protection).
|
|
18
|
+
*/
|
|
19
|
+
export declare function recommendInstanceType(input: SizingInput): InstanceType;
|
|
20
|
+
/** Map EC2 instance type to matching RDS instance class. */
|
|
21
|
+
export declare function rdsInstanceClass(instanceType: InstanceType): string;
|
|
22
|
+
/** Map EC2 instance type to matching ElastiCache node type. */
|
|
23
|
+
export declare function cacheNodeType(instanceType: InstanceType): string;
|
|
24
|
+
/** Recommended swap size in GB based on instance memory. */
|
|
25
|
+
export declare function swapSizeGb(instanceType: InstanceType): number;
|
|
26
|
+
export declare function isValidInstanceType(value: string): value is InstanceType;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EC2 instance type recommendation based on PRD scope signals.
|
|
3
|
+
* Pure function — no side effects, no AWS calls.
|
|
4
|
+
*/
|
|
5
|
+
export const VALID_INSTANCE_TYPES = ['t3.micro', 't3.small', 't3.medium', 't3.large'];
|
|
6
|
+
/**
|
|
7
|
+
* Recommend an EC2 instance type based on PRD signals.
|
|
8
|
+
* Never auto-recommends above t3.medium (cost protection).
|
|
9
|
+
*/
|
|
10
|
+
export function recommendInstanceType(input) {
|
|
11
|
+
// Static sites and prototypes always get micro
|
|
12
|
+
if (input.type === 'static-site' || input.type === 'prototype') {
|
|
13
|
+
return 't3.micro';
|
|
14
|
+
}
|
|
15
|
+
let score = 0;
|
|
16
|
+
if (input.database === 'postgres' || input.database === 'mysql')
|
|
17
|
+
score++;
|
|
18
|
+
if (input.cache === 'redis')
|
|
19
|
+
score++;
|
|
20
|
+
if (input.workers === 'yes')
|
|
21
|
+
score++;
|
|
22
|
+
if (input.payments === 'stripe' || input.payments === 'lemonsqueezy')
|
|
23
|
+
score++;
|
|
24
|
+
if (input.framework === 'next.js')
|
|
25
|
+
score++; // SSR is memory-hungry
|
|
26
|
+
if (score >= 4)
|
|
27
|
+
return 't3.medium';
|
|
28
|
+
if (score >= 2)
|
|
29
|
+
return 't3.small';
|
|
30
|
+
return 't3.micro';
|
|
31
|
+
}
|
|
32
|
+
/** Map EC2 instance type to matching RDS instance class. */
|
|
33
|
+
export function rdsInstanceClass(instanceType) {
|
|
34
|
+
return `db.${instanceType}`;
|
|
35
|
+
}
|
|
36
|
+
/** Map EC2 instance type to matching ElastiCache node type. */
|
|
37
|
+
export function cacheNodeType(instanceType) {
|
|
38
|
+
return `cache.${instanceType}`;
|
|
39
|
+
}
|
|
40
|
+
/** Recommended swap size in GB based on instance memory. */
|
|
41
|
+
export function swapSizeGb(instanceType) {
|
|
42
|
+
switch (instanceType) {
|
|
43
|
+
case 't3.micro': return 2;
|
|
44
|
+
case 't3.small': return 2;
|
|
45
|
+
case 't3.medium': return 1;
|
|
46
|
+
case 't3.large': return 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function isValidInstanceType(value) {
|
|
50
|
+
return VALID_INSTANCE_TYPES.includes(value);
|
|
51
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kongo Analytics — Campaign analytics and computed growth signal.
|
|
3
|
+
*
|
|
4
|
+
* Kongo doesn't have a dedicated growth-signal endpoint, so we compute it
|
|
5
|
+
* client-side from the campaign analytics data using statistical analysis.
|
|
6
|
+
*
|
|
7
|
+
* Real Kongo API endpoint:
|
|
8
|
+
* - GET /engine/campaigns/:campaignId/analytics — per-variant breakdown
|
|
9
|
+
*
|
|
10
|
+
* PRD Reference: PRD-kongo-integration.md §4.3
|
|
11
|
+
*/
|
|
12
|
+
import { KongoClient } from './client.js';
|
|
13
|
+
import type { CampaignAnalytics, AnalyticsPeriod, ComputedGrowthSignal } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Get campaign analytics with per-variant, per-source, and daily breakdown.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getCampaignAnalytics(client: KongoClient, campaignId: string, period?: AnalyticsPeriod): Promise<CampaignAnalytics>;
|
|
18
|
+
/**
|
|
19
|
+
* Compute a growth signal from campaign analytics.
|
|
20
|
+
*
|
|
21
|
+
* This replaces the hypothetical /growth-signal endpoint from the PRD.
|
|
22
|
+
* Uses two-proportion z-test to determine if any variant significantly
|
|
23
|
+
* outperforms the control (first variant by order).
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeGrowthSignal(campaignId: string, analytics: CampaignAnalytics): ComputedGrowthSignal;
|
|
26
|
+
/**
|
|
27
|
+
* Fetch analytics and compute growth signal in one call.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getGrowthSignal(client: KongoClient, campaignId: string, period?: AnalyticsPeriod): Promise<ComputedGrowthSignal>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kongo Analytics — Campaign analytics and computed growth signal.
|
|
3
|
+
*
|
|
4
|
+
* Kongo doesn't have a dedicated growth-signal endpoint, so we compute it
|
|
5
|
+
* client-side from the campaign analytics data using statistical analysis.
|
|
6
|
+
*
|
|
7
|
+
* Real Kongo API endpoint:
|
|
8
|
+
* - GET /engine/campaigns/:campaignId/analytics — per-variant breakdown
|
|
9
|
+
*
|
|
10
|
+
* PRD Reference: PRD-kongo-integration.md §4.3
|
|
11
|
+
*/
|
|
12
|
+
// ── Analytics Operations ─────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Get campaign analytics with per-variant, per-source, and daily breakdown.
|
|
15
|
+
*/
|
|
16
|
+
export async function getCampaignAnalytics(client, campaignId, period) {
|
|
17
|
+
const query = {};
|
|
18
|
+
if (period)
|
|
19
|
+
query.period = period;
|
|
20
|
+
return client.get(`/engine/campaigns/${encodeURIComponent(campaignId)}/analytics`, query);
|
|
21
|
+
}
|
|
22
|
+
// ── Computed Growth Signal ───────────────────────────────
|
|
23
|
+
// Minimum sample size per variant before making recommendations
|
|
24
|
+
const MIN_SAMPLE_SIZE = 100;
|
|
25
|
+
// Minimum total views before any signal is meaningful
|
|
26
|
+
const MIN_TOTAL_VIEWS = 200;
|
|
27
|
+
// Z-score threshold for 95% confidence
|
|
28
|
+
const Z_95 = 1.96;
|
|
29
|
+
/**
|
|
30
|
+
* Compute a growth signal from campaign analytics.
|
|
31
|
+
*
|
|
32
|
+
* This replaces the hypothetical /growth-signal endpoint from the PRD.
|
|
33
|
+
* Uses two-proportion z-test to determine if any variant significantly
|
|
34
|
+
* outperforms the control (first variant by order).
|
|
35
|
+
*/
|
|
36
|
+
export function computeGrowthSignal(campaignId, analytics) {
|
|
37
|
+
const timestamp = new Date().toISOString();
|
|
38
|
+
const variants = analytics.byVariant;
|
|
39
|
+
// Not enough data yet
|
|
40
|
+
if (analytics.summary.totalViews < MIN_TOTAL_VIEWS || variants.length < 2) {
|
|
41
|
+
return {
|
|
42
|
+
campaignId,
|
|
43
|
+
timestamp,
|
|
44
|
+
winningVariantId: null,
|
|
45
|
+
confidence: 0,
|
|
46
|
+
conversionRateDelta: 0,
|
|
47
|
+
recommendation: 'wait',
|
|
48
|
+
reasoning: variants.length < 2
|
|
49
|
+
? 'Need at least 2 variants to evaluate'
|
|
50
|
+
: `Insufficient data: ${analytics.summary.totalViews} views (need ${MIN_TOTAL_VIEWS})`,
|
|
51
|
+
sampleSize: { control: variants[0]?.views ?? 0, variant: 0 },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Control = first variant by creation order (the original, pre-variation baseline).
|
|
55
|
+
// NOT the worst performer — using worst inflates z-scores and causes false positives.
|
|
56
|
+
// Tiebreaker by variantId for deterministic selection across JS engines.
|
|
57
|
+
const control = [...variants].sort((a, b) => a.order - b.order || a.variantId.localeCompare(b.variantId))[0];
|
|
58
|
+
const challengers = variants.filter(v => v.variantId !== control.variantId);
|
|
59
|
+
if (challengers.length === 0) {
|
|
60
|
+
return {
|
|
61
|
+
campaignId,
|
|
62
|
+
timestamp,
|
|
63
|
+
winningVariantId: null,
|
|
64
|
+
confidence: 0,
|
|
65
|
+
conversionRateDelta: 0,
|
|
66
|
+
recommendation: 'wait',
|
|
67
|
+
reasoning: 'Only the control variant exists — need at least one challenger',
|
|
68
|
+
sampleSize: { control: control.views, variant: 0 },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Find best challenger by CVR (guard against NaN from 0-view variants)
|
|
72
|
+
const best = challengers
|
|
73
|
+
.filter(v => v.views > 0)
|
|
74
|
+
.sort((a, b) => b.cvr - a.cvr)[0];
|
|
75
|
+
if (!best) {
|
|
76
|
+
return {
|
|
77
|
+
campaignId,
|
|
78
|
+
timestamp,
|
|
79
|
+
winningVariantId: null,
|
|
80
|
+
confidence: 0,
|
|
81
|
+
conversionRateDelta: 0,
|
|
82
|
+
recommendation: 'wait',
|
|
83
|
+
reasoning: 'No challengers with traffic data yet',
|
|
84
|
+
sampleSize: { control: control.views, variant: 0 },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Check minimum sample sizes
|
|
88
|
+
if (best.views < MIN_SAMPLE_SIZE || control.views < MIN_SAMPLE_SIZE) {
|
|
89
|
+
return {
|
|
90
|
+
campaignId,
|
|
91
|
+
timestamp,
|
|
92
|
+
winningVariantId: null,
|
|
93
|
+
confidence: 0,
|
|
94
|
+
conversionRateDelta: 0,
|
|
95
|
+
recommendation: 'wait',
|
|
96
|
+
reasoning: `Insufficient per-variant data: best=${best.views}, control=${control.views} views (need ${MIN_SAMPLE_SIZE} each)`,
|
|
97
|
+
sampleSize: { control: control.views, variant: best.views },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Two-proportion z-test (one-tailed: is best better than control?)
|
|
101
|
+
const { zScore, pValue } = twoProportionZTest(best.conversions, best.views, control.conversions, control.views);
|
|
102
|
+
// Confidence = 1 - p_value (one-tailed). For two-tailed 95%, need p < 0.025.
|
|
103
|
+
// We use one-tailed because we have a directional hypothesis (best > control).
|
|
104
|
+
const confidence = 1 - pValue;
|
|
105
|
+
const conversionRateDelta = best.cvr - control.cvr;
|
|
106
|
+
const recommendation = computeRecommendation(confidence, conversionRateDelta, analytics.summary.totalViews);
|
|
107
|
+
return {
|
|
108
|
+
campaignId,
|
|
109
|
+
timestamp,
|
|
110
|
+
winningVariantId: confidence >= 0.95 ? best.variantId : null,
|
|
111
|
+
confidence,
|
|
112
|
+
conversionRateDelta,
|
|
113
|
+
recommendation,
|
|
114
|
+
reasoning: formatReasoning(best, control, confidence, recommendation),
|
|
115
|
+
sampleSize: { control: control.views, variant: best.views },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Fetch analytics and compute growth signal in one call.
|
|
120
|
+
*/
|
|
121
|
+
export async function getGrowthSignal(client, campaignId, period) {
|
|
122
|
+
const analytics = await getCampaignAnalytics(client, campaignId, period);
|
|
123
|
+
return computeGrowthSignal(campaignId, analytics);
|
|
124
|
+
}
|
|
125
|
+
// ── Statistical Helpers ──────────────────────────────────
|
|
126
|
+
function twoProportionZTest(successA, nA, successB, nB) {
|
|
127
|
+
const pA = successA / nA;
|
|
128
|
+
const pB = successB / nB;
|
|
129
|
+
const pPooled = (successA + successB) / (nA + nB);
|
|
130
|
+
const se = Math.sqrt(pPooled * (1 - pPooled) * (1 / nA + 1 / nB));
|
|
131
|
+
if (nA === 0 || nB === 0 || !(se > 0))
|
|
132
|
+
return { zScore: 0, pValue: 1 };
|
|
133
|
+
const zScore = (pA - pB) / se;
|
|
134
|
+
// One-tailed p-value: P(Z >= z) = 1 - CDF(z)
|
|
135
|
+
const pValue = 1 - normalCdf(zScore);
|
|
136
|
+
return { zScore, pValue };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Approximate normal CDF using Abramowitz and Stegun formula.
|
|
140
|
+
* Returns P(Z <= z) for standard normal distribution.
|
|
141
|
+
*/
|
|
142
|
+
function normalCdf(z) {
|
|
143
|
+
if (z < -6)
|
|
144
|
+
return 0;
|
|
145
|
+
if (z > 6)
|
|
146
|
+
return 1;
|
|
147
|
+
const absZ = Math.abs(z);
|
|
148
|
+
const t = 1 / (1 + 0.2316419 * absZ);
|
|
149
|
+
const d = 0.3989422804014327; // 1/sqrt(2*PI)
|
|
150
|
+
const p = d * Math.exp(-absZ * absZ / 2) *
|
|
151
|
+
(t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.8212560 + t * 1.3302744)))));
|
|
152
|
+
return z > 0 ? 1 - p : p;
|
|
153
|
+
}
|
|
154
|
+
function computeRecommendation(confidence, conversionRateDelta, totalViews) {
|
|
155
|
+
// High confidence winner
|
|
156
|
+
if (confidence >= 0.95 && conversionRateDelta > 0)
|
|
157
|
+
return 'scale';
|
|
158
|
+
// Moderate confidence, positive trend — keep iterating
|
|
159
|
+
if (confidence >= 0.80 && conversionRateDelta > 0)
|
|
160
|
+
return 'iterate';
|
|
161
|
+
// High confidence that all challengers underperform the control
|
|
162
|
+
if (confidence >= 0.95 && conversionRateDelta < 0 && totalViews > 500)
|
|
163
|
+
return 'kill';
|
|
164
|
+
// Not enough signal yet
|
|
165
|
+
return 'wait';
|
|
166
|
+
}
|
|
167
|
+
function formatReasoning(best, control, confidence, recommendation) {
|
|
168
|
+
const delta = (best.cvr - control.cvr).toFixed(2);
|
|
169
|
+
switch (recommendation) {
|
|
170
|
+
case 'scale':
|
|
171
|
+
return `Variant "${best.label}" outperforms control by ${delta}pp CVR with ${(confidence * 100).toFixed(1)}% confidence. Scale this variant.`;
|
|
172
|
+
case 'iterate':
|
|
173
|
+
return `Variant "${best.label}" shows +${delta}pp CVR lift at ${(confidence * 100).toFixed(1)}% confidence. Continue testing for stronger signal.`;
|
|
174
|
+
case 'kill':
|
|
175
|
+
return `No variant shows significant improvement. Consider killing this campaign.`;
|
|
176
|
+
case 'wait':
|
|
177
|
+
return `Insufficient data for recommendation. Current best: "${best.label}" at ${best.cvr.toFixed(2)}% CVR.`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kongo Campaigns — Campaign lifecycle operations.
|
|
3
|
+
*
|
|
4
|
+
* Create, list, update, publish/unpublish, and delete campaigns.
|
|
5
|
+
* Campaigns group pages + variants for A/B testing with rotation strategies.
|
|
6
|
+
*
|
|
7
|
+
* Real Kongo API endpoints:
|
|
8
|
+
* - POST /engine/campaigns — create
|
|
9
|
+
* - GET /engine/campaigns — list (cursor pagination)
|
|
10
|
+
* - GET /engine/campaigns/:campaignId — get detail
|
|
11
|
+
* - PUT /engine/campaigns/:campaignId — update
|
|
12
|
+
* - DELETE /engine/campaigns/:campaignId — archive (soft delete)
|
|
13
|
+
* - POST /engine/campaigns/:campaignId/publish — publish
|
|
14
|
+
* - DELETE /engine/campaigns/:campaignId/publish — unpublish
|
|
15
|
+
*
|
|
16
|
+
* PRD Reference: PRD-kongo-integration.md §4.2
|
|
17
|
+
*/
|
|
18
|
+
import { KongoClient } from './client.js';
|
|
19
|
+
import type { CreateCampaignRequest, CampaignDetail, UpdateCampaignRequest, PublishResult, ListCampaignsOptions, PaginatedResponse } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Create a new campaign.
|
|
22
|
+
* Requires a templateId (page ID) to base variants on.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createCampaign(client: KongoClient, config: CreateCampaignRequest): Promise<CampaignDetail>;
|
|
25
|
+
/**
|
|
26
|
+
* Get a single campaign by ID.
|
|
27
|
+
* Returns full details including template info, variants, and stats.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getCampaign(client: KongoClient, campaignId: string): Promise<CampaignDetail>;
|
|
30
|
+
/**
|
|
31
|
+
* List campaigns with cursor-based pagination.
|
|
32
|
+
*/
|
|
33
|
+
export declare function listCampaigns(client: KongoClient, options?: ListCampaignsOptions): Promise<PaginatedResponse<CampaignDetail>>;
|
|
34
|
+
/**
|
|
35
|
+
* Update a campaign's rotation strategy, tracking, access gate, or metadata.
|
|
36
|
+
* Slug and template cannot be changed after creation.
|
|
37
|
+
*/
|
|
38
|
+
export declare function updateCampaign(client: KongoClient, campaignId: string, updates: UpdateCampaignRequest): Promise<CampaignDetail>;
|
|
39
|
+
/**
|
|
40
|
+
* Archive a campaign (soft delete). Unpublishes automatically.
|
|
41
|
+
*/
|
|
42
|
+
export declare function deleteCampaign(client: KongoClient, campaignId: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Publish a campaign — provisions DNS CNAME at {slug}.kongo.io.
|
|
45
|
+
* Requires at least 1 active variant. Idempotent.
|
|
46
|
+
*/
|
|
47
|
+
export declare function publishCampaign(client: KongoClient, campaignId: string): Promise<PublishResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Unpublish a campaign — removes DNS record.
|
|
50
|
+
*/
|
|
51
|
+
export declare function unpublishCampaign(client: KongoClient, campaignId: string): Promise<void>;
|
|
52
|
+
export declare function batchGetCampaignStatuses(client: KongoClient): Promise<CampaignDetail[]>;
|