thumbgate 1.23.0 → 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.
Files changed (40) hide show
  1. package/.claude-plugin/marketplace.json +5 -5
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.well-known/llms.txt +26 -11
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +69 -34
  6. package/adapters/claude/.mcp.json +2 -2
  7. package/adapters/mcp/server-stdio.js +1 -1
  8. package/adapters/opencode/opencode.json +1 -1
  9. package/bin/cli.js +57 -16
  10. package/bin/postinstall.js +11 -22
  11. package/config/gate-templates.json +72 -0
  12. package/config/github-about.json +1 -1
  13. package/config/post-deploy-marketing-pages.json +10 -0
  14. package/package.json +6 -6
  15. package/public/agent-manager.html +3 -3
  16. package/public/agents-cost-savings.html +3 -3
  17. package/public/ai-malpractice-prevention.html +726 -149
  18. package/public/blog.html +3 -3
  19. package/public/codex-enterprise.html +3 -3
  20. package/public/codex-plugin.html +4 -4
  21. package/public/compare.html +6 -6
  22. package/public/dashboard.html +211 -126
  23. package/public/guide.html +5 -5
  24. package/public/index.html +187 -47
  25. package/public/learn.html +24 -10
  26. package/public/lessons.html +2 -2
  27. package/public/numbers.html +6 -6
  28. package/public/pricing.html +6 -5
  29. package/public/pro.html +23 -0
  30. package/scripts/billing.js +17 -0
  31. package/scripts/commercial-offer.js +75 -0
  32. package/scripts/dashboard.js +53 -1
  33. package/scripts/gates-engine.js +3 -3
  34. package/scripts/plausible-server-events.js +2 -1
  35. package/scripts/rate-limiter.js +16 -12
  36. package/scripts/seo-gsd.js +167 -1
  37. package/scripts/telemetry-analytics.js +310 -0
  38. package/scripts/visitor-journey.js +172 -0
  39. package/src/api/server.js +65 -29
  40. package/adapters/chatgpt/openapi.yaml +0 -1705
@@ -12,7 +12,7 @@
12
12
  "@context": "https://schema.org",
13
13
  "@type": "WebPage",
14
14
  "name": "ThumbGate Lessons Learned",
15
- "url": "https://thumbgate-production.up.railway.app/lessons",
15
+ "url": "https://thumbgate.ai/lessons",
16
16
  "dateModified": "2026-04-20",
17
17
  "author": {
18
18
  "@type": "Person",
@@ -26,7 +26,7 @@
26
26
  "publisher": {
27
27
  "@type": "Organization",
28
28
  "name": "ThumbGate",
29
- "url": "https://thumbgate-production.up.railway.app"
29
+ "url": "https://thumbgate.ai"
30
30
  }
31
31
  }
32
32
  </script>
@@ -10,9 +10,9 @@
10
10
  <meta property="og:title" content="ThumbGate — The Numbers">
11
11
  <meta property="og:description" content="Generated first-party operational snapshot: configured gates, recorded interventions, and explicit zero-evidence caveats.">
12
12
  <meta property="og:type" content="website">
13
- <meta property="og:url" content="https://thumbgate-production.up.railway.app/numbers">
13
+ <meta property="og:url" content="https://thumbgate.ai/numbers">
14
14
  <meta name="twitter:card" content="summary_large_image">
15
- <link rel="canonical" href="https://thumbgate-production.up.railway.app/numbers">
15
+ <link rel="canonical" href="https://thumbgate.ai/numbers">
16
16
  <link rel="icon" type="image/png" href="/thumbgate-icon.png">
17
17
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
18
18
  <script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
@@ -25,8 +25,8 @@
25
25
  "alternateName": "thumbgate",
26
26
  "applicationCategory": "DeveloperApplication",
27
27
  "operatingSystem": "Cross-platform, Node.js >=18.18.0",
28
- "softwareVersion": "1.23.0",
29
- "url": "https://thumbgate-production.up.railway.app/numbers",
28
+ "softwareVersion": "1.23.2",
29
+ "url": "https://thumbgate.ai/numbers",
30
30
  "dateModified": "2026-05-07",
