thumbgate 1.23.1 → 1.23.2
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 +5 -5
- package/.claude-plugin/plugin.json +2 -2
- package/.well-known/llms.txt +26 -11
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +69 -34
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +39 -16
- package/bin/postinstall.js +11 -22
- package/config/gate-templates.json +72 -0
- package/config/github-about.json +1 -1
- package/config/post-deploy-marketing-pages.json +6 -1
- package/package.json +5 -5
- package/public/agent-manager.html +3 -3
- package/public/agents-cost-savings.html +3 -3
- package/public/ai-malpractice-prevention.html +278 -7
- package/public/blog.html +3 -3
- package/public/codex-enterprise.html +3 -3
- package/public/codex-plugin.html +4 -4
- package/public/compare.html +6 -6
- package/public/dashboard.html +211 -126
- package/public/guide.html +5 -5
- package/public/index.html +156 -47
- package/public/learn.html +24 -10
- package/public/lessons.html +2 -2
- package/public/numbers.html +6 -6
- package/public/pricing.html +6 -5
- package/public/pro.html +1 -0
- package/scripts/billing.js +17 -0
- package/scripts/commercial-offer.js +4 -1
- package/scripts/dashboard.js +53 -1
- package/scripts/gates-engine.js +3 -3
- package/scripts/plausible-server-events.js +2 -1
- package/scripts/rate-limiter.js +16 -12
- package/scripts/seo-gsd.js +167 -1
- package/scripts/telemetry-analytics.js +310 -0
- package/scripts/visitor-journey.js +172 -0
- package/src/api/server.js +65 -29
- package/adapters/chatgpt/openapi.yaml +0 -1705
package/src/api/server.js
CHANGED
|
@@ -1532,15 +1532,8 @@ function buildCheckoutIntentHref(baseUrl, metadata = {}, overrides = {}) {
|
|
|
1532
1532
|
});
|
|
1533
1533
|
}
|
|
1534
1534
|
|
|
1535
|
-
function renderCheckoutIntentPage({
|
|
1536
|
-
|
|
1537
|
-
confirmHiddenParams,
|
|
1538
|
-
}) {
|
|
1539
|
-
const safeWorkflowIntakeHref = escapeHtmlAttribute(workflowIntakeHref);
|
|
1540
|
-
const hiddenFields = (confirmHiddenParams || [])
|
|
1541
|
-
.map(([k, v]) => `<input type="hidden" name="${escapeHtmlAttribute(k)}" value="${escapeHtmlAttribute(v)}">`)
|
|
1542
|
-
.join('');
|
|
1543
|
-
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}form{margin:0}input[type=email]{width:100%;box-sizing:border-box;padding:14px 16px;border:1px solid #374151;border-radius:8px;background:#111827;color:#fff;font-size:15px;margin:16px 0 0;outline:none}input[type=email]:focus{border-color:#22d3ee}input[type=email]::placeholder{color:#64748b}button.primary{background:#22d3ee;color:#000;padding:16px;text-align:center;border-radius:8px;font-weight:700;font-size:16px;margin:10px 0;border:none;cursor:pointer;width:100%}a{display:block;text-decoration:none}a.secondary{border:1px solid #374151;color:#cbd5e1;padding:12px;text-align:center;border-radius:8px;margin:8px 0 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}.choice-note{font-size:13px;color:#94a3b8;margin-top:14px}.back{text-align:center;color:#64748b;font-size:12px;margin-top:24px}.back a{color:#64748b;display:inline}.email-note{font-size:12px;color:#64748b;margin:4px 0 0}</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>The npm package runs your gates locally. <strong>Pro</strong> is what keeps them working across every machine, every agent runtime, and every breaking-change week.</p><form action="/checkout/pro" method="GET" data-i="pro_checkout_confirmed">${hiddenFields}<input type="hidden" name="confirm" value="1"><input type="email" name="customer_email" placeholder="you@company.com" required autocomplete="email"><p class="email-note">Pre-fills your Stripe receipt. We only email if you ask.</p><button type="submit" class="primary">Pay $19/mo with Stripe →</button></form><a class="secondary" data-i="workflow_sprint_intake" href="${safeWorkflowIntakeHref}">Not sure yet? Send the workflow first</a><p class="choice-note">Cancel anytime. 7-day refund, no questions. Diagnostics and sprints have their own pages.</p><div class="trust"><div class="trust-item">Lessons synced across all your machines — no local SQLite to babysit</div><div class="trust-item">Adapter matrix kept current for Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode — version drift is our problem, not yours</div><div class="trust-item">Hosted dashboard: gate stats, DPO export, org-wide rule library</div><div class="trust-item">24×7 ops on the rule engine — SonarCloud regressions fixed in <24h</div></div><p class="back"><a href="/">← Back to thumbgate.ai</a></p></main><script>document.querySelector('form').addEventListener('submit',e=>{if(navigator.sendBeacon)navigator.sendBeacon('/v1/telemetry/ping',new Blob([JSON.stringify({eventType:'checkout_interstitial_cta_clicked',clientType:'web',page:'/checkout/pro',ctaId:'pro_checkout_confirmed',ctaPlacement:'checkout_interstitial',customerEmail:document.querySelector('input[name=customer_email]').value})],{type:'application/json'}))})</script></html>`;
|
|
1535
|
+
function renderCheckoutIntentPage() {
|
|
1536
|
+
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><script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.tagged-events.js"></script><script>window.plausible=window.plausible||function(){(window.plausible.q=window.plausible.q||[]).push(arguments)};</script><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}form{margin:0}input[type=email]{width:100%;box-sizing:border-box;padding:14px 16px;border:1px solid #374151;border-radius:8px;background:#111827;color:#fff;font-size:15px;margin:16px 0 0;outline:none}input[type=email]:focus{border-color:#22d3ee}input[type=email]::placeholder{color:#64748b}button.primary{background:#22d3ee;color:#000;padding:16px;text-align:center;border-radius:8px;font-weight:700;font-size:16px;margin:10px 0;border:none;cursor:pointer;width:100%}a{display:block;text-decoration:none}a.secondary{border:1px solid #374151;color:#cbd5e1;padding:12px;text-align:center;border-radius:8px;margin:8px 0 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}.choice-note{font-size:13px;color:#94a3b8;margin-top:14px}.back{text-align:center;color:#64748b;font-size:12px;margin-top:24px}.back a{color:#64748b;display:inline}.email-note{font-size:12px;color:#64748b;margin:4px 0 0}</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>The npm package runs your gates locally. <strong>Pro</strong> is what keeps them working across every machine, every agent runtime, and every breaking-change week.</p><form action="/checkout/pro" method="GET" data-i="pro_checkout_confirmed"><input type="hidden" name="confirm" value="1"><input type="email" name="customer_email" placeholder="you@company.com" required autocomplete="email"><p class="email-note">Pre-fills your Stripe receipt. We only email if you ask.</p><button type="submit" class="primary">Pay $19/mo with Stripe →</button></form><a class="secondary" data-i="workflow_sprint_intake" href="/#workflow-sprint-intake">Not sure yet? Send the workflow first</a><p class="choice-note">Cancel anytime. 7-day refund, no questions. Diagnostics and sprints have their own pages.</p><div class="trust"><div class="trust-item">Lessons synced across all your machines — no local SQLite to babysit</div><div class="trust-item">Adapter matrix kept current for Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode — version drift is our problem, not yours</div><div class="trust-item">Hosted dashboard: gate stats, DPO export, org-wide rule library</div><div class="trust-item">24×7 ops on the rule engine — SonarCloud regressions fixed in <24h</div></div><p class="back"><a href="/">← Back to thumbgate.ai</a></p></main><script>document.querySelector('form').addEventListener('submit',e=>{if(navigator.sendBeacon)navigator.sendBeacon('/v1/telemetry/ping',new Blob([JSON.stringify({eventType:'checkout_interstitial_cta_clicked',clientType:'web',page:'/checkout/pro',ctaId:'pro_checkout_confirmed',ctaPlacement:'checkout_interstitial',customerEmail:document.querySelector('input[name=customer_email]').value})],{type:'application/json'}));try{window.plausible&&window.plausible('Checkout Pro Email Submitted',{props:{page:'/checkout/pro',source:'interstitial'}})}catch(_){}})</script></html>`;
|
|
1544
1537
|
}
|
|
1545
1538
|
|
|
1546
1539
|
function buildCheckoutBootstrapBody(parsed, req, journeyState = resolveJourneyState(req, parsed)) {
|
|
@@ -2522,6 +2515,16 @@ function renderRobotsTxt(runtimeConfig) {
|
|
|
2522
2515
|
'Disallow: /v1/billing/',
|
|
2523
2516
|
'',
|
|
2524
2517
|
'# AI crawler access — allow all major LLM crawlers',
|
|
2518
|
+
'User-agent: OAI-SearchBot',
|
|
2519
|
+
'Allow: /',
|
|
2520
|
+
'Disallow: /checkout/',
|
|
2521
|
+
'Disallow: /v1/billing/',
|
|
2522
|
+
'',
|
|
2523
|
+
'User-agent: ChatGPT-User',
|
|
2524
|
+
'Allow: /',
|
|
2525
|
+
'Disallow: /checkout/',
|
|
2526
|
+
'Disallow: /v1/billing/',
|
|
2527
|
+
'',
|
|
2525
2528
|
'User-agent: GPTBot',
|
|
2526
2529
|
'Allow: /',
|
|
2527
2530
|
'Disallow: /checkout/',
|
|
@@ -2530,9 +2533,18 @@ function renderRobotsTxt(runtimeConfig) {
|
|
|
2530
2533
|
'User-agent: ClaudeBot',
|
|
2531
2534
|
'Allow: /',
|
|
2532
2535
|
'',
|
|
2536
|
+
'User-agent: Claude-SearchBot',
|
|
2537
|
+
'Allow: /',
|
|
2538
|
+
'',
|
|
2539
|
+
'User-agent: Claude-User',
|
|
2540
|
+
'Allow: /',
|
|
2541
|
+
'',
|
|
2533
2542
|
'User-agent: PerplexityBot',
|
|
2534
2543
|
'Allow: /',
|
|
2535
2544
|
'',
|
|
2545
|
+
'User-agent: Perplexity-User',
|
|
2546
|
+
'Allow: /',
|
|
2547
|
+
'',
|
|
2536
2548
|
'User-agent: Googlebot',
|
|
2537
2549
|
'Allow: /',
|
|
2538
2550
|
'',
|
|
@@ -2547,6 +2559,7 @@ function renderRobotsTxt(runtimeConfig) {
|
|
|
2547
2559
|
'',
|
|
2548
2560
|
'# LLM context document — clean declarative content for AI retrieval',
|
|
2549
2561
|
`# ${runtimeConfig.appOrigin}/llm-context.md`,
|
|
2562
|
+
`# ${runtimeConfig.appOrigin}/llms.txt`,
|
|
2550
2563
|
'',
|
|
2551
2564
|
`Sitemap: ${runtimeConfig.appOrigin}/sitemap.xml`,
|
|
2552
2565
|
].join('\n');
|
|
@@ -2562,6 +2575,15 @@ function renderSitemapXml(runtimeConfig) {
|
|
|
2562
2575
|
{ path: '/codex-enterprise', changefreq: 'weekly', priority: '0.85' },
|
|
2563
2576
|
{ path: '/agents-cost-savings', changefreq: 'weekly', priority: '0.85' },
|
|
2564
2577
|
{ path: '/ai-malpractice-prevention', changefreq: 'weekly', priority: '0.9' },
|
|
2578
|
+
{ path: '/learn/background-agent-control-layer', changefreq: 'weekly', priority: '0.85' },
|
|
2579
|
+
{ path: '/learn/ac-dc-runtime-enforcement', changefreq: 'weekly', priority: '0.85' },
|
|
2580
|
+
{ path: '/learn/feedback-loop-vs-decision-layer', changefreq: 'weekly', priority: '0.9' },
|
|
2581
|
+
{ path: '/compare/claude-code-hooks', changefreq: 'weekly', priority: '0.85' },
|
|
2582
|
+
{ path: '/compare/bumblebee', changefreq: 'weekly', priority: '0.85' },
|
|
2583
|
+
{ path: '/compare/anthropic-containment', changefreq: 'weekly', priority: '0.85' },
|
|
2584
|
+
{ path: '/compare/oak-and-sparrow-gatekeeper', changefreq: 'weekly', priority: '0.85' },
|
|
2585
|
+
{ path: '/compare/arcjet', changefreq: 'weekly', priority: '0.85' },
|
|
2586
|
+
{ path: '/compare/anthropic-claude-for-legal', changefreq: 'weekly', priority: '0.9' },
|
|
2565
2587
|
...THUMBGATE_SEO_SITEMAP_ENTRIES,
|
|
2566
2588
|
];
|
|
2567
2589
|
return [
|
|
@@ -2833,7 +2855,8 @@ function renderCheckoutSuccessPage(runtimeConfig) {
|
|
|
2833
2855
|
</style>
|
|
2834
2856
|
<link rel="icon" type="image/png" href="/thumbgate-icon.png">
|
|
2835
2857
|
<link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
|
|
2836
|
-
<script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
|
|
2858
|
+
<script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.tagged-events.js"></script>
|
|
2859
|
+
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments); };</script>
|
|
2837
2860
|
</head>
|
|
2838
2861
|
<body>
|
|
2839
2862
|
<main>
|
|
@@ -3011,6 +3034,7 @@ function renderCheckoutSuccessPage(runtimeConfig) {
|
|
|
3011
3034
|
}
|
|
3012
3035
|
|
|
3013
3036
|
sendTelemetryOnce('checkout_paid_confirmed');
|
|
3037
|
+
try { window.plausible && window.plausible('Checkout Pro Success Page Confirmed', { props: { sessionId: sessionId || '', traceId: traceId || '', source: 'success_page' } }); } catch (_) {}
|
|
3014
3038
|
statusEl.textContent = 'ThumbGate Pro activated.';
|
|
3015
3039
|
const resolvedTraceId = body.traceId || traceId || '';
|
|
3016
3040
|
const emailStatus = body.trialEmail || {};
|
|
@@ -4118,7 +4142,7 @@ function createApiServer() {
|
|
|
4118
4142
|
return;
|
|
4119
4143
|
}
|
|
4120
4144
|
|
|
4121
|
-
if (isGetLikeRequest && pathname === '/.well-known/llms.txt') {
|
|
4145
|
+
if (isGetLikeRequest && (pathname === '/.well-known/llms.txt' || pathname === '/llms.txt')) {
|
|
4122
4146
|
const llmsTxtPath = path.join(__dirname, '..', '..', '.well-known', 'llms.txt');
|
|
4123
4147
|
try {
|
|
4124
4148
|
const content = fs.readFileSync(llmsTxtPath, 'utf8');
|
|
@@ -4849,26 +4873,12 @@ async function addContext(){
|
|
|
4849
4873
|
ctaId: analyticsMetadata.ctaId,
|
|
4850
4874
|
ctaPlacement: analyticsMetadata.ctaPlacement,
|
|
4851
4875
|
planId: analyticsMetadata.planId,
|
|
4876
|
+
billingCycle: analyticsMetadata.billingCycle,
|
|
4877
|
+
landingPath: analyticsMetadata.landingPath,
|
|
4852
4878
|
isBot: botClassification.isBot ? 'true' : 'false',
|
|
4853
4879
|
reason: botClassification.reason,
|
|
4854
4880
|
}, req.headers, eventType);
|
|
4855
|
-
const
|
|
4856
|
-
utmMedium: 'checkout_interstitial_recovery',
|
|
4857
|
-
utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_workflow_sprint',
|
|
4858
|
-
ctaId: 'checkout_interstitial_workflow_sprint_intake',
|
|
4859
|
-
ctaPlacement: 'checkout_interstitial',
|
|
4860
|
-
planId: 'team',
|
|
4861
|
-
});
|
|
4862
|
-
const confirmHiddenParams = [];
|
|
4863
|
-
for (const [key, value] of parsed.searchParams.entries()) {
|
|
4864
|
-
if (key !== 'confirm' && key !== 'customer_email') {
|
|
4865
|
-
confirmHiddenParams.push([key, value]);
|
|
4866
|
-
}
|
|
4867
|
-
}
|
|
4868
|
-
const html = renderCheckoutIntentPage({
|
|
4869
|
-
workflowIntakeHref,
|
|
4870
|
-
confirmHiddenParams,
|
|
4871
|
-
});
|
|
4881
|
+
const html = renderCheckoutIntentPage();
|
|
4872
4882
|
sendHtml(res, 200, html, responseHeaders);
|
|
4873
4883
|
return;
|
|
4874
4884
|
}
|
|
@@ -5597,6 +5607,7 @@ async function addContext(){
|
|
|
5597
5607
|
source,
|
|
5598
5608
|
telemetry: { rows: [], truncated: false, totalAfterSince: 0 },
|
|
5599
5609
|
funnel: { rows: [], truncated: false, totalAfterSince: 0 },
|
|
5610
|
+
journeySummary: null,
|
|
5600
5611
|
};
|
|
5601
5612
|
|
|
5602
5613
|
function readJsonlSince(p) {
|
|
@@ -5644,6 +5655,19 @@ async function addContext(){
|
|
|
5644
5655
|
result.funnel.truncated = all.length > limit;
|
|
5645
5656
|
}
|
|
5646
5657
|
|
|
5658
|
+
try {
|
|
5659
|
+
const { buildVisitorJourneySummary } = require('../../scripts/visitor-journey');
|
|
5660
|
+
result.journeySummary = buildVisitorJourneySummary({
|
|
5661
|
+
telemetryRows: wantTelemetry ? result.telemetry.rows : [],
|
|
5662
|
+
funnelRows: wantFunnel ? result.funnel.rows : [],
|
|
5663
|
+
limit: Math.min(limit, 500),
|
|
5664
|
+
});
|
|
5665
|
+
} catch (err) {
|
|
5666
|
+
result.journeySummary = {
|
|
5667
|
+
error: err && err.message ? err.message : 'journey_summary_unavailable',
|
|
5668
|
+
};
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5647
5671
|
sendJson(res, 200, result);
|
|
5648
5672
|
return;
|
|
5649
5673
|
}
|
|
@@ -6059,7 +6083,19 @@ async function addContext(){
|
|
|
6059
6083
|
|
|
6060
6084
|
// Public OpenAPI spec — no auth required (needed for ChatGPT GPT Store import)
|
|
6061
6085
|
if (isGetLikeRequest && (pathname === '/openapi.json' || pathname === '/openapi.yaml')) {
|
|
6062
|
-
const specPath =
|
|
6086
|
+
const specPath = [
|
|
6087
|
+
path.join(__dirname, '../../openapi/openapi.yaml'),
|
|
6088
|
+
path.join(__dirname, '../../adapters/chatgpt/openapi.yaml'),
|
|
6089
|
+
].find((candidate) => fs.existsSync(candidate));
|
|
6090
|
+
if (!specPath) {
|
|
6091
|
+
sendProblem(res, {
|
|
6092
|
+
type: PROBLEM_TYPES.NOT_FOUND,
|
|
6093
|
+
title: 'Not Found',
|
|
6094
|
+
status: 404,
|
|
6095
|
+
detail: 'OpenAPI spec not found.',
|
|
6096
|
+
});
|
|
6097
|
+
return;
|
|
6098
|
+
}
|
|
6063
6099
|
try {
|
|
6064
6100
|
const yaml = renderOpenApiYamlForRequest(fs.readFileSync(specPath, 'utf8'), req);
|
|
6065
6101
|
if (pathname === '/openapi.yaml') {
|