thumbgate 1.16.22 → 1.18.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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +11 -5
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/config/github-about.json +1 -1
- package/package.json +10 -5
- package/public/blog.html +18 -19
- package/public/compare.html +2 -2
- package/public/guide.html +1 -1
- package/public/index.html +166 -419
- package/public/numbers.html +2 -2
- package/scripts/auto-promote-gates.js +4 -1
- package/scripts/billing.js +62 -3
- package/scripts/feedback-to-rules.js +11 -1
- package/scripts/feedback_quality_eval.py +725 -0
- package/scripts/rate-limiter.js +15 -15
- package/src/api/server.js +91 -19
package/scripts/rate-limiter.js
CHANGED
|
@@ -12,28 +12,28 @@ const {
|
|
|
12
12
|
const USAGE_FILE = path.join(process.env.HOME || '/tmp', '.thumbgate', 'usage-limits.json');
|
|
13
13
|
|
|
14
14
|
// ──────────────────────────────────────────────────────────
|
|
15
|
-
//
|
|
16
|
-
//
|
|
15
|
+
// Free tier: generous on captures (habit formation) and rules
|
|
16
|
+
// (5 active gates), gated on Pro-only features (recall, search,
|
|
17
|
+
// exports). Dashboard, exports, and unlimited rules drive Pro.
|
|
17
18
|
// ──────────────────────────────────────────────────────────
|
|
18
19
|
const FREE_TIER_LIMITS = {
|
|
19
|
-
capture_feedback: { daily: Infinity, lifetime:
|
|
20
|
-
prevention_rules: { daily: Infinity, lifetime:
|
|
21
|
-
recall: { daily: 0, lifetime: 0,
|
|
22
|
-
search_lessons: { daily: 0, lifetime: 0,
|
|
23
|
-
search_thumbgate: { daily: 0, lifetime: 0,
|
|
24
|
-
commerce_recall: { daily: 0, lifetime: 0,
|
|
25
|
-
export_dpo: { daily: 0, lifetime: 0,
|
|
26
|
-
export_databricks: { daily: 0, lifetime: 0,
|
|
27
|
-
construct_context_pack: { daily: Infinity, lifetime:
|
|
20
|
+
capture_feedback: { daily: Infinity, lifetime: Infinity, label: 'feedback captures' },
|
|
21
|
+
prevention_rules: { daily: Infinity, lifetime: Infinity, label: 'prevention rules generated' },
|
|
22
|
+
recall: { daily: 0, lifetime: 0, label: 'recall queries (Pro only)' },
|
|
23
|
+
search_lessons: { daily: 0, lifetime: 0, label: 'lesson searches (Pro only)' },
|
|
24
|
+
search_thumbgate: { daily: 0, lifetime: 0, label: 'ThumbGate searches (Pro only)' },
|
|
25
|
+
commerce_recall: { daily: 0, lifetime: 0, label: 'commerce recalls (Pro only)' },
|
|
26
|
+
export_dpo: { daily: 0, lifetime: 0, label: 'DPO exports (Pro only)' },
|
|
27
|
+
export_databricks: { daily: 0, lifetime: 0, label: 'Databricks exports (Pro only)' },
|
|
28
|
+
construct_context_pack: { daily: Infinity, lifetime: Infinity, label: 'context packs' },
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
const FREE_TIER_MAX_GATES =
|
|
31
|
+
const FREE_TIER_MAX_GATES = 5; // 5 active prevention rules on free; Pro is unlimited
|
|
31
32
|
|
|
32
|
-
const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} — unlimited
|
|
33
|
+
const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} — unlimited rules, recall, lesson search, dashboard, and exports: ${PRO_MONTHLY_PAYMENT_LINK}\n Team: ${TEAM_PRICE_LABEL} after workflow qualification.`;
|
|
33
34
|
|
|
34
35
|
const PAYWALL_MESSAGES = {
|
|
35
|
-
|
|
36
|
-
prevention_rules: 'Free tier includes 1 prevention rule. Your agents need more protection — upgrade to Pro for unlimited rules.',
|
|
36
|
+
prevention_rules: 'Free tier includes 5 active prevention rules. Promote more or unlock unlimited rules with Pro.',
|
|
37
37
|
recall: 'Recall is a Pro feature. Your past feedback is stored locally — upgrade to search and reuse it.',
|
|
38
38
|
search_lessons: 'Lesson search is a Pro feature. Upgrade to find patterns in your agent\'s mistakes.',
|
|
39
39
|
default: 'This feature requires Pro. Start Pro — card required; billed today.',
|
package/src/api/server.js
CHANGED
|
@@ -16,6 +16,8 @@ const POSTHOG_STATIC_PATH_PREFIX = '/static/';
|
|
|
16
16
|
const FIRST_FAILURE_RULE_CHECKOUT_URL = 'https://buy.stripe.com/4gM6oHgH2bTw4lH6i73sI0z';
|
|
17
17
|
const QUICK_READ_CHECKOUT_URL = 'https://buy.stripe.com/aFa8wPgH29Lo4lH35V3sI0w';
|
|
18
18
|
const WORKFLOW_TEARDOWN_CHECKOUT_URL = 'https://buy.stripe.com/7sYfZhgH29LodWhdKz3sI0v';
|
|
19
|
+
const SPRINT_DIAGNOSTIC_CHECKOUT_URL = 'https://buy.stripe.com/3cI7sLgH25v8dWh5e33sI0o';
|
|
20
|
+
const WORKFLOW_SPRINT_CHECKOUT_URL = 'https://buy.stripe.com/8x25kDcqMaPs9G15e33sI0p';
|
|
19
21
|
|
|
20
22
|
function getPosthogProxyPath(pathname) {
|
|
21
23
|
return pathname.slice('/ingest'.length) || '/';
|
|
@@ -210,6 +212,7 @@ const NUMBERS_PAGE_PATH = path.resolve(__dirname, '../../public/numbers.html');
|
|
|
210
212
|
const LEARN_DIR = path.resolve(__dirname, '../../public/learn');
|
|
211
213
|
const GUIDES_DIR = path.resolve(__dirname, '../../public/guides');
|
|
212
214
|
const COMPARE_DIR = path.resolve(__dirname, '../../public/compare');
|
|
215
|
+
const USE_CASES_DIR = path.resolve(__dirname, '../../public/use-cases');
|
|
213
216
|
const PUBLIC_DIR = path.resolve(__dirname, '../../public');
|
|
214
217
|
const PUBLIC_ASSETS_DIR = path.resolve(__dirname, '../../public/assets');
|
|
215
218
|
const BUYER_INTENT_SCRIPT_PATH = path.resolve(__dirname, '../../public/js/buyer-intent.js');
|
|
@@ -400,6 +403,27 @@ const TRACKED_LINK_TARGETS = Object.freeze({
|
|
|
400
403
|
},
|
|
401
404
|
allowCustomerEmail: true,
|
|
402
405
|
},
|
|
406
|
+
// 2026-05-12: Aiventyx marketplace listing routes its Teams clicks through
|
|
407
|
+
// /go/teams (best-performing listing at ~62% CTR). Without this slug the
|
|
408
|
+
// server returned 404 + "Tracked link not found". Every Aiventyx Teams
|
|
409
|
+
// click between the URL swap and this deploy landed on that error page.
|
|
410
|
+
// Destination: 3-seat Team self-serve Stripe checkout (the path I shipped
|
|
411
|
+
// in PR #1877 — plan_id=team + seat_count=3 = $147/mo entry).
|
|
412
|
+
teams: {
|
|
413
|
+
path: '/checkout/pro',
|
|
414
|
+
ctaId: 'go_teams',
|
|
415
|
+
ctaPlacement: 'link_router',
|
|
416
|
+
eventType: 'cta_click',
|
|
417
|
+
defaults: {
|
|
418
|
+
utm_source: 'website',
|
|
419
|
+
utm_medium: 'link_router',
|
|
420
|
+
utm_campaign: 'team_self_serve',
|
|
421
|
+
plan_id: 'team',
|
|
422
|
+
seat_count: '3',
|
|
423
|
+
billing_cycle: 'monthly',
|
|
424
|
+
},
|
|
425
|
+
allowCustomerEmail: true,
|
|
426
|
+
},
|
|
403
427
|
install: {
|
|
404
428
|
path: '/guide',
|
|
405
429
|
ctaId: 'go_install',
|
|
@@ -1508,7 +1532,7 @@ function renderCheckoutIntentPage({
|
|
|
1508
1532
|
const teardownAction = safeWorkflowTeardownCheckoutHref
|
|
1509
1533
|
? `<a data-i="workflow_teardown_checkout" href="${safeWorkflowTeardownCheckoutHref}">Pay $99 teardown</a>`
|
|
1510
1534
|
: '';
|
|
1511
|
-
return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>body{background:#0a0a0a;color:#eee;font-family:system-ui,sans-serif}
|
|
1535
|
+
return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm — ThumbGate Pro</title><style>body{background:#0a0a0a;color:#eee;font-family:system-ui,-apple-system,sans-serif;line-height:1.5}main{max-width:520px;margin:8vh auto;padding:0 20px}.brand{display:flex;align-items:center;gap:10px;margin-bottom:24px;font-size:14px;color:#94a3b8}.brand-mark{width:24px;height:24px;background:#22d3ee;border-radius:6px;display:inline-block}h1{font-size:24px;margin:0 0 8px;color:#fff}.price{font-size:32px;font-weight:700;color:#22d3ee;margin:8px 0 4px}.price small{font-size:14px;color:#94a3b8;font-weight:400}p{color:#cbd5e1;margin:8px 0}a{display:block;text-decoration:none}a.primary{background:#22d3ee;color:#000;padding:16px;text-align:center;border-radius:8px;font-weight:700;font-size:16px;margin:20px 0 10px}a.secondary{border:1px solid #374151;color:#cbd5e1;padding:12px;text-align:center;border-radius:8px;margin:8px 0;font-size:14px}.trust{margin:24px 0;padding:16px;border:1px solid #1f2937;border-radius:8px;background:#0f172a}.trust-item{font-size:13px;color:#cbd5e1;padding:4px 0;display:flex;gap:8px}.trust-item::before{content:"✓";color:#22d3ee;font-weight:700}details{margin-top:32px;font-size:13px;color:#94a3b8}details summary{cursor:pointer;padding:8px 0}details a{border:1px solid #374151;color:#94a3b8;padding:10px;text-align:center;border-radius:6px;margin:6px 0;font-size:13px}.back{text-align:center;color:#64748b;font-size:12px;margin-top:24px}.back a{color:#64748b;display:inline}</style><main><div class="brand"><span class="brand-mark"></span><span>ThumbGate</span></div><h1>Start ThumbGate Pro</h1><div class="price">$19<small>/mo</small></div><p>Block every repeat AI-agent mistake. Local-first. MIT-licensed CLI included. Cancel anytime.</p><a class="primary" data-i="pro_checkout_confirmed" href="${safeConfirmHref}">Pay $19/mo with Stripe →</a><div class="trust"><div class="trust-item">6 paying customers, 18,000+ installs verified on npm</div><div class="trust-item">Cancel anytime — instant refund within 7 days</div><div class="trust-item">MIT open source · no vendor lock-in</div><div class="trust-item">Works with Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode</div></div><details><summary>Other paid paths (diagnostic, sprint, teardown, single-rule)</summary>${diagnosticAction.replace('<a ', '<a class="secondary" ')}${sprintAction.replace('<a ', '<a class="secondary" ')}${teardownAction.replace('<a ', '<a class="secondary" ')}${quickReadAction.replace('<a ', '<a class="secondary" ')}${firstRuleAction.replace('<a ', '<a class="secondary" ')}<a class="secondary" data-i="workflow_sprint_intake" href="${safeWorkflowIntakeHref}">Send workflow first (intake)</a><a class="secondary" data-i="team_paid_path" href="${safeTeamOptionsHref}">See all options</a></details><p class="back"><a href="/">← Back to thumbgate.ai</a></p></main><script>addEventListener('click',e=>{let a=e.target.closest('[data-i]');if(a&&navigator.sendBeacon)navigator.sendBeacon('/v1/telemetry/ping',new Blob([JSON.stringify({eventType:'checkout_interstitial_cta_clicked',clientType:'web',page:'/checkout/pro',ctaId:a.dataset.i,ctaPlacement:'checkout_interstitial'})],{type:'application/json'}))})</script></html>`;
|
|
1512
1536
|
}
|
|
1513
1537
|
|
|
1514
1538
|
function buildCheckoutBootstrapBody(parsed, req, journeyState = resolveJourneyState(req, parsed)) {
|
|
@@ -1587,6 +1611,12 @@ function normalizeTrackedLinkSlug(value) {
|
|
|
1587
1611
|
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
1588
1612
|
}
|
|
1589
1613
|
|
|
1614
|
+
function normalizePublicPageSlug(value) {
|
|
1615
|
+
return String(value || '')
|
|
1616
|
+
.replace(/\.html$/i, '')
|
|
1617
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1590
1620
|
function getTrackedLinkTarget(slug) {
|
|
1591
1621
|
const normalizedSlug = normalizeTrackedLinkSlug(slug);
|
|
1592
1622
|
return TRACKED_LINK_TARGETS[normalizedSlug]
|
|
@@ -1939,8 +1969,8 @@ function loadPublicMarketingTemplateHtml(templatePath, runtimeConfig, pageContex
|
|
|
1939
1969
|
'__CHECKOUT_FALLBACK_URL__': runtimeConfig.checkoutFallbackUrl,
|
|
1940
1970
|
'__PRO_PRICE_DOLLARS__': runtimeConfig.proPriceDollars,
|
|
1941
1971
|
'__PRO_PRICE_LABEL__': runtimeConfig.proPriceLabel,
|
|
1942
|
-
'__SPRINT_DIAGNOSTIC_CHECKOUT_URL__': runtimeConfig.sprintDiagnosticCheckoutUrl ||
|
|
1943
|
-
'__WORKFLOW_SPRINT_CHECKOUT_URL__': runtimeConfig.workflowSprintCheckoutUrl ||
|
|
1972
|
+
'__SPRINT_DIAGNOSTIC_CHECKOUT_URL__': runtimeConfig.sprintDiagnosticCheckoutUrl || SPRINT_DIAGNOSTIC_CHECKOUT_URL,
|
|
1973
|
+
'__WORKFLOW_SPRINT_CHECKOUT_URL__': runtimeConfig.workflowSprintCheckoutUrl || WORKFLOW_SPRINT_CHECKOUT_URL,
|
|
1944
1974
|
'__SPRINT_DIAGNOSTIC_PRICE_DOLLARS__': runtimeConfig.sprintDiagnosticPriceDollars || 499,
|
|
1945
1975
|
'__WORKFLOW_SPRINT_PRICE_DOLLARS__': runtimeConfig.workflowSprintPriceDollars || 1500,
|
|
1946
1976
|
'__GA_MEASUREMENT_ID__': runtimeConfig.gaMeasurementId || '',
|
|
@@ -4193,6 +4223,15 @@ async function addContext(){
|
|
|
4193
4223
|
return;
|
|
4194
4224
|
}
|
|
4195
4225
|
|
|
4226
|
+
if (isGetLikeRequest && pathname === '/lessons/') {
|
|
4227
|
+
res.writeHead(302, {
|
|
4228
|
+
Location: '/lessons',
|
|
4229
|
+
'Cache-Control': 'no-store',
|
|
4230
|
+
});
|
|
4231
|
+
res.end();
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4196
4235
|
if (isGetLikeRequest && pathname === '/lessons') {
|
|
4197
4236
|
try {
|
|
4198
4237
|
const html = loadLessonsPageHtml(req, expectedApiKey);
|
|
@@ -4245,7 +4284,7 @@ async function addContext(){
|
|
|
4245
4284
|
return;
|
|
4246
4285
|
}
|
|
4247
4286
|
|
|
4248
|
-
if (isGetLikeRequest && pathname === '/guide') {
|
|
4287
|
+
if (isGetLikeRequest && (pathname === '/guide' || pathname === '/guide.html')) {
|
|
4249
4288
|
try {
|
|
4250
4289
|
const html = fs.readFileSync(GUIDE_PAGE_PATH, 'utf-8');
|
|
4251
4290
|
sendHtml(res, 200, html, {}, { headOnly: isHeadRequest });
|
|
@@ -4255,7 +4294,7 @@ async function addContext(){
|
|
|
4255
4294
|
return;
|
|
4256
4295
|
}
|
|
4257
4296
|
|
|
4258
|
-
if (isGetLikeRequest && pathname === '/codex-plugin') {
|
|
4297
|
+
if (isGetLikeRequest && (pathname === '/codex-plugin' || pathname === '/codex-plugin.html')) {
|
|
4259
4298
|
try {
|
|
4260
4299
|
const html = fs.readFileSync(CODEX_PLUGIN_PAGE_PATH, 'utf-8');
|
|
4261
4300
|
sendHtml(res, 200, html, {}, { headOnly: isHeadRequest });
|
|
@@ -4265,7 +4304,7 @@ async function addContext(){
|
|
|
4265
4304
|
return;
|
|
4266
4305
|
}
|
|
4267
4306
|
|
|
4268
|
-
if (isGetLikeRequest && pathname === '/compare') {
|
|
4307
|
+
if (isGetLikeRequest && (pathname === '/compare' || pathname === '/compare.html')) {
|
|
4269
4308
|
try {
|
|
4270
4309
|
const html = fs.readFileSync(COMPARE_PAGE_PATH, 'utf-8');
|
|
4271
4310
|
sendHtml(res, 200, html, {}, { headOnly: isHeadRequest });
|
|
@@ -4275,7 +4314,7 @@ async function addContext(){
|
|
|
4275
4314
|
return;
|
|
4276
4315
|
}
|
|
4277
4316
|
|
|
4278
|
-
if (isGetLikeRequest && pathname === '/blog') {
|
|
4317
|
+
if (isGetLikeRequest && (pathname === '/blog' || pathname === '/blog.html')) {
|
|
4279
4318
|
try {
|
|
4280
4319
|
const blogPath = path.resolve(__dirname, '../../public/blog.html');
|
|
4281
4320
|
const html = fs.readFileSync(blogPath, 'utf-8');
|
|
@@ -4286,7 +4325,7 @@ async function addContext(){
|
|
|
4286
4325
|
return;
|
|
4287
4326
|
}
|
|
4288
4327
|
|
|
4289
|
-
if (isGetLikeRequest && pathname === '/learn') {
|
|
4328
|
+
if (isGetLikeRequest && (pathname === '/learn' || pathname === '/learn.html')) {
|
|
4290
4329
|
try {
|
|
4291
4330
|
const html = fs.readFileSync(LEARN_PAGE_PATH, 'utf-8');
|
|
4292
4331
|
sendHtml(res, 200, html, {}, { headOnly: isHeadRequest });
|
|
@@ -4296,6 +4335,24 @@ async function addContext(){
|
|
|
4296
4335
|
return;
|
|
4297
4336
|
}
|
|
4298
4337
|
|
|
4338
|
+
if (isGetLikeRequest && (pathname === '/guides' || pathname === '/guides/' || pathname === '/guides.html')) {
|
|
4339
|
+
res.writeHead(302, {
|
|
4340
|
+
Location: '/learn',
|
|
4341
|
+
'Cache-Control': 'no-store',
|
|
4342
|
+
});
|
|
4343
|
+
res.end();
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
if (isGetLikeRequest && (pathname === '/services' || pathname === '/services.html')) {
|
|
4348
|
+
res.writeHead(302, {
|
|
4349
|
+
Location: '/#workflow-sprint-intake',
|
|
4350
|
+
'Cache-Control': 'no-store',
|
|
4351
|
+
});
|
|
4352
|
+
res.end();
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4299
4356
|
if (isGetLikeRequest && (pathname === '/numbers' || pathname === '/numbers.html')) {
|
|
4300
4357
|
// Route through servePublicMarketingPage so landing_page_view telemetry
|
|
4301
4358
|
// + funnel-events.jsonl `discovery/landing_view` get captured with UTM
|
|
@@ -4331,7 +4388,7 @@ async function addContext(){
|
|
|
4331
4388
|
|
|
4332
4389
|
if (isGetLikeRequest && pathname.startsWith('/learn/')) {
|
|
4333
4390
|
try {
|
|
4334
|
-
const slug = pathname.replace('/learn/', '')
|
|
4391
|
+
const slug = normalizePublicPageSlug(pathname.replace('/learn/', ''));
|
|
4335
4392
|
const articlePath = path.join(LEARN_DIR, `${slug}.html`);
|
|
4336
4393
|
if (!articlePath.startsWith(LEARN_DIR)) {
|
|
4337
4394
|
sendJson(res, 403, { error: 'Forbidden' });
|
|
@@ -4347,7 +4404,7 @@ async function addContext(){
|
|
|
4347
4404
|
|
|
4348
4405
|
if (isGetLikeRequest && pathname.startsWith('/guides/')) {
|
|
4349
4406
|
try {
|
|
4350
|
-
const slug = pathname.replace('/guides/', '')
|
|
4407
|
+
const slug = normalizePublicPageSlug(pathname.replace('/guides/', ''));
|
|
4351
4408
|
const guidePath = path.join(GUIDES_DIR, `${slug}.html`);
|
|
4352
4409
|
if (!guidePath.startsWith(GUIDES_DIR)) { sendJson(res, 403, { error: 'Forbidden' }); return; }
|
|
4353
4410
|
const html = fs.readFileSync(guidePath, 'utf-8');
|
|
@@ -4358,7 +4415,7 @@ async function addContext(){
|
|
|
4358
4415
|
|
|
4359
4416
|
if (isGetLikeRequest && pathname.startsWith('/compare/') && pathname !== '/compare') {
|
|
4360
4417
|
try {
|
|
4361
|
-
const slug = pathname.replace('/compare/', '')
|
|
4418
|
+
const slug = normalizePublicPageSlug(pathname.replace('/compare/', ''));
|
|
4362
4419
|
const comparePath = path.join(COMPARE_DIR, `${slug}.html`);
|
|
4363
4420
|
if (!comparePath.startsWith(COMPARE_DIR)) { sendJson(res, 403, { error: 'Forbidden' }); return; }
|
|
4364
4421
|
const html = fs.readFileSync(comparePath, 'utf-8');
|
|
@@ -4367,6 +4424,17 @@ async function addContext(){
|
|
|
4367
4424
|
return;
|
|
4368
4425
|
}
|
|
4369
4426
|
|
|
4427
|
+
if (isGetLikeRequest && pathname.startsWith('/use-cases/')) {
|
|
4428
|
+
try {
|
|
4429
|
+
const slug = normalizePublicPageSlug(pathname.replace('/use-cases/', ''));
|
|
4430
|
+
const useCasePath = path.join(USE_CASES_DIR, `${slug}.html`);
|
|
4431
|
+
if (!useCasePath.startsWith(USE_CASES_DIR)) { sendJson(res, 403, { error: 'Forbidden' }); return; }
|
|
4432
|
+
const html = fs.readFileSync(useCasePath, 'utf-8');
|
|
4433
|
+
sendHtml(res, 200, html, {}, { headOnly: isHeadRequest });
|
|
4434
|
+
} catch { sendJson(res, 404, { error: 'Use case not found' }); }
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4370
4438
|
if (isGetLikeRequest && pathname.startsWith('/assets/')) {
|
|
4371
4439
|
const rel = pathname.slice('/assets/'.length);
|
|
4372
4440
|
const resolved = path.resolve(PUBLIC_ASSETS_DIR, rel);
|
|
@@ -4500,24 +4568,28 @@ async function addContext(){
|
|
|
4500
4568
|
ctaPlacement: 'checkout_interstitial',
|
|
4501
4569
|
planId: 'workflow_teardown',
|
|
4502
4570
|
});
|
|
4503
|
-
const diagnosticCheckoutHref =
|
|
4504
|
-
|
|
4571
|
+
const diagnosticCheckoutHref = buildCheckoutIntentHref(
|
|
4572
|
+
hostedConfig.sprintDiagnosticCheckoutUrl || SPRINT_DIAGNOSTIC_CHECKOUT_URL,
|
|
4573
|
+
analyticsMetadata,
|
|
4574
|
+
{
|
|
4505
4575
|
utmMedium: 'checkout_interstitial_paid_path',
|
|
4506
4576
|
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_diagnostic',
|
|
4507
4577
|
ctaId: 'checkout_interstitial_sprint_diagnostic_checkout',
|
|
4508
4578
|
ctaPlacement: 'checkout_interstitial',
|
|
4509
4579
|
planId: 'sprint_diagnostic',
|
|
4510
|
-
}
|
|
4511
|
-
|
|
4512
|
-
const sprintCheckoutHref =
|
|
4513
|
-
|
|
4580
|
+
}
|
|
4581
|
+
);
|
|
4582
|
+
const sprintCheckoutHref = buildCheckoutIntentHref(
|
|
4583
|
+
hostedConfig.workflowSprintCheckoutUrl || WORKFLOW_SPRINT_CHECKOUT_URL,
|
|
4584
|
+
analyticsMetadata,
|
|
4585
|
+
{
|
|
4514
4586
|
utmMedium: 'checkout_interstitial_paid_path',
|
|
4515
4587
|
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_workflow_sprint',
|
|
4516
4588
|
ctaId: 'checkout_interstitial_workflow_sprint_checkout',
|
|
4517
4589
|
ctaPlacement: 'checkout_interstitial',
|
|
4518
4590
|
planId: 'workflow_sprint',
|
|
4519
|
-
}
|
|
4520
|
-
|
|
4591
|
+
}
|
|
4592
|
+
);
|
|
4521
4593
|
const html = renderCheckoutIntentPage({
|
|
4522
4594
|
confirmHref: buildCheckoutConfirmHref(parsed),
|
|
4523
4595
|
firstRuleCheckoutHref,
|