31
31
  "creator": {
32
32
  "@type": "Person",
@@ -46,7 +46,7 @@
46
46
  "@type": "Dataset",
47
47
  "name": "ThumbGate Operational Snapshot",
48
48
  "description": "First-party operational snapshot from the ThumbGate pre-action check runtime: configured checks, recorded block/warn events, estimated token savings from recorded blocks, and Bayes error rate when the sample supports it.",
49
- "url": "https://thumbgate-production.up.railway.app/numbers",
49
+ "url": "https://thumbgate.ai/numbers",
50
50
  "license": "https://opensource.org/licenses/MIT",
51
51
  "creator": {
52
52
  "@type": "Person",
@@ -202,7 +202,7 @@
202
202
  <main class="container">
203
203
  <h1>The Numbers</h1>
204
204
  <p class="subtitle">Generated first-party operational snapshot from the ThumbGate runtime. This is not customer traction, install volume, revenue, or proof that a configured gate has fired.</p>
205
- <div class="freshness">Updated: 2026-05-07 · Version 1.23.0</div>
205
+ <div class="freshness">Updated: 2026-05-07 · Version 1.23.2</div>
206
206
  <div class="truth-note"><strong>Read this first:</strong> configured checks are inventory. Recorded blocks and warnings are usage evidence. This snapshot currently reports 0 recorded hard-block event(s) and 0 recorded warning event(s).</div>
207
207
 
208
208
  <h2>Gate enforcement</h2>
@@ -12,6 +12,7 @@ __GOOGLE_SITE_VERIFICATION_META__
12
12
  <meta property="og:url" content="__APP_ORIGIN__/pricing">
13
13
  <meta property="og:image" content="/og.png">
14
14
  <link rel="canonical" href="__APP_ORIGIN__/pricing">
15
+ <link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
15
16
  <link rel="icon" type="image/png" href="/thumbgate-icon.png">
16
17
  <link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
17
18
 
@@ -49,7 +50,7 @@ __GA_BOOTSTRAP__
49
50
  "@context": "https://schema.org",
50
51
  "@type": "FAQPage",
51
52
  "mainEntity": [
52
- { "@type": "Question", "name": "What does Pro add over the free CLI?", "acceptedAnswer": { "@type": "Answer", "text": "Free gives you unlimited captures and 5 active rules, running entirely on your machine. Pro is the hosted layer: lesson sync across machines, a dashboard without self-hosting, managed adapter updates, unlimited rules, and DPO export. You're paying for infrastructure we run, not features we hide." } },
53
+ { "@type": "Question", "name": "What does Pro add over the free CLI?", "acceptedAnswer": { "@type": "Answer", "text": "Free gives you 5 captures/day and 3 active rules, running entirely on your machine. Pro is the hosted layer: unlimited captures, unlimited rules, lesson sync across machines, a dashboard without self-hosting, managed adapter updates, and DPO export. You're paying for infrastructure we run, not features we hide." } },
53
54
  { "@type": "Question", "name": "Does ThumbGate send my code to the cloud?", "acceptedAnswer": { "@type": "Answer", "text": "No. The CLI is local-first — no data leaves your machine. Pro and Team add hosted sync for dashboards and shared lessons, but your source code stays local." } },
54
55
  { "@type": "Question", "name": "When should I pick Team over Pro?", "acceptedAnswer": { "@type": "Answer", "text": "When one engineer's correction should protect the whole team. Team shares the lesson database across seats so a fix in one repo prevents the same mistake in every repo." } },
55
56
  { "@type": "Question", "name": "Can I cancel anytime?", "acceptedAnswer": { "@type": "Answer", "text": "Yes. Pro and Team are month-to-month with a 7-day refund window on the first charge. Cancel from the billing portal and your subscription ends at the period close." } }
@@ -237,8 +238,8 @@ __GA_BOOTSTRAP__
237
238
  <div class="price">$0</div>
238
239
  <div class="price-sub">Block repeated mistakes daily. Forever free for solo devs.</div>
239
240
  <ul>
240
- <li>Unlimited feedback captures — every thumbs-down, every session</li>
241
- <li>Up to 5 active prevention rules</li>
241
+ <li>5 feedback captures/day (25 total) enough to see the value</li>
242
+ <li>Up to 3 active prevention rules</li>
242
243
  <li>All MCP integrations (Claude Code, Cursor, Codex, Gemini, Amp)</li>
243
244
  <li>PreToolUse hook blocking with built-in safety checks</li>
244
245
  <li>Runs 100% local — no account, no signup, no data leaves your machine</li>
@@ -257,7 +258,7 @@ __GA_BOOTSTRAP__
257
258
  <li><strong>Hosted lesson sync</strong> — corrections follow you across machines, no manual export</li>
258
259
  <li><strong>Managed adapter matrix</strong> — we track runtime changes in Claude Code, Cursor, Codex, Gemini, Amp so you don't</li>
259
260
  <li><strong>Hosted dashboard</strong> — see every blocked action, every rule that fired, without running your own server</li>
260
- <li><strong>Unlimited prevention rules</strong> — free caps at 5 auto-promoted rules</li>
261
+ <li><strong>Unlimited prevention rules</strong> — free caps at 3 auto-promoted rules</li>
261
262
  <li><strong>DPO + HuggingFace export</strong> — training data from your real corrections</li>
262
263
  <li><strong>Auto-connect</strong> — new agent surfaces appear automatically after setup</li>
263
264
  <li>7-day refund window. Cancel anytime.</li>
@@ -296,7 +297,7 @@ __GA_BOOTSTRAP__
296
297
  <div class="faq-list">
297
298
  <div class="faq-item">
298
299
  <div class="faq-q">What does Pro add over the free CLI?</div>
299
- <div class="faq-a">Free gives you unlimited captures and 5 active rules, running entirely on your machine. Pro is the hosted layer: lesson sync across machines, a dashboard without self-hosting, managed adapter updates, unlimited rules, and DPO export. You're paying for infrastructure we run, not features we hide.</div>
300
+ <div class="faq-a">Free gives you 5 captures/day and 3 active rules, running entirely on your machine. Pro is the hosted layer: unlimited captures, unlimited rules, lesson sync across machines, a dashboard without self-hosting, managed adapter updates, and DPO export. You're paying for infrastructure we run, not features we hide.</div>
300
301
  </div>
301
302
  <div class="faq-item">
302
303
  <div class="faq-q">Does ThumbGate send my code to the cloud?</div>
package/public/pro.html CHANGED
@@ -11,6 +11,7 @@ __GOOGLE_SITE_VERIFICATION_META__
11
11
  <meta property="og:type" content="website">
12
12
  <meta property="og:url" content="__APP_ORIGIN__/pro">
13
13
  <link rel="canonical" href="__APP_ORIGIN__/pro">
14
+ <link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
14
15
  <link rel="icon" type="image/png" href="/thumbgate-icon.png">
15
16
  <link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
16
17
  <meta property="og:image" content="/og.png">
@@ -815,6 +816,28 @@ __GA_BOOTSTRAP__
815
816
  </div>
816
817
  </section>
817
818
 
819
+ <section class="section" id="deterministic-loop">
820
+ <div class="container">
821
+ <div class="section-label">Why Pro now</div>
822
+ <h2 class="section-title">Black-box thumbs do not prove prevention. Pro gives the operator an audit loop.</h2>
823
+ <p class="section-intro">Native rating buttons can tell a vendor that an answer felt wrong. ThumbGate Pro gives you the operational record: the correction, the lesson, the rule, the blocked tool call, and the export path.</p>
824
+ <div class="grid-3">
825
+ <div class="feature-card">
826
+ <h3>Inspectable memory</h3>
827
+ <p>Search the exact lesson that came from a thumbs-down and see whether it is still active, warning-only, or blocking.</p>
828
+ </div>
829
+ <div class="feature-card">
830
+ <h3>Deterministic checks</h3>
831
+ <p>The enforcement layer evaluates tool name, arguments, working directory, command shape, confidence, and required evidence before the action runs.</p>
832
+ </div>
833
+ <div class="feature-card">
834
+ <h3>Exportable proof</h3>
835
+ <p>Take the same correction history into JSONL, DPO export, review packets, and team rollout conversations instead of trusting hidden memory.</p>
836
+ </div>
837
+ </div>
838
+ </div>
839
+ </section>
840
+
818
841
  <section class="section" id="why-pay">
819
842
  <div class="container">
820
843
  <div class="section-label">Why operators pay</div>
@@ -51,6 +51,7 @@ const {
51
51
  } = require('./analytics-window');
52
52
  const { ensureParentDir } = require('./fs-utils');
53
53
  const mailer = require('./mailer');
54
+ const { recordCheckoutFunnelEvent } = require('./plausible-server-events');
54
55
 
55
56
  function loadWorkflowSprintIntakeModule() {
56
57
  const modulePath = path.resolve(__dirname, 'workflow-sprint-intake.js');
@@ -3038,6 +3039,22 @@ async function handleWebhook(rawBody, signature) {
3038
3039
  attribution,
3039
3040
  });
3040
3041
  }
3042
+ // Fire Plausible purchase event so the funnel poller can measure
3043
+ // end-to-end conversion: visitor → CTA → checkout → email → Stripe → purchase.
3044
+ // Fire-and-forget (never blocks the webhook response).
3045
+ void recordCheckoutFunnelEvent('purchase', {
3046
+ page: '/success',
3047
+ props: {
3048
+ sessionId: session.id,
3049
+ customerId,
3050
+ traceId: traceId || '',
3051
+ packId: packId || '',
3052
+ amount: session.amount_total != null ? String(session.amount_total) : '',
3053
+ currency: session.currency || '',
3054
+ ...attribution,
3055
+ },
3056
+ });
3057
+
3041
3058
  return {
3042
3059
  handled: true,
3043
3060
  action: 'provisioned_api_key',
@@ -35,6 +35,78 @@ function normalizeSeatCount(value, fallback = TEAM_MIN_SEATS) {
35
35
  return Math.max(TEAM_MIN_SEATS, Math.round(parsed));
36
36
  }
37
37
 
38
+ function trackedProUrl(source = 'cli_receipt', content = 'value_receipt') {
39
+ try {
40
+ const url = new URL(PRO_MONTHLY_PAYMENT_LINK);
41
+ url.searchParams.set('utm_source', source);
42
+ url.searchParams.set('utm_medium', 'cli');
43
+ url.searchParams.set('utm_campaign', 'pro_conversion');
44
+ url.searchParams.set('utm_content', content);
45
+ return url.toString();
46
+ } catch (_) {
47
+ return PRO_MONTHLY_PAYMENT_LINK;
48
+ }
49
+ }
50
+
51
+ function pluralize(count, singular, plural = `${singular}s`) {
52
+ return Number(count) === 1 ? singular : plural;
53
+ }
54
+
55
+ function buildCaptureReceipt({ signal, feedbackId, memoryId, actionType } = {}) {
56
+ const normalizedSignal = String(signal || '').toUpperCase() || 'UNKNOWN';
57
+ const lines = [
58
+ '',
59
+ 'Value receipt',
60
+ '─'.repeat(50),
61
+ ` Stored proof : ${normalizedSignal} feedback${feedbackId ? ` (${feedbackId})` : ''}`,
62
+ memoryId ? ` Local memory : ${memoryId}` : ' Local memory : saved locally',
63
+ actionType ? ` Rule pressure : ${actionType}` : ' Rule pressure : available for promotion',
64
+ ' Free today : this proof protects this local machine',
65
+ ' Pro sync : keep this lesson, rule, and dashboard synced across machines and agent runtimes',
66
+ ' Next proof : npx thumbgate stats',
67
+ ' Cost proof : npx thumbgate cost',
68
+ '',
69
+ ` Solo Pro : ${PRO_PRICE_LABEL} for hosted sync, search, dashboard, and exports`,
70
+ ` Upgrade : ${trackedProUrl('cli_capture_receipt', actionType || normalizedSignal.toLowerCase())}`,
71
+ ` Team path : ${TEAM_PRICE_LABEL}; start with one repeated workflow failure`,
72
+ ' https://thumbgate.ai/#workflow-sprint-intake',
73
+ '',
74
+ ];
75
+ return lines.join('\n');
76
+ }
77
+
78
+ function buildStatsReceipt(stats = {}) {
79
+ const negatives = Number(stats.negatives || stats.totalNegative || 0);
80
+ const blocked = Number(stats.gatesBlocked || stats.blocked || 0);
81
+ const warned = Number(stats.gatesWarned || stats.warned || 0);
82
+ const gates = Number(stats.totalGates || 0);
83
+ const autoPromoted = Number(stats.autoPromotedGates || 0);
84
+ const hasFriction = negatives > 0 || blocked > 0 || warned > 0 || gates > 0;
85
+ if (!hasFriction) return '';
86
+
87
+ const interventions = blocked + warned;
88
+ const lines = [
89
+ '',
90
+ 'Paid-intent next step',
91
+ '─'.repeat(50),
92
+ ];
93
+ if (interventions > 0) {
94
+ lines.push(` Proof already seen : ${interventions} gate ${pluralize(interventions, 'intervention')}`);
95
+ }
96
+ if (gates > 0) {
97
+ lines.push(` Active prevention : ${gates} ${pluralize(gates, 'gate')} (${autoPromoted} auto-promoted)`);
98
+ }
99
+ if (negatives > 0) {
100
+ lines.push(` Failure pressure : ${negatives} negative ${pluralize(negatives, 'signal')}`);
101
+ }
102
+ lines.push(' Show the buyer : npx thumbgate cost');
103
+ lines.push(' Pro sync value : keep these lessons/rules visible across laptops, CI, containers, and agent runtimes');
104
+ lines.push(` Solo Pro : ${trackedProUrl('cli_stats_receipt', 'proof_seen')}`);
105
+ lines.push(' Team workflow : https://thumbgate.ai/#workflow-sprint-intake');
106
+ lines.push('');
107
+ return lines.join('\n');
108
+ }
109
+
38
110
  module.exports = {
39
111
  PRO_MONTHLY_PAYMENT_LINK,
40
112
  PRO_ANNUAL_PAYMENT_LINK,
@@ -51,4 +123,7 @@ module.exports = {
51
123
  normalizePlanId,
52
124
  normalizeBillingCycle,
53
125
  normalizeSeatCount,
126
+ buildCaptureReceipt,
127
+ buildStatsReceipt,
128
+ trackedProUrl,
54
129
  };
@@ -967,6 +967,20 @@ function computeAnalyticsSummary(feedbackDir, options = {}) {
967
967
  return {
968
968
  window: telemetry.window || analyticsWindow,
969
969
  telemetry,
970
+ firstPartyTrafficQuality: telemetry.trafficQuality || {
971
+ rawEvents: telemetry.totalEvents || 0,
972
+ externalEvents: 0,
973
+ excludedEvents: 0,
974
+ exclusionRate: 0,
975
+ byAudience: {},
976
+ byExclusionReason: {},
977
+ external: {
978
+ uniqueVisitors: 0,
979
+ pageViews: 0,
980
+ checkoutStarts: 0,
981
+ },
982
+ verdict: 'missing',
983
+ },
970
984
  funnel: {
971
985
  visitors: uniqueVisitors,
972
986
  sessions: telemetry.visitors ? telemetry.visitors.uniqueSessions || 0 : 0,
@@ -1149,16 +1163,42 @@ function computeInstrumentationReadiness(analytics, billing) {
1149
1163
  const coverage = billing && billing.coverage ? billing.coverage : {};
1150
1164
  const telemetry = analytics.telemetry || {};
1151
1165
  const visitors = telemetry.visitors || {};
1166
+ const quality = telemetry.trafficQuality || analytics.firstPartyTrafficQuality || {};
1167
+ const external = quality.external || {};
1152
1168
  const cli = telemetry.cli || {};
1169
+ const plausibleExportConfigured = Boolean(
1170
+ process.env.PLAUSIBLE_API_KEY && (process.env.PLAUSIBLE_SITE_ID || process.env.PLAUSIBLE_DOMAIN)
1171
+ );
1172
+ const posthogExportConfigured = Boolean(
1173
+ (process.env.POSTHOG_PERSONAL_API_KEY || process.env.POSTHOG_API_KEY) && process.env.POSTHOG_PROJECT_ID
1174
+ );
1175
+ const ga4ExportConfigured = Boolean(
1176
+ process.env.GA4_PROPERTY_ID && (process.env.GOOGLE_APPLICATION_CREDENTIALS || process.env.GOOGLE_CLIENT_EMAIL)
1177
+ );
1178
+ const dashboardGradeExportReady = plausibleExportConfigured || posthogExportConfigured || ga4ExportConfigured;
1153
1179
 
1154
1180
  return {
1155
- plausibleConfigured: /plausible\.io\/js\/script\.js|\/js\/analytics\.js/.test(landingPage),
1181
+ plausibleConfigured: /plausible\.io\/js\/script(?:\.tagged-events)?\.js|\/js\/analytics\.js/.test(landingPage),
1156
1182
  ga4Configured: Boolean(runtimeConfig.gaMeasurementId),
1157
1183
  googleSearchConsoleConfigured: Boolean(runtimeConfig.googleSiteVerification),
1158
1184
  softwareApplicationSchemaPresent: /"@type": "SoftwareApplication"/.test(landingPage),
1159
1185
  faqSchemaPresent: /"@type": "FAQPage"/.test(landingPage),
1160
1186
  telemetryEventsPresent: (telemetry.totalEvents || 0) > 0,
1161
1187
  uniqueVisitorsTracked: visitors.uniqueVisitors || 0,
1188
+ rawTelemetryEvents: quality.rawEvents || telemetry.totalEvents || 0,
1189
+ externalTelemetryEvents: quality.externalEvents || 0,
1190
+ excludedTelemetryEvents: quality.excludedEvents || 0,
1191
+ externalVisitorsTracked: external.uniqueVisitors || 0,
1192
+ externalPageViewsTracked: external.pageViews || 0,
1193
+ externalCheckoutStartsTracked: external.checkoutStarts || 0,
1194
+ externalVisitorPathsTracked: Array.isArray(external.visitorPaths) ? external.visitorPaths.length : 0,
1195
+ internalTestPollutionRate: quality.exclusionRate || 0,
1196
+ trafficQualityVerdict: quality.verdict || 'missing',
1197
+ topExcludedTrafficReason: quality.topExclusionReason || null,
1198
+ plausibleExportConfigured,
1199
+ posthogExportConfigured,
1200
+ ga4ExportConfigured,
1201
+ dashboardGradeExportReady,
1162
1202
  cliInstallsTracked: cli.uniqueInstalls || 0,
1163
1203
  funnelEventsPresent: (analytics.reconciliation.telemetryCheckoutStarts || 0) > 0,
1164
1204
  seoSignalsPresent: (analytics.seo.landingViews || 0) > 0,
@@ -1865,8 +1905,10 @@ function printDashboard(data) {
1865
1905
  console.log('');
1866
1906
  console.log('\uD83D\uDCBC Growth Analytics');
1867
1907
  console.log(` Unique Visitors : ${analytics.trafficMetrics.visitors}`);
1908
+ console.log(` External Visitors: ${instrumentation.externalVisitorsTracked}`);
1868
1909
  console.log(` Sessions : ${analytics.trafficMetrics.sessions}`);
1869
1910
  console.log(` Page Views : ${analytics.trafficMetrics.pageViews}`);
1911
+ console.log(` External Views : ${instrumentation.externalPageViewsTracked}`);
1870
1912
  console.log(` CTA Clicks : ${analytics.trafficMetrics.ctaClicks}`);
1871
1913
  console.log(` Leads : ${analytics.funnel.acquisitionLeads}`);
1872
1914
  console.log(` Sprint Leads : ${analytics.pipeline.workflowSprintLeads.total}`);
@@ -1877,6 +1919,7 @@ function printDashboard(data) {
1877
1919
  console.log(` Booked Revenue : $${(analytics.revenue.bookedRevenueCents / 100).toFixed(2)}`);
1878
1920
  console.log(` Matched Journeys : ${analytics.reconciliation.matchedPaidOrders}/${analytics.reconciliation.telemetryCheckoutStarts}`);
1879
1921
  console.log(` Buyer Loss : ${analytics.buyerLoss.totalSignals}`);
1922
+ console.log(` Data Quality : ${analytics.firstPartyTrafficQuality.verdict} (${instrumentation.excludedTelemetryEvents}/${instrumentation.rawTelemetryEvents} excluded)`);
1880
1923
  if (analytics.telemetry.visitors.topSource) {
1881
1924
  console.log(` Top Source : ${analytics.telemetry.visitors.topSource.key} (${analytics.telemetry.visitors.topSource.count}\u00D7)`);
1882
1925
  }
@@ -1896,6 +1939,15 @@ function printDashboard(data) {
1896
1939
  console.log(` GA4 : ${instrumentation.ga4Configured ? 'configured' : 'missing'}`);
1897
1940
  console.log(` Search Console : ${instrumentation.googleSearchConsoleConfigured ? 'configured' : 'missing'}`);
1898
1941
  console.log(` Telemetry Events : ${instrumentation.telemetryEventsPresent ? instrumentation.uniqueVisitorsTracked : 0} visitors`);
1942
+ console.log(` Clean Visitors : ${instrumentation.externalVisitorsTracked} external (${Math.round((instrumentation.internalTestPollutionRate || 0) * 100)}% internal/test/bot events)`);
1943
+ console.log(` Clean Paths : ${instrumentation.externalVisitorPathsTracked} first-party paths`);
1944
+ if (instrumentation.topExcludedTrafficReason) {
1945
+ console.log(` Top Exclusion : ${instrumentation.topExcludedTrafficReason.key} (${instrumentation.topExcludedTrafficReason.count}\u00D7)`);
1946
+ }
1947
+ console.log(` Plausible Export : ${instrumentation.plausibleExportConfigured ? 'configured' : 'missing API credentials'}`);
1948
+ console.log(` PostHog Export : ${instrumentation.posthogExportConfigured ? 'configured' : 'missing API credentials'}`);
1949
+ console.log(` GA4 Export : ${instrumentation.ga4ExportConfigured ? 'configured' : 'missing API credentials'}`);
1950
+ console.log(` Visitor Paths : ${instrumentation.dashboardGradeExportReady ? 'export-ready' : 'not dashboard-grade in repo'}`);
1899
1951
  console.log(` SEO Signals : ${instrumentation.seoSignalsPresent ? analytics.seo.landingViews : 0}`);
1900
1952
  console.log(` Buyer Loss : ${instrumentation.buyerLossSignalsPresent ? analytics.buyerLoss.totalSignals : 0}`);
1901
1953
  console.log(` Attribution : ${Math.round((instrumentation.trafficAttributionCoverage || 0) * 100)}% page-view coverage`);
@@ -1953,12 +1953,12 @@ function buildBlockActionProCta() {
1953
1953
  if (totalBlocks < 5) return null; // Too early — let them experience the product
1954
1954
 
1955
1955
  if (totalBlocks < 25) {
1956
- return '\n\n💡 Pro: sync rules across machines + dashboard analytics → thumbgate.ai/go/pro';
1956
+ return '\n\n💡 Pro: keep this rule synced across laptops, CI, containers, and agent runtimes → thumbgate.ai/go/pro';
1957
1957
  }
1958
1958
  if (totalBlocks < 100) {
1959
- return `\n\n💡 ${totalBlocks} actions blocked. Pro keeps rules in sync everywhere → thumbgate.ai/go/pro ($19/mo)`;
1959
+ return `\n\n💡 ${totalBlocks} actions blocked. Pro keeps these lessons/rules synced everywhere → thumbgate.ai/go/pro ($19/mo)`;
1960
1960
  }
1961
- return `\n\n💡 ${totalBlocks} mistakes caught. Your team could use this → thumbgate.ai/go/pro`;
1961
+ return `\n\n💡 ${totalBlocks} mistakes caught. Your team could use shared hosted enforcement → thumbgate.ai/go/pro`;
1962
1962
  } catch (_) {
1963
1963
  return null;
1964
1964
  }
@@ -25,7 +25,7 @@
25
25
 
26
26
  const https = require('node:https');
27
27
 
28
- const DEFAULT_PLAUSIBLE_DOMAIN = 'thumbgate-production.up.railway.app';
28
+ const DEFAULT_PLAUSIBLE_DOMAIN = 'thumbgate.ai';
29
29
  const PLAUSIBLE_ENDPOINT = 'https://plausible.io/api/event';
30
30
  const REQUEST_TIMEOUT_MS = 2_000;
31
31
 
@@ -142,6 +142,7 @@ const CHECKOUT_EVENT_NAMES = Object.freeze({
142
142
  view: 'Checkout Pro Viewed',
143
143
  emailSubmitted: 'Checkout Pro Email Submitted',
144
144
  stripeRedirect: 'Checkout Pro Stripe Redirect Started',
145
+ purchase: 'Checkout Pro Purchase Completed',
145
146
  });
146
147
 
147
148
  function recordCheckoutFunnelEvent(stage, options = {}) {
@@ -12,35 +12,37 @@ const {
12
12
  const USAGE_FILE = path.join(process.env.HOME || '/tmp', '.thumbgate', 'usage-limits.json');
13
13
 
14
14
  // ──────────────────────────────────────────────────────────
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.
15
+ // Free tier: tight enough to create upgrade pressure after
16
+ // real usage. Captures and rules are capped so heavy users
17
+ // hit the wall within the first week, not the first quarter.
18
18
  // ──────────────────────────────────────────────────────────
19
19
  const FREE_TIER_LIMITS = {
20
- capture_feedback: { daily: Infinity, lifetime: Infinity, label: 'feedback captures' },
21
- prevention_rules: { daily: Infinity, lifetime: Infinity, label: 'prevention rules generated' },
20
+ capture_feedback: { daily: 5, lifetime: 25, label: 'feedback captures (5/day, 25 total on free)' },
21
+ prevention_rules: { daily: 2, lifetime: 6, label: 'prevention rules generated (2/day on free)' },
22
22
  recall: { daily: 0, lifetime: 0, label: 'recall queries (Pro only)' },
23
23
  search_lessons: { daily: 0, lifetime: 0, label: 'lesson searches (Pro only)' },
24
24
  search_thumbgate: { daily: 0, lifetime: 0, label: 'ThumbGate searches (Pro only)' },
25
25
  commerce_recall: { daily: 0, lifetime: 0, label: 'commerce recalls (Pro only)' },
26
26
  export_dpo: { daily: 0, lifetime: 0, label: 'DPO exports (Pro only)' },
27
27
  export_databricks: { daily: 0, lifetime: 0, label: 'Databricks exports (Pro only)' },
28
- construct_context_pack: { daily: Infinity, lifetime: Infinity, label: 'context packs' },
28
+ construct_context_pack: { daily: 3, lifetime: Infinity, label: 'context packs (3/day on free)' },
29
29
  };
30
30
 
31
- const FREE_TIER_MAX_GATES = 5; // 5 active prevention rules on free; Pro is unlimited
32
- const FREE_TIER_DAILY_BLOCKS = 10; // 10 gate blocks/day on free; after limit, deny → warn + upgrade CTA
31
+ const FREE_TIER_MAX_GATES = 3; // 3 active prevention rules on free; Pro is unlimited
32
+ const FREE_TIER_DAILY_BLOCKS = 3; // 3 gate blocks/day on free; after limit, deny → warn + upgrade CTA
33
33
 
34
34
  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.`;
35
35
 
36
36
  const PAYWALL_MESSAGES = {
37
- prevention_rules: 'Free tier includes 5 active prevention rules. Promote more or unlock unlimited rules with Pro.',
37
+ capture_feedback: 'Free tier: 5 captures/day (25 total). Your feedback is stored locally upgrade to capture unlimited.',
38
+ prevention_rules: 'Free tier includes 3 active prevention rules and 2 rule generations/day. Upgrade to Pro for unlimited rules.',
38
39
  recall: 'Recall is a Pro feature. Your past feedback is stored locally — upgrade to search and reuse it.',
39
40
  search_lessons: 'Lesson search is a Pro feature. Upgrade to find patterns in your agent\'s mistakes.',
41
+ construct_context_pack: 'Free tier: 3 context packs/day. Upgrade to Pro for unlimited.',
40
42
  default: 'This feature requires Pro. Start Pro — card required; billed today.',
41
43
  };
42
44
 
43
- const TRIAL_DAYS = 14;
45
+ const TRIAL_DAYS = 7;
44
46
 
45
47
  function getInstallAgeDays() {
46
48
  try {
@@ -91,7 +93,8 @@ function isProTier(authContext) {
91
93
  const { isProLicensed } = require('./license');
92
94
  if (isProLicensed()) return true;
93
95
  } catch (_) {}
94
- // 14-day reverse trial: new installs get full Pro access
96
+ // 7-day reverse trial: new installs get full Pro access, then hit a clear
97
+ // hosted-sync/unlimited-rules pay moment while the product is still fresh.
95
98
  if (isInTrialPeriod()) return true;
96
99
  return false;
97
100
  }
@@ -164,9 +167,10 @@ function checkLimit(action, authContext) {
164
167
 
165
168
  // Check daily limit
166
169
  if (dailyLimit !== Infinity && dailyCurrent >= dailyLimit) {
170
+ const paywallMsg = PAYWALL_MESSAGES[action] || PAYWALL_MESSAGES.default;
167
171
  return {
168
172
  allowed: false,
169
- message: `Daily limit reached. ${UPGRADE_MESSAGE}`,
173
+ message: `Daily limit reached. ${paywallMsg}\n\n${UPGRADE_MESSAGE}`,
170
174
  used: dailyCurrent,
171
175
  limit: dailyLimit,
172
176
  limitType: 'daily',